Implemented and added usage of eventbus. Added a lot of UI interaction. Added a lot of DB work.

pull/2/head
Revertron 3 years ago
parent 9e6a01847e
commit 562c004b0d

@ -22,7 +22,7 @@ base64 = "0.11.0"
chrono = "0.4.9"
rand = "0.7.2"
sqlite = "0.25.3"
eventbus = "0.5.1"
uuid = { version = "0.8.2", features = ["serde", "v4"] }
[build-dependencies]
winres = "0.1"

@ -1,4 +1,4 @@
use crate::{Block, Transaction, Bytes};
use crate::{Block, Transaction, Bytes, Keystore};
use chrono::Utc;
use sqlite::{Connection, State, Readable, Statement, Error};
@ -25,7 +25,7 @@ impl Blockchain {
match self.db.prepare("SELECT * FROM blocks ORDER BY id DESC LIMIT 1;") {
Ok(mut statement) => {
while statement.next().unwrap() == State::Row {
match Self::get_block(&mut statement) {
match Self::get_block_from_statement(&mut statement) {
None => { println!("Something wrong with block in DB!"); }
Some(block) => {
println!("Loaded last block: {:?}", &block);
@ -52,56 +52,100 @@ impl Blockchain {
'prev_block_hash' BINARY,
'hash' BINARY
);
CREATE INDEX block_index ON blocks (id);"
CREATE INDEX block_index ON blocks (id);
CREATE TABLE transactions (id INTEGER PRIMARY KEY AUTOINCREMENT, identity BINARY, method TEXT, data TEXT, pub_key BINARY, signature BINARY);
CREATE INDEX ids ON transactions (identity);"
).expect("Error creating blocks table");
}
}
}
fn get_block(statement: &mut Statement) -> Option<Block> {
let index = statement.read::<i64>(0).unwrap() as u64;
let timestamp = statement.read::<i64>(1).unwrap();
let chain_name = statement.read::<String>(2).unwrap();
let version_flags = statement.read::<i64>(3).unwrap() as u32;
let difficulty = statement.read::<i64>(4).unwrap() as usize;
let random = statement.read::<i64>(5).unwrap() as u32;
let nonce = statement.read::<i64>(6).unwrap() as u64;
let transaction = Transaction::from_json(&statement.read::<String>(7).unwrap());
let prev_block_hash = Bytes::from_bytes(statement.read::<Vec<u8>>(8).unwrap().as_slice());
let hash = Bytes::from_bytes(statement.read::<Vec<u8>>(9).unwrap().as_slice());
Some(Block::from_all_params(index, timestamp, &chain_name, version_flags, difficulty, random, nonce, prev_block_hash, hash, transaction))
}
pub fn add_block(&mut self, block: Block) {
if self.check_block(&block, &self.last_block) {
println!("Adding block:\n{:?}", &block);
self.blocks.push(block.clone());
self.last_block = Some(block.clone());
let transaction = block.transaction.clone();
{
// Adding block to DB
let mut statement = self.db.prepare("INSERT INTO blocks (\
id, timestamp, chain_name, version_flags, difficulty,\
random, nonce, 'transaction', prev_block_hash, hash)\
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);").unwrap();
statement.bind(1, block.index as i64);
statement.bind(2, block.timestamp as i64);
statement.bind(3, block.chain_name.as_ref() as &str);
statement.bind(4, block.version_flags as i64);
statement.bind(5, block.difficulty as i64);
statement.bind(6, block.random as i64);
statement.bind(7, block.nonce as i64);
match &transaction {
None => { statement.bind(8, ""); }
Some(transaction) => {
statement.bind(8, transaction.to_string().as_ref() as &str);
}
}
statement.bind(9, block.prev_block_hash.as_bytes());
statement.bind(10, block.hash.as_bytes());
statement.next().expect("Error adding block to DB");
}
// Adding block to DB
let mut statement = self.db.prepare("INSERT INTO blocks (\
id, timestamp, chain_name, version_flags, difficulty,\
random, nonce, 'transaction', prev_block_hash, hash)\
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").unwrap();
statement.bind(1, block.index as i64);
statement.bind(2, block.timestamp as i64);
statement.bind(3, &*block.chain_name);
statement.bind(4, block.version_flags as i64);
statement.bind(5, block.difficulty as i64);
statement.bind(6, block.random as i64);
statement.bind(7, block.nonce as i64);
match block.transaction {
None => { statement.bind(8, ""); }
Some(transaction) => { statement.bind(8, &*transaction.to_string()); }
match &transaction {
None => {}
Some(transaction) => {
self.add_transaction(transaction);
}
}
statement.bind(9, block.prev_block_hash.as_bytes());
statement.bind(10, block.hash.as_bytes());
statement.next().expect("Error adding block to DB");
} else {
println!("Bad block found, ignoring:\n{:?}", &block);
}
}
fn add_transaction(&mut self, t: &Transaction) {
let mut statement = self.db.prepare("INSERT INTO transactions (identity, method, data, pub_key, signature) VALUES (?, ?, ?, ?, ?)").unwrap();
statement.bind(1, t.identity.as_bytes());
statement.bind(2, t.method.as_ref() as &str);
statement.bind(3, t.data.as_ref() as &str);
statement.bind(4, t.pub_key.as_bytes());
statement.bind(5, t.signature.as_bytes());
statement.next().expect("Error adding transaction to DB");
}
pub fn is_domain_available(&self, domain: &str, keystore: &Keystore) -> bool {
if domain.is_empty() {
return false;
}
let identity_hash = Transaction::hash_identity(domain);
let mut statement = self.db.prepare("SELECT pub_key FROM transactions WHERE identity = ? ORDER BY id DESC LIMIT 1;").unwrap();
statement.bind(1, identity_hash.as_bytes());
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(&keystore.get_public()) {
return false;
}
}
let parts: Vec<&str> = domain.rsplitn(2, ".").collect();
if parts.len() > 1 {
// We do not support third level domains
if parts.last().unwrap().contains(".") {
return false;
}
// Checking for available zone, for this domain
let identity_hash = Transaction::hash_identity(parts.first().unwrap());
let mut statement = self.db.prepare("SELECT identity FROM transactions WHERE identity = ? ORDER BY id DESC LIMIT 1;").unwrap();
statement.bind(1, identity_hash.as_bytes());
while let State::Row = statement.next().unwrap() {
// If there is such a zone
return true;
}
return false;
}
true
}
pub fn get_last_block(&self) -> Option<Block> {
self.last_block.clone()
}
@ -129,6 +173,20 @@ impl Blockchain {
return block.prev_block_hash == prev_block.as_ref().unwrap().hash;
}
fn get_block_from_statement(statement: &mut Statement) -> Option<Block> {
let index = statement.read::<i64>(0).unwrap() as u64;
let timestamp = statement.read::<i64>(1).unwrap();
let chain_name = statement.read::<String>(2).unwrap();
let version_flags = statement.read::<i64>(3).unwrap() as u32;
let difficulty = statement.read::<i64>(4).unwrap() as usize;
let random = statement.read::<i64>(5).unwrap() as u32;
let nonce = statement.read::<i64>(6).unwrap() as u64;
let transaction = Transaction::from_json(&statement.read::<String>(7).unwrap());
let prev_block_hash = Bytes::from_bytes(statement.read::<Vec<u8>>(8).unwrap().as_slice());
let hash = Bytes::from_bytes(statement.read::<Vec<u8>>(9).unwrap().as_slice());
Some(Block::from_all_params(index, timestamp, &chain_name, version_flags, difficulty, random, nonce, prev_block_hash, hash, transaction))
}
pub fn check_block_hash(block: &Block) -> bool {
// We need to clear Hash value to rehash it without it for check :(
let mut copy: Block = block.clone();

7
src/bulma.css vendored

@ -10829,4 +10829,9 @@ label.panel-block:hover {
background-color: #fafafa;
padding: 3rem 1.5rem 6rem;
}
/*# sourceMappingURL=bulma.css.map */
/*# sourceMappingURL=bulma.css.map */
// TODO move to another file
.container {
margin: 10pt;
}

@ -1,4 +1,5 @@
use crate::{Keystore, Blockchain};
use crate::{Keystore, Blockchain, Bus};
use crate::event::Event;
use std::collections::HashMap;
use serde::{Serialize, Deserialize, Serializer, Deserializer};
use serde::de::Error;
@ -9,12 +10,13 @@ pub struct Context {
pub(crate) settings: Settings,
pub(crate) keystore: Keystore,
pub(crate) blockchain: Blockchain,
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 }
Context { settings, keystore, blockchain, bus: Bus::new() }
}
/// Load keystore and return Context

@ -0,0 +1,10 @@
#[derive(Clone, PartialEq, Debug)]
pub enum Event {
MinerStarted,
MinerStopped,
KeyGeneratorStarted,
KeyGeneratorStopped,
NewBlockReceived,
BlockchainChanged,
ActionStopMining,
}

@ -8,9 +8,27 @@
<script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
{scripts}
</head>
<body>
<body onload="onLoad();">
<div class="mining_indicator" id="mining_indicator" onclick="miningIndicatorClick(this)">
<span></span>
<span></span>
</div>
<div id="modal_dialog" class="modal">
<div class="modal-background"></div>
<div class="modal-content">
<div class="box">
<p id="modal_text" class="is-centered">Do you really want to cancel mining?</p>
<br/>
<div class="buttons is-grouped is-centered">
<button class="button is-link" id="modal_positive_button" onclick="positiveButton();">Ok</button>
<button class="button is-link is-light" id="modal_negative_button" onclick="cancelButton();">Cancel</button>
</div>
</div>
</div>
</div>
<div class="container">
<br/>
<div class="columns">
<div class="column is-one-fifth">
<div class="menu">
@ -134,7 +152,7 @@
<div class="field">
<label class="label">New domain name</label>
<div class="control">
<input class="input" type="text" placeholder="example.ygg" id="new_domain">
<input class="input" type="text" placeholder="example.ygg" id="new_domain" oninput="onDomainChange(this)">
</div>
</div>
@ -154,7 +172,7 @@
<div class="field is-grouped">
<div class="control">
<button class="button is-link" onclick="createDomain();">Create domain</button>
<button class="button is-link" id="new_domain_button" onclick="createDomain();" disabled>Create domain</button>
</div>
</div>
</form>

@ -6,10 +6,14 @@ pub mod transaction;
pub use crate::transaction::Transaction;
pub mod utils;
pub use crate::utils::*;
pub mod simplebus;
pub use crate::simplebus::*;
pub mod keys;
pub use crate::keys::Keystore;
pub use crate::keys::Bytes;
pub mod miner;
pub mod context;
pub mod event;
pub use crate::context::Context;
pub use crate::context::Settings;

@ -0,0 +1,33 @@
.mining_indicator {
position:absolute;
right:10px;
width:30px;
height:30px;
display:inline-block;
padding:0px;
text-align:right;
float:right;
z-index: 10;
}
.mining_indicator span {
position:absolute;
display:inline-block;
width:30px;
height:30px;
border-radius:100%;
background:#3273dc;
-webkit-animation:mining_indicator 1.6s linear infinite;
animation:mining_indicator 1.6s linear infinite;
}
.mining_indicator span:last-child {
animation-delay:-0.8s;
-webkit-animation-delay:-0.8s;
}
@keyframes mining_indicator {
0% {transform: scale(0, 0);opacity:0.5;}
100% {transform: scale(1, 1);opacity:0;}
}
@-webkit-keyframes mining_indicator {
0% {-webkit-transform: scale(0, 0);opacity:0.5;}
100% {-webkit-transform: scale(1, 1);opacity:0;}
}

@ -1,17 +1,20 @@
#![windows_subsystem = "windows"]
extern crate web_view;
use alfis::{Blockchain, Block, Transaction, Keystore, Bytes, Settings, Context};
use alfis::miner::Miner;
use web_view::*;
use std::thread;
use rand::{Rng, RngCore};
use std::sync::{Arc, Mutex};
extern crate serde;
use serde::{Serialize, Deserialize};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use rand::{Rng, RngCore};
use serde::{Deserialize, Serialize};
use web_view::*;
use alfis::{Block, Blockchain, Bytes, Context, Keystore, Settings, Transaction};
use alfis::event::Event;
use alfis::miner::Miner;
extern crate serde;
extern crate serde_json;
const ONE_YEAR: u16 = 365;
@ -49,34 +52,61 @@ fn create_genesis_if_needed(context: &Arc<Mutex<Context>>, miner: &Arc<Mutex<Min
fn run_interface(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>) {
let file_content = include_str!("index.html");
let styles = inline_style(include_str!("bulma.css"));
let mut styles= inline_style(include_str!("bulma.css"));
styles.push_str(&inline_style(include_str!("loader.css")));
let scripts = inline_script(include_str!("scripts.js"));
let html = Content::Html(file_content.to_owned().replace("{styles}", &styles).replace("{scripts}", &scripts));
web_view::builder()
.title("ALFIS 0.1.0")
.content(Content::Html(file_content.to_owned().replace("{styles}", &styles).replace("{scripts}", &scripts)))
.content(html)
.size(1024, 720)
.resizable(true)
.debug(true)
.user_data(())
.invoke_handler(|_web_view, arg| {
.invoke_handler(|web_view, arg| {
use Cmd::*;
println!("Command {}", arg);
match serde_json::from_str(arg).unwrap() {
Loaded => {
web_view.eval("showMiningIndicator(false);");
let mut handle = web_view.handle();
let mut c = context.lock().unwrap();
c.bus.register(move |_uuid, e| {
println!("Got event from bus {:?}", &e);
let visible = match e {
Event::MinerStarted => { true }
Event::KeyGeneratorStarted => { true }
Event::MinerStopped => { false }
Event::KeyGeneratorStopped => { false }
_ => { false }
};
handle.dispatch(move |web_view| {
web_view.eval(&format!("showMiningIndicator({});", visible));
return WVResult::Ok(());
});
true
});
}
LoadKey { name, pass } => {
match Keystore::from_file(&name, &pass) {
None => {
println!("Error loading keystore '{}'!", &name);
},
Some(k) => {
Some(keystore) => {
let mut c = context.lock().unwrap();
c.set_keystore(k);
},
c.set_keystore(keystore);
}
}
},
CreateKey { name, pass } => {
create_key(context.clone(), &name, &pass);
}
CheckDomain { name} => {
let c = context.lock().unwrap();
let available = c.get_blockchain().is_domain_available(&name, &c.get_keystore());
web_view.eval(&format!("domainAvailable({})", available));
}
CreateDomain { name, records, tags } => {
let keystore = {
let mut guard = context.lock().unwrap();
@ -90,6 +120,9 @@ fn run_interface(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>) {
}
RenewDomain { name, days } => {}
TransferDomain { name, owner } => {}
StopMining => {
context.lock().unwrap().bus.post(Event::ActionStopMining);
}
}
//dbg!(&signature);
Ok(())
@ -131,21 +164,32 @@ fn create_key(context: Arc<Mutex<Context>>, filename: &str, password: &str) {
let mut mining = Arc::new(AtomicBool::new(true));
for _ in 0..num_cpus::get() {
let context = context.clone();
{ context.lock().unwrap().bus.post(Event::KeyGeneratorStarted); }
let filename= filename.to_owned();
let password= password.to_owned();
let mining = mining.clone();
thread::spawn(move || {
match generate_key(KEYSTORE_DIFFICULTY, mining.clone()) {
None => { println!("Keystore mining finished"); }
None => {
println!("Keystore mining finished");
context.lock().unwrap().bus.post(Event::KeyGeneratorStopped);
}
Some(keystore) => {
let mut c = context.lock().unwrap();
mining.store(false,Ordering::Relaxed);
keystore.save(&filename, &password);
c.set_keystore(keystore);
c.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> {
@ -168,12 +212,15 @@ fn generate_key(difficulty: usize, mining: Arc<AtomicBool>) -> Option<Keystore>
#[derive(Deserialize)]
#[serde(tag = "cmd", rename_all = "camelCase")]
pub enum Cmd {
Loaded,
LoadKey{name: String, pass: String},
CreateKey{name: String, pass: 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,
}
fn inline_style(s: &str) -> String {

@ -1,13 +1,16 @@
use crate::{Transaction, Block, Keystore, Bytes, Context, hash_is_good};
use std::sync::{Mutex, Arc, Condvar};
use crypto::digest::Digest;
use std::sync::atomic::{AtomicBool, Ordering, AtomicU32};
use chrono::Utc;
use crypto::sha2::Sha256;
use std::sync::{Arc, Condvar, Mutex};
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::thread;
use std::time::Duration;
use chrono::Utc;
use crypto::digest::Digest;
use crypto::sha2::Sha256;
use num_cpus;
use crate::{Block, Bytes, Context, hash_is_good, Keystore, Transaction};
use crate::event::Event;
pub struct Miner {
context: Arc<Mutex<Context>>,
keystore: Keystore,
@ -75,6 +78,13 @@ impl Miner {
}
}
});
let mining = self.mining.clone();
self.context.lock().unwrap().bus.register(move |uuid, e| {
if e == Event::ActionStopMining {
mining.store(false, Ordering::Relaxed);
}
false
});
}
pub fn is_mining(&self) -> bool {
@ -86,7 +96,8 @@ impl Miner {
let mut chain_name= String::new();
let mut version_flags= 0u32;
{
let c = context.lock().unwrap();
let mut c = context.lock().unwrap();
c.bus.post(Event::MinerStarted);
chain_name = c.settings.chain_name.clone();
version_flags = c.settings.version_flags;
}
@ -142,7 +153,9 @@ impl Miner {
},
Some(block) => {
count = live_threads.fetch_sub(1, Ordering::Relaxed);
context.lock().unwrap().blockchain.add_block(block);
let mut context = context.lock().unwrap();
context.blockchain.add_block(block);
context.bus.post(Event::MinerStopped);
mining.store(false, Ordering::Relaxed);
},
}

@ -1,3 +1,7 @@
function onLoad() {
external.invoke(JSON.stringify({cmd: 'loaded'}));
}
function openTab(element, tabName) {
// Declare all variables
var i, tabContent, tabLinks;
@ -59,4 +63,56 @@ function transferDomain() {
function sendAction(param) {
external.invoke(JSON.stringify(param));
}
function onDomainChange(element) {
external.invoke(JSON.stringify({cmd: 'checkDomain', name: element.value}));
}
function domainAvailable(available) {
input = document.getElementById("new_domain");
button = document.getElementById("new_domain_button");
if (available) {
input.className = "input";
button.disabled = false
} else {
input.className = "input is-danger";
button.disabled = true
}
}
function showModalDialog(text, callback) {
message = document.getElementById("modal_text");
message.textContent = text;
button_positive = document.getElementById("modal_positive_button");
button_positive.onclick = function() {
callback();
dialog = document.getElementById("modal_dialog");
dialog.className = "modal";
};
button_negative = document.getElementById("modal_negative_button");
button_negative.onclick = function() {
dialog = document.getElementById("modal_dialog");
dialog.className = "modal";
}
dialog = document.getElementById("modal_dialog");
dialog.className = "modal is-active";
}
function showMiningIndicator(visible) {
indicator = document.getElementById("mining_indicator");
if (visible) {
indicator.style.visibility = 'visible';
} else {
indicator.style.visibility = 'hidden';
}
}
function miningIndicatorClick(element) {
showModalDialog("Do you really want to stop mining?", function() {
external.invoke(JSON.stringify({cmd: 'stopMining'}));
});
}

@ -0,0 +1,63 @@
use crate::event::Event;
use uuid::Uuid;
use std::collections::HashMap;
pub struct Bus<T> {
listeners: HashMap<Uuid, Box<dyn FnMut(&Uuid, T) -> bool + Send + Sync>>
}
impl<T: Clone> Bus<T> {
pub fn new() -> Self {
Bus { listeners: HashMap::new() }
}
pub fn register<F>(&mut self, mut closure: F) -> Uuid where F: FnMut(&Uuid, T) -> bool + Send + Sync + 'static {
let uuid = Uuid::new_v4();
self.listeners.insert(uuid.clone(), Box::new(closure));
uuid
}
pub fn unregister(&mut self, uuid: &Uuid) {
self.listeners.remove(&uuid);
}
pub fn post(&mut self, event: T) {
self.listeners.retain(|uuid, closure| {
closure(uuid, event.clone())
});
}
}
mod tests {
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicI32, Ordering};
use std::thread;
use std::time::Duration;
use crate::Bus;
use crate::event::Event;
#[test]
fn test1() {
let mut string = Arc::new(Mutex::new(String::from("start")));
let mut bus = Arc::new(Mutex::new(Bus::new()));
let string_copy = string.clone();
{
bus.lock().unwrap().register(move |_uuid, e| {
println!("Event {:?} received!", e);
let mut copy = string_copy.lock().unwrap();
copy.clear();
copy.push_str("from thread");
false
});
}
let bus2 = bus.clone();
thread::spawn(move || {
bus2.lock().unwrap().post(Event::BlockchainChanged);
});
let mut guard = string.lock().unwrap();
thread::sleep(Duration::from_millis(100));
println!("string = {}", &guard);
}
}

@ -20,11 +20,8 @@ pub struct Transaction {
impl Transaction {
pub fn from_str(identity: String, method: String, data: String, pub_key: Bytes) -> Self {
let mut buf: [u8; 32] = [0; 32];
let mut digest = Sha256::new();
digest.input_str(&identity);
digest.result(&mut buf);
return Self::new(Bytes::from_bytes(&buf), method, data, pub_key);
let bytes = Self::hash_identity(&identity);
return Self::new(bytes, method, data, pub_key);
}
pub fn new(identity: Bytes, method: String, data: String, pub_key: Bytes) -> Self {
@ -51,6 +48,14 @@ impl Transaction {
// Let it panic if something is not okay
serde_json::to_string(&self).unwrap()
}
pub fn hash_identity(identity: &str) -> Bytes {
let mut buf: [u8; 32] = [0; 32];
let mut digest = Sha256::new();
digest.input_str(identity);
digest.result(&mut buf);
Bytes::from_bytes(&buf)
}
}
impl fmt::Debug for Transaction {

Loading…
Cancel
Save