Merge pull request #30 from Revertron/feature/v4

Feature/v4
pull/31/head
Revertron 4 years ago committed by GitHub
commit 6a4a8d4106
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,6 +1,6 @@
[package]
name = "alfis"
version = "0.3.14"
version = "0.4.0"
authors = ["Revertron <alfis@revertron.com>"]
edition = "2018"
build = "build.rs"
@ -17,6 +17,8 @@ toml = "0.5.8"
digest = "0.9.0"
sha2 = "0.9.3"
ed25519-dalek = "1.0"
x25519-dalek = "1.1"
chacha20poly1305 = "0.7.1"
signature = "1.3.0"
blakeout = "0.3.0"
num_cpus = "1.13.0"
@ -40,10 +42,14 @@ open = { version = "1.6.0", optional = true }
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.7", features = ["impl-default", "wincon", "shellscalingapi"]}
thread-priority = "0.2.1"
[target.'cfg(target_os = "linux")'.dependencies]
thread-priority = "0.2.1"
[build-dependencies]
minreq = { version = "2.3.1", features = ["punycode", "https-rustls"] }
rust-crypto = "^0.2"
rust-crypto = "^0.2" # TODO change to sha2
winres = "0.1"
[dev-dependencies]

@ -1,5 +1,5 @@
# The hash of first block in a chain to know with which nodes to work
origin = "00000102C2F9BFD2803284D93327F089D60FC72A06F19AF2384567F2646B8348"
origin = "0AE588D62D710422A7972EA1E8A659CC8E93DB59489ACE32C499CD279B000000"
# A path to your key file to load autamatically
key_file = "default.key"
@ -31,4 +31,6 @@ forwarders = ["94.140.14.14:53", "94.140.15.15:53"]
#Mining options
[mining]
# How many CPU threads to spawn for mining, zero = number of CPU cores
threads = 0
threads = 0
# Set lower priority for mining threads
lower = true

