Major refactoring. Changed mining algorithm to Blakeout. Changed keypair mining algorithm.

This commit is contained in:
Revertron
2021-03-10 22:21:50 +01:00
parent 5c2373b40d
commit 5d57473122
23 changed files with 824 additions and 713 deletions
+2 -2
View File
@@ -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
View File
@@ -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)]
@@ -71,11 +69,3 @@ impl Block {
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;
}
@@ -457,26 +459,3 @@ impl Blockchain {
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(&copy).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(&copy.as_bytes(), copy.pub_key.as_bytes(), block.signature.as_bytes())
}
+4 -1
View File
@@ -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;
+9
View File
@@ -0,0 +1,9 @@
/// Represents a result of block check on block's arrival
#[derive(PartialEq)]
pub enum BlockQuality {
Good,
Twin,
Future,
Bad,
Fork,
}
+2 -2
View File
@@ -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));
+81
View File
@@ -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(&copy).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(&copy.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;
}
+4 -2
View File
@@ -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::*;
+7 -18
View File
@@ -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,
@@ -76,14 +76,3 @@ impl Serialize for Transaction {
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
View File
@@ -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
View File
@@ -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
}
}
+45
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
-19
View File
@@ -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
View File
@@ -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)
}
+5
View File
@@ -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">
+6 -4
View File
@@ -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;
}