Implemented support for multiple keys.

pull/119/head
Revertron 3 years ago
parent 6dceac8612
commit 31ba599662

@ -1,6 +1,6 @@
[package]
name = "alfis"
version = "0.5.4"
version = "0.5.5"
authors = ["Revertron <alfis@revertron.com>"]
edition = "2018"
build = "build.rs"

@ -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

@ -253,12 +253,12 @@ impl Chain {
Ok(())
}
pub fn get_sign_block(&self, keystore: &Option<Keystore>) -> Option<Block> {
pub fn get_sign_block(&self, keys: &Vec<Keystore>) -> 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<Bytes> = 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<Keystore>) -> HashMap<Bytes, (String, i64, DomainData)> {
pub fn get_my_domains(&self, keystore: Option<&Keystore>) -> HashMap<Bytes, (String, i64, DomainData)> {
if keystore.is_none() {
return HashMap::new();
}

@ -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<Keystore>,
pub keystores: Vec<Keystore>,
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<Keystore>, chain: Chain) -> Context {
pub fn new(app_version: String, settings: Settings, keystores: Vec<Keystore>, 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<Keystore> {
self.keystore.clone()
pub fn get_keystore(&self) -> Option<&Keystore> {
self.keystores.get(self.active_key)
}
pub fn set_keystore(&mut self, keystore: Option<Keystore>) {
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<Keystore> {
&self.keystores
}
pub fn has_keys(&self) -> bool {
!self.keystores.is_empty()
}
pub fn set_keystores(&mut self, keystore: Vec<Keystore>) {
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 {

@ -247,7 +247,7 @@ pub fn create_key(context: Arc<Mutex<Context>>) {
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 });
}
}

@ -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<Mutex<Context>> = 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<Mutex<Context>>) {
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<Mutex<Context>>, miner: &Arc<Mutex<Min
let last_block = context.get_chain().last_block();
let origin = context.settings.origin.clone();
if origin.is_empty() && last_block.is_none() {
if let Some(keystore) = &context.keystore {
if let Some(keystore) = context.get_keystore() {
// If blockchain is empty, we are going to mine a Genesis block
let transaction = Transaction::origin(Chain::get_zones_hash(), keystore.get_public(), keystore.get_encryption_public());
let block = Block::new(Some(transaction), keystore.get_public(), Bytes::default(), ORIGIN_DIFFICULTY);

@ -149,13 +149,13 @@ impl Miner {
if !signing_waits {
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::<i64>() % 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::<i64>() % BLOCK_SIGNERS_START_RANDOM);
jobs.push(MineJob { start, block, keystore: keystore.unwrap() });
jobs.push(MineJob { start, block, keystore });
}
}
}

@ -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<String>,
#[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(),

@ -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<Mutex<Context>>, miner: Arc<Mutex<Miner>>) {
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<Mutex<Context>>, web_view: &mut WebView<()>
}
fn action_save_key(context: &Arc<Mutex<Context>>) {
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<Mutex<Context>>) {
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<Mutex<Context>>) {
}
}
fn action_select_key(context: &Arc<Mutex<Context>>, 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<Mutex<Context>>, 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<Mutex<Context>>, 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<Mutex<Context>>, 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<Mutex<Context>>, 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<Mutex<Context>>, 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<Context>, 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<Context>, handle: &Handle<()>) {
});
}
fn send_keys_to_ui(context: &MutexGuard<Context>, 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<Mutex<Context>>, miner: Arc<Mutex<Miner>>, 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!<br>Load or mine the keys and try again.");
let _ = web_view.eval("domainMiningUnavailable();");
return;
@ -356,7 +398,7 @@ fn action_create_domain(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>,
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::<DomainData>(&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#"<style type="text/css">{}</style>"#, s)
}

@ -57,6 +57,24 @@
<svg viewBox="0 0 24 24" style="width: 20px; height: 20px;"><path d="M12,17A2,2 0 0,0 14,15C14,13.89 13.1,13 12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V10C4,8.89 4.9,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z"></path></svg>
</span>
</div>
<div class="control">
<div class="dropdown" id="keys_dropdown" onclick="toggle(this, event);">
<div class="dropdown-trigger">
<button class="button" aria-haspopup="true" aria-controls="keys_menu">
<span id="keys_current_name">No keys</span>
<span class="icon is-small">
<svg viewBox="0 0 24 24" style="width: 20px; height: 20px;"><path d="M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z"></path></svg>
</span>
</button>
</div>
<div class="dropdown-menu" id="keys_menu" role="menu">
<div class="dropdown-content" id="keys_links">
</div>
</div>
</div>
</div>
<div class="buttons has-addons">
<button class="button is-link is-light" onclick="loadKey();" title="Load keypair from file">Load key</button>
<button class="button is-link is-light" id="save_key" onclick="saveKey();" disabled title="Save current keypair to file">Save key</button>

@ -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 += "<a id=\"key-{id}\" class=\"dropdown-item {class}\" onclick=\"selectKey({index}, event);\" title=\"{title}\">{name}</a>"
.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();
}
Loading…
Cancel
Save