2
0
mirror of https://github.com/Revertron/Alfis synced 2024-11-15 06:12:52 +00:00

Decoupled domain records change from domain renewal.

This commit is contained in:
Revertron 2022-04-13 13:02:58 +02:00
parent 90c5ba7e83
commit 5b5943a4aa
8 changed files with 112 additions and 55 deletions

14
Cargo.lock generated
View File

@ -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",

View File

@ -1,6 +1,6 @@
[package]
name = "alfis"
version = "0.6.12"
version = "0.7.0"
authors = ["Revertron <alfis@revertron.com>"]
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"

View File

@ -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<Transaction> {
pub fn get_domain_renewal_time(&self, identity_hash: &Bytes) -> Option<i64> {
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::<i64>(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<i64> {
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::<i64>(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<Transaction> {
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::<i64>(0).unwrap() as u64;
let timestamp = statement.read::<i64>(1).unwrap();
let identity = Bytes::from_bytes(&statement.read::<Vec<u8>>(2).unwrap());
let confirmation = Bytes::from_bytes(&statement.read::<Vec<u8>>(3).unwrap());
let class = String::from(CLASS_DOMAIN);
let data = statement.read::<String>(4).unwrap();
let signing = Bytes::from_bytes(&statement.read::<Vec<u8>>(5).unwrap());
let encryption = Bytes::from_bytes(&statement.read::<Vec<u8>>(6).unwrap());
let timestamp = statement.read::<i64>(0).unwrap();
let identity = Bytes::from_bytes(&statement.read::<Vec<u8>>(1).unwrap());
let data = statement.read::<String>(2).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
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::<DomainData>(&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::<DomainData>(&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<Bytes> {

View File

@ -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;

View File

@ -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) {

View File

@ -53,8 +53,8 @@ pub fn run_interface(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>) {
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<Context>, handle: &Handle<()>) {
}
}
fn action_create_domain(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, web_view: &mut WebView<()>, name: String, data: String, signing: String, encryption: String) {
fn action_create_domain(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, 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<Mutex<Context>>, miner: Arc<Mutex<Miner>>,
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<Mutex<Context>>, miner: Arc<Mutex<Miner>>, class: &str, name: &str, mut data: DomainData, difficulty: u32, keystore: &Keystore, signing: Bytes, encryption: Bytes) {
fn create_domain(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, 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<Mutex<Context>>, miner: Arc<Mutex<Miner>>, 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 }

View File

@ -204,7 +204,7 @@
<div class="modal-content">
<div class="box" id="new_domain_dialog_box">
<button class="delete" aria-label="close" onclick="closeDialog('new_domain_dialog')"></button>
<label class="label">New domain:</label>
<label class="label">Domain name:</label>
<div class="field is-grouped is-fullwidth">
<div class="control field has-addons is-expanded">
<div class="control is-expanded has-icons-left">
@ -265,6 +265,13 @@
<div class="list mt-2" id="domain_records">
<!-- Here will be our domain records, added by dialog -->
</div>
<div class="field mt-3">
<label class="control">
<input type="checkbox" id="renewal"> Renew domain by using higher difficulty while mining (works after block number 4999, until then it does nothing).
</label>
<p class="help">Starting from block 5000 the change of DNS records is decoupled from domain renewal.</p>
</div>
</div>
</div>
</div>

View File

@ -302,8 +302,9 @@ function createDomain() {
data.info = document.getElementById("info_text").value;
data.records = recordsBuffer;
data.contacts = getContacts();
var renewal = document.getElementById("renewal").checked;
data = JSON.stringify(data);
external.invoke(JSON.stringify({cmd: 'mineDomain', name: domain, data: data, signing: ownerSigning, encryption: ownerEncryption}));
external.invoke(JSON.stringify({cmd: 'mineDomain', name: domain, data: data, signing: ownerSigning, encryption: ownerEncryption, renewal: renewal}));
}
function getContacts() {