@ -113,6 +113,10 @@ if command -v systemctl >/dev/null; then
systemctl disable alfis || true
fi
EOF
cat > /tmp/$PKGNAME/debian/postrm << EOF
#!/bin/sh
rm /var/lib/alfis/blockchain.db
EOF
sudo cp alfis /tmp/$PKGNAME/usr/bin/
cp contrib/systemd/*.service /tmp/$PKGNAME/etc/systemd/system/

@ -1,12 +1,11 @@
extern crate serde;
extern crate serde_json;
extern crate num_bigint;
extern crate num_traits;
use std::fmt::Debug;
use serde::{Serialize, Deserialize};
use crate::bytes::Bytes;
use crate::Transaction;
use crate::blockchain::hash_utils::hash_difficulty;
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub struct Block {
@ -68,4 +67,16 @@ impl Block {
pub fn as_bytes(&self) -> Vec<u8> {
Vec::from(serde_json::to_string(&self).unwrap().as_bytes())
}
pub fn is_better_than(&self, other: &Block) -> bool {
if self.transaction.is_none() && other.transaction.is_some() {
return false;
}
if hash_difficulty(self.hash.as_slice()) < hash_difficulty(other.hash.as_slice()) {
return false;
}
// TODO add more checks
true
}
}

@ -1,5 +1,7 @@
use std::cell::RefCell;
use std::collections::{HashSet, HashMap};
use std::fs;
use std::path::Path;
use chrono::Utc;
#[allow(unused_imports)]
@ -8,49 +10,40 @@ use sqlite::{Connection, State, Statement};
use crate::{Block, Bytes, Keystore, Transaction, check_domain, get_domain_zone};
use crate::commons::constants::*;
use crate::blockchain::enums::{BlockQuality, MineResult};
use crate::blockchain::enums::BlockQuality::*;
use crate::blockchain::types::{BlockQuality, MineResult, Options};
use crate::blockchain::types::BlockQuality::*;
use crate::blockchain::hash_utils::*;
use crate::settings::Settings;
use crate::keys::check_public_key_strength;
use std::cmp::{min, max};
use crate::blockchain::transaction::{ZoneData, DomainData};
use std::ops::Deref;
use crate::dns::protocol::DnsRecord;
use crate::blockchain::enums::MineResult::*;
use crate::blockchain::types::MineResult::*;
const DB_NAME: &str = "blockchain.db";
const SQL_CREATE_TABLES: &str = "CREATE TABLE blocks (
'id' BIGINT NOT NULL PRIMARY KEY,
'timestamp' BIGINT NOT NULL,
'version' INT,
'difficulty' INTEGER,
'random' INTEGER,
'nonce' INTEGER,
'transaction' TEXT,
'prev_block_hash' BINARY,
'hash' BINARY,
'pub_key' BINARY,
'signature' BINARY);
CREATE INDEX block_index ON blocks (id);
CREATE TABLE transactions (id INTEGER PRIMARY KEY AUTOINCREMENT, identity BINARY, confirmation BINARY, method TEXT, data TEXT, pub_key BINARY);
CREATE INDEX ids ON transactions (identity);";
const TEMP_DB_NAME: &str = "temp.db";
const SQL_CREATE_TABLES: &str = include_str!("sql/create_db.sql");
const SQL_ADD_BLOCK: &str = "INSERT INTO blocks (id, timestamp, version, difficulty, random, nonce, 'transaction',\
prev_block_hash, hash, pub_key, signature) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);";
const SQL_REPLACE_BLOCK: &str = "UPDATE blocks SET timestamp = ?, version = ?, difficulty = ?, random = ?, nonce = ?, 'transaction' = ?,\
prev_block_hash = ?, hash = ?, pub_key = ?, signature = ? WHERE id = ?;";
const SQL_GET_LAST_BLOCK: &str = "SELECT * FROM blocks ORDER BY id DESC LIMIT 1;";
const SQL_ADD_TRANSACTION: &str = "INSERT INTO transactions (identity, confirmation, method, data, pub_key) VALUES (?, ?, ?, ?, ?)";
const SQL_ADD_DOMAIN: &str = "INSERT INTO domains (id, timestamp, identity, confirmation, data, pub_key) VALUES (?, ?, ?, ?, ?, ?)";
const SQL_ADD_ZONE: &str = "INSERT INTO zones (id, timestamp, identity, confirmation, data, pub_key) VALUES (?, ?, ?, ?, ?, ?)";
const SQL_DELETE_DOMAIN: &str = "DELETE FROM domains WHERE id = ?";
const SQL_DELETE_ZONE: &str = "DELETE FROM zones WHERE id = ?";
const SQL_GET_BLOCK_BY_ID: &str = "SELECT * FROM blocks WHERE id=? LIMIT 1;";
const SQL_GET_LAST_FULL_BLOCK: &str = "SELECT * FROM blocks WHERE `transaction`<>'' ORDER BY id DESC LIMIT 1;";
const SQL_GET_LAST_FULL_BLOCK_FOR_KEY: &str = "SELECT * FROM blocks WHERE `transaction`<>'' AND pub_key = ? ORDER BY id DESC LIMIT 1;";
const SQL_GET_PUBLIC_KEY_BY_ID: &str = "SELECT pub_key FROM transactions WHERE identity = ? ORDER BY id DESC LIMIT 1;";
const SQL_GET_ID_BY_ID: &str = "SELECT identity FROM transactions WHERE identity = ? ORDER BY id DESC LIMIT 1;";
const SQL_GET_TRANSACTION_BY_ID: &str = "SELECT * FROM transactions WHERE identity = ? ORDER BY id DESC LIMIT 1;";
const SQL_GET_TRANSACTIONS_WITH_ZONE: &str = "SELECT data FROM transactions WHERE data LIKE '%difficulty%';";
const SQL_GET_DOMAIN_PUBLIC_KEY_BY_ID: &str = "SELECT pub_key FROM domains WHERE identity = ? ORDER BY id DESC LIMIT 1;";
const SQL_GET_ZONE_PUBLIC_KEY_BY_ID: &str = "SELECT pub_key FROM zones WHERE identity = ? ORDER BY id DESC LIMIT 1;";
const SQL_GET_DOMAIN_BY_ID: &str = "SELECT * FROM domains WHERE identity = ? ORDER BY id DESC LIMIT 1;";
const SQL_GET_ZONES: &str = "SELECT data FROM zones;";
const SQL_GET_OPTIONS: &str = "SELECT * FROM options;";
pub struct Chain {
origin: Bytes,
pub version: u32,
pub blocks: Vec<Block>,
last_block: Option<Block>,
last_full_block: Option<Block>,
max_height: u64,
@ -63,22 +56,22 @@ impl Chain {
let origin = settings.get_origin();
let db = sqlite::open(DB_NAME).expect("Unable to open blockchain DB");
let mut chain = Chain {
origin,
version: CHAIN_VERSION,
blocks: Vec::new(),
last_block: None,
last_full_block: None,
max_height: 0,
db,
zones: RefCell::new(HashSet::new()),
};
let zones = RefCell::new(HashSet::new());
let mut chain = Chain { origin, last_block: None, last_full_block: None, max_height: 0, db, zones };
chain.init_db();
chain
}
/// Reads options from DB or initializes and writes them to DB if not found
fn init_db(&mut self) {
let options = self.get_options();
if !self.origin.is_zero() && !options.origin.is_empty() && self.origin.to_string() != options.origin {
self.clear_db();
}
if options.version < DB_VERSION {
self.migrate_db(options.version, DB_VERSION);
}
// Trying to get last block from DB to check its version
let block: Option<Block> = match self.db.prepare(SQL_GET_LAST_BLOCK) {
Ok(mut statement) => {
@ -98,21 +91,14 @@ impl Chain {
}
result
}
Err(_) => {
info!("No blockchain database found. Creating new.");
self.db.execute(SQL_CREATE_TABLES).expect("Error creating blocks table");
Err(e) => {
info!("No blockchain database found. Creating new. {}", e);
self.db.execute(SQL_CREATE_TABLES).expect("Error creating DB tables");
None
}
};
// If some block loaded we check its version and determine if we need some migration
if let Some(block) = block {
self.max_height = block.index;
if self.version > block.version {
self.migrate_db(block.version, self.version);
} else if self.version < block.version {
error!("Version downgrade {}->{} is not supported!", block.version, self.version);
panic!();
}
// Cache some info
self.last_block = Some(block.clone());
if block.transaction.is_some() {
@ -127,9 +113,41 @@ impl Chain {
debug!("Migrating DB from {} to {}", from, to);
}
fn clear_db(&mut self) {
warn!("Clearing DB");
// We cannot close DB connection and recreate file,
// therefore we switch our db to temporary file, delete main DB and switch back.
// I know that this is a crutch, but this way I don't need to use Option<db> :)
self.db = sqlite::open(TEMP_DB_NAME).expect("Unable to open temporary blockchain DB");
let file = Path::new(DB_NAME);
if fs::remove_file(&file).is_err() {
panic!("Unable to remove database!");
}
self.db = sqlite::open(DB_NAME).expect("Unable to open blockchain DB");
let file = Path::new(TEMP_DB_NAME);
let _ = fs::remove_file(&file).is_err();
}
fn get_options(&self) -> Options {
let mut options = Options::empty();
if let Ok(mut statement) = self.db.prepare(SQL_GET_OPTIONS) {
while let State::Row = statement.next().unwrap() {
let name = statement.read::<String>(0).unwrap();
let value = statement.read::<String>(1).unwrap();
match name.as_ref() {
"origin" => options.origin = value,
"version" => options.version = value.parse().unwrap(),
_ => {}
}
}
}
options
}
pub fn add_block(&mut self, block: Block) {
debug!("Adding block:\n{:?}", &block);
self.blocks.push(block.clone());
let index = block.index;
let timestamp = block.timestamp;
self.last_block = Some(block.clone());
if block.transaction.is_some() {
self.last_full_block = Some(block.clone());
@ -137,9 +155,37 @@ impl Chain {
let transaction = block.transaction.clone();
if self.add_block_to_table(block).is_ok() {
if let Some(transaction) = transaction {
self.add_transaction_to_table(&transaction).expect("Error adding transaction");
self.add_transaction_to_table(index, timestamp, &transaction).expect("Error adding transaction");
}
}
}
pub fn replace_block(&mut self, index: u64, block: Block) -> sqlite::Result<()> {
debug!("Replacing block {} with:\n{:?}", index, &block);
let old_block = self.get_block(index).unwrap();
if old_block.transaction.is_some() {
let mut statement = self.db.prepare(SQL_DELETE_DOMAIN)?;
statement.bind(1, index as i64)?;
statement.next()?;
let mut statement = self.db.prepare(SQL_DELETE_ZONE)?;
statement.bind(1, index as i64)?;
statement.next()?;
}
let index = block.index;
let timestamp = block.timestamp;
self.last_block = Some(block.clone());
if block.transaction.is_some() {
self.last_full_block = Some(block.clone());
}
let transaction = block.transaction.clone();
if self.replace_block_in_table(block).is_ok() {
if let Some(transaction) = transaction {
self.add_transaction_to_table(index, timestamp, &transaction).expect("Error adding transaction");
}
}
Ok(())
}
/// Adds block to blocks table
@ -164,14 +210,43 @@ impl Chain {
statement.next()
}
/// Replaces block in blocks table on arrival of better block from some fork
fn replace_block_in_table(&mut self, block: Block) -> sqlite::Result<State> {
let mut statement = self.db.prepare(SQL_REPLACE_BLOCK)?;
statement.bind(1, block.timestamp as i64)?;
statement.bind(2, block.version as i64)?;
statement.bind(3, block.difficulty as i64)?;
statement.bind(4, block.random as i64)?;
statement.bind(5, block.nonce as i64)?;
match &block.transaction {
None => { statement.bind(6, "")?; }
Some(transaction) => {
statement.bind(6, transaction.to_string().as_str())?;
}
}
statement.bind(7, &**block.prev_block_hash)?;
statement.bind(8, &**block.hash)?;
statement.bind(9, &**block.pub_key)?;
statement.bind(10, &**block.signature)?;
statement.bind(11, block.index as i64)?;
statement.next()
}
/// Adds transaction to transactions table
fn add_transaction_to_table(&mut self, t: &Transaction) -> sqlite::Result<State> {
let mut statement = self.db.prepare(SQL_ADD_TRANSACTION)?;
statement.bind(1, &**t.identity)?;
statement.bind(2, &**t.confirmation)?;
statement.bind(3, t.method.as_ref() as &str)?;
statement.bind(4, t.data.as_ref() as &str)?;
statement.bind(5, &**t.pub_key)?;
fn add_transaction_to_table(&mut self, index: u64, timestamp: i64, t: &Transaction) -> sqlite::Result<State> {
let sql = match t.class.as_ref() {
"domain" => SQL_ADD_DOMAIN,
"zone" => SQL_ADD_ZONE,
_ => return Err(sqlite::Error { code: None, message: None })
};
let mut statement = self.db.prepare(sql)?;
statement.bind(1, index as i64)?;
statement.bind(2, timestamp)?;
statement.bind(3, &**t.identity)?;
statement.bind(4, &**t.confirmation)?;
statement.bind(5, t.data.as_ref() as &str)?;
statement.bind(6, &**t.pub_key)?;
statement.next()
}
@ -244,7 +319,7 @@ impl Chain {
return false;
}
let identity_hash = hash_identity(domain, None);
if !self.is_id_available(&identity_hash, &keystore.get_public()) {
if !self.is_id_available(&identity_hash, &keystore.get_public(), false) {
return false;
}
@ -260,8 +335,13 @@ impl Chain {
}
/// Checks if this identity is free or is owned by the same pub_key
pub fn is_id_available(&self, identity: &Bytes, public_key: &Bytes) -> bool {
let mut statement = self.db.prepare(SQL_GET_PUBLIC_KEY_BY_ID).unwrap();
pub fn is_id_available(&self, identity: &Bytes, public_key: &Bytes, zone: bool) -> bool {
let sql = match zone {
true => { SQL_GET_ZONE_PUBLIC_KEY_BY_ID }
false => { SQL_GET_DOMAIN_PUBLIC_KEY_BY_ID }
};
let mut statement = self.db.prepare(sql).unwrap();
statement.bind(1, &***identity).expect("Error in bind");
while let State::Row = statement.next().unwrap() {
let pub_key = Bytes::from_bytes(&statement.read::<Vec<u8>>(0).unwrap());
@ -274,7 +354,7 @@ impl Chain {
pub fn get_zones(&self) -> Vec<ZoneData> {
let mut map = HashMap::new();
match self.db.prepare(SQL_GET_TRANSACTIONS_WITH_ZONE) {
match self.db.prepare(SQL_GET_ZONES) {
Ok(mut statement) => {
while statement.next().unwrap() == State::Row {
let data = statement.read::<String>(0).unwrap();
@ -300,7 +380,7 @@ impl Chain {
// Checking for existing zone in DB
let identity_hash = hash_identity(zone, None);
if self.is_id_in_blockchain(&identity_hash) {
if self.is_id_in_blockchain(&identity_hash, true) {
// If there is such a zone
self.zones.borrow_mut().insert(zone.to_owned());
return true;
@ -309,9 +389,13 @@ impl Chain {
}
/// Checks if some id exists in our blockchain
pub fn is_id_in_blockchain(&self, id: &Bytes) -> bool {
pub fn is_id_in_blockchain(&self, id: &Bytes, zone: bool) -> bool {
let sql = match zone {
true => { SQL_GET_ZONE_PUBLIC_KEY_BY_ID }
false => { SQL_GET_DOMAIN_PUBLIC_KEY_BY_ID }
};
// Checking for existing zone in DB
let mut statement = self.db.prepare(SQL_GET_ID_BY_ID).unwrap();
let mut statement = self.db.prepare(sql).unwrap();
statement.bind(1, &***id).expect("Error in bind");
while let State::Row = statement.next().unwrap() {
// If there is such a zone
@ -320,7 +404,7 @@ impl Chain {
false
}
pub fn can_mine_domain(&self, domain: &str, records: &str, pub_key: &Bytes) -> MineResult {
pub fn can_mine_domain(&self, domain: &str, pub_key: &Bytes) -> MineResult {
let name = domain.to_lowercase();
if !check_domain(&name, true) {
return WrongName;
@ -334,13 +418,10 @@ impl Chain {
return NotOwned;
}
}
if serde_json::from_str::<Vec<DnsRecord>>(&records).is_err() {
return WrongData;
}
let identity_hash = hash_identity(&name, None);
if let Some(last) = self.get_last_full_block(Some(&pub_key)) {
let new_id = !self.is_id_in_blockchain(&identity_hash);
let time = last.timestamp + FULL_BLOCKS_INTERVAL - Utc::now().timestamp();
let new_id = !self.is_id_in_blockchain(&identity_hash, false);
let time = last.timestamp + NEW_DOMAINS_INTERVAL - Utc::now().timestamp();
if new_id && time > 0 {
return Cooldown { time }
}
@ -356,15 +437,20 @@ impl Chain {
}
let identity_hash = hash_identity(domain, None);
let mut statement = self.db.prepare(SQL_GET_TRANSACTION_BY_ID).unwrap();
let mut statement = self.db.prepare(SQL_GET_DOMAIN_BY_ID).unwrap();
statement.bind(1, &**identity_hash).expect("Error in bind");
while let State::Row = statement.next().unwrap() {
let identity = Bytes::from_bytes(&statement.read::<Vec<u8>>(1).unwrap());
let confirmation = Bytes::from_bytes(&statement.read::<Vec<u8>>(2).unwrap());
let method = statement.read::<String>(3).unwrap();
let data = statement.read::<String>(4).unwrap();
let pub_key = Bytes::from_bytes(&statement.read::<Vec<u8>>(5).unwrap());
let transaction = Transaction { identity, confirmation, method, data, pub_key };
let timestamp = statement.read::<i64>(1).unwrap();
if timestamp < Utc::now().timestamp() - DOMAIN_LIFETIME {
// This domain is too old
return None;
}
let identity = Bytes::from_bytes(&statement.read::<Vec<u8>>(2).unwrap());
let confirmation = Bytes::from_bytes(&statement.read::<Vec<u8>>(3).unwrap());
let method = statement.read::<String>(4).unwrap();
let data = statement.read::<String>(5).unwrap();
let pub_key = Bytes::from_bytes(&statement.read::<Vec<u8>>(6).unwrap());
let transaction = Transaction { identity, confirmation, class: method, data, pub_key };
debug!("Found transaction for domain {}: {:?}", domain, &transaction);
if transaction.check_identity(domain) {
return Some(transaction);
@ -381,18 +467,13 @@ impl Chain {
}
pub fn get_zone_difficulty(&self, zone: &str) -> u32 {
match self.get_domain_transaction(zone) {
None => { u32::max_value() }
Some(transaction) => {
match serde_json::from_str::<ZoneData>(&transaction.data) {
Ok(data) => { data.difficulty }
Err(_) => {
warn!("Wrong data for zone {}!", zone);
u32::max_value()
}
}
let zones = self.get_zones();
for z in zones.iter() {
if z.name.eq(zone) {
return z.difficulty;
}
}
u32::max_value()
}
pub fn last_block(&self) -> Option<Block> {
@ -441,7 +522,7 @@ impl Chain {
/// Check if this block can be added to our blockchain
pub fn check_new_block(&self, block: &Block) -> BlockQuality {
let timestamp = Utc::now().timestamp();
if block.timestamp > timestamp {
if block.timestamp > timestamp + 60 {
warn!("Ignoring block from the future:\n{:?}", &block);
return Bad;
}
@ -449,15 +530,21 @@ impl Chain {
warn!("Ignoring block with weak public key:\n{:?}", &block);
return Bad;
}
let difficulty = match block.transaction {
None => { LOCKER_DIFFICULTY }
Some(_) => { BLOCK_DIFFICULTY }
let difficulty = match &block.transaction {
None => {
if block.index == 1 {
ZONE_DIFFICULTY
} else {
LOCKER_DIFFICULTY
}
}
Some(t) => { self.get_difficulty_for_transaction(&t) }
};
if block.difficulty < difficulty {
warn!("Block difficulty is lower than needed");
return Bad;
}
if !hash_is_good(&block.hash, block.difficulty as usize) {
if hash_difficulty(&block.hash) < block.difficulty {
warn!("Ignoring block with low difficulty:\n{:?}", &block);
return Bad;
}
@ -470,23 +557,18 @@ impl Chain {
return Bad;
}
if let Some(transaction) = &block.transaction {
if !self.is_id_available(&transaction.identity, &block.pub_key) {
// TODO check for zone transaction
if !self.is_id_available(&transaction.identity, &block.pub_key, false) {
warn!("Block {:?} is trying to spoof an identity!", &block);
return Bad;
}
if let Some(last) = self.get_last_full_block(Some(&block.pub_key)) {
let new_id = !self.is_id_in_blockchain(&transaction.identity);
if new_id && last.timestamp + FULL_BLOCKS_INTERVAL > block.timestamp {
let new_id = !self.is_id_in_blockchain(&transaction.identity, false);
if new_id && last.timestamp + NEW_DOMAINS_INTERVAL > block.timestamp {
warn!("Block {:?} is mined too early!", &block);
return Bad;
}
}
if let Ok(data) = serde_json::from_str::<DomainData>(&transaction.data) {
if self.get_zone_difficulty(&data.zone) > block.difficulty {
warn!("Block {:?} is mined with too low difficulty!", &block);
return Bad;
}
}
}
match &self.last_block {
None => {
@ -505,25 +587,25 @@ impl Chain {
return Bad;
}
if last_block.index + 1 < block.index {
warn!("Block is from the future, how is this possible?");
warn!("Block {} arrived too early.", block.index);
return Future;
}
if block.index > LOCKER_BLOCK_START {
if block.index >= LOCKER_BLOCK_START {
// If this block is locked part of blockchain
if let Some(full_block) = &self.last_full_block {
let locker_blocks = self.height() - full_block.index;
if locker_blocks < LOCKER_BLOCK_SIGNS {
// Last full block is not locked enough
if block.transaction.is_some() {
warn!("Someone mined full block over full block");
warn!("Not enough signing blocks over full {} block!", full_block.index);
return Bad;
} else {
if self.check_block_for_lock(&block, full_block) == Bad {
if self.check_block_for_signing(&block, full_block) == Bad {
return Bad;
}
}
} else if locker_blocks < LOCKER_BLOCK_LOCKERS && block.transaction.is_none() {
if self.check_block_for_lock(&block, full_block) == Bad {
if self.check_block_for_signing(&block, full_block) == Bad {
return Bad;
}
}
@ -531,7 +613,7 @@ impl Chain {
}
if block.index <= last_block.index {
if last_block.hash == block.hash {
if block.index == last_block.index && last_block.hash == block.hash {
debug!("Ignoring block {}, we already have it", block.index);
return Twin;
}
@ -552,27 +634,47 @@ impl Chain {
Good
}
fn check_block_for_lock(&self, block: &Block, full_block: &Block) -> BlockQuality {
fn get_difficulty_for_transaction(&self, transaction: &Transaction) -> u32 {
match transaction.class.as_ref() {
"domain" => {
return match serde_json::from_str::<DomainData>(&transaction.data) {
Ok(data) => {
for zone in self.get_zones().iter() {
if zone.name == data.zone {
return zone.difficulty;
}
}
u32::max_value()
}
Err(_) => { u32::max_value() }
}
}
"zone" => { ZONE_DIFFICULTY }
_ => { u32::max_value() }
}
}
fn check_block_for_signing(&self, block: &Block, full_block: &Block) -> BlockQuality {
// If we got a locker/signing block
let lockers: HashSet<Bytes> = self.get_block_lockers(full_block).into_iter().collect();
if !lockers.contains(&block.pub_key) {
warn!("Ignoring block {}, as wrong locker", block.index);
let signers: HashSet<Bytes> = self.get_block_signers(full_block).into_iter().collect();
if !signers.contains(&block.pub_key) {
warn!("Ignoring block {} from '{:?}', as wrong signer!", block.index, &block.pub_key);
return Bad;
}
// If this locker's public key has already locked/signed that block we return error
// If this signers' public key has already locked/signed that block we return error
for i in (full_block.index + 1)..block.index {
let locker = self.get_block(i).expect("Error in DB!");
if locker.pub_key == block.pub_key {
warn!("Ignoring block {}, already locked by this key", block.index);
let signer = self.get_block(i).expect("Error in DB!");
if signer.pub_key == block.pub_key {
warn!("Ignoring block {} from '{:?}', already signed by this key", block.index, &block.pub_key);
return Bad;
}
}
Good
}
/// Gets a public key of a node that needs to mine "locker" block above this block
/// Gets public keys of a node that needs to mine "signature" block above this block
/// block - last full block
pub fn get_block_lockers(&self, block: &Block) -> Vec<Bytes> {
pub fn get_block_signers(&self, block: &Block) -> Vec<Bytes> {
let mut result = Vec::new();
if block.index < LOCKER_BLOCK_START {
return result;
@ -592,7 +694,7 @@ impl Chain {
count += 1;
}
}
trace!("Got lockers for block {}: {:?}", block.index, &result);
trace!("Got signers for block {}: {:?}", block.index, &result);
result
}

@ -1,20 +0,0 @@
/// Represents a result of block check on block's arrival
#[derive(PartialEq)]
pub enum BlockQuality {
Good,
Twin,
Future,
Bad,
Fork,
}
#[derive(Debug)]
pub enum MineResult {
Fine,
WrongName,
WrongData,
WrongKey,
WrongZone,
NotOwned,
Cooldown { time: i64 }
}

@ -1,9 +1,8 @@
use blakeout::Blakeout;
use num_bigint::BigUint;
use num_traits::One;
use crate::{Block, Bytes, Keystore};
use sha2::{Sha256, Digest};
use std::convert::TryInto;
/// Checks block's hash and returns true on valid hash or false otherwise
pub fn check_block_hash(block: &Block) -> bool {
@ -31,12 +30,17 @@ pub fn check_block_signature(block: &Block) -> bool {
/// Hashes some identity (domain in case of DNS). If you give it a public key, it will hash with it as well.
/// Giving public key is needed to create a confirmation field in [Transaction]
pub fn hash_identity(identity: &str, key: Option<&Bytes>) -> Bytes {
let mut digest = Sha256::default();
digest.update(identity.as_bytes());
if let Some(key) = key {
digest.update(key.as_slice());
let base = hash_sha256(identity.as_bytes());
let identity = hash_sha256(&base);
match key {
None => { Bytes::from_bytes(&identity) }
Some(key) => {
let mut buf = Vec::new();
buf.append(&mut identity.clone());
buf.append(&mut key.to_vec());
Bytes::from_bytes(&hash_sha256(&buf))
}
}
Bytes::from_bytes(&digest.finalize()[..])
}
/// There is no default PartialEq implementation for arrays > 32 in size
@ -54,10 +58,56 @@ pub fn same_hash(left: &[u8], right: &[u8]) -> bool {
result
}
/// Checks if this hash contains enough zeroes
pub fn hash_is_good(hash: &[u8], difficulty: usize) -> bool {
let target = BigUint::one() << ((hash.len() << 3) - difficulty);
let hash_int = BigUint::from_bytes_be(&hash);
/// Returns hash difficulty
pub fn hash_difficulty(hash: &[u8]) -> u32 {
let bytes: [u8; 8] = hash[..8].try_into().unwrap();
let int_start = u64::from_be_bytes(bytes);
let bytes: [u8; 8] = hash[hash.len() - 8..].try_into().unwrap();
let int_end = u64::from_be_bytes(bytes);
int_start.leading_zeros() + int_end.trailing_zeros()
}
pub fn hash_difficulty_key(hash: &[u8]) -> u32 {
let bytes: [u8; 8] = hash[..8].try_into().unwrap();
let int = u64::from_be_bytes(bytes);
int.leading_zeros()
}
pub fn hash_sha256(data: &[u8]) -> Vec<u8> {
let mut digest = Sha256::default();
digest.update(data.as_ref());
Vec::from(&digest.finalize()[..])
}
#[cfg(test)]
mod tests {
use crate::blockchain::hash_utils::hash_sha256;
use std::convert::TryInto;
#[test]
pub fn test_hash() {
let id = b"example.com";
let key = b"some_key";
let base = hash_sha256(id);
let identity = hash_sha256(&base);
return hash_int < target;
let mut buf = Vec::new();
buf.append(&mut identity.clone());
buf.append(&mut key.to_vec());
let confirmation = hash_sha256(&buf);
println!("result1 = {:?}", &base);
println!("result2 = {:?}", &identity);
println!("result3 = {:?}", &confirmation);
}
#[test]
fn test_hash_is_good() {
let hash = vec!(0u8,0u8,0u8,255,255,255,255,255);
let bytes: [u8; 8] = hash[..8].try_into().unwrap();
let int = u64::from_be_bytes(bytes);
println!("int = {}", int);
}
}

@ -7,5 +7,5 @@ pub mod block;
pub mod chain;
pub mod filter;
pub mod hash_utils;
pub mod enums;
pub mod types;

@ -0,0 +1,36 @@
CREATE TABLE blocks (
'id' BIGINT NOT NULL PRIMARY KEY,
'timestamp' BIGINT NOT NULL,
'version' INT,
'difficulty' INTEGER,
'random' INTEGER,
'nonce' INTEGER,
'transaction' TEXT,
'prev_block_hash' BINARY,
'hash' BINARY,
'pub_key' BINARY,
'signature' BINARY
);
CREATE INDEX block_index ON blocks (id);
CREATE INDEX keys ON blocks (pub_key);
CREATE TABLE domains (
'id' BIGINT NOT NULL PRIMARY KEY,
'timestamp' BIGINT NOT NULL,
'identity' BINARY,
'confirmation' BINARY,
'data' TEXT,
'pub_key' BINARY
);
CREATE INDEX ids ON domains ('identity');
CREATE TABLE zones (
'id' BIGINT NOT NULL PRIMARY KEY,
'timestamp' BIGINT NOT NULL,
'identity' BINARY,
'confirmation' BINARY,
'data' TEXT,
'pub_key' BINARY
);
CREATE TABLE options ('name' TEXT NOT NULL, 'value' TEXT NOT NULL);

@ -15,7 +15,7 @@ extern crate serde_json;
pub struct Transaction {
pub identity: Bytes,
pub confirmation: Bytes,
pub method: String,
pub class: String,
pub data: String,
pub pub_key: Bytes,
}
@ -28,7 +28,7 @@ impl Transaction {
}
pub fn new(identity: Bytes, confirmation: Bytes, method: String, data: String, pub_key: Bytes) -> Self {
Transaction { identity, confirmation, method, data, pub_key }
Transaction { identity, confirmation, class: method, data, pub_key }
}
pub fn from_json(json: &str) -> Option<Self> {
@ -60,9 +60,9 @@ impl fmt::Debug for Transaction {
fmt.debug_struct("Transaction")
.field("identity", &self.identity)
.field("confirmation", &self.confirmation)
.field("method", &self.method)
.field("class", &self.class)
.field("data", &self.data)
.field("pub", &&self.pub_key)
.field("pub_key", &&self.pub_key)
.finish()
}
}
@ -72,7 +72,7 @@ impl Serialize for Transaction {
let mut structure = serializer.serialize_struct("Transaction", 5).unwrap();
structure.serialize_field("identity", &self.identity)?;
structure.serialize_field("confirmation", &self.confirmation)?;
structure.serialize_field("method", &self.method)?;
structure.serialize_field("class", &self.class)?;
structure.serialize_field("data", &self.data)?;
structure.serialize_field("pub_key", &self.pub_key)?;
structure.end()
@ -81,24 +81,43 @@ impl Serialize for Transaction {
#[derive(Clone, Serialize, Deserialize, PartialEq)]
pub struct DomainData {
pub domain: Bytes,
pub zone: String,
pub records: Vec<DnsRecord>
pub records: Vec<DnsRecord>,
pub contacts: Vec<ContactsData>,
#[serde(default)]
pub owners: Vec<Bytes>
}
impl DomainData {
pub fn new(zone: String, records: Vec<DnsRecord>) -> Self {
Self { zone, records }
pub fn new(domain: Bytes, zone: String, records: Vec<DnsRecord>, contacts: Vec<ContactsData>, owners: Vec<Bytes>) -> Self {
Self { domain, zone, records, contacts, owners }
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct ZoneData {
pub name: String,
pub difficulty: u32
pub difficulty: u32,
pub yggdrasil: bool,
#[serde(default)]
pub owners: Vec<Bytes>
}
impl Display for ZoneData {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.write_str(&format!("{} ({})", self.name, self.difficulty))
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct ContactsData {
pub name: String,
pub value: String
}
impl Display for ContactsData {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.write_str(&format!("{}: {}", self.name, self.value))
}
}

@ -0,0 +1,36 @@
/// Represents a result of block check on block's arrival
#[derive(PartialEq)]
pub enum BlockQuality {
Good,
Twin,
Future,
Bad,
Fork,
}
#[derive(Debug)]
pub enum MineResult {
Fine,
WrongName,
WrongData,
WrongKey,
WrongZone,
NotOwned,
Cooldown { time: i64 },
}
#[derive(Debug)]
pub struct Options {
pub origin: String,
pub version: u32,
}
impl Options {
pub fn new(origin: String, version: u32) -> Self {
Options { origin, version }
}
pub fn empty() -> Self {
Options { origin: String::new(), version: 0 }
}
}

@ -1,9 +1,10 @@
pub const CHAIN_VERSION: u32 = 2;
pub const DB_VERSION: u32 = 0;
pub const CHAIN_VERSION: u32 = 0;
pub const ZONE_DIFFICULTY: u32 = 22;
pub const BLOCK_DIFFICULTY: u32 = 20;
pub const LOCKER_DIFFICULTY: u32 = 14;
pub const KEYSTORE_DIFFICULTY: usize = 23;
pub const ZONE_DIFFICULTY: u32 = 28;
pub const ZONE_MIN_DIFFICULTY: u32 = 22;
pub const LOCKER_DIFFICULTY: u32 = 16;
pub const KEYSTORE_DIFFICULTY: u32 = 23;
pub const LOCKER_BLOCK_START: u64 = 35;
pub const LOCKER_BLOCK_LOCKERS: u64 = 7;
@ -11,7 +12,11 @@ pub const LOCKER_BLOCK_SIGNS: u64 = 4;
pub const LOCKER_BLOCK_TIME: i64 = 300;
pub const LOCKER_BLOCK_INTERVAL: u64 = 50;
pub const FULL_BLOCKS_INTERVAL: i64 = 86400; // One day in seconds
pub const NEW_DOMAINS_INTERVAL: i64 = 86400; // One day in seconds
pub const DOMAIN_LIFETIME: i64 = 86400 * 365; // One year
pub const ZONE_MAX_LENGTH: usize = 10;
pub const MAX_RECONNECTS: u32 = 5;
pub const MAX_RECONNECTS: u32 = 5;
pub const CLASS_ZONE: &str = "zone";
pub const CLASS_DOMAIN: &str = "domain";

@ -5,6 +5,9 @@ pub mod constants;
pub use constants::*;
use std::net::IpAddr;
#[cfg(not(target_os = "macos"))]
use thread_priority::*;
/// Convert bytes array to HEX format
pub fn to_hex(buf: &[u8]) -> String {
let mut result = String::new();
@ -91,6 +94,25 @@ pub fn is_yggdrasil(addr: &IpAddr) -> bool {
false
}
#[cfg(target_os = "windows")]
#[allow(unused_variables)]
pub fn setup_miner_thread(cpu: u32) {
let _ = set_current_thread_priority(ThreadPriority::Min);
//let _ = set_current_thread_ideal_processor(IdealProcessor::from(cpu));
}
#[cfg(target_os = "linux")]
#[allow(unused_variables)]
pub fn setup_miner_thread(cpu: u32) {
let _ = set_current_thread_priority(ThreadPriority::Min);
}
#[cfg(target_os = "macos")]
#[allow(unused_variables)]
pub fn setup_miner_thread(cpu: u32) {
// MacOS is not supported by thread_priority crate
}
#[cfg(test)]
mod test {
use crate::{check_domain, is_yggdrasil};

@ -0,0 +1,55 @@
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
use chacha20poly1305::aead::{Aead, NewAead};
use std::fmt::{Debug, Formatter};
use std::fmt;
const FAILURE: &str = "encryption failure!";
/// A small wrap-up to use Chacha20 encryption for domain names.
#[derive(Clone)]
pub struct Chacha {
pub cipher: ChaCha20Poly1305
}
impl Chacha {
pub fn new(seed: &[u8]) -> Self {
let key = Key::from_slice(seed);
let cipher = ChaCha20Poly1305::new(key);
Chacha { cipher }
}
pub fn encrypt(&self, data: &[u8], nonce: &[u8]) -> Vec<u8> {
let nonce = Nonce::from_slice(nonce);
Vec::from(self.cipher.encrypt(nonce, data.as_ref()).expect(FAILURE))
}
pub fn decrypt(&self, data: &[u8], nonce: &[u8]) -> Vec<u8> {
let nonce = Nonce::from_slice(nonce);
Vec::from(self.cipher.decrypt(nonce, data.as_ref()).expect(FAILURE))
}
}
impl Debug for Chacha {
fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
fmt.write_str("ChaCha20Poly1305")
}
}
#[cfg(test)]
mod tests {
use crate::crypto::Chacha;
use crate::to_hex;
#[test]
pub fn test_curved_chacha() {
let buf = b"178135D209C697625E3EC71DA5C760382E54936F824EE5083908DA66B14ECE18";
let keys1 = Chacha::new(b"178135D209C697625E3EC71DA5C76038", );
let bytes = keys1.encrypt(b"TEST", &buf[..12]);
println!("{}", to_hex(&bytes));
let keys2 = Chacha::new(b"178135D209C697625E3EC71DA5C76038");
let bytes2 = keys2.decrypt(&bytes, &buf[..12]);
assert_eq!(String::from_utf8(bytes2).unwrap(), "TEST");
}
}

@ -0,0 +1,3 @@
mod chacha;
pub use chacha::Chacha;

@ -4,6 +4,7 @@ use crate::{Bytes, Keystore};
pub enum Event {
MinerStarted,
MinerStopped { success: bool, full: bool },
MinerStats { thread: usize, speed: u64, max_diff: u32 },
KeyGeneratorStarted,
KeyGeneratorStopped,
KeyCreated { path: String, public: String, hash: String },

@ -16,7 +16,7 @@ use ed25519_dalek::Keypair;
use log::{debug, error, info, trace, warn};
use crate::blockchain::hash_utils::*;
use crate::Context;
use crate::{Context, setup_miner_thread};
use crate::event::Event;
use crate::commons::KEYSTORE_DIFFICULTY;
use crate::bytes::Bytes;
@ -27,36 +27,42 @@ use self::ed25519_dalek::{Signer, PublicKey, Verifier, SecretKey};
use self::ed25519_dalek::ed25519::signature::Signature;
use rand_old::{CryptoRng, RngCore};
use rand_old::rngs::OsRng;
use crate::crypto::Chacha;
#[derive(Debug)]
pub struct Keystore {
keypair: Keypair,
hash: RefCell<Bytes>,
path: String,
chacha: Chacha
}
impl Keystore {
pub fn new() -> Self {
let mut csprng = OsRng::default();
let keypair = ed25519_dalek::Keypair::generate(&mut csprng);
Keystore { keypair, hash: RefCell::new(Bytes::default()), path: String::new() }
let chacha = get_chacha(&keypair);
Keystore { keypair, hash: RefCell::new(Bytes::default()), path: String::new(), chacha }
}
pub fn from_random<R>(csprng: &mut R) -> Self where R: CryptoRng + RngCore {
let keypair = ed25519_dalek::Keypair::generate(csprng);
Keystore { keypair, hash: RefCell::new(Bytes::default()), path: String::new() }
let chacha = get_chacha(&keypair);
Keystore { keypair, hash: RefCell::new(Bytes::default()), path: String::new(), chacha }
}
pub fn from_bytes(seed: &[u8]) -> Self {
let keypair = Keypair::from_bytes(seed).expect("Error creating keypair from bytes!");
Keystore { keypair, hash: RefCell::new(Bytes::default()), path: String::new() }
let chacha = get_chacha(&keypair);
Keystore { keypair, hash: RefCell::new(Bytes::default()), path: String::new(), chacha }
}
pub fn from_random_bytes(key: &[u8]) -> Self {
let secret = SecretKey::from_bytes(&key).unwrap();
let public = PublicKey::from(&secret);
let keypair = Keypair { secret, public };
Keystore { keypair, hash: RefCell::new(Bytes::default()), path: String::new() }
let chacha = get_chacha(&keypair);
Keystore { keypair, hash: RefCell::new(Bytes::default()), path: String::new(), chacha }
}
pub fn from_file(filename: &str, _password: &str) -> Option<Self> {
@ -131,12 +137,22 @@ impl Keystore {
Err(_) => { false }
}
}
pub fn encrypt(&self, message: &[u8], nonce: &[u8]) -> Bytes {
let encrypted = self.chacha.encrypt(message, nonce);
Bytes::from_bytes(&encrypted)
}
pub fn decrypt(&self, message: &[u8], nonce: &[u8]) -> Bytes {
let decrypted = self.chacha.decrypt(message, nonce);
Bytes::from_bytes(&decrypted)
}
}
impl Clone for Keystore {
fn clone(&self) -> Self {
let keypair = Keypair::from_bytes(&self.keypair.to_bytes()).unwrap();
Self { keypair, hash: RefCell::new(Bytes::default()), path: self.path.clone() }
Self { keypair, hash: RefCell::new(Bytes::default()), path: self.path.clone(), chacha: self.chacha.clone() }
}
}
@ -148,26 +164,30 @@ impl PartialEq for Keystore {
/// Checks if some public key is "strong" enough to mine domains
/// TODO Optimize by caching Blakeout somewhere
pub fn check_public_key_strength(key: &Bytes, strength: usize) -> bool {
pub fn check_public_key_strength(key: &Bytes, strength: u32) -> bool {
let bytes = blakeout_data(&key);
hash_is_good(&bytes, strength)
hash_difficulty_key(&bytes) >= strength
}
pub fn create_key(context: Arc<Mutex<Context>>) {
let mining = Arc::new(AtomicBool::new(true));
let miners_count = Arc::new(AtomicUsize::new(0));
{ context.lock().unwrap().bus.post(Event::KeyGeneratorStarted); }
context.lock().unwrap().bus.post(Event::KeyGeneratorStarted);
let lower = context.lock().unwrap().settings.mining.lower;
let threads = context.lock().unwrap().settings.mining.threads;
let threads = match threads {
0 => num_cpus::get(),
_ => threads
};
for _cpu in 0..threads {
for cpu in 0..threads {
let context = Arc::clone(&context);
let mining = mining.clone();
let miners_count = miners_count.clone();
thread::spawn(move || {
miners_count.fetch_add(1, atomic::Ordering::SeqCst);
if lower {
setup_miner_thread(cpu as u32);
}
match generate_key(KEYSTORE_DIFFICULTY, mining.clone()) {
None => {
debug!("Keystore mining finished");
@ -198,7 +218,7 @@ pub fn create_key(context: Arc<Mutex<Context>>) {
});
}
fn generate_key(difficulty: usize, mining: Arc<AtomicBool>) -> Option<Keystore> {
fn generate_key(difficulty: u32, mining: Arc<AtomicBool>) -> Option<Keystore> {
use self::rand::RngCore;
let mut rng = rand::thread_rng();
let mut time = Instant::now();
@ -210,7 +230,7 @@ fn generate_key(difficulty: usize, mining: Arc<AtomicBool>) -> Option<Keystore>
let keystore = Keystore::from_random_bytes(&buf);
digest.reset();
digest.update(keystore.get_public().as_slice());
if hash_is_good(digest.result(), difficulty) {
if hash_difficulty_key(digest.result()) >= difficulty {
info!("Generated keypair with public key: {:?} and hash {:?}", &keystore.get_public(), &keystore.get_hash());
return Some(keystore);
}
@ -227,6 +247,13 @@ fn generate_key(difficulty: usize, mining: Arc<AtomicBool>) -> Option<Keystore>
}
}
fn get_chacha(keypair: &Keypair) -> Chacha {
let mut digest = Blakeout::new();
digest.update(&keypair.to_bytes());
let seed = digest.result();
Chacha::new(seed)
}
#[cfg(test)]
mod tests {
use crate::Keystore;

@ -25,4 +25,5 @@ pub mod dns_utils;
pub mod settings;
pub mod bytes;
pub mod x_zones;
pub mod crypto;

@ -15,8 +15,7 @@ use simple_logger::SimpleLogger;
#[cfg(windows)]
use winapi::um::wincon::{ATTACH_PARENT_PROCESS, AttachConsole, FreeConsole};
use alfis::{Block, Bytes, Chain, Miner, Context, Network, Settings, dns_utils, Keystore};
use alfis::commons::BLOCK_DIFFICULTY;
use alfis::{Block, Bytes, Chain, Miner, Context, Network, Settings, dns_utils, Keystore, ZONE_DIFFICULTY};
#[cfg(feature = "webgui")]
mod web_ui;
@ -151,10 +150,10 @@ fn create_genesis_if_needed(context: &Arc<Mutex<Context>>, miner: &Arc<Mutex<Min
let context = context.lock().unwrap();
let last_block = context.get_chain().last_block();
let origin = context.settings.origin.clone();
if origin.eq("") && last_block.is_none() {
if origin.is_empty() && last_block.is_none() {
if let Some(keystore) = &context.keystore {
// If blockchain is empty, we are going to mine a Genesis block
let block = Block::new(None, context.get_keystore().unwrap().get_public(), Bytes::default(), BLOCK_DIFFICULTY);
let block = Block::new(None, context.get_keystore().unwrap().get_public(), Bytes::default(), ZONE_DIFFICULTY);
miner.lock().unwrap().add_block(block, keystore.clone());
}
}

@ -1,16 +1,16 @@
use std::sync::{Arc, Condvar, Mutex};
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::thread;
use std::time::Duration;
use std::time::{Duration, Instant};
use chrono::Utc;
#[allow(unused_imports)]
use log::{debug, error, info, trace, warn};
use num_cpus;
use crate::{Block, Bytes, Context, Keystore};
use crate::{Block, Bytes, Context, Keystore, setup_miner_thread};
use crate::commons::{CHAIN_VERSION, LOCKER_DIFFICULTY, KEYSTORE_DIFFICULTY};
use crate::blockchain::enums::BlockQuality;
use crate::blockchain::types::BlockQuality;
use crate::blockchain::hash_utils::*;
use crate::keys::check_public_key_strength;
use crate::event::Event;
@ -141,6 +141,7 @@ impl Miner {
context.lock().unwrap().bus.post(Event::MinerStarted);
let thread_spawn_interval = Duration::from_millis(10);
let live_threads = Arc::new(AtomicU32::new(0u32));
let lower = context.lock().unwrap().settings.mining.lower;
let cpus = num_cpus::get();
let threads = context.lock().unwrap().settings.mining.threads;
let threads = match threads {
@ -148,15 +149,18 @@ impl Miner {
_ => threads
};
debug!("Starting {} threads for mining", threads);
for _cpu in 0..threads {
for cpu in 0..threads {
let context = Arc::clone(&context);
let job = job.clone();
let mining = Arc::clone(&mining);
let live_threads = Arc::clone(&live_threads);
thread::spawn(move || {
live_threads.fetch_add(1, Ordering::SeqCst);
if lower {
setup_miner_thread(cpu as u32);
}
let full = job.block.transaction.is_some();
match find_hash(Arc::clone(&context), job.block, Arc::clone(&mining)) {
match find_hash(Arc::clone(&context), job.block, Arc::clone(&mining), cpu) {
None => {
debug!("Mining was cancelled");
let count = live_threads.fetch_sub(1, Ordering::SeqCst);
@ -199,12 +203,14 @@ pub struct MineJob {
keystore: Keystore
}
fn find_hash(context: Arc<Mutex<Context>>, mut block: Block, running: Arc<AtomicBool>) -> Option<Block> {
let difficulty = block.difficulty as usize;
fn find_hash(context: Arc<Mutex<Context>>, mut block: Block, running: Arc<AtomicBool>, thread: usize) -> Option<Block> {
let difficulty = block.difficulty;
let full = block.transaction.is_some();
let mut digest = Blakeout::new();
let mut max_diff = 0;
loop {
block.random = rand::random();
block.timestamp = Utc::now().timestamp();
let next_allowed_block = {
let context = context.lock().unwrap();
// We use this block to fill some fields of our block as well
@ -221,24 +227,43 @@ fn find_hash(context: Arc<Mutex<Context>>, mut block: Block, running: Arc<Atomic
continue;
}
debug!("Mining block {}", serde_json::to_string(&block).unwrap());
let mut time = Instant::now();
let mut prev_nonce = 0;
for nonce in 0..std::u64::MAX {
if !running.load(Ordering::Relaxed) {
return None;
}
block.timestamp = Utc::now().timestamp();
block.nonce = nonce;
digest.reset();
digest.update(&block.as_bytes());
if hash_is_good(digest.result(), difficulty) {
let diff = hash_difficulty(digest.result());
if diff >= difficulty {
block.hash = Bytes::from_bytes(digest.result());
return Some(block);
}
if diff > max_diff {
max_diff = diff;
}
let elapsed = time.elapsed().as_millis();
if elapsed >= 1000 {
block.timestamp = Utc::now().timestamp();
if elapsed > 5000 {
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.lock() {
context.bus.post(Event::MinerStats { thread, speed, max_diff})
}
time = Instant::now();
prev_nonce = nonce;
}
if nonce % 1000 == 0 {
if let Ok(context) = context.lock() {
if context.chain.height() >= block.index {
break;
if block.index > 1 {
if let Ok(context) = context.lock() {
if context.chain.height() >= block.index {
break;
}
}
}
}

@ -16,9 +16,10 @@ use log::{trace, debug, info, warn, error};
use std::net::{SocketAddr, IpAddr, SocketAddrV4, Shutdown};
use std::collections::HashSet;
use crate::{Context, Block, p2p::Message, p2p::State, p2p::Peer, p2p::Peers, Bytes, is_yggdrasil};
use crate::blockchain::enums::BlockQuality;
use crate::blockchain::types::BlockQuality;
use crate::commons::CHAIN_VERSION;
use std::sync::atomic::{AtomicBool, Ordering};
use chrono::Utc;
const SERVER: Token = Token(0);
const POLL_TIMEOUT: Option<Duration> = Some(Duration::from_millis(3000));
@ -49,7 +50,7 @@ impl Network {
let mut server = TcpListener::bind(addr).expect("Can't bind to address");
debug!("Started node listener on {}", server.local_addr().unwrap());
let mut events = Events::with_capacity(64);
let mut events = Events::with_capacity(1024);
let mut poll = Poll::new().expect("Unable to create poll");
poll.registry().register(&mut server, SERVER, Interest::READABLE).expect("Error registering poll");
let context = Arc::clone(&self.context);
@ -132,7 +133,7 @@ impl Network {
}
events.clear();
if peers_timer.elapsed().as_millis() > 100 {
if peers_timer.elapsed().as_millis() > 500 {
// Send pings to idle peers
let (height, hash) = {
let mut context = context.lock().unwrap();
@ -143,7 +144,7 @@ impl Network {
}
(height, context.chain.last_hash())
};
mine_locker_block(Arc::clone(&context));
mine_signing_block(Arc::clone(&context));
peers.send_pings(poll.registry(), height, hash);
peers.connect_new_peers(poll.registry(), &mut unique_token, yggdrasil_only);
peers_timer = Instant::now();
@ -243,6 +244,7 @@ fn handle_connection_event(context: Arc<Mutex<Context>>, peers: &mut Peers, regi
if event.is_writable() {
//trace!("Socket {} is writable", event.token().0);
let my_id = peers.get_my_id().to_owned();
match peers.get_mut_peer(&event.token()) {
None => {}
Some(peer) => {
@ -251,7 +253,7 @@ fn handle_connection_event(context: Arc<Mutex<Context>>, peers: &mut Peers, regi
debug!("Connected to peer {}, sending hello...", &peer.get_addr());
let data: String = {
let c = context.lock().unwrap();
let message = Message::hand(&c.app_version, &c.settings.origin, CHAIN_VERSION, c.settings.net.public, peer.get_rand());
let message = Message::hand(&c.app_version, &c.settings.origin, CHAIN_VERSION, c.settings.net.public, &my_id);
serde_json::to_string(&message).unwrap()
};
send_message(peer.get_stream(), &data.into_bytes()).unwrap_or_else(|e| warn!("Error sending hello {}", e));
@ -471,10 +473,16 @@ fn handle_message(context: Arc<Mutex<Context>>, message: Message, peers: &mut Pe
}
BlockQuality::Twin => { debug!("Ignoring duplicate block {}", block.index); }
BlockQuality::Future => { debug!("Ignoring future block {}", block.index); }
BlockQuality::Bad => { debug!("Ignoring bad block {} with hash {:?}", block.index, block.hash); }
// TODO deal with forks
BlockQuality::Bad => {
// TODO save bad public keys to banned table
debug!("Ignoring bad block {} with hash {:?}", block.index, block.hash);
}
BlockQuality::Fork => {
debug!("Ignoring forked block {} with hash {:?}", block.index, block.hash);
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) {
context.chain.replace_block(block.index, block).expect("Error replacing block with fork");
}
//let peer = peers.get_mut_peer(token).unwrap();
//deal_with_fork(context, peer, block);
}
@ -487,21 +495,24 @@ fn handle_message(context: Arc<Mutex<Context>>, message: Message, peers: &mut Pe
}
/// Sends an Event to miner to start mining locker block if "locker" is our public key
fn mine_locker_block(context: Arc<Mutex<Context>>) {
fn mine_signing_block(context: Arc<Mutex<Context>>) {
let mut context = context.lock().unwrap();
if let Some(block) = context.chain.last_block() {
if let Some(block) = context.chain.get_last_full_block(None) {
if block.timestamp + 60 > Utc::now().timestamp() {
return;
}
if let Some(keystore) = &context.keystore {
if block.index < context.chain.max_height() {
trace!("No locker mining while syncing");
trace!("No signing while syncing");
return;
}
let lockers: HashSet<Bytes> = context.chain.get_block_lockers(&block).into_iter().collect();
if lockers.contains(&keystore.get_public()) {
info!("We have an honor to mine locker block!");
let signers: HashSet<Bytes> = context.chain.get_block_signers(&block).into_iter().collect();
if signers.contains(&keystore.get_public()) {
info!("We have an honor to mine signing block!");
let keystore = Box::new(keystore.clone());
context.bus.post(crate::event::Event::ActionMineLocker { index: block.index + 1, hash: block.hash, keystore });
} else if !lockers.is_empty() {
info!("Locker block must be mined by other nodes");
} else if !signers.is_empty() {
info!("Signing block must be mined by other nodes");
}
}
}

@ -2,14 +2,14 @@ use std::net::SocketAddr;
use std::collections::HashMap;
use mio::net::TcpStream;
use crate::p2p::State;
use crate::{Block, commons};
use crate::Block;
#[derive(Debug)]
pub struct Peer {
addr: SocketAddr,
stream: TcpStream,
state: State,
rand: String,
id: String,
height: u64,
inbound: bool,
public: bool,
@ -26,7 +26,7 @@ impl Peer {
addr,
stream,
state,
rand: commons::random_string(6),
id: String::new(),
height: 0,
inbound,
public: false,
@ -58,8 +58,8 @@ impl Peer {
self.state = state;
}
pub fn get_rand(&self) -> &str {
&self.rand
pub fn get_id(&self) -> &str {
&self.id
}
pub fn set_height(&mut self, height: u64) {

@ -9,20 +9,21 @@ use rand::random;
use rand::seq::IteratorRandom;
#[allow(unused_imports)]
use log::{trace, debug, info, warn, error};
use crate::{Bytes, is_yggdrasil};
use crate::{Bytes, is_yggdrasil, commons};
use crate::commons::MAX_RECONNECTS;
pub struct Peers {
peers: HashMap<Token, Peer>,
new_peers: Vec<SocketAddr>,
ignored: HashSet<IpAddr>
ignored: HashSet<IpAddr>,
my_id: String
}
const PING_PERIOD: u64 = 60;
impl Peers {
pub fn new() -> Self {
Peers { peers: HashMap::new(), new_peers: Vec::new(), ignored: HashSet::new() }
Peers { peers: HashMap::new(), new_peers: Vec::new(), ignored: HashSet::new(), my_id: commons::random_string(6) }
}
pub fn add_peer(&mut self, token: Token, peer: Peer) {
@ -133,13 +134,12 @@ impl Peers {
}
}
pub fn get_my_id(&self) -> &str {
&self.my_id
}
pub fn is_our_own_connect(&self, rand: &str) -> bool {
match self.peers.values().find(|p| p.get_rand() == rand) {
None => { false }
Some(p) => {
!p.is_inbound()
}
}
self.my_id.eq(rand)
}
pub fn get_peers_for_exchange(&self, peer_address: &SocketAddr) -> Vec<String> {

@ -1,5 +1,5 @@
use std::fs::File;
use std::io::{Read,};
use std::io::Read;
use serde::{Deserialize, Serialize};
#[allow(unused_imports)]
@ -84,7 +84,9 @@ impl Default for Dns {
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Mining {
#[serde(default)]
pub threads: usize
pub threads: usize,
#[serde(default)]
pub lower: bool
}
#[derive(Clone, Debug, Serialize, Deserialize)]

@ -8,22 +8,23 @@ use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
use web_view::Content;
use chrono::{DateTime, Local};
#[allow(unused_imports)]
use log::{debug, error, info, LevelFilter, trace, warn};
use serde::Deserialize;
use web_view::Content;
use alfis::{Block, Bytes, Context, Keystore, Transaction, get_domain_zone};
use alfis::miner::Miner;
use alfis::{keys, check_domain};
use alfis::event::Event;
use alfis::{Block, Bytes, Context, get_domain_zone, Keystore, Transaction, ZONE_MIN_DIFFICULTY};
use alfis::{check_domain, keys};
use alfis::blockchain::transaction::{DomainData, ZoneData};
use alfis::blockchain::types::MineResult;
use alfis::commons::{ZONE_DIFFICULTY, ZONE_MAX_LENGTH, CLASS_DOMAIN, CLASS_ZONE};
use alfis::dns::protocol::DnsRecord;
use alfis::commons::{ZONE_MAX_LENGTH, ZONE_DIFFICULTY};
use alfis::event::Event;
use alfis::miner::Miner;
use Cmd::*;
use alfis::blockchain::transaction::{DomainData, ZoneData};
use self::web_view::{WebView, Handle};
use alfis::blockchain::enums::MineResult;
use chrono::{DateTime, Local};
use self::web_view::{Handle, WebView};
pub fn run_interface(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>) {
let file_content = include_str!("webview/index.html");
@ -51,12 +52,14 @@ pub fn run_interface(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>) {
SaveKey => { action_save_key(&context); }
CheckRecord { data } => { action_check_record(web_view, data); }
CheckDomain { name } => { action_check_domain(&context, web_view, name); }
MineDomain { name, records } => {
action_create_domain(Arc::clone(&context), Arc::clone(&miner), web_view, name, &records);
MineDomain { name, data } => {
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); }
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() {
@ -165,7 +168,7 @@ fn action_load_key(context: &Arc<Mutex<Context>>, web_view: &mut WebView<()>) {
None => {
error!("Error loading keystore '{}'!", &file_name);
show_warning(web_view, "Error loading key!<br>Key cannot be loaded or its difficulty is not enough.");
event_fail(web_view, &format!("Error loading key from '{}'!", &file_name));
event_fail(web_view, &format!("Error loading key from \\'{}\\'!", &file_name));
}
Some(keystore) => {
info!("Loaded keystore with key: {:?}", &keystore.get_public());
@ -184,7 +187,12 @@ fn action_load_key(context: &Arc<Mutex<Context>>, web_view: &mut WebView<()>) {
fn action_loaded(context: &Arc<Mutex<Context>>, web_view: &mut WebView<()>) {
web_view.eval("showMiningIndicator(false, false);").expect("Error evaluating!");
let handle: Handle<()> = web_view.handle();
let status = Arc::new(Mutex::new(Status::new()));
let threads = context.lock().unwrap().settings.mining.threads;
let threads = match threads {
0 => num_cpus::get(),
_ => threads
};
let status = Arc::new(Mutex::new(Status::new(threads)));
let context_copy = Arc::clone(&context);
let mut c = context.lock().unwrap();
c.bus.register(move |_uuid, e| {
@ -199,7 +207,7 @@ fn action_loaded(context: &Arc<Mutex<Context>>, web_view: &mut WebView<()>) {
Event::KeyCreated { path, public, hash } => {
event_handle_luck(&handle, "Key successfully created! Don\\'t forget to save it!");
let mut s = format!("keystoreChanged('{}', '{}', '{}');", &path, &public, &hash);
s.push_str(" showSuccess('You've got a new key! Don't forget to save it!')");
s.push_str(" showSuccess('You\\'ve got a new key! Don\\'t forget to save it!')");
s
}
Event::KeyLoaded { path, public, hash } |
@ -211,7 +219,7 @@ fn action_loaded(context: &Arc<Mutex<Context>>, web_view: &mut WebView<()>) {
event_handle_info(&handle, "Mining started");
String::from("setLeftStatusBarText('Mining...'); showMiningIndicator(true, false);")
}
Event::MinerStopped {success, full} => {
Event::MinerStopped { success, full} => {
status.mining = false;
let mut s = if status.syncing {
String::from("setLeftStatusBarText('Syncing...'); showMiningIndicator(true, true);")
@ -232,6 +240,17 @@ fn action_loaded(context: &Arc<Mutex<Context>>, web_view: &mut WebView<()>) {
}
s
}
Event::MinerStats { thread, speed, max_diff } => {
if status.max_diff < max_diff {
status.max_diff = max_diff;
}
status.set_thread_speed(thread, speed);
if thread == threads - 1 {
format!("setLeftStatusBarText('Mining speed {} H/s, max found difficulty {}.'); showMiningIndicator(true, false);", status.get_speed(), status.max_diff)
} else {
String::new()
}
}
Event::KeyGeneratorStopped => {
status.mining = false;
if status.syncing {
@ -300,8 +319,8 @@ fn action_loaded(context: &Arc<Mutex<Context>>, web_view: &mut WebView<()>) {
event_info(web_view, "Application loaded");
}
fn action_create_domain(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, web_view: &mut WebView<()>, name: String, records: &String) {
debug!("Creating domain with records: {}", records);
fn action_create_domain(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, web_view: &mut WebView<()>, name: String, data: String) {
debug!("Creating domain with data: {}", &data);
let c = Arc::clone(&context);
let context = context.lock().unwrap();
if context.get_keystore().is_none() {
@ -310,18 +329,25 @@ fn action_create_domain(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>,
}
let keystore = context.get_keystore().unwrap();
let pub_key = keystore.get_public();
match context.chain.can_mine_domain(&name, &records, &pub_key) {
let mut data = match serde_json::from_str::<DomainData>(&data) {
Ok(data) => { data }
Err(_) => {
show_warning(web_view, "Something wrong with domain data. I cannot mine it.");
return;
}
};
match context.chain.can_mine_domain(&name, &pub_key) {
MineResult::Fine => {
let zone = get_domain_zone(&name);
let difficulty = context.chain.get_zone_difficulty(&zone);
if let Ok(records) = serde_json::from_str::<Vec<DnsRecord>>(&records) {
let data = DomainData::new(zone.clone(), records);
let data = serde_json::to_string(&data).unwrap();
std::mem::drop(context);
create_domain(c, miner, &name, &data, difficulty, &keystore);
let _ = web_view.eval("domainMiningStarted();");
event_info(web_view, &format!("Mining of domain \\'{}\\' has started", &name));
}
let last_block = context.chain.last_block().unwrap();
let encrypted = keystore.encrypt(name.as_bytes(), &last_block.hash.as_slice()[..12]);
data.domain = encrypted;
let data = serde_json::to_string(&data).unwrap();
std::mem::drop(context);
create_domain(c, miner, CLASS_DOMAIN, &name, &data, difficulty, &keystore);
let _ = web_view.eval("domainMiningStarted();");
event_info(web_view, &format!("Mining of domain \\'{}\\' has started", &name));
}
MineResult::WrongName => { show_warning(web_view, "You can't mine this domain!"); }
MineResult::WrongData => { show_warning(web_view, "You have an error in records!"); }
@ -343,24 +369,45 @@ fn action_create_zone(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, we
return;
}
let data = data.to_lowercase();
if serde_json::from_str::<ZoneData>(&data).is_err() {
warn!("Something wrong with zone data!");
show_warning(web_view, "Something wrong with zone data!");
return;
}
let mut data = match serde_json::from_str::<ZoneData>(&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_domain(Arc::clone(&context), miner.clone(), &name, &data, ZONE_DIFFICULTY, &keystore);
create_domain(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_domain(Arc::clone(&context), miner.clone(), &name, &data, ZONE_DIFFICULTY, &keystore);
create_domain(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!");
@ -448,14 +495,14 @@ fn format_event_now(kind: &str, message: &str) -> String {
format!("addEvent('{}', '{}', '{}');", kind, time.format("%d.%m.%y %X"), message)
}
fn create_domain(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, name: &str, data: &str, difficulty: u32, keystore: &Keystore) {
fn create_domain(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, 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, "dns".to_owned(), data.to_owned(), keystore.get_public().clone());
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());
}
@ -471,7 +518,7 @@ pub enum Cmd {
MineZone { name: String, data: String },
CheckRecord { data: String },
CheckDomain { name: String },
MineDomain { name: String, records: String },
MineDomain { name: String, data: String },
TransferDomain { name: String, owner: String },
StopMining,
Open { link: String },
@ -483,12 +530,24 @@ struct Status {
pub synced_blocks: u64,
pub sync_height: u64,
pub nodes_connected: usize,
pub chain_height: u64
pub chain_height: u64,
pub max_diff: u32,
pub speed: Vec<u64>
}
impl Status {
fn new() -> Self {
Status { mining: false, syncing: false, synced_blocks: 0, sync_height: 0, nodes_connected: 0, chain_height: 0 }
fn new(threads: usize) -> Self {
let mut speed = Vec::with_capacity(threads);
speed.resize(threads, 0u64);
Status { mining: false, syncing: false, synced_blocks: 0, sync_height: 0, nodes_connected: 0, chain_height: 0, max_diff: 0, speed }
}
fn set_thread_speed(&mut self, thread: usize, speed: u64) {
self.speed[thread] = speed;
}
fn get_speed(&self) -> u64 {
self.speed.iter().sum()
}
}

@ -60,7 +60,7 @@
<div class="tab row page" id="tab_credentials">
<div class="field is-grouped">
<div class="control is-expanded has-icons-left">
<input class="input is-expanded" type="text" id="public_key_hash" placeholder="No key loaded" readonly>
<input class="input is-expanded" type="text" id="public_key" placeholder="No key loaded" readonly>
<span class="icon is-small is-left">
<svg viewBox="0 0 24 24" style="width: 20px; height: 20px;"><path d="M12,17A2,2 0 0,0 14,15C14,13.89 13.1,13 12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V10C4,8.89 4.9,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z"></path></svg>
</span>
@ -130,13 +130,17 @@
<div class="control has-icons-left">
<input class="input" type="number" placeholder="Difficulty: 15-30" id="new_zone_difficulty" name="Just a name" oninput="onZoneChange()">
<span class="icon is-small is-left">
<i class="fas fa-fire"></i>
<svg viewBox="0 0 24 24" style="width: 24px; height: 24px;"><path d="M17.66 11.2C17.43 10.9 17.15 10.64 16.89 10.38C16.22 9.78 15.46 9.35 14.82 8.72C13.33 7.26 13 4.85 13.95 3C13 3.23 12.17 3.75 11.46 4.32C8.87 6.4 7.85 10.07 9.07 13.22C9.11 13.32 9.15 13.42 9.15 13.55C9.15 13.77 9 13.97 8.8 14.05C8.57 14.15 8.33 14.09 8.14 13.93C8.08 13.88 8.04 13.83 8 13.76C6.87 12.33 6.69 10.28 7.45 8.64C5.78 10 4.87 12.3 5 14.47C5.06 14.97 5.12 15.47 5.29 15.97C5.43 16.57 5.7 17.17 6 17.7C7.08 19.43 8.95 20.67 10.96 20.92C13.1 21.19 15.39 20.8 17.03 19.32C18.86 17.66 19.5 15 18.56 12.72L18.43 12.46C18.22 12 17.66 11.2 17.66 11.2M14.5 17.5C14.22 17.74 13.76 18 13.4 18.1C12.28 18.5 11.16 17.94 10.5 17.28C11.69 17 12.4 16.12 12.61 15.23C12.78 14.43 12.46 13.77 12.33 13C12.21 12.26 12.23 11.63 12.5 10.94C12.69 11.32 12.89 11.7 13.13 12C13.9 13 15.11 13.44 15.37 14.8C15.41 14.94 15.43 15.08 15.43 15.23C15.46 16.05 15.1 16.95 14.5 17.5H14.5Z"></path></svg>
</span>
</div>
<div class="buttons has-addons">
<button class="button is-info" id="new_zone_button" onclick="createZone();">Mine zone</button>
</div>
</div>
<label class="checkbox mb-1">
<input type="checkbox" id="yggdrasil_only">
Restrict this zone to <a onclick="open_link('https://yggdrasil-network.github.io');">Yggdrasil</a> only.
</label>
<p class="help">If you feel that we need another zone you can mine that too. Just select a name, a difficulty for domains in that zone, and hit "Mine zone".</p>
</div>

@ -161,6 +161,12 @@ function createDomain() {
var new_domain = document.getElementById("new_domain").value.toLowerCase();
var new_dom_records = JSON.stringify(recordsBuffer);
var domain = new_domain + "." + currentZone.name;
var data = {};
data.domain = [];
data.zone = currentZone.name;
data.records = new_dom_records;
data.owners = []; // TODO make a dialog to fill them
data.contacts = []; // TODO make a dialog to fill them
external.invoke(JSON.stringify({cmd: 'mineDomain', name: domain, records: new_dom_records}));
}
@ -172,9 +178,12 @@ function domainMiningStarted() {
function createZone() {
var new_zone = document.getElementById("new_zone").value;
var difficulty = document.getElementById("new_zone_difficulty").value;
obj = {};
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}));
}
@ -338,9 +347,9 @@ function keystoreChanged(path, pub_key, hash) {
if (path == '') {
path = "In memory";
}
var public_key_hash = document.getElementById("public_key_hash");
public_key_hash.value = hash;
public_key_hash.title = path + "\n" + pub_key;
var public_key_field = document.getElementById("public_key");
public_key_field.value = pub_key;
public_key_field.title = path + "\n" + hash;
var save_key = document.getElementById("save_key");
save_key.disabled = false;

Loading…
Cancel
Save