+8
-2
@@ -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]
|
||||
|
||||
+4
-2
@@ -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/
|
||||
|
||||
+13
-2
@@ -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
|
||||
}
|
||||
}
|
||||
+218
-116
@@ -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,11 +155,39 @@ 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
|
||||
fn add_block_to_table(&mut self, block: Block) -> sqlite::Result<State> {
|
||||
let mut statement = self.db.prepare(SQL_ADD_BLOCK)?;
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
return hash_int < target;
|
||||
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);
|
||||
|
||||
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 },
|
||||
|
||||
+39
-12
@@ -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;
|
||||
|
||||
|
||||
+3
-4
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+38
-13
@@ -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;
|
||||
}
|
||||
|
||||
if nonce % 1000 == 0 {
|
||||
if let Ok(context) = context.lock() {
|
||||
if context.chain.height() >= block.index {
|
||||
break;
|
||||
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 block.index > 1 {
|
||||
if let Ok(context) = context.lock() {
|
||||
if context.chain.height() >= block.index {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+27
-16
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -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
-9
@@ -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> {
|
||||
|
||||
+4
-2
@@ -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)]
|
||||
|
||||
+101
-42
@@ -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::dns::protocol::DnsRecord;
|
||||
use alfis::commons::{ZONE_MAX_LENGTH, ZONE_DIFFICULTY};
|
||||
use Cmd::*;
|
||||
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 self::web_view::{WebView, Handle};
|
||||
use alfis::blockchain::enums::MineResult;
|
||||
use chrono::{DateTime, Local};
|
||||
use alfis::blockchain::types::MineResult;
|
||||
use alfis::commons::{ZONE_DIFFICULTY, ZONE_MAX_LENGTH, CLASS_DOMAIN, CLASS_ZONE};
|
||||
use alfis::dns::protocol::DnsRecord;
|
||||
use alfis::event::Event;
|
||||
use alfis::miner::Miner;
|
||||
use Cmd::*;
|
||||
|
||||
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>
|
||||
|
||||
|
||||
+13
-4
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user