Fixed the consensus about expired domains.

pull/295/head
Revertron 2 years ago
parent beb09ed01c
commit a612f73649

2
Cargo.lock generated

@ -84,7 +84,7 @@ dependencies = [
[[package]] [[package]]
name = "alfis" name = "alfis"
version = "0.7.8" version = "0.8.0"
dependencies = [ dependencies = [
"base64", "base64",
"bincode", "bincode",

@ -1,6 +1,6 @@
[package] [package]
name = "alfis" name = "alfis"
version = "0.7.8" version = "0.8.0"
authors = ["Revertron <alfis@revertron.com>"] authors = ["Revertron <alfis@revertron.com>"]
edition = "2021" edition = "2021"
build = "build.rs" build = "build.rs"

@ -12,7 +12,7 @@ use sqlite::{Connection, State, Statement};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use crate::blockchain::hash_utils::*; use crate::blockchain::hash_utils::*;
use crate::blockchain::transaction::DomainData; use crate::blockchain::transaction::{DomainData, DomainState};
use crate::blockchain::types::BlockQuality::*; use crate::blockchain::types::BlockQuality::*;
use crate::blockchain::types::MineResult::*; use crate::blockchain::types::MineResult::*;
use crate::blockchain::types::{BlockQuality, MineResult, Options, ZoneData}; use crate::blockchain::types::{BlockQuality, MineResult, Options, ZoneData};
@ -141,12 +141,9 @@ impl Chain {
if WRONG_HASHES.contains(&block.hash) { if WRONG_HASHES.contains(&block.hash) {
error!("Block {} has bad hash:\n{:?}", block.index, &block); error!("Block {} has bad hash:\n{:?}", block.index, &block);
info!("Truncating database from block {}...", block.index); info!("Truncating database from block {}...", block.index);
match self.truncate_db_from_block(block.index) { if let Err(e) = self.truncate_db_from_block(block.index) {
Ok(_) => {} error!("{}", e);
Err(e) => { panic!("Error truncating database! Please, delete 'blockchain.db' and restart.");
error!("{}", e);
panic!("Error truncating database! Please, delete 'blockchain.db' and restart.");
}
} }
break; break;
} }
@ -487,40 +484,62 @@ impl Chain {
None None
} }
/// Checks if any domain is available to mine for this client (pub_key) pub fn can_mine_domain(&self, height: u64, domain: &str, pub_key: &Bytes) -> MineResult {
pub fn is_domain_available(&self, height: u64, domain: &str, public_key: &Bytes) -> bool { let name = domain.to_lowercase();
if domain.is_empty() { if !check_domain(&name, true) {
return false; return WrongName;
} }
let identity_hash = hash_identity(domain, None); let zone = get_domain_zone(&name);
if !self.is_id_available(height, &identity_hash, public_key) { if !self.is_available_zone(&zone) {
warn!("Domain {} is not available!", domain); return WrongZone;
return false;
} }
let parts: Vec<&str> = domain.rsplitn(2, '.').collect(); let (transaction, state) = self.get_domain_transaction_and_state(&name);
if parts.len() > 1 { if let Some(transaction) = transaction {
// We do not support third level domains let owner = transaction.signing.eq(pub_key);
if parts.last().unwrap().contains('.') { match state {
return false; DomainState::NotFound => {}
DomainState::Alive { .. } => if !owner {
return NotOwned;
},
DomainState::Expired { .. } => if !owner {
return NotOwned;
},
DomainState::Free { .. } => {}
} }
return self.is_available_zone(parts.first().unwrap());
} }
true let identity_hash = hash_identity(&name, None);
self.can_mine_identity(&identity_hash, height, Utc::now().timestamp(), pub_key)
} }
/// Checks if this identity is free or is owned by the same pub_key fn can_mine_identity(&self, identity_hash: &Bytes, height: u64, time: i64, pub_key: &Bytes) -> MineResult {
pub fn is_id_available(&self, height: u64, identity: &Bytes, public_key: &Bytes) -> bool { if let Some(last) = self.get_last_full_block(height, Some(pub_key)) {
let mut statement = self.db.prepare(SQL_GET_DOMAIN_OWNER_BY_ID).unwrap(); // If this domain/identity is new
statement.bind(1, height as i64).expect("Error in bind"); let want_new_domain = !self.is_domain_in_blockchain(height, &identity_hash);
statement.bind(2, identity.as_slice()).expect("Error in bind"); // And the user hasn't mined a domain in previous 24h, then we allow her to mine
while let State::Row = statement.next().unwrap() { let time = last.timestamp + NEW_DOMAINS_INTERVAL - time;
let pub_key = Bytes::from_bytes(&statement.read::<Vec<u8>>(0).unwrap()); if want_new_domain && time > 0 {
if !pub_key.eq(public_key) { return Cooldown { time };
return false;
} }
} }
true Fine
}
/// Checks if this identity is free or is owned by the same pub_key
pub fn is_id_available(&self, height: u64, time: i64, identity: &Bytes, public_key: &Bytes) -> bool {
let (transaction, state) = self.get_identity_transaction_and_state(identity, height, time);
if transaction.is_none() {
return true;
}
if transaction.unwrap().signing.eq(public_key) {
return true;
}
match state {
DomainState::NotFound => true,
DomainState::Alive { .. } => false,
DomainState::Expired { .. } => false,
DomainState::Free { .. } => true
}
} }
pub fn get_zones(&self) -> &Vec<ZoneData> { pub fn get_zones(&self) -> &Vec<ZoneData> {
@ -566,39 +585,12 @@ impl Chain {
false false
} }
pub fn can_mine_domain(&self, height: u64, domain: &str, pub_key: &Bytes) -> MineResult { pub fn get_domain_renewal_time(&self, time: i64, identity_hash: &Bytes) -> Option<i64> {
let name = domain.to_lowercase();
if !check_domain(&name, true) {
return WrongName;
}
let zone = get_domain_zone(&name);
if !self.is_available_zone(&zone) {
return WrongZone;
}
if let Some(transaction) = self.get_domain_transaction(&name) {
if transaction.signing.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_domain_in_blockchain(height, &identity_hash);
let time = last.timestamp + NEW_DOMAINS_INTERVAL - Utc::now().timestamp();
if new_id && time > 0 {
return Cooldown { time };
}
}
Fine
}
pub fn get_domain_renewal_time(&self, identity_hash: &Bytes) -> Option<i64> {
let mut statement = self.db.prepare(SQL_GET_DOMAIN_UPDATE_TIME).unwrap(); let mut statement = self.db.prepare(SQL_GET_DOMAIN_UPDATE_TIME).unwrap();
statement.bind(1, identity_hash.as_slice()).expect("Error in bind"); statement.bind(1, identity_hash.as_slice()).expect("Error in bind");
if let State::Row = statement.next().unwrap() { if let State::Row = statement.next().unwrap() {
let timestamp = statement.read::<i64>(0).unwrap(); let timestamp = statement.read::<i64>(0).unwrap();
if timestamp < Utc::now().timestamp() - DOMAIN_LIFETIME { if timestamp < time - DOMAIN_LIFETIME {
// This domain is too old // This domain is too old
return None; return None;
} }
@ -622,21 +614,20 @@ impl Chain {
None None
} }
pub fn get_domain_transaction_by_id(&self, identity_hash: &Bytes, height: u64) -> Option<Transaction> { pub fn get_identity_transaction_and_state(&self, identity_hash: &Bytes, height: u64, time: i64) -> (Option<Transaction>, DomainState) {
if self.get_domain_renewal_time(identity_hash).is_none() {
// Domain has expired
return None;
}
let mut statement = self.db.prepare(SQL_GET_DOMAIN_BY_ID).unwrap(); let mut statement = self.db.prepare(SQL_GET_DOMAIN_BY_ID).unwrap();
statement.bind(1, identity_hash.as_slice()).expect("Error in bind"); statement.bind(1, identity_hash.as_slice()).expect("Error in bind");
statement.bind(2, height as i64).expect("Error in bind"); statement.bind(2, height as i64).expect("Error in bind");
if let State::Row = statement.next().unwrap() { if let State::Row = statement.next().unwrap() {
let timestamp = statement.read::<i64>(1).unwrap(); let timestamp = statement.read::<i64>(1).unwrap();
if timestamp < Utc::now().timestamp() - DOMAIN_LIFETIME { // Determine current state of the domain
// This domain is too old let state = if timestamp + DOMAIN_LIFETIME >= time {
return None; DomainState::Alive { renewed_time: timestamp, until: timestamp + DOMAIN_LIFETIME }
} } else if timestamp + DOMAIN_LIFETIME + DOMAIN_RENEW_TIME >= time {
DomainState::Expired { renewed_time: timestamp, until: timestamp + DOMAIN_LIFETIME + DOMAIN_RENEW_TIME }
} else {
DomainState::Free { renewed_time: timestamp }
};
let identity = Bytes::from_bytes(&statement.read::<Vec<u8>>(2).unwrap()); let identity = Bytes::from_bytes(&statement.read::<Vec<u8>>(2).unwrap());
let confirmation = Bytes::from_bytes(&statement.read::<Vec<u8>>(3).unwrap()); let confirmation = Bytes::from_bytes(&statement.read::<Vec<u8>>(3).unwrap());
let class = String::from(CLASS_DOMAIN); let class = String::from(CLASS_DOMAIN);
@ -644,30 +635,37 @@ impl Chain {
let signing = Bytes::from_bytes(&statement.read::<Vec<u8>>(5).unwrap()); let signing = Bytes::from_bytes(&statement.read::<Vec<u8>>(5).unwrap());
let encryption = Bytes::from_bytes(&statement.read::<Vec<u8>>(6).unwrap()); let encryption = Bytes::from_bytes(&statement.read::<Vec<u8>>(6).unwrap());
let transaction = Transaction { identity, confirmation, class, data, signing, encryption }; let transaction = Transaction { identity, confirmation, class, data, signing, encryption };
return Some(transaction); return (Some(transaction), state);
} }
None (None, DomainState::NotFound)
} }
/// Gets full Transaction info for any domain. Used by DNS part. /// Gets full Transaction info for any domain. Used by DNS part.
pub fn get_domain_transaction(&self, domain: &str) -> Option<Transaction> { pub fn get_domain_transaction_and_state(&self, domain: &str) -> (Option<Transaction>, DomainState) {
if domain.is_empty() { if domain.is_empty() {
return None; return (None, DomainState::NotFound);
} }
let identity_hash = hash_identity(domain, None); let identity_hash = hash_identity(domain, None);
if let Some(transaction) = self.get_domain_transaction_by_id(&identity_hash, self.get_height()) { let (transaction, state) = self.get_identity_transaction_and_state(&identity_hash, self.get_height(), Utc::now().timestamp());
if let Some(transaction) = transaction {
debug!("Found transaction for domain {}: {:?}", domain, &transaction); debug!("Found transaction for domain {}: {:?}", domain, &transaction);
if transaction.check_identity(domain) { if transaction.check_identity(domain) {
return Some(transaction); return (Some(transaction), state);
} }
} }
None (None, DomainState::NotFound)
} }
pub fn get_domain_info(&self, domain: &str) -> Option<String> { pub fn get_domain_info(&self, domain: &str) -> Option<String> {
match self.get_domain_transaction(domain) { match self.get_domain_transaction_and_state(domain) {
None => None, (None, _) => None,
Some(transaction) => Some(transaction.data) (Some(transaction), state) => {
if matches!(state, DomainState::Alive {..}) {
Some(transaction.data)
} else {
None
}
}
} }
} }
@ -715,7 +713,9 @@ impl Chain {
let signing = Bytes::from_bytes(&statement.read::<Vec<u8>>(3).unwrap()); let signing = Bytes::from_bytes(&statement.read::<Vec<u8>>(3).unwrap());
// Get the last transaction for this id and check if it is still ours // Get the last transaction for this id and check if it is still ours
if let Some(transaction) = self.get_domain_transaction_by_id(&identity, height) { // TODO use state to show it in UI
let (transaction, _state) = self.get_identity_transaction_and_state(&identity, height, Utc::now().timestamp());
if let Some(transaction) = transaction {
if transaction.signing != signing { if transaction.signing != signing {
trace!("Identity {:?} is not ours anymore, skipping", &identity); trace!("Identity {:?} is not ours anymore, skipping", &identity);
continue; continue;
@ -730,7 +730,7 @@ impl Chain {
domain = String::from("unknown"); domain = String::from("unknown");
} }
// TODO optimize // TODO optimize
match self.get_domain_renewal_time(&identity) { match self.get_domain_renewal_time(timestamp, &identity) {
None => result.insert(identity, (domain, timestamp, data)), None => result.insert(identity, (domain, timestamp, data)),
Some(t) => result.insert(identity, (domain, t, data)) Some(t) => result.insert(identity, (domain, t, data))
}; };
@ -855,18 +855,13 @@ impl Chain {
return Bad; return Bad;
} }
// If this domain is not available to this public key // If this domain is not available to this public key
if !self.is_id_available(block.index - 1, &transaction.identity, &block.pub_key) { if !self.is_id_available(block.index - 1, timestamp, &transaction.identity, &block.pub_key) {
warn!("Block {:?} is trying to spoof an identity!", &block); warn!("Block {:?} is trying to spoof an identity!", &block);
return Bad; return Bad;
} }
if let Some(last) = self.get_last_full_block(block.index, Some(&block.pub_key)) { if self.can_mine_identity(&transaction.identity, block.index, block.timestamp, &block.pub_key) != Fine {
if last.index < block.index { warn!("Block {:?} is mined too early!", &block);
let new_id = !self.is_domain_in_blockchain(block.index, &transaction.identity); return Bad;
if new_id && last.timestamp + NEW_DOMAINS_INTERVAL > block.timestamp {
warn!("Block {:?} is mined too early!", &block);
return Bad;
}
}
} }
// Check if yggdrasil only property of zone is not violated // Check if yggdrasil only property of zone is not violated
if let Some(block_data) = transaction.get_domain_data() { if let Some(block_data) = transaction.get_domain_data() {
@ -1015,7 +1010,7 @@ impl Chain {
} }
// Weeks since this domain was changed + 1, but not more than 7 weeks. // Weeks since this domain was changed + 1, but not more than 7 weeks.
// So max discount will be 8 bits of difficulty. // So max discount will be 8 bits of difficulty.
(min((Utc::now().timestamp() - timestamp) / ONE_WEEK, 7i64) + 1) as u32 (min((time - timestamp) / ONE_WEEK, 7i64) + 1) as u32
} }
} }
} }

@ -126,6 +126,18 @@ impl DomainData {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum DomainState {
// Not in blockchain, free to mine
NotFound,
// Active, not expired domain
Alive { renewed_time: i64, until: i64 },
// Expired, but can be renewed only by owner
Expired { renewed_time: i64, until: i64 },
// Expired and can be recaptured by anyone
Free { renewed_time: i64 }
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct Origin { pub struct Origin {
zones: Bytes zones: Bytes

@ -13,7 +13,7 @@ pub enum BlockQuality {
Fork Fork
} }
#[derive(Debug)] #[derive(Debug, PartialEq)]
pub enum MineResult { pub enum MineResult {
Fine, Fine,
WrongName, WrongName,

@ -28,6 +28,8 @@ pub const BLOCK_SIGNERS_START_RANDOM: i64 = 90;
pub const NEW_DOMAINS_INTERVAL: i64 = 86400; // One day in seconds pub const NEW_DOMAINS_INTERVAL: i64 = 86400; // One day in seconds
pub const ONE_WEEK: i64 = 86400 * 7; // One week in seconds pub const ONE_WEEK: i64 = 86400 * 7; // One week in seconds
pub const DOMAIN_LIFETIME: i64 = 86400 * 365; // One year pub const DOMAIN_LIFETIME: i64 = 86400 * 365; // One year
/// Time for the owner to remine his domain and not to loose it
pub const DOMAIN_RENEW_TIME: i64 = 86400 * 30; // One month
pub const MAX_RECORDS: usize = 30; pub const MAX_RECORDS: usize = 30;
pub const MAX_DATA_LEN: usize = 255; pub const MAX_DATA_LEN: usize = 255;
@ -49,4 +51,4 @@ pub const MIN_CONNECTED_NODES_START_SYNC: usize = 4;
pub const MAX_READ_BLOCK_TIME: u128 = 100; pub const MAX_READ_BLOCK_TIME: u128 = 100;
pub const MAX_RECONNECTS: u32 = 5; pub const MAX_RECONNECTS: u32 = 5;
pub const MAX_IDLE_SECONDS: u64 = 180; pub const MAX_IDLE_SECONDS: u64 = 180;
pub const MAX_NODES: usize = 20; pub const MAX_NODES: usize = 15;

@ -460,7 +460,7 @@ impl Network {
let my_id = self.peers.get_my_id().to_owned(); let my_id = self.peers.get_my_id().to_owned();
let answer = match message { let answer = match message {
Message::Hand { app_version, origin, version, public, rand_id } => { Message::Hand { app_version, origin, version, public, rand_id } => {
if app_version.starts_with("0.6") { if !version_compatible(&app_version) {
info!("Banning peer with version {}", &app_version); info!("Banning peer with version {}", &app_version);
return State::Banned; return State::Banned;
} }
@ -495,7 +495,7 @@ impl Network {
if self.peers.is_tween_connect(&rand_id) { if self.peers.is_tween_connect(&rand_id) {
return State::Twin; return State::Twin;
} }
if app_version.starts_with("0.6") { if !version_compatible(&app_version) {
info!("Banning peer with version {}", &app_version); info!("Banning peer with version {}", &app_version);
return State::Banned; return State::Banned;
} }
@ -916,4 +916,11 @@ fn would_block(err: &io::Error) -> bool {
fn interrupted(err: &io::Error) -> bool { fn interrupted(err: &io::Error) -> bool {
err.kind() == io::ErrorKind::Interrupted err.kind() == io::ErrorKind::Interrupted
}
fn version_compatible(version: &str) -> bool {
let my_version = env!("CARGO_PKG_VERSION");
let parts = my_version.split('.').collect::<Vec<&str>>();
let major = format!("{}.{}", parts[0], parts[1]);
version.starts_with(&major)
} }

@ -126,7 +126,10 @@ fn action_check_domain(context: &Arc<Mutex<Context>>, web_view: &mut WebView<()>
let c = context.lock().unwrap(); let c = context.lock().unwrap();
if let Some(keystore) = c.get_keystore() { if let Some(keystore) = c.get_keystore() {
let name = name.to_lowercase(); let name = name.to_lowercase();
let available = c.get_chain().is_domain_available(c.get_chain().get_height(), &name, &keystore.get_public()); let available = match c.chain.can_mine_domain(c.chain.get_height(), &name, &keystore.get_public()) {
MineResult::Fine => true,
_ => false
};
web_view.eval(&format!("domainAvailable({})", available)).expect("Error evaluating!"); web_view.eval(&format!("domainAvailable({})", available)).expect("Error evaluating!");
} }
} }

@ -191,7 +191,8 @@ function editDomain(domain, event) {
recordsBuffer.push(v); recordsBuffer.push(v);
}); });
} }
document.getElementById("new_domain").value = title.replace("." + domain_data.zone, ""); currentDomain = title.replace("." + domain_data.zone, "");
document.getElementById("new_domain").value = currentDomain;
if (typeof domain_data.info !== 'undefined') { if (typeof domain_data.info !== 'undefined') {
document.getElementById("info_text").value = domain_data.info; document.getElementById("info_text").value = domain_data.info;
} }

Loading…
Cancel
Save