Major refactoring. Changed mining algorithm to Blakeout. Changed keypair mining algorithm.
This commit is contained in:
+2
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "alfis"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
authors = ["Revertron <alfis@revertron.com>"]
|
||||
edition = "2018"
|
||||
build = "build.rs"
|
||||
@@ -15,6 +15,7 @@ log = "0.4.14"
|
||||
toml = "0.5.8"
|
||||
simple_logger = "1.11.0"
|
||||
rust-crypto = "^0.2"
|
||||
blakeout = "0.1.0"
|
||||
num_cpus = "1.13.0"
|
||||
byteorder = "1.3.2"
|
||||
web-view = { version = "0.7.2", features = [] }
|
||||
@@ -24,7 +25,6 @@ serde_json = "1.0.42"
|
||||
num-bigint = "0.2"
|
||||
num-traits = "0.2"
|
||||
bincode = "1.2.0"
|
||||
groestl = "0.8.0"
|
||||
base64 = "0.11.0"
|
||||
chrono = { version = "0.4.13", features = ["serde"] }
|
||||
rand = "0.7.2"
|
||||
|
||||
+1
-11
@@ -5,9 +5,7 @@ extern crate num_traits;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use crypto::sha2::Sha256;
|
||||
use crypto::digest::Digest;
|
||||
use crate::keys::Bytes;
|
||||
use crate::bytes::Bytes;
|
||||
use crate::Transaction;
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
|
||||
@@ -70,12 +68,4 @@ impl Block {
|
||||
pub fn as_bytes(&self) -> Vec<u8> {
|
||||
Vec::from(serde_json::to_string(&self).unwrap().as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hash(data: &[u8]) -> Bytes {
|
||||
let mut buf: [u8; 32] = [0; 32];
|
||||
let mut digest = Sha256::new();
|
||||
digest.input(data);
|
||||
digest.result(&mut buf);
|
||||
Bytes::new(buf.to_vec())
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use chrono::Utc;
|
||||
#[allow(unused_imports)]
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use sqlite::{Connection, State, Statement};
|
||||
|
||||
use crate::{Block, Bytes, Keystore, Transaction, hash_is_good};
|
||||
use crate::{Block, Bytes, Keystore, Transaction};
|
||||
use crate::blockchain::constants::{BLOCK_DIFFICULTY, CHAIN_VERSION, LOCKER_BLOCK_COUNT, LOCKER_BLOCK_INTERVAL, LOCKER_BLOCK_START, LOCKER_DIFFICULTY};
|
||||
use crate::blockchain::enums::BlockQuality;
|
||||
use crate::blockchain::enums::BlockQuality::*;
|
||||
use crate::blockchain::hash_utils::*;
|
||||
use crate::settings::Settings;
|
||||
#[allow(unused_imports)]
|
||||
use log::{trace, debug, info, warn, error};
|
||||
use std::collections::HashSet;
|
||||
use std::cell::RefCell;
|
||||
use chrono::Utc;
|
||||
use crate::blockchain::transaction::hash_identity;
|
||||
use crate::blockchain::blockchain::BlockQuality::*;
|
||||
use crate::blockchain::{BLOCK_DIFFICULTY, CHAIN_VERSION, LOCKER_BLOCK_START, LOCKER_DIFFICULTY, LOCKER_BLOCK_COUNT, LOCKER_BLOCK_INTERVAL};
|
||||
|
||||
const DB_NAME: &str = "blockchain.db";
|
||||
const SQL_CREATE_TABLES: &str = "CREATE TABLE blocks (
|
||||
@@ -37,7 +39,7 @@ const SQL_GET_PUBLIC_KEY_BY_ID: &str = "SELECT pub_key FROM transactions WHERE i
|
||||
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;";
|
||||
|
||||
pub struct Blockchain {
|
||||
pub struct Chain {
|
||||
origin: Bytes,
|
||||
pub version: u32,
|
||||
pub blocks: Vec<Block>,
|
||||
@@ -48,12 +50,12 @@ pub struct Blockchain {
|
||||
zones: RefCell<HashSet<String>>,
|
||||
}
|
||||
|
||||
impl Blockchain {
|
||||
impl Chain {
|
||||
pub fn new(settings: &Settings) -> Self {
|
||||
let origin = settings.get_origin();
|
||||
|
||||
let db = sqlite::open(DB_NAME).expect("Unable to open blockchain DB");
|
||||
let mut blockchain = Blockchain {
|
||||
let mut chain = Chain {
|
||||
origin,
|
||||
version: CHAIN_VERSION,
|
||||
blocks: Vec::new(),
|
||||
@@ -63,8 +65,8 @@ impl Blockchain {
|
||||
db,
|
||||
zones: RefCell::new(HashSet::new()),
|
||||
};
|
||||
blockchain.init_db();
|
||||
blockchain
|
||||
chain.init_db();
|
||||
chain
|
||||
}
|
||||
|
||||
/// Reads options from DB or initializes and writes them to DB if not found
|
||||
@@ -147,21 +149,21 @@ impl Blockchain {
|
||||
statement.bind(7, transaction.to_string().as_str())?;
|
||||
}
|
||||
}
|
||||
statement.bind(8, block.prev_block_hash.as_bytes())?;
|
||||
statement.bind(9, block.hash.as_bytes())?;
|
||||
statement.bind(10, block.pub_key.as_bytes())?;
|
||||
statement.bind(11, block.signature.as_bytes())?;
|
||||
statement.bind(8, block.prev_block_hash.as_slice())?;
|
||||
statement.bind(9, block.hash.as_slice())?;
|
||||
statement.bind(10, block.pub_key.as_slice())?;
|
||||
statement.bind(11, block.signature.as_slice())?;
|
||||
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.as_bytes())?;
|
||||
statement.bind(2, t.confirmation.as_bytes())?;
|
||||
statement.bind(1, t.identity.as_slice())?;
|
||||
statement.bind(2, t.confirmation.as_slice())?;
|
||||
statement.bind(3, t.method.as_ref() as &str)?;
|
||||
statement.bind(4, t.data.as_ref() as &str)?;
|
||||
statement.bind(5, t.pub_key.as_bytes())?;
|
||||
statement.bind(5, t.pub_key.as_slice())?;
|
||||
statement.next()
|
||||
}
|
||||
|
||||
@@ -239,7 +241,7 @@ impl Blockchain {
|
||||
/// 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();
|
||||
statement.bind(1, identity.as_bytes()).expect("Error in bind");
|
||||
statement.bind(1, identity.as_slice()).expect("Error in bind");
|
||||
while let State::Row = statement.next().unwrap() {
|
||||
let pub_key = Bytes::from_bytes(statement.read::<Vec<u8>>(0).unwrap().as_slice());
|
||||
if !pub_key.eq(public_key) {
|
||||
@@ -258,7 +260,7 @@ impl Blockchain {
|
||||
// Checking for existing zone in DB
|
||||
let identity_hash = hash_identity(zone, None);
|
||||
let mut statement = self.db.prepare(SQL_GET_ID_BY_ID).unwrap();
|
||||
statement.bind(1, identity_hash.as_bytes()).expect("Error in bind");
|
||||
statement.bind(1, identity_hash.as_slice()).expect("Error in bind");
|
||||
while let State::Row = statement.next().unwrap() {
|
||||
// If there is such a zone
|
||||
self.zones.borrow_mut().insert(zone.to_owned());
|
||||
@@ -275,7 +277,7 @@ impl Blockchain {
|
||||
let identity_hash = hash_identity(domain, None);
|
||||
|
||||
let mut statement = self.db.prepare(SQL_GET_TRANSACTION_BY_ID).unwrap();
|
||||
statement.bind(1, identity_hash.as_bytes()).expect("Error in bind");
|
||||
statement.bind(1, identity_hash.as_slice()).expect("Error in bind");
|
||||
while let State::Row = statement.next().unwrap() {
|
||||
let identity = Bytes::from_bytes(statement.read::<Vec<u8>>(1).unwrap().as_slice());
|
||||
let confirmation = Bytes::from_bytes(statement.read::<Vec<u8>>(2).unwrap().as_slice());
|
||||
@@ -343,7 +345,7 @@ impl Blockchain {
|
||||
warn!("Block difficulty is lower than needed");
|
||||
return Bad;
|
||||
}
|
||||
if !hash_is_good(block.hash.as_bytes(), block.difficulty as usize) {
|
||||
if !hash_is_good(block.hash.as_slice(), block.difficulty as usize) {
|
||||
warn!("Ignoring block with low difficulty:\n{:?}", &block);
|
||||
return Bad;
|
||||
}
|
||||
@@ -456,27 +458,4 @@ impl Blockchain {
|
||||
let signature = Bytes::from_bytes(statement.read::<Vec<u8>>(10).unwrap().as_slice());
|
||||
Some(Block::from_all_params(index, timestamp, version, difficulty, random, nonce, prev_block_hash, hash, pub_key, signature, transaction))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum BlockQuality {
|
||||
Good,
|
||||
Twin,
|
||||
Future,
|
||||
Bad,
|
||||
Fork,
|
||||
}
|
||||
|
||||
pub fn check_block_hash(block: &Block) -> bool {
|
||||
let mut copy: Block = block.clone();
|
||||
copy.hash = Bytes::default();
|
||||
copy.signature = Bytes::default();
|
||||
let data = serde_json::to_string(©).unwrap();
|
||||
crate::blockchain::block::hash(data.as_bytes()) == block.hash
|
||||
}
|
||||
|
||||
pub fn check_block_signature(block: &Block) -> bool {
|
||||
let mut copy = block.clone();
|
||||
copy.signature = Bytes::default();
|
||||
Keystore::check(©.as_bytes(), copy.pub_key.as_bytes(), block.signature.as_bytes())
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
pub const CHAIN_VERSION: u32 = 1;
|
||||
|
||||
pub const BLOCK_DIFFICULTY: u32 = 24;
|
||||
pub const LOCKER_DIFFICULTY: u32 = 18;
|
||||
pub const CHAIN_VERSION: u32 = 1;
|
||||
pub const KEYSTORE_DIFFICULTY: usize = 25;
|
||||
|
||||
pub const LOCKER_BLOCK_START: u64 = 10;
|
||||
pub const LOCKER_BLOCK_COUNT: u64 = 3;
|
||||
pub const LOCKER_BLOCK_INTERVAL: i64 = 300;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
/// Represents a result of block check on block's arrival
|
||||
#[derive(PartialEq)]
|
||||
pub enum BlockQuality {
|
||||
Good,
|
||||
Twin,
|
||||
Future,
|
||||
Bad,
|
||||
Fork,
|
||||
}
|
||||
@@ -33,11 +33,11 @@ impl DnsFilter for BlockchainFilter {
|
||||
}
|
||||
debug!("Searching domain {} and record {}", &search, &subdomain);
|
||||
|
||||
let data = self.context.lock().unwrap().blockchain.get_domain_info(&search);
|
||||
let data = self.context.lock().unwrap().chain.get_domain_info(&search);
|
||||
match data {
|
||||
None => {
|
||||
debug!("Not found data for domain {}", &search);
|
||||
if self.context.lock().unwrap().blockchain.is_zone_in_blockchain(parts[0]) {
|
||||
if self.context.lock().unwrap().chain.is_zone_in_blockchain(parts[0]) {
|
||||
// Create DnsPacket
|
||||
let mut packet = DnsPacket::new();
|
||||
packet.questions.push(DnsQuestion::new(String::from(qname), qtype));
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
use blakeout::Blakeout;
|
||||
use crypto::digest::Digest;
|
||||
use crypto::sha2::Sha256;
|
||||
use num_bigint::BigUint;
|
||||
use num_traits::One;
|
||||
|
||||
use crate::{Block, Bytes, Keystore};
|
||||
|
||||
/// Creates needed hasher by current blockchain version
|
||||
fn get_hasher_for_version(version: u32) -> Box<dyn Digest> {
|
||||
match version {
|
||||
2 => Box::new(Blakeout::default()),
|
||||
_ => Box::new(Sha256::new())
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks block's hash and returns true on valid hash or false otherwise
|
||||
pub fn check_block_hash(block: &Block) -> bool {
|
||||
let mut copy: Block = block.clone();
|
||||
copy.hash = Bytes::default();
|
||||
copy.signature = Bytes::default();
|
||||
let data = serde_json::to_string(©).unwrap();
|
||||
let mut hasher = get_hasher_for_version(block.version);
|
||||
hash_data(&mut *hasher, data.as_bytes()) == block.hash
|
||||
}
|
||||
|
||||
/// Hashes data by given hasher
|
||||
pub fn hash_data(digest: &mut dyn Digest, data: &[u8]) -> Bytes {
|
||||
let mut buf = match digest.output_bytes() {
|
||||
32 => Bytes::zero32(),
|
||||
64 => Bytes::zero64(),
|
||||
_ => panic!("Supplied wrong digest!")
|
||||
};
|
||||
|
||||
digest.input(data);
|
||||
digest.result(buf.as_mut_slice());
|
||||
buf
|
||||
}
|
||||
|
||||
/// Checks block's signature, returns true if the signature is valid, false otherwise
|
||||
pub fn check_block_signature(block: &Block) -> bool {
|
||||
let mut copy = block.clone();
|
||||
copy.signature = Bytes::default();
|
||||
Keystore::check(©.as_bytes(), copy.pub_key.as_slice(), block.signature.as_slice())
|
||||
}
|
||||
|
||||
/// 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 buf: [u8; 32] = [0; 32];
|
||||
let mut digest = Sha256::new();
|
||||
digest.input_str(identity);
|
||||
if let Some(key) = key {
|
||||
digest.input(key.as_slice());
|
||||
}
|
||||
digest.result(&mut buf);
|
||||
Bytes::from_bytes(&buf)
|
||||
}
|
||||
|
||||
/// There is no default PartialEq implementation for arrays > 32 in size
|
||||
pub fn same_hash(left: &[u8], right: &[u8]) -> bool {
|
||||
if left.len() != right.len() {
|
||||
return false;
|
||||
}
|
||||
// We iterate whole slices to eliminate timing attacks
|
||||
let mut result = true;
|
||||
for (x, y) in left.iter().zip(right) {
|
||||
if x != y {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
return hash_int < target;
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
pub mod transaction;
|
||||
pub mod block;
|
||||
pub mod blockchain;
|
||||
pub mod chain;
|
||||
pub mod filter;
|
||||
pub mod constants;
|
||||
pub mod hash_utils;
|
||||
pub mod enums;
|
||||
|
||||
pub use transaction::Transaction;
|
||||
pub use block::Block;
|
||||
pub use blockchain::Blockchain;
|
||||
pub use chain::Chain;
|
||||
pub use constants::*;
|
||||
@@ -1,14 +1,14 @@
|
||||
use crate::keys::*;
|
||||
use std::fmt;
|
||||
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
use serde::ser::SerializeStruct;
|
||||
|
||||
use crate::blockchain::hash_utils::*;
|
||||
use crate::bytes::Bytes;
|
||||
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
|
||||
use serde::{Serialize, Deserialize, Serializer};
|
||||
use serde::ser::SerializeStruct;
|
||||
use std::fmt;
|
||||
use crypto::sha2::Sha256;
|
||||
use crypto::digest::Digest;
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
pub struct Transaction {
|
||||
pub identity: Bytes,
|
||||
@@ -75,15 +75,4 @@ impl Serialize for Transaction {
|
||||
structure.serialize_field("pub_key", &self.pub_key)?;
|
||||
structure.end()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hash_identity(identity: &str, key: Option<&Bytes>) -> Bytes {
|
||||
let mut buf: [u8; 32] = [0; 32];
|
||||
let mut digest = Sha256::new();
|
||||
digest.input_str(identity);
|
||||
if let Some(key) = key {
|
||||
digest.input(key.as_bytes());
|
||||
}
|
||||
digest.result(&mut buf);
|
||||
Bytes::from_bytes(&buf)
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::convert::TryInto;
|
||||
use std::fmt;
|
||||
use std::fmt::{Formatter, Error};
|
||||
|
||||
use num_bigint::BigUint;
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
// For deserialization
|
||||
use serde::de::{Error as DeError, Visitor};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Bytes {
|
||||
data: Vec<u8>
|
||||
}
|
||||
|
||||
impl Bytes {
|
||||
pub fn new(data: Vec<u8>) -> Self {
|
||||
Bytes { data }
|
||||
}
|
||||
|
||||
pub fn from_bytes(data: &[u8]) -> Self {
|
||||
Bytes { data: Vec::from(data) }
|
||||
}
|
||||
|
||||
pub fn length(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.data.is_empty()
|
||||
}
|
||||
|
||||
pub fn is_zero(&self) -> bool {
|
||||
if self.data.is_empty() {
|
||||
return true;
|
||||
}
|
||||
for x in self.data.iter() {
|
||||
if *x != 0 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Returns a byte slice of the hash contents.
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
self.data.as_slice()
|
||||
}
|
||||
|
||||
/// Returns a mutable byte slice (to fill by hasher)
|
||||
pub fn as_mut_slice(&mut self) -> &mut [u8] {
|
||||
self.data.as_mut_slice()
|
||||
}
|
||||
|
||||
pub fn as_vec(&self) -> &Vec<u8> {
|
||||
&self.data
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
crate::utils::to_hex(&self.data)
|
||||
}
|
||||
|
||||
pub fn get_tail_u64(&self) -> u64 {
|
||||
let index = self.data.len() - 8;
|
||||
let bytes: [u8; 8] = self.data[index..].try_into().unwrap();
|
||||
u64::from_be_bytes(bytes)
|
||||
}
|
||||
|
||||
pub fn zero32() -> Self {
|
||||
Bytes { data: [0u8; 32].to_vec() }
|
||||
}
|
||||
|
||||
pub fn zero64() -> Self {
|
||||
Bytes { data: [0u8; 64].to_vec() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Bytes {
|
||||
fn default() -> Bytes {
|
||||
Bytes { data: Vec::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Bytes {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
crate::blockchain::hash_utils::same_hash(&self.data, &other.data)
|
||||
}
|
||||
|
||||
fn ne(&self, other: &Self) -> bool {
|
||||
!crate::blockchain::hash_utils::same_hash(&self.data, &other.data)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Bytes {}
|
||||
|
||||
impl PartialOrd for Bytes {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
let self_hash_int = BigUint::from_bytes_be(&self.data);
|
||||
let other_hash_int = BigUint::from_bytes_be(&other.data);
|
||||
Some(self_hash_int.cmp(&other_hash_int))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Bytes {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
let self_hash_int = BigUint::from_bytes_be(&self.data);
|
||||
let other_hash_int = BigUint::from_bytes_be(&other.data);
|
||||
self_hash_int.cmp(&other_hash_int)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Bytes {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.write_str(&crate::utils::to_hex(&self.data))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Bytes {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> where
|
||||
S: Serializer {
|
||||
serializer.serialize_str(&crate::utils::to_hex(&self.data))
|
||||
}
|
||||
}
|
||||
|
||||
struct BytesVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for BytesVisitor {
|
||||
type Value = Bytes;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> Result<(), Error> {
|
||||
formatter.write_str("32 or 64 bytes")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> where E: DeError, {
|
||||
if value.len() == 64 || value.len() == 128 {
|
||||
Ok(Bytes::new(crate::from_hex(value).unwrap()))
|
||||
} else {
|
||||
Err(E::custom("Key must be 32 or 64 bytes!"))
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E> where E: DeError, {
|
||||
if value.len() == 32 || value.len() == 64 {
|
||||
Ok(Bytes::from_bytes(value))
|
||||
} else {
|
||||
Err(E::custom("Key must be 32 or 64 bytes!"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dd> Deserialize<'dd> for Bytes {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'dd>>::Error> where D: Deserializer<'dd> {
|
||||
deserializer.deserialize_str(BytesVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::bytes::Bytes;
|
||||
|
||||
#[test]
|
||||
pub fn test_tail_bytes() {
|
||||
let bytes = Bytes::new(vec![0, 255, 255, 255, 0, 255, 255, 255]);
|
||||
assert_eq!(bytes.get_tail_u64(), 72057589759737855u64);
|
||||
}
|
||||
}
|
||||
+6
-7
@@ -1,20 +1,19 @@
|
||||
use crate::{Blockchain, Bus, Keystore};
|
||||
use crate::{Chain, Bus, Keystore, Settings};
|
||||
use crate::event::Event;
|
||||
use crate::settings::Settings;
|
||||
#[allow(unused_imports)]
|
||||
use log::{trace, debug, info, warn, error};
|
||||
|
||||
pub struct Context {
|
||||
pub settings: Settings,
|
||||
pub keystore: Keystore,
|
||||
pub blockchain: Blockchain,
|
||||
pub chain: Chain,
|
||||
pub bus: Bus<Event>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Creating an essential context to work with
|
||||
pub fn new(settings: Settings, keystore: Keystore, blockchain: Blockchain) -> Context {
|
||||
Context { settings, keystore, blockchain, bus: Bus::new() }
|
||||
pub fn new(settings: Settings, keystore: Keystore, chain: Chain) -> Context {
|
||||
Context { settings, keystore, chain, bus: Bus::new() }
|
||||
}
|
||||
|
||||
/// Load keystore and return Context
|
||||
@@ -39,7 +38,7 @@ impl Context {
|
||||
self.keystore = keystore;
|
||||
}
|
||||
|
||||
pub fn get_blockchain(&self) -> &Blockchain {
|
||||
&self.blockchain
|
||||
pub fn get_chain(&self) -> &Chain {
|
||||
&self.chain
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -24,4 +24,4 @@ pub mod resolve;
|
||||
pub mod server;
|
||||
pub mod filter;
|
||||
|
||||
mod netutil;
|
||||
mod netutil;
|
||||
@@ -0,0 +1,45 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::{Context, Settings};
|
||||
use crate::blockchain::filter::BlockchainFilter;
|
||||
use crate::dns::server::{DnsServer, DnsUdpServer, DnsTcpServer};
|
||||
use crate::dns::context::{ServerContext, ResolveStrategy};
|
||||
#[allow(unused_imports)]
|
||||
use log::{debug, error, info, LevelFilter, trace, warn};
|
||||
|
||||
/// Starts UDP and TCP DNS-servers
|
||||
pub fn start_dns_server(context: &Arc<Mutex<Context>>, settings: &Settings) {
|
||||
let server_context = create_server_context(context.clone(), &settings);
|
||||
|
||||
if server_context.enable_udp {
|
||||
let udp_server = DnsUdpServer::new(server_context.clone(), settings.dns.threads);
|
||||
if let Err(e) = udp_server.run_server() {
|
||||
error!("Failed to bind UDP listener: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
if server_context.enable_tcp {
|
||||
let tcp_server = DnsTcpServer::new(server_context.clone(), settings.dns.threads);
|
||||
if let Err(e) = tcp_server.run_server() {
|
||||
error!("Failed to bind TCP listener: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates DNS-context with all needed settings
|
||||
fn create_server_context(context: Arc<Mutex<Context>>, settings: &Settings) -> Arc<ServerContext> {
|
||||
let mut server_context = ServerContext::new();
|
||||
server_context.allow_recursive = true;
|
||||
server_context.dns_listen = settings.dns.listen.clone();
|
||||
server_context.resolve_strategy = match settings.dns.forwarders.is_empty() {
|
||||
true => { ResolveStrategy::Recursive }
|
||||
false => { ResolveStrategy::Forward { upstreams: settings.dns.forwarders.clone() } }
|
||||
};
|
||||
server_context.filters.push(Box::new(BlockchainFilter::new(context)));
|
||||
match server_context.initialize() {
|
||||
Ok(_) => {}
|
||||
Err(e) => { panic!("DNS server failed to initialize: {:?}", e); }
|
||||
}
|
||||
|
||||
Arc::new(server_context)
|
||||
}
|
||||
+3
-3
@@ -6,9 +6,9 @@ pub enum Event {
|
||||
MinerStopped,
|
||||
KeyGeneratorStarted,
|
||||
KeyGeneratorStopped,
|
||||
KeyCreated { path: String, public: String },
|
||||
KeyLoaded { path: String, public: String },
|
||||
KeySaved { path: String, public: String },
|
||||
KeyCreated { path: String, public: String, hash: String },
|
||||
KeyLoaded { path: String, public: String, hash: String },
|
||||
KeySaved { path: String, public: String, hash: String },
|
||||
NewBlockReceived,
|
||||
BlockchainChanged,
|
||||
ActionStopMining,
|
||||
|
||||
+96
-155
@@ -2,29 +2,39 @@ extern crate crypto;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
|
||||
use crypto::ed25519::{keypair, signature, verify};
|
||||
use rand::{thread_rng, Rng};
|
||||
use std::fmt;
|
||||
use std::fmt::{Error, Formatter};
|
||||
use std::thread;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use serde::{Serialize, Deserialize, Serializer, Deserializer};
|
||||
// For deserialization
|
||||
use serde::de::{Error as DeError, Visitor};
|
||||
use std::sync::{Arc, atomic, Mutex};
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize};
|
||||
|
||||
use crypto::ed25519::{keypair, signature, verify};
|
||||
#[allow(unused_imports)]
|
||||
use log::{trace, debug, info, warn, error};
|
||||
use crate::hash_is_good;
|
||||
use std::cmp::Ordering;
|
||||
use num_bigint::BigUint;
|
||||
use std::convert::TryInto;
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use rand::{Rng, RngCore, thread_rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
use thread_priority::{set_current_thread_priority, ThreadPriority};
|
||||
|
||||
use crate::blockchain::hash_utils::*;
|
||||
use crate::Context;
|
||||
use crate::event::Event;
|
||||
use crate::blockchain::KEYSTORE_DIFFICULTY;
|
||||
use crate::bytes::Bytes;
|
||||
use blakeout::Blakeout;
|
||||
use self::crypto::digest::Digest;
|
||||
use std::time::Instant;
|
||||
use std::cell::RefCell;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Keystore {
|
||||
private_key: Bytes,
|
||||
public_key: Bytes,
|
||||
#[serde(skip)]
|
||||
hash: RefCell<Bytes>,
|
||||
#[serde(skip)]
|
||||
path: String,
|
||||
#[serde(skip)]
|
||||
seed: Vec<u8>,
|
||||
@@ -36,12 +46,12 @@ impl Keystore {
|
||||
let mut rng = thread_rng();
|
||||
rng.fill(&mut buf);
|
||||
let (private, public) = keypair(&buf);
|
||||
Keystore { private_key: Bytes::from_bytes(&private), public_key: Bytes::from_bytes(&public), path: String::new(), seed: Vec::from(&buf[..]) }
|
||||
Keystore { private_key: Bytes::from_bytes(&private), public_key: Bytes::from_bytes(&public), hash: RefCell::new(Bytes::default()), path: String::new(), seed: Vec::from(&buf[..]) }
|
||||
}
|
||||
|
||||
pub fn from_bytes(seed: &[u8]) -> Self {
|
||||
let (private, public) = keypair(&seed);
|
||||
Keystore { private_key: Bytes::from_bytes(&private), public_key: Bytes::from_bytes(&public), path: String::new(), seed: Vec::from(seed) }
|
||||
Keystore { private_key: Bytes::from_bytes(&private), public_key: Bytes::from_bytes(&public), hash: RefCell::new(Bytes::default()), path: String::new(), seed: Vec::from(seed) }
|
||||
}
|
||||
|
||||
pub fn from_file(filename: &str, _password: &str) -> Option<Self> {
|
||||
@@ -82,159 +92,96 @@ impl Keystore {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn get_hash(&self) -> Bytes {
|
||||
if self.hash.borrow().is_empty() {
|
||||
self.hash.replace(hash_data(&mut Blakeout::default(), &self.public_key.as_slice()));
|
||||
}
|
||||
self.hash.borrow().clone()
|
||||
}
|
||||
|
||||
pub fn sign(&self, message: &[u8]) -> [u8; 64] {
|
||||
signature(message, self.private_key.data.as_slice())
|
||||
signature(message, self.private_key.as_slice())
|
||||
}
|
||||
|
||||
pub fn check(message: &[u8], public_key: &[u8], signature: &[u8]) -> bool {
|
||||
verify(message, public_key, signature)
|
||||
}
|
||||
|
||||
pub fn hash_is_good(&self, difficulty: usize) -> bool {
|
||||
hash_is_good(self.public_key.as_bytes(), difficulty)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Bytes {
|
||||
data: Vec<u8>
|
||||
}
|
||||
|
||||
impl Bytes {
|
||||
pub fn new(data: Vec<u8>) -> Self {
|
||||
Bytes { data }
|
||||
}
|
||||
|
||||
pub fn from_bytes(data: &[u8]) -> Self {
|
||||
Bytes { data: Vec::from(data) }
|
||||
}
|
||||
|
||||
pub fn length(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.data.is_empty()
|
||||
}
|
||||
|
||||
pub fn is_zero(&self) -> bool {
|
||||
if self.data.is_empty() {
|
||||
return true;
|
||||
}
|
||||
for x in self.data.iter() {
|
||||
if *x != 0 {
|
||||
return false;
|
||||
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); }
|
||||
for _ in 0..num_cpus::get() {
|
||||
let context = context.clone();
|
||||
let mining = mining.clone();
|
||||
let miners_count = miners_count.clone();
|
||||
thread::spawn(move || {
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let _ = set_current_thread_priority(ThreadPriority::Min);
|
||||
miners_count.fetch_add(1, atomic::Ordering::SeqCst);
|
||||
match generate_key(KEYSTORE_DIFFICULTY, mining.clone()) {
|
||||
None => {
|
||||
debug!("Keystore mining finished");
|
||||
}
|
||||
Some(keystore) => {
|
||||
mining.store(false, atomic::Ordering::SeqCst);
|
||||
let mut context = context.lock().unwrap();
|
||||
let hash = keystore.get_hash().to_string();
|
||||
info!("Key mined successfully: {:?}, hash: {}", &keystore.get_public(), &hash);
|
||||
context.bus.post(Event::KeyCreated { path: keystore.get_path().to_owned(), public: keystore.get_public().to_string(), hash });
|
||||
context.set_keystore(keystore);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
let miners = miners_count.fetch_sub(1, atomic::Ordering::SeqCst) - 1;
|
||||
if miners == 0 {
|
||||
context.lock().unwrap().bus.post(Event::KeyGeneratorStopped);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns a byte slice of the hash contents.
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.data.as_slice()
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
crate::utils::to_hex(&self.data)
|
||||
}
|
||||
|
||||
pub fn get_tail_u64(&self) -> u64 {
|
||||
let index = self.data.len() - 8;
|
||||
let bytes: [u8; 8] = self.data[index..].try_into().unwrap();
|
||||
u64::from_be_bytes(bytes)
|
||||
}
|
||||
|
||||
pub fn zero32() -> Self {
|
||||
Bytes { data: [0u8; 32].to_vec() }
|
||||
}
|
||||
|
||||
pub fn zero64() -> Self {
|
||||
Bytes { data: [0u8; 64].to_vec() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Bytes {
|
||||
fn default() -> Bytes {
|
||||
Bytes { data: Vec::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Bytes {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
crate::utils::same_hash(&self.data, &other.data)
|
||||
}
|
||||
|
||||
fn ne(&self, other: &Self) -> bool {
|
||||
!crate::utils::same_hash(&self.data, &other.data)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Bytes {}
|
||||
|
||||
impl PartialOrd for Bytes {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
let self_hash_int = BigUint::from_bytes_be(&self.data);
|
||||
let other_hash_int = BigUint::from_bytes_be(&other.data);
|
||||
Some(self_hash_int.cmp(&other_hash_int))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Bytes {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
let self_hash_int = BigUint::from_bytes_be(&self.data);
|
||||
let other_hash_int = BigUint::from_bytes_be(&other.data);
|
||||
self_hash_int.cmp(&other_hash_int)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Bytes {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.write_str(&crate::utils::to_hex(&self.data))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Bytes {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> where
|
||||
S: Serializer {
|
||||
serializer.serialize_str(&crate::utils::to_hex(&self.data))
|
||||
}
|
||||
}
|
||||
|
||||
struct BytesVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for BytesVisitor {
|
||||
type Value = Bytes;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> Result<(), Error> {
|
||||
formatter.write_str("32 or 64 bytes")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> where E: DeError, {
|
||||
if value.len() == 64 || value.len() == 128 {
|
||||
Ok(Bytes::new(crate::from_hex(value).unwrap()))
|
||||
context.lock().unwrap().bus.register(move |_uuid, e| {
|
||||
if e == Event::ActionStopMining {
|
||||
info!("Stopping keystore miner");
|
||||
mining.store(false, atomic::Ordering::SeqCst);
|
||||
false
|
||||
} else {
|
||||
Err(E::custom("Key must be 32 or 64 bytes!"))
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E> where E: DeError, {
|
||||
if value.len() == 32 || value.len() == 64 {
|
||||
Ok(Bytes::from_bytes(value))
|
||||
} else {
|
||||
Err(E::custom("Key must be 32 or 64 bytes!"))
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
impl<'dd> Deserialize<'dd> for Bytes {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'dd>>::Error> where D: Deserializer<'dd> {
|
||||
deserializer.deserialize_str(BytesVisitor)
|
||||
fn generate_key(difficulty: usize, mining: Arc<AtomicBool>) -> Option<Keystore> {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut buf = [0u8; 32];
|
||||
let mut digest = Blakeout::default();
|
||||
let mut time = Instant::now();
|
||||
let mut count = 0u128;
|
||||
loop {
|
||||
rng.fill_bytes(&mut buf);
|
||||
let keystore = Keystore::from_bytes(&buf);
|
||||
digest.reset();
|
||||
digest.input(keystore.public_key.as_slice());
|
||||
digest.result(&mut buf);
|
||||
if hash_is_good(&buf, difficulty) {
|
||||
info!("Generated keypair: {:?}", &keystore);
|
||||
return Some(keystore);
|
||||
}
|
||||
if !mining.load(atomic::Ordering::SeqCst) {
|
||||
return None;
|
||||
}
|
||||
let elapsed = time.elapsed().as_millis();
|
||||
if elapsed >= 60000 {
|
||||
debug!("Mining speed {} H/s", count / 60);
|
||||
time = Instant::now();
|
||||
count = 0;
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{Keystore, Bytes};
|
||||
use crate::{Bytes, Keystore};
|
||||
|
||||
#[test]
|
||||
pub fn test_signature() {
|
||||
@@ -242,12 +189,6 @@ mod tests {
|
||||
let data = b"{ identity: 178135D209C697625E3EC71DA5C760382E54936F824EE5083908DA66B14ECE18,\
|
||||
confirmation: A4A0AFECD1A511825226F0D3437C6C6BDAE83554040AA7AEB49DEFEAB0AE9EA4 }";
|
||||
let signature = keystore.sign(data);
|
||||
assert!(Keystore::check(data, keystore.get_public().as_bytes(), &signature), "Wrong signature!")
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_tail_bytes() {
|
||||
let bytes = Bytes::new(vec![0, 255, 255, 255, 0, 255, 255, 255]);
|
||||
assert_eq!(bytes.get_tail_u64(), 72057589759737855u64);
|
||||
assert!(Keystore::check(data, keystore.get_public().as_slice(), &signature), "Wrong signature!")
|
||||
}
|
||||
}
|
||||
+7
-3
@@ -1,10 +1,12 @@
|
||||
pub use blockchain::block::Block;
|
||||
pub use blockchain::transaction::Transaction;
|
||||
|
||||
pub use crate::blockchain::Blockchain;
|
||||
pub use crate::blockchain::Chain;
|
||||
pub use crate::context::Context;
|
||||
pub use settings::Settings;
|
||||
pub use crate::keys::Bytes;
|
||||
pub use crate::miner::Miner;
|
||||
pub use crate::p2p::Network;
|
||||
pub use crate::settings::Settings;
|
||||
pub use crate::bytes::Bytes;
|
||||
pub use crate::keys::Keystore;
|
||||
pub use crate::simplebus::*;
|
||||
pub use crate::utils::*;
|
||||
@@ -18,5 +20,7 @@ pub mod context;
|
||||
pub mod event;
|
||||
pub mod p2p;
|
||||
pub mod dns;
|
||||
pub mod dns_utils;
|
||||
pub mod settings;
|
||||
pub mod bytes;
|
||||
|
||||
|
||||
+18
-412
@@ -2,44 +2,25 @@
|
||||
// This is silently ignored on non-windows systems.
|
||||
// See https://msdn.microsoft.com/en-us/library/4cc7ya5b.aspx for more details.
|
||||
#![windows_subsystem = "windows"]
|
||||
extern crate web_view;
|
||||
extern crate tinyfiledialogs as tfd;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
|
||||
use std::env;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(windows)]
|
||||
use winapi::um::wincon::{AttachConsole, FreeConsole, ATTACH_PARENT_PROCESS};
|
||||
|
||||
use rand::RngCore;
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
use thread_priority::*;
|
||||
use serde::Deserialize;
|
||||
use web_view::*;
|
||||
use getopts::Options;
|
||||
use simple_logger::{SimpleLogger};
|
||||
#[allow(unused_imports)]
|
||||
use log::{trace, debug, info, warn, error, LevelFilter};
|
||||
use log::{debug, error, info, LevelFilter, trace, warn};
|
||||
use simple_logger::SimpleLogger;
|
||||
#[cfg(windows)]
|
||||
use winapi::um::wincon::{ATTACH_PARENT_PROCESS, AttachConsole, FreeConsole};
|
||||
|
||||
use alfis::{Blockchain, Bytes, Context, Keystore, Transaction, check_domain, Block};
|
||||
use alfis::event::Event;
|
||||
use alfis::miner::Miner;
|
||||
use alfis::p2p::Network;
|
||||
use alfis::settings::Settings;
|
||||
use alfis::dns::context::{ServerContext, ResolveStrategy};
|
||||
use alfis::dns::server::{DnsServer, DnsUdpServer, DnsTcpServer};
|
||||
use alfis::dns::protocol::DnsRecord;
|
||||
use alfis::blockchain::filter::BlockchainFilter;
|
||||
use alfis::{Block, Bytes, Chain, Miner, Context, Network, Settings, dns_utils, Keystore};
|
||||
|
||||
mod web_ui;
|
||||
|
||||
const KEYSTORE_DIFFICULTY: usize = 24;
|
||||
const SETTINGS_FILENAME: &str = "alfis.toml";
|
||||
const LOG_TARGET_MAIN: &str = "alfis::Main";
|
||||
const LOG_TARGET_UI: &str = "alfis::UI";
|
||||
|
||||
fn main() {
|
||||
// When linked with the windows subsystem windows won't automatically attach
|
||||
@@ -95,23 +76,23 @@ fn main() {
|
||||
}
|
||||
Some(keystore) => { keystore }
|
||||
};
|
||||
let blockchain: Blockchain = Blockchain::new(&settings);
|
||||
let chain: Chain = Chain::new(&settings);
|
||||
if opt_matches.opt_present("l") {
|
||||
for i in 1..(blockchain.height() + 1) {
|
||||
if let Some(block) = blockchain.get_block(i) {
|
||||
for i in 1..(chain.height() + 1) {
|
||||
if let Some(block) = chain.get_block(i) {
|
||||
info!(target: LOG_TARGET_MAIN, "{:?}", &block);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
match blockchain.get_block(1) {
|
||||
match chain.get_block(1) {
|
||||
None => { info!(target: LOG_TARGET_MAIN, "No blocks found in DB"); }
|
||||
Some(block) => { trace!(target: LOG_TARGET_MAIN, "Loaded DB with origin {:?}", &block.hash); }
|
||||
}
|
||||
let settings_copy = settings.clone();
|
||||
let context: Arc<Mutex<Context>> = Arc::new(Mutex::new(Context::new(settings, keystore, blockchain)));
|
||||
start_dns_server(&context, &settings_copy);
|
||||
let context: Arc<Mutex<Context>> = Arc::new(Mutex::new(Context::new(settings, keystore, chain)));
|
||||
dns_utils::start_dns_server(&context, &settings_copy);
|
||||
|
||||
let mut miner_obj = Miner::new(context.clone());
|
||||
miner_obj.start_mining_thread();
|
||||
@@ -127,7 +108,7 @@ fn main() {
|
||||
thread::sleep(sleep);
|
||||
}
|
||||
} else {
|
||||
run_interface(context.clone(), miner.clone());
|
||||
web_ui::run_interface(context.clone(), miner.clone());
|
||||
}
|
||||
|
||||
// Without explicitly detaching the console cmd won't redraw it's prompt.
|
||||
@@ -137,393 +118,18 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
fn start_dns_server(context: &Arc<Mutex<Context>>, settings: &Settings) {
|
||||
let server_context = create_server_context(context.clone(), &settings);
|
||||
|
||||
if server_context.enable_udp {
|
||||
let udp_server = DnsUdpServer::new(server_context.clone(), settings.dns.threads);
|
||||
if let Err(e) = udp_server.run_server() {
|
||||
error!(target: LOG_TARGET_MAIN, "Failed to bind UDP listener: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
if server_context.enable_tcp {
|
||||
let tcp_server = DnsTcpServer::new(server_context.clone(), settings.dns.threads);
|
||||
if let Err(e) = tcp_server.run_server() {
|
||||
error!(target: LOG_TARGET_MAIN, "Failed to bind TCP listener: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_genesis_if_needed(context: &Arc<Mutex<Context>>, miner: &Arc<Mutex<Miner>>) {
|
||||
// If there is no origin in settings and no blockchain in DB, generate genesis block
|
||||
let context = context.lock().unwrap();
|
||||
let last_block = context.get_blockchain().last_block();
|
||||
let last_block = context.get_chain().last_block();
|
||||
let origin = context.settings.origin.clone();
|
||||
if origin.eq("") && last_block.is_none() {
|
||||
// If blockchain is empty, we are going to mine a Genesis block
|
||||
create_genesis(miner.clone(), &context.get_keystore());
|
||||
let block = Block::new(None, context.get_keystore().get_public(), Bytes::default());
|
||||
miner.lock().unwrap().add_block(block);
|
||||
}
|
||||
}
|
||||
|
||||
fn run_interface(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>) {
|
||||
let file_content = include_str!("webview/index.html");
|
||||
let mut styles = inline_style(include_str!("webview/bulma.css"));
|
||||
styles.push_str(&inline_style(include_str!("webview/busy_indicator.css")));
|
||||
let scripts = inline_script(include_str!("webview/scripts.js"));
|
||||
|
||||
let html = Content::Html(file_content.to_owned().replace("{styles}", &styles).replace("{scripts}", &scripts));
|
||||
let title = format!("ALFIS {}", env!("CARGO_PKG_VERSION"));
|
||||
let mut interface = web_view::builder()
|
||||
.title(&title)
|
||||
.content(html)
|
||||
.size(1023, 720)
|
||||
.min_size(895, 350)
|
||||
.resizable(true)
|
||||
.debug(false)
|
||||
.user_data(())
|
||||
.invoke_handler(|web_view, arg| {
|
||||
use Cmd::*;
|
||||
debug!(target: LOG_TARGET_UI, "Command {}", arg);
|
||||
match serde_json::from_str(arg).unwrap() {
|
||||
Loaded => {
|
||||
web_view.eval("showMiningIndicator(false, false);").expect("Error evaluating!");
|
||||
let handle = web_view.handle();
|
||||
let mut status = Status::new();
|
||||
let mut c = context.lock().unwrap();
|
||||
c.bus.register(move |_uuid, e| {
|
||||
debug!(target: LOG_TARGET_UI, "Got event from bus {:?}", &e);
|
||||
let eval = match e {
|
||||
Event::KeyCreated { path, public } => { format!("keystoreChanged('{}', '{}');", &path, &public) }
|
||||
Event::KeyLoaded { path, public } => { format!("keystoreChanged('{}', '{}');", &path, &public) }
|
||||
Event::KeySaved { path, public } => { format!("keystoreChanged('{}', '{}');", &path, &public) }
|
||||
Event::MinerStarted => {
|
||||
status.mining = true;
|
||||
String::from("setLeftStatusBarText('Mining...'); showMiningIndicator(true, false);")
|
||||
}
|
||||
Event::KeyGeneratorStarted => {
|
||||
status.mining = true;
|
||||
String::from("setLeftStatusBarText('Mining...'); showMiningIndicator(true, false);")
|
||||
}
|
||||
Event::MinerStopped | Event::KeyGeneratorStopped => {
|
||||
status.mining = false;
|
||||
if status.syncing {
|
||||
String::from("setLeftStatusBarText('Syncing...'); showMiningIndicator(true, true);")
|
||||
} else {
|
||||
String::from("setLeftStatusBarText('Idle'); showMiningIndicator(false, false);")
|
||||
}
|
||||
}
|
||||
Event::Syncing { have, height } => {
|
||||
status.syncing = true;
|
||||
status.synced_blocks = have;
|
||||
status.sync_height = height;
|
||||
if status.mining {
|
||||
String::from("setLeftStatusBarText('Mining...'); showMiningIndicator(true, false);")
|
||||
} else {
|
||||
format!("setLeftStatusBarText('Synchronizing {}/{}'); showMiningIndicator(true, true);", have, height)
|
||||
}
|
||||
}
|
||||
Event::SyncFinished => {
|
||||
status.syncing = false;
|
||||
if status.mining {
|
||||
String::from("setLeftStatusBarText('Mining...'); showMiningIndicator(true, false);")
|
||||
} else {
|
||||
format!("setLeftStatusBarText('Idle'); showMiningIndicator(false, false);")
|
||||
}
|
||||
}
|
||||
Event::NetworkStatus { nodes, blocks } => {
|
||||
if status.mining || status.syncing || nodes < 3 {
|
||||
format!("setRightStatusBarText('Nodes: {}, Blocks: {}')", nodes, blocks)
|
||||
} else {
|
||||
format!("setLeftStatusBarText('Idle'); setRightStatusBarText('Nodes: {}, Blocks: {}')", nodes, blocks)
|
||||
}
|
||||
}
|
||||
_ => { String::new() }
|
||||
};
|
||||
|
||||
if !eval.is_empty() {
|
||||
debug!(target: LOG_TARGET_UI, "Evaluating {}", &eval);
|
||||
handle.dispatch(move |web_view| {
|
||||
web_view.eval(&eval.replace("\\", "\\\\"))
|
||||
}).expect("Error dispatching!");
|
||||
}
|
||||
true
|
||||
});
|
||||
let eval = format!("keystoreChanged('{}', '{}');", c.keystore.get_path(), &c.keystore.get_public().to_string());
|
||||
debug!(target: LOG_TARGET_UI, "Evaluating {}", &eval);
|
||||
web_view.eval(&eval.replace("\\", "\\\\")).expect("Error evaluating!");
|
||||
}
|
||||
LoadKey {} => {
|
||||
let result = tfd::open_file_dialog("Open keys file", "", Some((&["*.key"], "*.key")));
|
||||
match result {
|
||||
None => {}
|
||||
Some(file_name) => {
|
||||
match Keystore::from_file(&file_name, "") {
|
||||
None => {
|
||||
error!(target: LOG_TARGET_UI, "Error loading keystore '{}'!", &file_name);
|
||||
}
|
||||
Some(keystore) => {
|
||||
info!(target: LOG_TARGET_UI, "Loaded keystore with key: {:?}", &keystore.get_public());
|
||||
let mut c = context.lock().unwrap();
|
||||
c.bus.post(Event::KeyLoaded { path: keystore.get_path().to_owned(), public: keystore.get_public().to_string() });
|
||||
c.set_keystore(keystore);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CreateKey {} => {
|
||||
create_key(context.clone());
|
||||
}
|
||||
SaveKey {} => {
|
||||
let result = tfd::save_file_dialog_with_filter("Save keys file", "", &["*.key"], "Key files (*.key)");
|
||||
match result {
|
||||
None => {}
|
||||
Some(new_path) => {
|
||||
let mut c = context.lock().unwrap();
|
||||
let path = new_path.clone();
|
||||
let public = c.keystore.get_public().to_string();
|
||||
c.keystore.save(&new_path, "");
|
||||
info!(target: LOG_TARGET_UI, "Key file saved to {}", &path);
|
||||
c.bus.post(Event::KeySaved { path, public });
|
||||
}
|
||||
}
|
||||
}
|
||||
CheckDomain { name } => {
|
||||
let name = name.to_lowercase();
|
||||
let c = context.lock().unwrap();
|
||||
let available = c.get_blockchain().is_domain_available(&name, &c.get_keystore());
|
||||
web_view.eval(&format!("domainAvailable({})", available)).expect("Error evaluating!");
|
||||
}
|
||||
CreateDomain { name, records, .. } => {
|
||||
debug!(target: LOG_TARGET_UI, "Got records: {}", records);
|
||||
let name = name.to_lowercase();
|
||||
if !check_domain(&name, true) {
|
||||
return Ok(());
|
||||
}
|
||||
if serde_json::from_str::<Vec<DnsRecord>>(&records).is_ok() {
|
||||
let (keystore, transaction) = {
|
||||
let context = context.lock().unwrap();
|
||||
(context.get_keystore(), context.blockchain.get_domain_transaction(&name))
|
||||
};
|
||||
match transaction {
|
||||
None => {
|
||||
create_domain(miner.clone(), name, records, &keystore);
|
||||
}
|
||||
Some(transaction) => {
|
||||
if transaction.pub_key == keystore.get_public() {
|
||||
create_domain(miner.clone(), name, records, &keystore);
|
||||
} else {
|
||||
warn!(target: LOG_TARGET_UI, "Tried to mine not owned domain!");
|
||||
let _ = web_view.eval(&format!("showWarning('{}');", "You cannot change domain that you don't own!"));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!(target: LOG_TARGET_UI, "Error in DNS records for domain!");
|
||||
let _ = web_view.eval(&format!("showWarning('{}');", "Something wrong with your records! Please, correct the error and try again."));
|
||||
}
|
||||
}
|
||||
ChangeDomain { .. } => {}
|
||||
RenewDomain { .. } => {}
|
||||
TransferDomain { .. } => {}
|
||||
CheckZone { name } => {
|
||||
let name = name.to_lowercase();
|
||||
if !check_domain(&name, false) {
|
||||
web_view.eval("zoneAvailable(false)").expect("Error evaluating!");
|
||||
} else {
|
||||
let c = context.lock().unwrap();
|
||||
let available = c.get_blockchain().is_domain_available(&name, &c.get_keystore());
|
||||
web_view.eval(&format!("zoneAvailable({})", available)).expect("Error evaluating!");
|
||||
}
|
||||
}
|
||||
CreateZone { name, data } => {
|
||||
let name = name.to_lowercase();
|
||||
let data = data.to_lowercase();
|
||||
let (keystore, transaction) = {
|
||||
let context = context.lock().unwrap();
|
||||
(context.get_keystore(), context.blockchain.get_domain_transaction(&name))
|
||||
};
|
||||
match transaction {
|
||||
None => {
|
||||
create_domain(miner.clone(), name, data, &keystore);
|
||||
}
|
||||
Some(transaction) => {
|
||||
if transaction.pub_key == keystore.get_public() {
|
||||
create_domain(miner.clone(), name, data, &keystore);
|
||||
} else {
|
||||
warn!(target: LOG_TARGET_UI, "Tried to mine not owned domain!");
|
||||
let _ = web_view.eval(&format!("showWarning('{}');", "You cannot change domain that you don't own!"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
StopMining => {
|
||||
context.lock().unwrap().bus.post(Event::ActionStopMining);
|
||||
}
|
||||
}
|
||||
//dbg!(&signature);
|
||||
Ok(())
|
||||
})
|
||||
.build()
|
||||
.expect("Error building GUI");
|
||||
|
||||
// We use this ugly loop to lower CPU usage a lot.
|
||||
// If we use .run() or only .step() in a loop without sleeps it will try
|
||||
// to support 60FPS and uses more CPU than it should.
|
||||
let pause = Duration::from_millis(25);
|
||||
let mut start = Instant::now();
|
||||
loop {
|
||||
match interface.step() {
|
||||
None => {
|
||||
info!(target: LOG_TARGET_UI, "Interface closed, exiting");
|
||||
break;
|
||||
}
|
||||
Some(result) => {
|
||||
match result {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
error!(target: LOG_TARGET_UI, "Something wrong with webview, exiting");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if start.elapsed().as_millis() > 1 {
|
||||
thread::sleep(pause);
|
||||
start = Instant::now();
|
||||
}
|
||||
}
|
||||
interface.exit();
|
||||
}
|
||||
|
||||
fn create_genesis(miner: Arc<Mutex<Miner>>, keystore: &Keystore) {
|
||||
let block = Block::new(None, keystore.get_public(), Bytes::default());
|
||||
let mut miner_guard = miner.lock().unwrap();
|
||||
miner_guard.add_block(block);
|
||||
}
|
||||
|
||||
fn create_domain<S: Into<String>>(miner: Arc<Mutex<Miner>>, name: S, data: S, keystore: &Keystore) {
|
||||
let name = name.into();
|
||||
info!(target: LOG_TARGET_UI, "Generating domain or zone {}", name);
|
||||
//let tags_vector: Vec<String> = tags.into().trim().split(",").map(|s| s.trim()).map(String::from).collect();
|
||||
let transaction = Transaction::from_str(name.into(), "dns".into(), data.into(), keystore.get_public().clone());
|
||||
let block = Block::new(Some(transaction), keystore.get_public(), Bytes::default());
|
||||
let mut miner_guard = miner.lock().unwrap();
|
||||
miner_guard.add_block(block);
|
||||
}
|
||||
|
||||
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); }
|
||||
for _ in 0..num_cpus::get() {
|
||||
let context = context.clone();
|
||||
let mining = mining.clone();
|
||||
let miners_count = miners_count.clone();
|
||||
thread::spawn(move || {
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let _ = set_current_thread_priority(ThreadPriority::Min);
|
||||
miners_count.fetch_add(1, Ordering::Relaxed);
|
||||
match generate_key(KEYSTORE_DIFFICULTY, mining.clone()) {
|
||||
None => {
|
||||
debug!(target: LOG_TARGET_UI, "Keystore mining finished");
|
||||
}
|
||||
Some(keystore) => {
|
||||
info!(target: LOG_TARGET_UI, "Key mined successfully: {:?}", &keystore.get_public());
|
||||
let mut c = context.lock().unwrap();
|
||||
mining.store(false, Ordering::Relaxed);
|
||||
c.bus.post(Event::KeyCreated { path: keystore.get_path().to_owned(), public: keystore.get_public().to_string() });
|
||||
c.set_keystore(keystore);
|
||||
}
|
||||
}
|
||||
let miners = miners_count.fetch_sub(1, Ordering::Relaxed) - 1;
|
||||
if miners == 0 {
|
||||
context.lock().unwrap().bus.post(Event::KeyGeneratorStopped);
|
||||
}
|
||||
});
|
||||
}
|
||||
context.lock().unwrap().bus.register(move |_uuid, e| {
|
||||
if e == Event::ActionStopMining {
|
||||
mining.store(false, Ordering::Relaxed);
|
||||
}
|
||||
false
|
||||
});
|
||||
}
|
||||
|
||||
fn generate_key(difficulty: usize, mining: Arc<AtomicBool>) -> Option<Keystore> {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut buf = [0u8; 32];
|
||||
loop {
|
||||
rng.fill_bytes(&mut buf);
|
||||
let keystore = Keystore::from_bytes(&buf);
|
||||
if keystore.hash_is_good(difficulty) {
|
||||
info!(target: LOG_TARGET_UI, "Generated keypair: {:?}", &keystore);
|
||||
return Some(keystore);
|
||||
}
|
||||
if !mining.load(Ordering::Relaxed) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_server_context(context: Arc<Mutex<Context>>, settings: &Settings) -> Arc<ServerContext> {
|
||||
let mut server_context = ServerContext::new();
|
||||
server_context.allow_recursive = true;
|
||||
server_context.dns_listen = settings.dns.listen.clone();
|
||||
server_context.resolve_strategy = match settings.dns.forwarders.is_empty() {
|
||||
true => { ResolveStrategy::Recursive }
|
||||
false => { ResolveStrategy::Forward { upstreams: settings.dns.forwarders.clone() } }
|
||||
};
|
||||
server_context.filters.push(Box::new(BlockchainFilter::new(context)));
|
||||
match server_context.initialize() {
|
||||
Ok(_) => {}
|
||||
Err(e) => { panic!("DNS server failed to initialize: {:?}", e); }
|
||||
}
|
||||
|
||||
Arc::new(server_context)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "cmd", rename_all = "camelCase")]
|
||||
pub enum Cmd {
|
||||
Loaded,
|
||||
LoadKey {},
|
||||
CreateKey {},
|
||||
SaveKey {},
|
||||
CheckZone { name: String },
|
||||
CreateZone { name: String, data: String },
|
||||
CheckDomain { name: String },
|
||||
CreateDomain { name: String, records: String, tags: String },
|
||||
ChangeDomain { name: String, records: String, tags: String },
|
||||
RenewDomain { name: String, days: u16 },
|
||||
TransferDomain { name: String, owner: String },
|
||||
StopMining,
|
||||
}
|
||||
|
||||
struct Status {
|
||||
pub mining: bool,
|
||||
pub syncing: bool,
|
||||
pub synced_blocks: u64,
|
||||
pub sync_height: u64,
|
||||
pub nodes_connected: usize,
|
||||
pub chain_height: u64
|
||||
}
|
||||
|
||||
impl Status {
|
||||
fn new() -> Self {
|
||||
Status { mining: false, syncing: false, synced_blocks: 0, sync_height: 0, nodes_connected: 0, chain_height: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
fn inline_style(s: &str) -> String {
|
||||
format!(r#"<style type="text/css">{}</style>"#, s)
|
||||
}
|
||||
|
||||
fn inline_script(s: &str) -> String {
|
||||
format!(r#"<script type="text/javascript">{}</script>"#, s)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alfis::dns::protocol::{DnsRecord, TransientTtl};
|
||||
|
||||
+8
-7
@@ -12,9 +12,10 @@ use num_cpus;
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
use thread_priority::*;
|
||||
|
||||
use crate::{Block, Bytes, Context, hash_is_good};
|
||||
use crate::blockchain::blockchain::BlockQuality;
|
||||
use crate::{Block, Bytes, Context};
|
||||
use crate::blockchain::{BLOCK_DIFFICULTY, CHAIN_VERSION, LOCKER_DIFFICULTY};
|
||||
use crate::blockchain::enums::BlockQuality;
|
||||
use crate::blockchain::hash_utils::*;
|
||||
use crate::event::Event;
|
||||
|
||||
pub struct Miner {
|
||||
@@ -112,7 +113,7 @@ impl Miner {
|
||||
info!("Mining locker block");
|
||||
block.difficulty = LOCKER_DIFFICULTY;
|
||||
block.pub_key = context.lock().unwrap().keystore.get_public();
|
||||
match context.lock().unwrap().blockchain.last_block() {
|
||||
match context.lock().unwrap().chain.last_block() {
|
||||
None => {}
|
||||
Some(last_block) => {
|
||||
info!("Last block found");
|
||||
@@ -127,8 +128,8 @@ impl Miner {
|
||||
}
|
||||
} else {
|
||||
block.difficulty = BLOCK_DIFFICULTY;
|
||||
block.index = context.lock().unwrap().blockchain.height() + 1;
|
||||
block.prev_block_hash = match context.lock().unwrap().blockchain.last_block() {
|
||||
block.index = context.lock().unwrap().chain.height() + 1;
|
||||
block.prev_block_hash = match context.lock().unwrap().chain.last_block() {
|
||||
None => { Bytes::default() }
|
||||
Some(block) => { block.hash }
|
||||
};
|
||||
@@ -161,13 +162,13 @@ impl Miner {
|
||||
let index = block.index;
|
||||
let mut context = context.lock().unwrap();
|
||||
block.signature = Bytes::from_bytes(&context.keystore.sign(&block.as_bytes()));
|
||||
if context.blockchain.check_new_block(&block) != BlockQuality::Good {
|
||||
if context.chain.check_new_block(&block) != BlockQuality::Good {
|
||||
warn!("Error adding mined block!");
|
||||
if index == 0 {
|
||||
error!("To mine genesis block you need to make 'origin' an empty string in config.");
|
||||
}
|
||||
} else {
|
||||
context.blockchain.add_block(block);
|
||||
context.chain.add_block(block);
|
||||
}
|
||||
context.bus.post(Event::MinerStopped);
|
||||
mining.store(false, Ordering::SeqCst);
|
||||
|
||||
+17
-17
@@ -15,7 +15,7 @@ use log::{trace, debug, info, warn, error};
|
||||
|
||||
use crate::{Context, Block, p2p::Message, p2p::State, p2p::Peer, p2p::Peers};
|
||||
use std::net::{SocketAddr, IpAddr, SocketAddrV4, ToSocketAddrs};
|
||||
use crate::blockchain::blockchain::BlockQuality;
|
||||
use crate::blockchain::enums::BlockQuality;
|
||||
use crate::blockchain::CHAIN_VERSION;
|
||||
use chrono::Utc;
|
||||
|
||||
@@ -95,7 +95,7 @@ impl Network {
|
||||
if !handle_connection_event(context.clone(), &mut peers, &poll.registry(), &event) {
|
||||
let _ = peers.close_peer(poll.registry(), &token);
|
||||
let mut context = context.lock().unwrap();
|
||||
let blocks_count = context.blockchain.height();
|
||||
let blocks_count = context.chain.height();
|
||||
context.bus.post(crate::event::Event::NetworkStatus { nodes: peers.get_peers_active_count(), blocks: blocks_count });
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,7 @@ impl Network {
|
||||
// Send pings to idle peers
|
||||
let (height, hash) = {
|
||||
let context = context.lock().unwrap();
|
||||
(context.blockchain.height(), context.blockchain.last_hash())
|
||||
(context.chain.height(), context.chain.last_hash())
|
||||
};
|
||||
mine_locker_block(context.clone());
|
||||
peers.send_pings(poll.registry(), height, hash);
|
||||
@@ -193,7 +193,7 @@ fn handle_connection_event(context: Arc<Mutex<Context>>, peers: &mut Peers, regi
|
||||
if from.elapsed().as_secs() >= 30 {
|
||||
let data: String = {
|
||||
let c = context.lock().unwrap();
|
||||
let message = Message::ping(c.blockchain.height(), c.blockchain.last_hash());
|
||||
let message = Message::ping(c.chain.height(), c.chain.last_hash());
|
||||
serde_json::to_string(&message).unwrap()
|
||||
};
|
||||
send_message(peer.get_stream(), &data.into_bytes());
|
||||
@@ -267,7 +267,7 @@ fn handle_message(context: Arc<Mutex<Context>>, message: Message, peers: &mut Pe
|
||||
let (my_height, my_hash, my_origin, my_version) = {
|
||||
let context = context.lock().unwrap();
|
||||
// TODO cache it somewhere
|
||||
(context.blockchain.height(), context.blockchain.last_hash(), &context.settings.origin.clone(), CHAIN_VERSION)
|
||||
(context.chain.height(), context.chain.last_hash(), &context.settings.origin.clone(), CHAIN_VERSION)
|
||||
};
|
||||
match message {
|
||||
Message::Hand { origin, version, public } => {
|
||||
@@ -290,10 +290,10 @@ fn handle_message(context: Arc<Mutex<Context>>, message: Message, peers: &mut Pe
|
||||
peer.set_height(height);
|
||||
peer.set_active(true);
|
||||
let mut context = context.lock().unwrap();
|
||||
let blocks_count = context.blockchain.height();
|
||||
let blocks_count = context.chain.height();
|
||||
context.bus.post(crate::event::Event::NetworkStatus { nodes: active_count + 1, blocks: blocks_count });
|
||||
if peer.is_higher(my_height) {
|
||||
context.blockchain.update_max_height(height);
|
||||
context.chain.update_max_height(height);
|
||||
context.bus.post(crate::event::Event::Syncing { have: my_height, height});
|
||||
State::message(Message::GetBlock { index: my_height + 1 })
|
||||
} else {
|
||||
@@ -321,7 +321,7 @@ fn handle_message(context: Arc<Mutex<Context>>, message: Message, peers: &mut Pe
|
||||
let is_higher = peer.is_higher(my_height);
|
||||
|
||||
let mut context = context.lock().unwrap();
|
||||
let blocks_count = context.blockchain.height();
|
||||
let blocks_count = context.chain.height();
|
||||
context.bus.post(crate::event::Event::NetworkStatus { nodes: peers.get_peers_active_count(), blocks: blocks_count });
|
||||
|
||||
if is_higher {
|
||||
@@ -342,7 +342,7 @@ fn handle_message(context: Arc<Mutex<Context>>, message: Message, peers: &mut Pe
|
||||
}
|
||||
Message::GetBlock { index } => {
|
||||
let context = context.lock().unwrap();
|
||||
match context.blockchain.get_block(index) {
|
||||
match context.chain.get_block(index) {
|
||||
Some(block) => State::message(Message::block(block.index, serde_json::to_string(&block).unwrap())),
|
||||
None => State::Error
|
||||
}
|
||||
@@ -359,11 +359,11 @@ fn handle_message(context: Arc<Mutex<Context>>, message: Message, peers: &mut Pe
|
||||
let peers_count = peers.get_peers_active_count();
|
||||
thread::spawn(move || {
|
||||
let mut context = context.lock().unwrap();
|
||||
let max_height = context.blockchain.max_height();
|
||||
match context.blockchain.check_new_block(&block) {
|
||||
let max_height = context.chain.max_height();
|
||||
match context.chain.check_new_block(&block) {
|
||||
BlockQuality::Good => {
|
||||
context.blockchain.add_block(block);
|
||||
let my_height = context.blockchain.height();
|
||||
context.chain.add_block(block);
|
||||
let my_height = context.chain.height();
|
||||
context.bus.post(crate::event::Event::BlockchainChanged);
|
||||
// If it was the last block to sync
|
||||
if my_height == max_height {
|
||||
@@ -392,12 +392,12 @@ 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>>) {
|
||||
let mut context = context.lock().unwrap();
|
||||
if let Some(block) = context.blockchain.last_block() {
|
||||
if block.index < context.blockchain.max_height() {
|
||||
if let Some(block) = context.chain.last_block() {
|
||||
if block.index < context.chain.max_height() {
|
||||
info!("No locker mining while syncing");
|
||||
return;
|
||||
}
|
||||
match context.blockchain.get_block_locker(&block, Utc::now().timestamp()) {
|
||||
match context.chain.get_block_locker(&block, Utc::now().timestamp()) {
|
||||
Some(key) => {
|
||||
if key == context.keystore.get_public() {
|
||||
info!("We have an honor to mine locker block!");
|
||||
@@ -419,7 +419,7 @@ fn deal_with_fork(context: MutexGuard<Context>, peer: &mut Peer, block: Block) {
|
||||
if vector[0].index == 0 {
|
||||
return;
|
||||
}
|
||||
if let Some(prev_block) = context.blockchain.get_block(vector[0].index - 1) {
|
||||
if let Some(prev_block) = context.chain.get_block(vector[0].index - 1) {
|
||||
// If this block is not root of the fork (we need to go ~deeper~ more backwards)
|
||||
if vector[0].prev_block_hash != prev_block.hash {
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use std::num;
|
||||
use num_bigint::BigUint;
|
||||
use num_traits::One;
|
||||
|
||||
/// Convert bytes array to HEX format
|
||||
pub fn to_hex(buf: &[u8]) -> String {
|
||||
@@ -56,23 +54,6 @@ fn split_n(s: &str, n: usize) -> Vec<&str> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// There is no default PartialEq implementation for arrays > 32 in size
|
||||
pub fn same_hash(left: &[u8], right: &[u8]) -> bool {
|
||||
for (x, y) in left.iter().zip(right) {
|
||||
if x != y {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
return hash_int < target;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::check_domain;
|
||||
|
||||
+304
@@ -0,0 +1,304 @@
|
||||
extern crate web_view;
|
||||
extern crate tinyfiledialogs as tfd;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use web_view::Content;
|
||||
#[allow(unused_imports)]
|
||||
use log::{debug, error, info, LevelFilter, trace, warn};
|
||||
use serde::Deserialize;
|
||||
|
||||
use alfis::{Block, Bytes, Context, Keystore, Transaction};
|
||||
use alfis::miner::Miner;
|
||||
use alfis::{keys, check_domain};
|
||||
use alfis::event::Event;
|
||||
use alfis::dns::protocol::DnsRecord;
|
||||
use Cmd::*;
|
||||
|
||||
pub fn run_interface(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>) {
|
||||
let file_content = include_str!("webview/index.html");
|
||||
let mut styles = inline_style(include_str!("webview/bulma.css"));
|
||||
styles.push_str(&inline_style(include_str!("webview/busy_indicator.css")));
|
||||
let scripts = inline_script(include_str!("webview/scripts.js"));
|
||||
|
||||
let html = Content::Html(file_content.to_owned().replace("{styles}", &styles).replace("{scripts}", &scripts));
|
||||
let title = format!("ALFIS {}", env!("CARGO_PKG_VERSION"));
|
||||
let mut interface = web_view::builder()
|
||||
.title(&title)
|
||||
.content(html)
|
||||
.size(1023, 720)
|
||||
.min_size(895, 350)
|
||||
.resizable(true)
|
||||
.debug(false)
|
||||
.user_data(())
|
||||
.invoke_handler(|web_view, arg| {
|
||||
debug!("Command {}", arg);
|
||||
match serde_json::from_str(arg).unwrap() {
|
||||
Loaded => {
|
||||
web_view.eval("showMiningIndicator(false, false);").expect("Error evaluating!");
|
||||
let handle = web_view.handle();
|
||||
let mut status = Status::new();
|
||||
let mut c = context.lock().unwrap();
|
||||
c.bus.register(move |_uuid, e| {
|
||||
debug!("Got event from bus {:?}", &e);
|
||||
let eval = match e {
|
||||
Event::KeyCreated { path, public, hash } => { format!("keystoreChanged('{}', '{}', '{}');", &path, &public, &hash) }
|
||||
Event::KeyLoaded { path, public, hash } => { format!("keystoreChanged('{}', '{}', '{}');", &path, &public, &hash) }
|
||||
Event::KeySaved { path, public, hash } => { format!("keystoreChanged('{}', '{}', '{}');", &path, &public, &hash) }
|
||||
Event::MinerStarted => {
|
||||
status.mining = true;
|
||||
String::from("setLeftStatusBarText('Mining...'); showMiningIndicator(true, false);")
|
||||
}
|
||||
Event::KeyGeneratorStarted => {
|
||||
status.mining = true;
|
||||
String::from("setLeftStatusBarText('Mining...'); showMiningIndicator(true, false);")
|
||||
}
|
||||
Event::MinerStopped | Event::KeyGeneratorStopped => {
|
||||
status.mining = false;
|
||||
if status.syncing {
|
||||
String::from("setLeftStatusBarText('Syncing...'); showMiningIndicator(true, true);")
|
||||
} else {
|
||||
String::from("setLeftStatusBarText('Idle'); showMiningIndicator(false, false);")
|
||||
}
|
||||
}
|
||||
Event::Syncing { have, height } => {
|
||||
status.syncing = true;
|
||||
status.synced_blocks = have;
|
||||
status.sync_height = height;
|
||||
if status.mining {
|
||||
String::from("setLeftStatusBarText('Mining...'); showMiningIndicator(true, false);")
|
||||
} else {
|
||||
format!("setLeftStatusBarText('Synchronizing {}/{}'); showMiningIndicator(true, true);", have, height)
|
||||
}
|
||||
}
|
||||
Event::SyncFinished => {
|
||||
status.syncing = false;
|
||||
if status.mining {
|
||||
String::from("setLeftStatusBarText('Mining...'); showMiningIndicator(true, false);")
|
||||
} else {
|
||||
format!("setLeftStatusBarText('Idle'); showMiningIndicator(false, false);")
|
||||
}
|
||||
}
|
||||
Event::NetworkStatus { nodes, blocks } => {
|
||||
if status.mining || status.syncing || nodes < 3 {
|
||||
format!("setRightStatusBarText('Nodes: {}, Blocks: {}')", nodes, blocks)
|
||||
} else {
|
||||
format!("setLeftStatusBarText('Idle'); setRightStatusBarText('Nodes: {}, Blocks: {}')", nodes, blocks)
|
||||
}
|
||||
}
|
||||
_ => { String::new() }
|
||||
};
|
||||
|
||||
if !eval.is_empty() {
|
||||
debug!("Evaluating {}", &eval);
|
||||
handle.dispatch(move |web_view| {
|
||||
web_view.eval(&eval.replace("\\", "\\\\"))
|
||||
}).expect("Error dispatching!");
|
||||
}
|
||||
true
|
||||
});
|
||||
let eval = format!("keystoreChanged('{}', '{}', '{}');", c.keystore.get_path(), &c.keystore.get_public().to_string(), &c.keystore.get_hash().to_string());
|
||||
debug!("Evaluating {}", &eval);
|
||||
web_view.eval(&eval.replace("\\", "\\\\")).expect("Error evaluating!");
|
||||
}
|
||||
LoadKey {} => {
|
||||
let result = tfd::open_file_dialog("Open keys file", "", Some((&["*.key"], "*.key")));
|
||||
match result {
|
||||
None => {}
|
||||
Some(file_name) => {
|
||||
match Keystore::from_file(&file_name, "") {
|
||||
None => {
|
||||
error!("Error loading keystore '{}'!", &file_name);
|
||||
}
|
||||
Some(keystore) => {
|
||||
info!("Loaded keystore with key: {:?}", &keystore.get_public());
|
||||
let mut c = context.lock().unwrap();
|
||||
let path = keystore.get_path().to_owned();
|
||||
let public = keystore.get_public().to_string();
|
||||
let hash = keystore.get_hash().to_string();
|
||||
c.bus.post(Event::KeyLoaded { path, public, hash });
|
||||
c.set_keystore(keystore);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CreateKey {} => {
|
||||
keys::create_key(context.clone());
|
||||
}
|
||||
SaveKey {} => {
|
||||
let result = tfd::save_file_dialog_with_filter("Save keys file", "", &["*.key"], "Key files (*.key)");
|
||||
match result {
|
||||
None => {}
|
||||
Some(new_path) => {
|
||||
let mut c = context.lock().unwrap();
|
||||
let path = new_path.clone();
|
||||
let public = c.keystore.get_public().to_string();
|
||||
let hash = c.keystore.get_hash().to_string();
|
||||
c.keystore.save(&new_path, "");
|
||||
info!("Key file saved to {}", &path);
|
||||
c.bus.post(Event::KeySaved { path, public, hash });
|
||||
}
|
||||
}
|
||||
}
|
||||
CheckDomain { name } => {
|
||||
let name = name.to_lowercase();
|
||||
let c = context.lock().unwrap();
|
||||
let available = c.get_chain().is_domain_available(&name, &c.get_keystore());
|
||||
web_view.eval(&format!("domainAvailable({})", available)).expect("Error evaluating!");
|
||||
}
|
||||
CreateDomain { name, records, .. } => {
|
||||
debug!("Got records: {}", records);
|
||||
let name = name.to_lowercase();
|
||||
if !check_domain(&name, true) {
|
||||
return Ok(());
|
||||
}
|
||||
if serde_json::from_str::<Vec<DnsRecord>>(&records).is_ok() {
|
||||
let (keystore, transaction) = {
|
||||
let context = context.lock().unwrap();
|
||||
(context.get_keystore(), context.chain.get_domain_transaction(&name))
|
||||
};
|
||||
match transaction {
|
||||
None => {
|
||||
create_domain(miner.clone(), name, records, &keystore);
|
||||
}
|
||||
Some(transaction) => {
|
||||
if transaction.pub_key == keystore.get_public() {
|
||||
create_domain(miner.clone(), name, records, &keystore);
|
||||
} else {
|
||||
warn!("Tried to mine not owned domain!");
|
||||
let _ = web_view.eval(&format!("showWarning('{}');", "You cannot change domain that you don't own!"));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("Error in DNS records for domain!");
|
||||
let _ = web_view.eval(&format!("showWarning('{}');", "Something wrong with your records! Please, correct the error and try again."));
|
||||
}
|
||||
}
|
||||
ChangeDomain { .. } => {}
|
||||
RenewDomain { .. } => {}
|
||||
TransferDomain { .. } => {}
|
||||
CheckZone { name } => {
|
||||
let name = name.to_lowercase();
|
||||
if !check_domain(&name, false) {
|
||||
web_view.eval("zoneAvailable(false)").expect("Error evaluating!");
|
||||
} else {
|
||||
let c = context.lock().unwrap();
|
||||
let available = c.get_chain().is_domain_available(&name, &c.get_keystore());
|
||||
web_view.eval(&format!("zoneAvailable({})", available)).expect("Error evaluating!");
|
||||
}
|
||||
}
|
||||
CreateZone { name, data } => {
|
||||
let name = name.to_lowercase();
|
||||
let data = data.to_lowercase();
|
||||
let (keystore, transaction) = {
|
||||
let context = context.lock().unwrap();
|
||||
(context.get_keystore(), context.chain.get_domain_transaction(&name))
|
||||
};
|
||||
match transaction {
|
||||
None => {
|
||||
create_domain(miner.clone(), name, data, &keystore);
|
||||
}
|
||||
Some(transaction) => {
|
||||
if transaction.pub_key == keystore.get_public() {
|
||||
create_domain(miner.clone(), name, data, &keystore);
|
||||
} else {
|
||||
warn!("Tried to mine not owned domain!");
|
||||
let _ = web_view.eval(&format!("showWarning('{}');", "You cannot change domain that you don't own!"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
StopMining => {
|
||||
context.lock().unwrap().bus.post(Event::ActionStopMining);
|
||||
}
|
||||
}
|
||||
//dbg!(&signature);
|
||||
Ok(())
|
||||
})
|
||||
.build()
|
||||
.expect("Error building GUI");
|
||||
|
||||
// We use this ugly loop to lower CPU usage a lot.
|
||||
// If we use .run() or only .step() in a loop without sleeps it will try
|
||||
// to support 60FPS and uses more CPU than it should.
|
||||
let pause = Duration::from_millis(25);
|
||||
let mut start = Instant::now();
|
||||
loop {
|
||||
match interface.step() {
|
||||
None => {
|
||||
info!("Interface closed, exiting");
|
||||
break;
|
||||
}
|
||||
Some(result) => {
|
||||
match result {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
error!("Something wrong with webview, exiting");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if start.elapsed().as_millis() > 1 {
|
||||
thread::sleep(pause);
|
||||
start = Instant::now();
|
||||
}
|
||||
}
|
||||
interface.exit();
|
||||
}
|
||||
|
||||
fn create_domain<S: Into<String>>(miner: Arc<Mutex<Miner>>, name: S, data: S, keystore: &Keystore) {
|
||||
let name = name.into();
|
||||
info!("Generating domain or zone {}", name);
|
||||
//let tags_vector: Vec<String> = tags.into().trim().split(",").map(|s| s.trim()).map(String::from).collect();
|
||||
let transaction = Transaction::from_str(name.into(), "dns".into(), data.into(), keystore.get_public().clone());
|
||||
let block = Block::new(Some(transaction), keystore.get_public(), Bytes::default());
|
||||
let mut miner_guard = miner.lock().unwrap();
|
||||
miner_guard.add_block(block);
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "cmd", rename_all = "camelCase")]
|
||||
pub enum Cmd {
|
||||
Loaded,
|
||||
LoadKey {},
|
||||
CreateKey {},
|
||||
SaveKey {},
|
||||
CheckZone { name: String },
|
||||
CreateZone { name: String, data: String },
|
||||
CheckDomain { name: String },
|
||||
CreateDomain { name: String, records: String, tags: String },
|
||||
ChangeDomain { name: String, records: String, tags: String },
|
||||
RenewDomain { name: String, days: u16 },
|
||||
TransferDomain { name: String, owner: String },
|
||||
StopMining,
|
||||
}
|
||||
|
||||
struct Status {
|
||||
pub mining: bool,
|
||||
pub syncing: bool,
|
||||
pub synced_blocks: u64,
|
||||
pub sync_height: u64,
|
||||
pub nodes_connected: usize,
|
||||
pub chain_height: u64
|
||||
}
|
||||
|
||||
impl Status {
|
||||
fn new() -> Self {
|
||||
Status { mining: false, syncing: false, synced_blocks: 0, sync_height: 0, nodes_connected: 0, chain_height: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
fn inline_style(s: &str) -> String {
|
||||
format!(r#"<style type="text/css">{}</style>"#, s)
|
||||
}
|
||||
|
||||
fn inline_script(s: &str) -> String {
|
||||
format!(r#"<script type="text/javascript">{}</script>"#, s)
|
||||
}
|
||||
@@ -150,6 +150,11 @@
|
||||
<p id="key_public_key">Not loaded</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Public key hash</label>
|
||||
<p id="key_public_hash">Not loaded</p>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
|
||||
@@ -281,12 +281,14 @@ function setRightStatusBarText(text) {
|
||||
bar.innerHTML = text;
|
||||
}
|
||||
|
||||
function keystoreChanged(path, pub_key) {
|
||||
function keystoreChanged(path, pub_key, hash) {
|
||||
if (path == '') {
|
||||
path = "In memory";
|
||||
}
|
||||
key_file_name = document.getElementById("key_file_name");
|
||||
var key_file_name = document.getElementById("key_file_name");
|
||||
key_file_name.innerHTML = path;
|
||||
key_file_key = document.getElementById("key_public_key");
|
||||
key_file_key.innerHTML = pub_key;
|
||||
var key_public_key = document.getElementById("key_public_key");
|
||||
key_public_key.innerHTML = pub_key;
|
||||
var key_public_hash = document.getElementById("key_public_hash");
|
||||
key_public_hash.innerHTML = hash;
|
||||
}
|
||||
Reference in New Issue
Block a user