From 31ba599662e606e3fa59cf5591fb1152b7c9d43c Mon Sep 17 00:00:00 2001 From: Revertron Date: Fri, 14 May 2021 14:14:45 +0200 Subject: [PATCH] Implemented support for multiple keys. --- Cargo.toml | 2 +- alfis.toml | 4 +-- src/blockchain/chain.rs | 34 +++++++++++----------- src/context.rs | 58 +++++++++++++++++++++++++++++++------ src/keystore.rs | 2 +- src/main.rs | 28 ++++++++++++++---- src/miner.rs | 12 ++++---- src/settings.rs | 8 ++++-- src/web_ui.rs | 63 ++++++++++++++++++++++++++++++++++++----- src/webview/index.html | 18 ++++++++++++ src/webview/scripts.js | 48 +++++++++++++++++++++++++++++++ 11 files changed, 229 insertions(+), 48 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 51e6fa0..4ccf4d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "alfis" -version = "0.5.4" +version = "0.5.5" authors = ["Revertron "] edition = "2018" build = "build.rs" diff --git a/alfis.toml b/alfis.toml index 14e3aaa..666c518 100644 --- a/alfis.toml +++ b/alfis.toml @@ -1,7 +1,7 @@ # The hash of first block in a chain to know with which nodes to work origin = "0000001D2A77D63477172678502E51DE7F346061FF7EB188A2445ECA3FC0780E" -# A path to your key file to load automatically -key_file = "key1.toml" +# Paths to your key files to load automatically +key_files = ["key1.toml", "key2.toml", "key3.toml", "key4.toml", "key5.toml"] # How many last blocks to check on start check_blocks = 8 diff --git a/src/blockchain/chain.rs b/src/blockchain/chain.rs index 8d37b0e..a8ed963 100644 --- a/src/blockchain/chain.rs +++ b/src/blockchain/chain.rs @@ -253,12 +253,12 @@ impl Chain { Ok(()) } - pub fn get_sign_block(&self, keystore: &Option) -> Option { + pub fn get_sign_block(&self, keys: &Vec) -> Option<(Block, Keystore)> { if self.get_height() < BLOCK_SIGNERS_START { trace!("Too early to start block signings"); return None; } - if keystore.is_none() { + if keys.is_empty() { trace!("We can't sign blocks without keys"); return None; } @@ -288,22 +288,24 @@ impl Chain { None => { return None; } }; - let keystore = keystore.clone().unwrap().clone(); let signers: HashSet = self.get_block_signers(&block).into_iter().collect(); - if signers.contains(&keystore.get_public()) { - for index in block.index..=self.get_height() { - let b = self.get_block(index).unwrap(); - if b.pub_key == keystore.get_public() { - info!("We already mined signing block for block {}", block.index); - return None; + 'key_loop: for keystore in keys { + if signers.contains(&keystore.get_public()) { + for index in block.index..=self.get_height() { + let b = self.get_block(index).unwrap(); + if b.pub_key == keystore.get_public() { + debug!("We already mined signing block for block {} by {:?}", block.index, &b.pub_key); + continue 'key_loop; + } } - } - info!("We have an honor to mine signing block!"); - let mut block = Block::new(None, Bytes::default(), last_hash, SIGNER_DIFFICULTY); - block.index = last_index + 1; - return Some(block); - } else if !signers.is_empty() { + info!("We have an honor to mine signing block!"); + let mut block = Block::new(None, Bytes::default(), last_hash, SIGNER_DIFFICULTY); + block.index = last_index + 1; + return Some((block, keystore.clone())); + } + } + if !signers.is_empty() { info!("Signing block must be mined by other nodes"); } None @@ -590,7 +592,7 @@ impl Chain { } } - pub fn get_my_domains(&self, keystore: &Option) -> HashMap { + pub fn get_my_domains(&self, keystore: Option<&Keystore>) -> HashMap { if keystore.is_none() { return HashMap::new(); } diff --git a/src/context.rs b/src/context.rs index b1d630a..b29b333 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,4 +1,4 @@ -use crate::{Chain, Keystore, Settings}; +use crate::{Chain, Keystore, Settings, Bytes}; #[allow(unused_imports)] use log::{trace, debug, info, warn, error}; use crate::miner::MinerState; @@ -6,29 +6,71 @@ use crate::miner::MinerState; pub struct Context { pub app_version: String, pub settings: Settings, - pub keystore: Option, + pub keystores: Vec, + active_key: usize, pub chain: Chain, pub miner_state: MinerState, } impl Context { /// Creating an essential context to work with - pub fn new(app_version: String, settings: Settings, keystore: Option, chain: Chain) -> Context { + pub fn new(app_version: String, settings: Settings, keystores: Vec, chain: Chain) -> Context { Context { app_version, settings, - keystore, + keystores, + active_key: 0, chain, miner_state: MinerState { mining: false, full: false } } } - pub fn get_keystore(&self) -> Option { - self.keystore.clone() + pub fn get_keystore(&self) -> Option<&Keystore> { + self.keystores.get(self.active_key) } - pub fn set_keystore(&mut self, keystore: Option) { - self.keystore = keystore; + pub fn get_keystore_mut(&mut self) -> Option<&mut Keystore> { + self.keystores.get_mut(self.active_key) + } + + pub fn get_keystores(&self) -> &Vec { + &self.keystores + } + + pub fn has_keys(&self) -> bool { + !self.keystores.is_empty() + } + + pub fn set_keystores(&mut self, keystore: Vec) { + self.keystores = keystore; + self.active_key = 0; + } + + pub fn add_keystore(&mut self, keystore: Keystore) { + self.keystores.push(keystore); + self.active_key = self.keystores.len() - 1; + } + + pub fn select_key_by_index(&mut self, index: usize) -> bool { + if index < self.keystores.len() { + self.active_key = index; + return true; + } + false + } + + pub fn select_key_by_public(&mut self, public: &Bytes) -> bool { + for (i, key) in self.keystores.iter().enumerate() { + if key.get_public().eq(public) { + self.active_key = i; + return true; + } + } + false + } + + pub fn get_active_key_index(&self) -> usize { + self.active_key } pub fn get_chain(&self) -> &Chain { diff --git a/src/keystore.rs b/src/keystore.rs index f76ab61..36be60e 100644 --- a/src/keystore.rs +++ b/src/keystore.rs @@ -247,7 +247,7 @@ pub fn create_key(context: Arc>) { let path = keystore.get_path().to_owned(); let public = keystore.get_public().to_string(); info!("Key mined successfully! Public key: {}, hash: {}", &public, &hash); - context.set_keystore(Some(keystore)); + context.add_keystore(keystore); post(Event::KeyCreated { path, public, hash }); } } diff --git a/src/main.rs b/src/main.rs index 79007e6..faf9a0e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -110,7 +110,6 @@ fn main() { let settings = Settings::load(&config_name).expect(&format!("Cannot load settings from {}!", &config_name)); debug!(target: LOG_TARGET_MAIN, "Loaded settings: {:?}", &settings); - let keystore = Keystore::from_file(&settings.key_file, ""); let chain: Chain = Chain::new(&settings, DB_NAME); if opt_matches.opt_present("b") { for i in 1..(chain.get_height() + 1) { @@ -121,7 +120,26 @@ fn main() { return; } let settings_copy = settings.clone(); - let context = Context::new(env!("CARGO_PKG_VERSION").to_owned(), settings, keystore, chain); + let mut keys = Vec::new(); + if settings.key_files.len() > 0 { + for name in &settings.key_files { + match Keystore::from_file(name, "") { + None => { warn!("Error loading keyfile from {}", name); } + Some(keystore) => { + info!("Successfully loaded keyfile {}", name); + keys.push(keystore); + } + } + } + } else { + match Keystore::from_file(&settings.key_file, "") { + None => { warn!("Error loading keyfile from {}", &settings.key_file); } + Some(keystore) => { + keys.push(keystore); + } + } + } + let context = Context::new(env!("CARGO_PKG_VERSION").to_owned(), settings, keys, chain); let context: Arc> = Arc::new(Mutex::new(context)); // If we just need to generate keys @@ -137,7 +155,7 @@ fn main() { let mining_copy = Arc::clone(&mining_copy); let filename = filename.clone(); thread::spawn(move || { - if let Some(mut keystore) = context_copy.lock().unwrap().get_keystore() { + if let Some(keystore) = context_copy.lock().unwrap().get_keystore_mut() { keystore.save(&filename, ""); mining_copy.store(false, Ordering::Relaxed); } @@ -237,7 +255,7 @@ fn setup_logger(opt_matches: &Matches) { /// Gets own domains by current loaded keystore and writes them to log fn print_my_domains(context: &Arc>) { let context = context.lock().unwrap(); - let domains = context.chain.get_my_domains(&context.keystore); + let domains = context.chain.get_my_domains(context.get_keystore()); debug!("Domains: {:?}", &domains); } @@ -248,7 +266,7 @@ fn create_genesis_if_needed(context: &Arc>, miner: &Arc() % BLOCK_SIGNERS_START_RANDOM); - jobs.push(MineJob { start, block, keystore: keystore.unwrap() }); + jobs.push(MineJob { start, block, keystore }); } } } @@ -177,13 +177,13 @@ impl Miner { } else { // If our queue is empty if let Ok(context) = context.lock() { - let keystore = context.get_keystore(); + let keystores = context.get_keystores(); // Ask the blockchain if we have to sign something - if let Some(block) = context.chain.get_sign_block(&keystore) { + if let Some((block, keystore)) = context.chain.get_sign_block(keystores) { info!("Got signing job, adding to queue"); // We start mining sign block after some time, not everyone in the same time let start = Utc::now().timestamp() + (rand::random::() % BLOCK_SIGNERS_START_RANDOM); - jobs.push(MineJob { start, block, keystore: keystore.unwrap() }); + jobs.push(MineJob { start, block, keystore }); } } } diff --git a/src/settings.rs b/src/settings.rs index 20fb434..2003780 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -12,7 +12,10 @@ pub struct Settings { #[serde(default)] pub origin: String, #[serde(default)] + #[deprecated] pub key_file: String, + #[serde(default)] + pub key_files: Vec, #[serde(default = "default_check_blocks")] pub check_blocks: u64, #[serde(default)] @@ -52,8 +55,9 @@ impl Settings { impl Default for Settings { fn default() -> Self { Self { - origin: String::from("00002883BB006454F795BE6902770B1A18D897B33A0AB1631F53C37C2F41F800"), - key_file: String::from("default.key"), + origin: String::from("0000001D2A77D63477172678502E51DE7F346061FF7EB188A2445ECA3FC0780E"), + key_file: String::from("key1.toml"), + key_files: Vec::new(), check_blocks: default_check_blocks(), net: Net::default(), dns: Default::default(), diff --git a/src/web_ui.rs b/src/web_ui.rs index 7739b40..3024f20 100644 --- a/src/web_ui.rs +++ b/src/web_ui.rs @@ -11,7 +11,7 @@ use std::time::{Duration, Instant}; use chrono::{DateTime, Local}; #[allow(unused_imports)] use log::{debug, error, info, LevelFilter, trace, warn}; -use serde::Deserialize; +use serde::{Serialize, Deserialize}; use web_view::Content; use alfis::{Block, Bytes, Context, Keystore, Transaction}; @@ -52,6 +52,7 @@ pub fn run_interface(context: Arc>, miner: Arc>) { LoadKey => { action_load_key(&context, web_view); } CreateKey => { keystore::create_key(Arc::clone(&context)); } SaveKey => { action_save_key(&context); } + SelectKey { index } => { action_select_key(&context, web_view, index); } CheckRecord { data } => { action_check_record(web_view, data); } CheckDomain { name } => { action_check_domain(&context, web_view, name); } MineDomain { name, data, signing, encryption } => { @@ -130,7 +131,7 @@ fn action_check_domain(context: &Arc>, web_view: &mut WebView<()> } fn action_save_key(context: &Arc>) { - if context.lock().unwrap().get_keystore().is_none() { + if !context.lock().unwrap().has_keys() { return; } let result = tfd::save_file_dialog_with_filter("Save keys file", "", &["*.toml"], "Key files (*.toml)"); @@ -141,7 +142,7 @@ fn action_save_key(context: &Arc>) { new_path.push_str(".toml"); } let path = new_path.clone(); - if let Some(mut keystore) = context.lock().unwrap().get_keystore() { + if let Some(keystore) = context.lock().unwrap().get_keystore_mut() { let public = keystore.get_public().to_string(); let hash = keystore.get_hash().to_string(); keystore.save(&new_path, ""); @@ -152,6 +153,20 @@ fn action_save_key(context: &Arc>) { } } +fn action_select_key(context: &Arc>, web_view: &mut WebView<()>, index: usize) { + if context.lock().unwrap().select_key_by_index(index) { + let (path, public, hash) = { + let keystore = context.lock().unwrap().get_keystore().cloned().unwrap(); + let path = keystore.get_path().to_owned(); + let public = keystore.get_public().to_string(); + let hash = keystore.get_hash().to_string(); + (path, public, hash) + }; + post(Event::KeyLoaded { path, public, hash }); + web_view.eval(&format!("keySelected({})", index)).expect("Error evaluating!"); + } +} + fn action_load_key(context: &Arc>, web_view: &mut WebView<()>) { let result = tfd::open_file_dialog("Open keys file", "", Some((&["*.key", "*.toml"], "Key files"))); match result { @@ -169,7 +184,12 @@ fn action_load_key(context: &Arc>, web_view: &mut WebView<()>) { let public = keystore.get_public().to_string(); let hash = keystore.get_hash().to_string(); post(Event::KeyLoaded { path, public, hash }); - context.lock().unwrap().set_keystore(Some(keystore)); + + if !context.lock().unwrap().select_key_by_public(&keystore.get_public()) { + context.lock().unwrap().add_keystore(keystore); + } else { + warn!("This key is already loaded!"); + } } } } @@ -200,6 +220,7 @@ fn action_loaded(context: &Arc>, web_view: &mut WebView<()>) { let eval = match e { Event::KeyCreated { path, public, hash } => { load_domains(&mut context, &handle); + send_keys_to_ui(&mut context, &handle); event_handle_luck(&handle, "Key successfully created! Don\\'t forget to save it!"); let mut s = format!("keystoreChanged('{}', '{}', '{}');", &path, &public, &hash); s.push_str(" showSuccess('New key mined successfully! Save it to a safe place!')"); @@ -208,6 +229,7 @@ fn action_loaded(context: &Arc>, web_view: &mut WebView<()>) { Event::KeyLoaded { path, public, hash } | Event::KeySaved { path, public, hash } => { load_domains(&mut context, &handle); + send_keys_to_ui(&mut context, &handle); format!("keystoreChanged('{}', '{}', '{}');", &path, &public, &hash) } Event::MinerStarted | Event::KeyGeneratorStarted => { @@ -320,6 +342,7 @@ fn action_loaded(context: &Arc>, web_view: &mut WebView<()>) { if let Ok(zones) = serde_json::to_string(&zones) { let _ = web_view.eval(&format!("zonesChanged('{}');", &zones)); } + send_keys_to_ui(&c, &web_view.handle()); event_info(web_view, "Application loaded"); } @@ -327,7 +350,7 @@ fn load_domains(context: &mut MutexGuard, handle: &Handle<()>) { let _ = handle.dispatch(move |web_view|{ web_view.eval("clearMyDomains();") }); - let domains = context.chain.get_my_domains(&context.keystore); + let domains = context.chain.get_my_domains(context.get_keystore()); debug!("Domains: {:?}", &domains.values()); for (_identity, (domain, timestamp, data)) in domains { let d = serde_json::to_string(&data).unwrap(); @@ -341,11 +364,30 @@ fn load_domains(context: &mut MutexGuard, handle: &Handle<()>) { }); } +fn send_keys_to_ui(context: &MutexGuard, handle: &Handle<()>) { + let keys = { + let mut keys = Vec::new(); + for key in context.get_keystores() { + let path = key.get_path().replace("\\", "/"); + let parts: Vec<&str> = path.rsplitn(2, "/").collect(); + keys.push(KeysForJS { file_name: parts[0].to_owned(), public: key.get_public().to_string() }); + } + keys + }; + if !keys.is_empty() { + let index = context.get_active_key_index(); + let _ = handle.dispatch(move |web_view| { + let command = format!("keysChanged('{}'); keySelected({});", serde_json::to_string(&keys).unwrap(), index); + web_view.eval(&command) + }); + } +} + fn action_create_domain(context: Arc>, miner: Arc>, web_view: &mut WebView<()>, name: String, data: String, signing: String, encryption: String) { debug!("Creating domain with data: {}", &data); let c = Arc::clone(&context); let context = context.lock().unwrap(); - if context.get_keystore().is_none() { + if !context.has_keys() { show_warning(web_view, "You don't have keys loaded!
Load or mine the keys and try again."); let _ = web_view.eval("domainMiningUnavailable();"); return; @@ -356,7 +398,7 @@ fn action_create_domain(context: Arc>, miner: Arc>, info!("Waiting for last full block to be signed. Try again later."); return; } - let keystore = context.get_keystore().unwrap(); + let keystore = context.get_keystore().unwrap().clone(); let pub_key = keystore.get_public(); let data = match serde_json::from_str::(&data) { Ok(data) => { data } @@ -536,6 +578,7 @@ pub enum Cmd { LoadKey, CreateKey, SaveKey, + SelectKey { index: usize }, CheckRecord { data: String }, CheckDomain { name: String }, MineDomain { name: String, data: String, signing: String, encryption: String }, @@ -571,6 +614,12 @@ impl Status { } } +#[derive(Serialize)] +struct KeysForJS { + file_name: String, + public: String +} + fn inline_style(s: &str) -> String { format!(r#""#, s) } diff --git a/src/webview/index.html b/src/webview/index.html index 010abc1..1189242 100644 --- a/src/webview/index.html +++ b/src/webview/index.html @@ -57,6 +57,24 @@ + +
+ +
+
diff --git a/src/webview/scripts.js b/src/webview/scripts.js index fa0fe0d..eafe3e1 100644 --- a/src/webview/scripts.js +++ b/src/webview/scripts.js @@ -4,6 +4,8 @@ var ownerEncryption = ""; var availableZones = []; var myDomains = []; var currentZone; +var currentSelectedKey = -1; +var keysLoaded = []; document.addEventListener('click', function (event) { closeDropdowns(); @@ -576,4 +578,50 @@ function changeZone(zone, event) { } }); refreshZonesList(); +} + +function refreshKeysMenu() { + var buf = ""; + keysLoaded.forEach(function(value, index, array) { + var file_name = value.file_name; + if (file_name == "") { + file_name = "[Not saved]"; + } + var public = value.public; + + var add_class = ""; + if (currentSelectedKey == index) { + add_class = "is-active"; + } + buf += "{name}" + .replace("{id}", index) + .replace("{index}", index) + .replace("{class}", add_class) + .replace("{title}", public) + .replace("{name}", file_name); + }); + var links = document.getElementById("keys_links"); + links.innerHTML = buf; + if (currentSelectedKey >= 0) { + var cur_name = document.getElementById("keys_current_name"); + cur_name.innerHTML = keysLoaded[currentSelectedKey].file_name; + } +} + +function keysChanged(json) { + keysLoaded = JSON.parse(json); + refreshKeysMenu(); +} + +function selectKey(index, event) { + event.stopPropagation(); + closeDropdowns(); + if (currentSelectedKey != index) { + external.invoke(JSON.stringify({cmd: 'selectKey', index: parseInt(index)})); + } +} + +function keySelected(index) { + currentSelectedKey = index; + refreshKeysMenu(); } \ No newline at end of file