diff --git a/Cargo.lock b/Cargo.lock index c775937..e1f9b01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,7 +84,7 @@ dependencies = [ [[package]] name = "alfis" -version = "0.6.12" +version = "0.7.0" dependencies = [ "base64", "bincode", @@ -719,9 +719,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.104" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2f96d100e1cf1929e7719b7edb3b90ab5298072638fccd77be9ce942ecdfce" +checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd" [[package]] name = "log" @@ -734,9 +734,9 @@ dependencies = [ [[package]] name = "lru" -version = "0.7.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb87f3080f6d1d69e8c564c0fcfde1d7aa8cc451ce40cae89479111f03bc0eb" +checksum = "32613e41de4c47ab04970c348ca7ae7382cf116625755af070b008a15516a889" dependencies = [ "hashbrown", ] @@ -1291,9 +1291,9 @@ dependencies = [ [[package]] name = "thread-priority" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b3fe6ec8f0ac600de217a06f30b95f9aee46f93158fe7df97797eeee70f1f65" +checksum = "696668a68983ad737e08e11e9afb701e962cab9f07f2a4ff339316b2d5b0870d" dependencies = [ "cfg-if", "libc", diff --git a/Cargo.toml b/Cargo.toml index 66f963b..dec9cf1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "alfis" -version = "0.6.12" +version = "0.7.0" authors = ["Revertron "] edition = "2021" build = "build.rs" @@ -39,7 +39,7 @@ sqlite = "0.26.0" uuid = { version = "0.8.2", features = ["serde", "v4"] } mio = { version = "0.8.2", features = ["os-poll", "net"] } ureq = { version = "2.4", optional = true } -lru = "0.7.3" +lru = "0.7.5" derive_more = "0.99.17" lazy_static = "1.4.0" @@ -50,7 +50,7 @@ open = { version = "2.1.1", optional = true } [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.9", features = ["impl-default", "wincon", "shellscalingapi"] } -thread-priority = "0.8.0" +thread-priority = "0.8.2" [target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies] thread-priority = "0.8.2" diff --git a/src/blockchain/chain.rs b/src/blockchain/chain.rs index a97ad0b..67fd74f 100644 --- a/src/blockchain/chain.rs +++ b/src/blockchain/chain.rs @@ -1,5 +1,5 @@ use std::cell::RefCell; -use std::cmp::max; +use std::cmp::{max, min}; use std::collections::{HashMap, HashSet}; use std::fs; use std::ops::Deref; @@ -37,10 +37,11 @@ const SQL_GET_LAST_FULL_BLOCK: &str = "SELECT * FROM blocks WHERE id < ? AND `tr const SQL_GET_LAST_FULL_BLOCK_FOR_KEY: &str = "SELECT * FROM blocks WHERE id < ? AND `transaction`<>'' AND pub_key = ? ORDER BY id DESC LIMIT 1;"; const SQL_GET_DOMAIN_OWNER_BY_ID: &str = "SELECT signing FROM domains WHERE id < ? AND 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_DOMAINS_BY_KEY: &str = "SELECT * FROM domains WHERE signing = ? ORDER BY id;"; +const SQL_GET_DOMAINS_BY_KEY: &str = "SELECT timestamp, identity, data, signing FROM domains WHERE signing = ? ORDER BY id;"; const SQL_GET_DOMAINS_COUNT: &str = "SELECT count(DISTINCT identity) FROM domains;"; const SQL_GET_USERS_COUNT: &str = "SELECT count(DISTINCT pub_key) FROM blocks;"; const SQL_GET_USER_BLOCK_COUNT: &str = "SELECT count(pub_key) FROM blocks WHERE pub_key = ? AND id < ?"; +const SQL_GET_DOMAIN_UPDATE_TIME: &str = "SELECT domains.timestamp FROM blocks JOIN domains ON blocks.id = domains.id WHERE difficulty >= 23 AND identity = ? ORDER BY domains.id DESC LIMIT 1;"; const SQL_GET_OPTIONS: &str = "SELECT * FROM options;"; @@ -558,7 +559,7 @@ impl Chain { statement.bind(1, height as i64).expect("Error in bind"); statement.bind(2, &***id).expect("Error in bind"); if let State::Row = statement.next().unwrap() { - // If there is such a zone + // If there is such an ID return true; } false @@ -591,7 +592,40 @@ impl Chain { Fine } - pub fn get_id_transaction(&self, identity_hash: &Bytes) -> Option { + pub fn get_domain_renewal_time(&self, identity_hash: &Bytes) -> Option { + let mut statement = self.db.prepare(SQL_GET_DOMAIN_UPDATE_TIME).unwrap(); + statement.bind(1, identity_hash.as_slice()).expect("Error in bind"); + if let State::Row = statement.next().unwrap() { + let timestamp = statement.read::(0).unwrap(); + if timestamp < Utc::now().timestamp() - DOMAIN_LIFETIME { + // This domain is too old + return None; + } + return Some(timestamp); + } + None + } + + pub fn get_domain_update_time(&self, identity_hash: &Bytes) -> Option { + let mut statement = self.db.prepare(SQL_GET_DOMAIN_BY_ID).unwrap(); + statement.bind(1, identity_hash.as_slice()).expect("Error in bind"); + if let State::Row = statement.next().unwrap() { + let timestamp = statement.read::(1).unwrap(); + if timestamp < Utc::now().timestamp() - DOMAIN_LIFETIME { + // This domain is too old + return None; + } + return Some(timestamp); + } + None + } + + pub fn get_domain_transaction_by_id(&self, identity_hash: &Bytes) -> Option { + 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(); statement.bind(1, identity_hash.as_slice()).expect("Error in bind"); if let State::Row = statement.next().unwrap() { @@ -618,7 +652,7 @@ impl Chain { return None; } let identity_hash = hash_identity(domain, None); - if let Some(transaction) = self.get_id_transaction(&identity_hash) { + if let Some(transaction) = self.get_domain_transaction_by_id(&identity_hash) { debug!("Found transaction for domain {}: {:?}", domain, &transaction); if transaction.check_identity(domain) { return Some(transaction); @@ -671,33 +705,31 @@ impl Chain { let mut statement = self.db.prepare(SQL_GET_DOMAINS_BY_KEY).unwrap(); statement.bind(1, &**pub_key).expect("Error in bind"); while let State::Row = statement.next().unwrap() { - let _index = statement.read::(0).unwrap() as u64; - let timestamp = statement.read::(1).unwrap(); - let identity = Bytes::from_bytes(&statement.read::>(2).unwrap()); - let confirmation = Bytes::from_bytes(&statement.read::>(3).unwrap()); - let class = String::from(CLASS_DOMAIN); - let data = statement.read::(4).unwrap(); - let signing = Bytes::from_bytes(&statement.read::>(5).unwrap()); - let encryption = Bytes::from_bytes(&statement.read::>(6).unwrap()); + let timestamp = statement.read::(0).unwrap(); + let identity = Bytes::from_bytes(&statement.read::>(1).unwrap()); + let data = statement.read::(2).unwrap(); + let signing = Bytes::from_bytes(&statement.read::>(3).unwrap()); // Get the last transaction for this id and check if it is still ours - if let Some(transaction) = self.get_id_transaction(&identity) { + if let Some(transaction) = self.get_domain_transaction_by_id(&identity) { if transaction.signing != signing { trace!("Identity {:?} is not ours anymore, skipping", &identity); continue; } } - let transaction = Transaction { identity: identity.clone(), confirmation: confirmation.clone(), class, data, signing, encryption }; //trace!("Found transaction for domain {:?}", &transaction); - if let Some(data) = transaction.get_domain_data() { + if let Ok(data) = serde_json::from_str::(&data) { let decrypted = keystore.decrypt(data.encrypted.as_slice()); let mut domain = String::from_utf8(decrypted.to_vec()).unwrap(); if domain.is_empty() { domain = String::from("unknown"); } - trace!("Found my domain {}", domain); - result.insert(identity, (domain, timestamp, data)); + // TODO optimize + match self.get_domain_renewal_time(&identity) { + None => result.insert(identity, (domain, timestamp, data)), + Some(t) => result.insert(identity, (domain, t, data)) + }; } } result @@ -786,7 +818,7 @@ impl Chain { SIGNER_DIFFICULTY } } - Some(t) => self.get_difficulty_for_transaction(t, block.index) + Some(t) => self.get_difficulty_for_transaction(t) }; if block.difficulty < difficulty { warn!("Block difficulty is lower than needed"); @@ -955,14 +987,12 @@ impl Chain { true } - fn get_difficulty_for_transaction(&self, transaction: &Transaction, index: u64) -> u32 { + fn get_difficulty_for_transaction(&self, transaction: &Transaction) -> u32 { match transaction.class.as_ref() { CLASS_DOMAIN => { // If this domain is already in blockchain we approve slightly smaller difficulty - let discount = match self.is_domain_in_blockchain(index, &transaction.identity) { - true => { 1 } - false => { 0 } - }; + let discount = self.get_identity_discount(&transaction.identity, false); + // TODO move this check somewhere appropriate return match serde_json::from_str::(&transaction.data) { Ok(_) => DOMAIN_DIFFICULTY - discount, Err(_) => { @@ -976,6 +1006,20 @@ impl Chain { } } + pub fn get_identity_discount(&self, identity: &Bytes, renewal: bool) -> u32 { + match self.get_domain_update_time(identity) { + None => 0u32, + Some(timestamp) => { + if renewal || self.get_height() < BLOCKS_WITHOUT_DISCOUNT { + return 1; + } + // Weeks since this domain was changed + 1, but not more than 7 weeks. + // So max discount will be 8 bits of difficulty. + (min((Utc::now().timestamp() - timestamp) / ONE_WEEK, 7i64) + 1) as u32 + } + } + } + /// Gets public keys of a node that needs to mine "signature" block above this block /// block - last full block pub fn get_block_signers(&self, block: &Block) -> Vec { diff --git a/src/commons/constants.rs b/src/commons/constants.rs index 40252d8..f4623e6 100644 --- a/src/commons/constants.rs +++ b/src/commons/constants.rs @@ -7,6 +7,7 @@ pub const ORIGIN_DIFFICULTY: u32 = 28; pub const DOMAIN_DIFFICULTY: u32 = 24; pub const SIGNER_DIFFICULTY: u32 = 16; pub const KEYSTORE_DIFFICULTY: u32 = 23; +pub const BLOCKS_WITHOUT_DISCOUNT: u64 = 4999; /// Blocks start to be signed starting from this index pub const BLOCK_SIGNERS_START: u64 = 35; @@ -25,6 +26,7 @@ pub const LIMITED_CONFIDENCE_DEPTH: u64 = 4; pub const BLOCK_SIGNERS_START_RANDOM: i64 = 90; pub const NEW_DOMAINS_INTERVAL: i64 = 86400; // One day in seconds +pub const ONE_WEEK: i64 = 86400 * 7; // One week in seconds pub const DOMAIN_LIFETIME: i64 = 86400 * 365; // One year pub const MAX_RECORDS: usize = 30; pub const MAX_DATA_LEN: usize = 255; diff --git a/src/p2p/network.rs b/src/p2p/network.rs index dcfe442..45654c2 100644 --- a/src/p2p/network.rs +++ b/src/p2p/network.rs @@ -451,20 +451,29 @@ impl Network { if self.peers.is_our_own_connect(&rand_id) { warn!("Detected loop connect"); State::SendLoop - } else if origin.eq(my_origin) && version == my_version { + } else if origin.eq(my_origin) { let peer = self.peers.get_mut_peer(token).unwrap(); - peer.set_public(public); - peer.set_active(true); debug!("Incoming v{} on {}", &app_version, peer.get_addr().ip()); let app_version = self.context.lock().unwrap().app_version.clone(); - State::message(Message::shake(&app_version, &origin, version, me_public, &my_id, my_height)) + if version == my_version { + peer.set_public(public); + peer.set_active(true); + } else { + warn!("Handshake from unsupported version: {} (local version: {})", version, my_version); + } + State::message(Message::shake(&app_version, &origin, my_version, me_public, &my_id, my_height)) } else { - warn!("Handshake from unsupported chain or version: {}, {}", &origin, version); + warn!("Handshake from unsupported chain: {}", &origin); State::Banned } } Message::Shake { app_version, origin, version, public, rand_id, height } => { - if origin.ne(my_origin) || version != my_version { + if origin.ne(my_origin) { + return State::Banned; + } else if version > my_version { + warn!("Can't work with newer blockchain version {} and ALFIS version {}, please upgrade!", version, &app_version); + return State::Banned; + } else if version != my_version { return State::Banned; } if self.peers.is_tween_connect(&rand_id) { diff --git a/src/web_ui.rs b/src/web_ui.rs index 6962262..2787636 100644 --- a/src/web_ui.rs +++ b/src/web_ui.rs @@ -53,8 +53,8 @@ pub fn run_interface(context: Arc>, miner: Arc>) { SelectKey { index } => { action_select_key(&context, web_view, index); } CheckRecord { data } => { action_check_record(web_view, data); } CheckDomain { name } => { action_check_domain(&context, web_view, name); } - MineDomain { name, data, signing, encryption } => { - action_create_domain(Arc::clone(&context), Arc::clone(&miner), web_view, name, data, signing, encryption); + MineDomain { name, data, signing, encryption, renewal } => { + action_create_domain(Arc::clone(&context), Arc::clone(&miner), web_view, name, data, signing, encryption, renewal); } TransferDomain { .. } => {} StopMining => { post(Event::ActionStopMining); } @@ -390,7 +390,7 @@ fn send_keys_to_ui(context: &MutexGuard, handle: &Handle<()>) { } } -fn action_create_domain(context: Arc>, miner: Arc>, web_view: &mut WebView<()>, name: String, data: String, signing: String, encryption: String) { +fn action_create_domain(context: Arc>, miner: Arc>, web_view: &mut WebView<()>, name: String, data: String, signing: String, encryption: String, renewal: bool) { debug!("Creating domain with data: {}", &data); let c = Arc::clone(&context); let context = context.lock().unwrap(); @@ -443,7 +443,7 @@ fn action_create_domain(context: Arc>, miner: Arc>, match context.chain.can_mine_domain(context.chain.get_height(), &name, &pub_key) { MineResult::Fine => { std::mem::drop(context); - create_domain(c, miner, CLASS_DOMAIN, &name, data, DOMAIN_DIFFICULTY, &keystore, signing, encryption); + create_domain(c, miner, CLASS_DOMAIN, &name, data, DOMAIN_DIFFICULTY, &keystore, signing, encryption, renewal); let _ = web_view.eval("domainMiningStarted();"); event_info(web_view, &format!("Mining of domain \\'{}\\' has started", &name)); } @@ -561,7 +561,7 @@ fn format_event_now(kind: &str, message: &str) -> String { } #[allow(clippy::too_many_arguments)] -fn create_domain(context: Arc>, miner: Arc>, class: &str, name: &str, mut data: DomainData, difficulty: u32, keystore: &Keystore, signing: Bytes, encryption: Bytes) { +fn create_domain(context: Arc>, miner: Arc>, class: &str, name: &str, mut data: DomainData, difficulty: u32, keystore: &Keystore, signing: Bytes, encryption: Bytes, renewal: bool) { let name = name.to_owned(); let encrypted = CryptoBox::encrypt(encryption.as_slice(), name.as_bytes()).expect("Error encrypting domain name!"); data.encrypted = Bytes::from_bytes(&encrypted); @@ -574,13 +574,7 @@ fn create_domain(context: Arc>, miner: Arc>, class: }; let transaction = Transaction::from_str(name, class.to_owned(), data, signing, encryption); // If this domain is already in blockchain we approve slightly smaller difficulty - let discount = { - let context = context.lock().unwrap(); - match context.chain.is_domain_in_blockchain(context.chain.get_height(), &transaction.identity) { - true => { 1 } - false => { 0 } - } - }; + let discount = context.lock().unwrap().chain.get_identity_discount(&transaction.identity, renewal); let block = Block::new(Some(transaction), keystore.get_public(), Bytes::default(), difficulty - discount); miner.lock().unwrap().add_block(block, keystore.clone()); } @@ -595,7 +589,7 @@ pub enum Cmd { SelectKey { index: usize }, CheckRecord { data: String }, CheckDomain { name: String }, - MineDomain { name: String, data: String, signing: String, encryption: String }, + MineDomain { name: String, data: String, signing: String, encryption: String, renewal: bool }, TransferDomain { name: String, owner: String }, StopMining, Open { link: String } diff --git a/src/webview/index.html b/src/webview/index.html index 1536663..da5b7a8 100644 --- a/src/webview/index.html +++ b/src/webview/index.html @@ -204,7 +204,7 @@