Reworked handling appropriate (mined) keys absence. Now that info handled in UI as well. It won't allow users without keys to mine domains or zones.
This commit is contained in:
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
# Settings
|
# Settings
|
||||||
origin = "00000102C2F9BFD2803284D93327F089D60FC72A06F19AF2384567F2646B8348"
|
origin = "00000102C2F9BFD2803284D93327F089D60FC72A06F19AF2384567F2646B8348"
|
||||||
key_file = "default.key"
|
key_file = "default1.key"
|
||||||
listen = "[::]:4244"
|
listen = "[::]:4244"
|
||||||
public = false
|
public = true
|
||||||
|
|
||||||
# Bootstrap nodes
|
# Bootstrap nodes
|
||||||
#peers = ["test-ip4.alfis.name:4244", "test-ip6.alfis.name:4244"]
|
#peers = ["test-ip4.alfis.name:4244", "test-ip6.alfis.name:4244"]
|
||||||
|
|||||||
+5
-5
@@ -6,7 +6,7 @@ use log::{trace, debug, info, warn, error};
|
|||||||
pub struct Context {
|
pub struct Context {
|
||||||
pub app_version: String,
|
pub app_version: String,
|
||||||
pub settings: Settings,
|
pub settings: Settings,
|
||||||
pub keystore: Keystore,
|
pub keystore: Option<Keystore>,
|
||||||
pub chain: Chain,
|
pub chain: Chain,
|
||||||
pub x_zones: ExternalZones,
|
pub x_zones: ExternalZones,
|
||||||
pub bus: Bus<Event>,
|
pub bus: Bus<Event>,
|
||||||
@@ -14,7 +14,7 @@ pub struct Context {
|
|||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
/// Creating an essential context to work with
|
/// Creating an essential context to work with
|
||||||
pub fn new(app_version: String, settings: Settings, keystore: Keystore, chain: Chain) -> Context {
|
pub fn new(app_version: String, settings: Settings, keystore: Option<Keystore>, chain: Chain) -> Context {
|
||||||
Context { app_version, settings, keystore, chain, x_zones: ExternalZones::new(), bus: Bus::new() }
|
Context { app_version, settings, keystore, chain, x_zones: ExternalZones::new(), bus: Bus::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,17 +26,17 @@ impl Context {
|
|||||||
warn!("Error loading keystore '{}'!", filename);
|
warn!("Error loading keystore '{}'!", filename);
|
||||||
},
|
},
|
||||||
Some(keystore) => {
|
Some(keystore) => {
|
||||||
self.keystore = keystore;
|
self.keystore = Some(keystore);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_keystore(&self) -> Keystore {
|
pub fn get_keystore(&self) -> Option<Keystore> {
|
||||||
self.keystore.clone()
|
self.keystore.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_keystore(&mut self, keystore: Keystore) {
|
pub fn set_keystore(&mut self, keystore: Option<Keystore>) {
|
||||||
self.keystore = keystore;
|
self.keystore = keystore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-2
@@ -244,8 +244,14 @@ impl DnsServer for DnsUdpServer {
|
|||||||
let mut req_buffer = BytePacketBuffer::new();
|
let mut req_buffer = BytePacketBuffer::new();
|
||||||
let (_, src) = match socket.recv_from(&mut req_buffer.buf) {
|
let (_, src) = match socket.recv_from(&mut req_buffer.buf) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(e) => {
|
Err(err) => {
|
||||||
debug!("Failed to read from UDP socket: {:?}", e);
|
if let Some(code) = err.raw_os_error() {
|
||||||
|
if code == 10004 {
|
||||||
|
debug!("UDP service loop has finished");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug!("Failed to read from UDP socket: {:?}", err);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -347,6 +353,12 @@ impl DnsServer for DnsTcpServer {
|
|||||||
let stream = match wrap_stream {
|
let stream = match wrap_stream {
|
||||||
Ok(stream) => stream,
|
Ok(stream) => stream,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
if let Some(code) = err.raw_os_error() {
|
||||||
|
if code == 10004 {
|
||||||
|
debug!("TCP service loop has finished");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
warn!("Failed to accept TCP connection: {:?}", err);
|
warn!("Failed to accept TCP connection: {:?}", err);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -1,4 +1,4 @@
|
|||||||
use crate::Bytes;
|
use crate::{Bytes, Keystore};
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
@@ -12,7 +12,8 @@ pub enum Event {
|
|||||||
NewBlockReceived,
|
NewBlockReceived,
|
||||||
BlockchainChanged { index: u64 },
|
BlockchainChanged { index: u64 },
|
||||||
ActionStopMining,
|
ActionStopMining,
|
||||||
ActionMineLocker { index: u64, hash: Bytes },
|
ActionMineLocker { index: u64, hash: Bytes, keystore: Box<Keystore> },
|
||||||
|
ActionQuit,
|
||||||
NetworkStatus { nodes: usize, blocks: u64 },
|
NetworkStatus { nodes: usize, blocks: u64 },
|
||||||
Syncing { have: u64, height: u64 },
|
Syncing { have: u64, height: u64 },
|
||||||
SyncFinished,
|
SyncFinished,
|
||||||
|
|||||||
+7
-1
@@ -128,6 +128,12 @@ impl Clone for Keystore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Keystore {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.keypair.to_bytes().eq(&other.keypair.to_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks if some public key is "strong" enough to mine domains
|
/// Checks if some public key is "strong" enough to mine domains
|
||||||
/// TODO Optimize by caching Blakeout somewhere
|
/// TODO Optimize by caching Blakeout somewhere
|
||||||
pub fn check_public_key_strength(key: &Bytes, strength: usize) -> bool {
|
pub fn check_public_key_strength(key: &Bytes, strength: usize) -> bool {
|
||||||
@@ -155,7 +161,7 @@ pub fn create_key(context: Arc<Mutex<Context>>) {
|
|||||||
let hash = keystore.get_hash().to_string();
|
let hash = keystore.get_hash().to_string();
|
||||||
info!("Key mined successfully: {:?}, hash: {}", &keystore.get_public(), &hash);
|
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.bus.post(Event::KeyCreated { path: keystore.get_path().to_owned(), public: keystore.get_public().to_string(), hash });
|
||||||
context.set_keystore(keystore);
|
context.set_keystore(Some(keystore));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let miners = miners_count.fetch_sub(1, atomic::Ordering::SeqCst) - 1;
|
let miners = miners_count.fetch_sub(1, atomic::Ordering::SeqCst) - 1;
|
||||||
|
|||||||
+8
-11
@@ -80,13 +80,7 @@ fn main() {
|
|||||||
|
|
||||||
let settings = Settings::load(&config_name);
|
let settings = Settings::load(&config_name);
|
||||||
info!(target: LOG_TARGET_MAIN, "Loaded settings: {:?}", &settings);
|
info!(target: LOG_TARGET_MAIN, "Loaded settings: {:?}", &settings);
|
||||||
let keystore: Keystore = match Keystore::from_file(&settings.key_file, "") {
|
let keystore = Keystore::from_file(&settings.key_file, "");
|
||||||
None => {
|
|
||||||
warn!(target: LOG_TARGET_MAIN, "Generated temporary keystore. Please, generate full-privileged keys.");
|
|
||||||
Keystore::new()
|
|
||||||
}
|
|
||||||
Some(keystore) => { keystore }
|
|
||||||
};
|
|
||||||
let chain: Chain = Chain::new(&settings);
|
let chain: Chain = Chain::new(&settings);
|
||||||
if opt_matches.opt_present("l") {
|
if opt_matches.opt_present("l") {
|
||||||
for i in 1..(chain.height() + 1) {
|
for i in 1..(chain.height() + 1) {
|
||||||
@@ -102,7 +96,8 @@ fn main() {
|
|||||||
Some(block) => { trace!(target: LOG_TARGET_MAIN, "Loaded DB with origin {:?}", &block.hash); }
|
Some(block) => { trace!(target: LOG_TARGET_MAIN, "Loaded DB with origin {:?}", &block.hash); }
|
||||||
}
|
}
|
||||||
let settings_copy = settings.clone();
|
let settings_copy = settings.clone();
|
||||||
let context: Arc<Mutex<Context>> = Arc::new(Mutex::new(Context::new(env!("CARGO_PKG_VERSION").to_owned(), settings, keystore, chain)));
|
let context = Context::new(env!("CARGO_PKG_VERSION").to_owned(), settings, keystore, chain);
|
||||||
|
let context: Arc<Mutex<Context>> = Arc::new(Mutex::new(context));
|
||||||
dns_utils::start_dns_server(&context, &settings_copy);
|
dns_utils::start_dns_server(&context, &settings_copy);
|
||||||
|
|
||||||
let mut miner_obj = Miner::new(Arc::clone(&context));
|
let mut miner_obj = Miner::new(Arc::clone(&context));
|
||||||
@@ -136,9 +131,11 @@ fn create_genesis_if_needed(context: &Arc<Mutex<Context>>, miner: &Arc<Mutex<Min
|
|||||||
let last_block = context.get_chain().last_block();
|
let last_block = context.get_chain().last_block();
|
||||||
let origin = context.settings.origin.clone();
|
let origin = context.settings.origin.clone();
|
||||||
if origin.eq("") && last_block.is_none() {
|
if origin.eq("") && last_block.is_none() {
|
||||||
// If blockchain is empty, we are going to mine a Genesis block
|
if let Some(keystore) = &context.keystore {
|
||||||
let block = Block::new(None, context.get_keystore().get_public(), Bytes::default(), BLOCK_DIFFICULTY);
|
// If blockchain is empty, we are going to mine a Genesis block
|
||||||
miner.lock().unwrap().add_block(block);
|
let block = Block::new(None, context.get_keystore().unwrap().get_public(), Bytes::default(), BLOCK_DIFFICULTY);
|
||||||
|
miner.lock().unwrap().add_block(block, keystore.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+29
-22
@@ -8,17 +8,18 @@ use chrono::Utc;
|
|||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
use num_cpus;
|
use num_cpus;
|
||||||
|
|
||||||
use crate::{Block, Bytes, Context};
|
use crate::{Block, Bytes, Context, Keystore};
|
||||||
use crate::commons::{CHAIN_VERSION, LOCKER_DIFFICULTY, KEYSTORE_DIFFICULTY};
|
use crate::commons::{CHAIN_VERSION, LOCKER_DIFFICULTY, KEYSTORE_DIFFICULTY};
|
||||||
use crate::blockchain::enums::BlockQuality;
|
use crate::blockchain::enums::BlockQuality;
|
||||||
use crate::blockchain::hash_utils::*;
|
use crate::blockchain::hash_utils::*;
|
||||||
use crate::keys::check_public_key_strength;
|
use crate::keys::check_public_key_strength;
|
||||||
use crate::event::Event;
|
use crate::event::Event;
|
||||||
use blakeout::Blakeout;
|
use blakeout::Blakeout;
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
pub struct Miner {
|
pub struct Miner {
|
||||||
context: Arc<Mutex<Context>>,
|
context: Arc<Mutex<Context>>,
|
||||||
blocks: Arc<Mutex<Vec<Block>>>,
|
jobs: Arc<Mutex<Vec<MineJob>>>,
|
||||||
running: Arc<AtomicBool>,
|
running: Arc<AtomicBool>,
|
||||||
mining: Arc<AtomicBool>,
|
mining: Arc<AtomicBool>,
|
||||||
cond_var: Arc<Condvar>
|
cond_var: Arc<Condvar>
|
||||||
@@ -28,15 +29,15 @@ impl Miner {
|
|||||||
pub fn new(context: Arc<Mutex<Context>>) -> Self {
|
pub fn new(context: Arc<Mutex<Context>>) -> Self {
|
||||||
Miner {
|
Miner {
|
||||||
context,
|
context,
|
||||||
blocks: Arc::new(Mutex::new(Vec::new())),
|
jobs: Arc::new(Mutex::new(Vec::new())),
|
||||||
running: Arc::new(AtomicBool::new(false)),
|
running: Arc::new(AtomicBool::new(false)),
|
||||||
mining: Arc::new(AtomicBool::new(false)),
|
mining: Arc::new(AtomicBool::new(false)),
|
||||||
cond_var: Arc::new(Condvar::new())
|
cond_var: Arc::new(Condvar::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_block(&mut self, block: Block) {
|
pub fn add_block(&mut self, block: Block, keystore: Keystore) {
|
||||||
self.blocks.lock().unwrap().push(block);
|
self.jobs.lock().unwrap().push(MineJob { block, keystore });
|
||||||
self.cond_var.notify_one();
|
self.cond_var.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@ impl Miner {
|
|||||||
|
|
||||||
pub fn start_mining_thread(&mut self) {
|
pub fn start_mining_thread(&mut self) {
|
||||||
let context = Arc::clone(&self.context);
|
let context = Arc::clone(&self.context);
|
||||||
let blocks = self.blocks.clone();
|
let blocks = self.jobs.clone();
|
||||||
let running = self.running.clone();
|
let running = self.running.clone();
|
||||||
let mining = self.mining.clone();
|
let mining = self.mining.clone();
|
||||||
let cond_var = self.cond_var.clone();
|
let cond_var = self.cond_var.clone();
|
||||||
@@ -73,7 +74,7 @@ impl Miner {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
let mining = self.mining.clone();
|
let mining = self.mining.clone();
|
||||||
let blocks = self.blocks.clone();
|
let blocks = self.jobs.clone();
|
||||||
let cond_var = self.cond_var.clone();
|
let cond_var = self.cond_var.clone();
|
||||||
self.context.lock().unwrap().bus.register(move |_uuid, e| {
|
self.context.lock().unwrap().bus.register(move |_uuid, e| {
|
||||||
match e {
|
match e {
|
||||||
@@ -82,11 +83,11 @@ impl Miner {
|
|||||||
Event::ActionStopMining => {
|
Event::ActionStopMining => {
|
||||||
mining.store(false, Ordering::SeqCst);
|
mining.store(false, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
Event::ActionMineLocker { index, hash } => {
|
Event::ActionMineLocker { index, hash, keystore } => {
|
||||||
if !mining.load(Ordering::SeqCst) {
|
if !mining.load(Ordering::SeqCst) {
|
||||||
let mut block = Block::new(None, Bytes::default(), hash, LOCKER_DIFFICULTY);
|
let mut block = Block::new(None, Bytes::default(), hash, LOCKER_DIFFICULTY);
|
||||||
block.index = index;
|
block.index = index;
|
||||||
blocks.lock().unwrap().push(block);
|
blocks.lock().unwrap().push(MineJob { block, keystore: keystore.deref().clone() });
|
||||||
cond_var.notify_all();
|
cond_var.notify_all();
|
||||||
info!("Added a locker block to mine");
|
info!("Added a locker block to mine");
|
||||||
}
|
}
|
||||||
@@ -101,16 +102,16 @@ impl Miner {
|
|||||||
self.running.load(Ordering::Relaxed)
|
self.running.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mine_internal(context: Arc<Mutex<Context>>, mut block: Block, mining: Arc<AtomicBool>) {
|
fn mine_internal(context: Arc<Mutex<Context>>, mut job: MineJob, mining: Arc<AtomicBool>) {
|
||||||
// Clear signature and hash just in case
|
// Clear signature and hash just in case
|
||||||
block.signature = Bytes::default();
|
job.block.signature = Bytes::default();
|
||||||
block.hash = Bytes::default();
|
job.block.hash = Bytes::default();
|
||||||
block.version = CHAIN_VERSION;
|
job.block.version = CHAIN_VERSION;
|
||||||
// If this block needs to be a locker
|
// If this block needs to be a locker
|
||||||
if block.index > 0 && !block.prev_block_hash.is_empty() {
|
if job.block.index > 0 && !job.block.prev_block_hash.is_empty() {
|
||||||
info!("Mining locker block");
|
info!("Mining locker block");
|
||||||
block.pub_key = context.lock().unwrap().keystore.get_public();
|
job.block.pub_key = job.keystore.get_public();
|
||||||
if !check_public_key_strength(&block.pub_key, KEYSTORE_DIFFICULTY) {
|
if !check_public_key_strength(&job.block.pub_key, KEYSTORE_DIFFICULTY) {
|
||||||
warn!("Can not mine block with weak public key!");
|
warn!("Can not mine block with weak public key!");
|
||||||
context.lock().unwrap().bus.post(Event::MinerStopped);
|
context.lock().unwrap().bus.post(Event::MinerStopped);
|
||||||
mining.store(false, Ordering::SeqCst);
|
mining.store(false, Ordering::SeqCst);
|
||||||
@@ -121,7 +122,7 @@ impl Miner {
|
|||||||
Some(last_block) => {
|
Some(last_block) => {
|
||||||
info!("Last block found");
|
info!("Last block found");
|
||||||
// If we were doing something else and got new block before we could mine this block
|
// If we were doing something else and got new block before we could mine this block
|
||||||
if last_block.index > block.index || last_block.hash != block.prev_block_hash {
|
if last_block.index > job.block.index || last_block.hash != job.block.prev_block_hash {
|
||||||
warn!("We missed block to lock");
|
warn!("We missed block to lock");
|
||||||
context.lock().unwrap().bus.post(Event::MinerStopped);
|
context.lock().unwrap().bus.post(Event::MinerStopped);
|
||||||
mining.store(false, Ordering::SeqCst);
|
mining.store(false, Ordering::SeqCst);
|
||||||
@@ -130,8 +131,8 @@ impl Miner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
block.index = context.lock().unwrap().chain.height() + 1;
|
job.block.index = context.lock().unwrap().chain.height() + 1;
|
||||||
block.prev_block_hash = match context.lock().unwrap().chain.last_block() {
|
job.block.prev_block_hash = match context.lock().unwrap().chain.last_block() {
|
||||||
None => { Bytes::default() }
|
None => { Bytes::default() }
|
||||||
Some(block) => { block.hash }
|
Some(block) => { block.hash }
|
||||||
};
|
};
|
||||||
@@ -144,12 +145,12 @@ impl Miner {
|
|||||||
debug!("Starting {} threads for mining", cpus);
|
debug!("Starting {} threads for mining", cpus);
|
||||||
for _cpu in 0..cpus {
|
for _cpu in 0..cpus {
|
||||||
let context = Arc::clone(&context);
|
let context = Arc::clone(&context);
|
||||||
let block = block.clone();
|
let job = job.clone();
|
||||||
let mining = Arc::clone(&mining);
|
let mining = Arc::clone(&mining);
|
||||||
let live_threads = Arc::clone(&live_threads);
|
let live_threads = Arc::clone(&live_threads);
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
live_threads.fetch_add(1, Ordering::SeqCst);
|
live_threads.fetch_add(1, Ordering::SeqCst);
|
||||||
match find_hash(Arc::clone(&context), block, Arc::clone(&mining)) {
|
match find_hash(Arc::clone(&context), job.block, Arc::clone(&mining)) {
|
||||||
None => {
|
None => {
|
||||||
debug!("Mining was cancelled");
|
debug!("Mining was cancelled");
|
||||||
let count = live_threads.fetch_sub(1, Ordering::SeqCst);
|
let count = live_threads.fetch_sub(1, Ordering::SeqCst);
|
||||||
@@ -162,7 +163,7 @@ impl Miner {
|
|||||||
Some(mut block) => {
|
Some(mut block) => {
|
||||||
let index = block.index;
|
let index = block.index;
|
||||||
let mut context = context.lock().unwrap();
|
let mut context = context.lock().unwrap();
|
||||||
block.signature = Bytes::from_bytes(&context.keystore.sign(&block.as_bytes()));
|
block.signature = Bytes::from_bytes(&job.keystore.sign(&block.as_bytes()));
|
||||||
if context.chain.check_new_block(&block) != BlockQuality::Good {
|
if context.chain.check_new_block(&block) != BlockQuality::Good {
|
||||||
warn!("Error adding mined block!");
|
warn!("Error adding mined block!");
|
||||||
if index == 0 {
|
if index == 0 {
|
||||||
@@ -184,6 +185,12 @@ impl Miner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct MineJob {
|
||||||
|
block: Block,
|
||||||
|
keystore: Keystore
|
||||||
|
}
|
||||||
|
|
||||||
fn find_hash(context: Arc<Mutex<Context>>, mut block: Block, running: Arc<AtomicBool>) -> Option<Block> {
|
fn find_hash(context: Arc<Mutex<Context>>, mut block: Block, running: Arc<AtomicBool>) -> Option<Block> {
|
||||||
let difficulty = block.difficulty as usize;
|
let difficulty = block.difficulty as usize;
|
||||||
let full = block.transaction.is_some();
|
let full = block.transaction.is_some();
|
||||||
|
|||||||
+35
-10
@@ -18,6 +18,7 @@ use std::collections::HashSet;
|
|||||||
use crate::{Context, Block, p2p::Message, p2p::State, p2p::Peer, p2p::Peers, Bytes};
|
use crate::{Context, Block, p2p::Message, p2p::State, p2p::Peer, p2p::Peers, Bytes};
|
||||||
use crate::blockchain::enums::BlockQuality;
|
use crate::blockchain::enums::BlockQuality;
|
||||||
use crate::commons::CHAIN_VERSION;
|
use crate::commons::CHAIN_VERSION;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
const SERVER: Token = Token(0);
|
const SERVER: Token = Token(0);
|
||||||
const POLL_TIMEOUT: Option<Duration> = Some(Duration::from_millis(3000));
|
const POLL_TIMEOUT: Option<Duration> = Some(Duration::from_millis(3000));
|
||||||
@@ -40,6 +41,9 @@ impl Network {
|
|||||||
(c.settings.listen.clone(), c.settings.peers.clone())
|
(c.settings.listen.clone(), c.settings.peers.clone())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let running = Arc::new(AtomicBool::new(true));
|
||||||
|
subscribe_to_bus(&mut self.context, Arc::clone(&running));
|
||||||
|
|
||||||
// Starting server socket
|
// Starting server socket
|
||||||
let addr = listen_addr.parse().expect("Error parsing listen address");
|
let addr = listen_addr.parse().expect("Error parsing listen address");
|
||||||
let mut server = TcpListener::bind(addr).expect("Can't bind to address");
|
let mut server = TcpListener::bind(addr).expect("Can't bind to address");
|
||||||
@@ -62,6 +66,9 @@ impl Network {
|
|||||||
loop {
|
loop {
|
||||||
// Poll Mio for events, blocking until we get an event.
|
// Poll Mio for events, blocking until we get an event.
|
||||||
poll.poll(&mut events, POLL_TIMEOUT).expect("Error polling sockets");
|
poll.poll(&mut events, POLL_TIMEOUT).expect("Error polling sockets");
|
||||||
|
if !running.load(Ordering::SeqCst) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Process each event.
|
// Process each event.
|
||||||
for event in events.iter() {
|
for event in events.iter() {
|
||||||
@@ -125,11 +132,26 @@ impl Network {
|
|||||||
peers.send_pings(poll.registry(), height, hash);
|
peers.send_pings(poll.registry(), height, hash);
|
||||||
peers.connect_new_peers(poll.registry(), &mut unique_token);
|
peers.connect_new_peers(poll.registry(), &mut unique_token);
|
||||||
}
|
}
|
||||||
|
info!("Network loop finished");
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn subscribe_to_bus(context: &mut Arc<Mutex<Context>>, running: Arc<AtomicBool>) {
|
||||||
|
use crate::event::Event;
|
||||||
|
context.lock().unwrap().bus.register(move |_uuid, e| {
|
||||||
|
match e {
|
||||||
|
Event::ActionQuit => {
|
||||||
|
running.store(false, Ordering::SeqCst);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_connection_event(context: Arc<Mutex<Context>>, peers: &mut Peers, registry: &Registry, event: &Event) -> bool {
|
fn handle_connection_event(context: Arc<Mutex<Context>>, peers: &mut Peers, registry: &Registry, event: &Event) -> bool {
|
||||||
if event.is_error() || (event.is_read_closed() && event.is_write_closed()) {
|
if event.is_error() || (event.is_read_closed() && event.is_write_closed()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -415,16 +437,19 @@ fn handle_message(context: Arc<Mutex<Context>>, message: Message, peers: &mut Pe
|
|||||||
fn mine_locker_block(context: Arc<Mutex<Context>>) {
|
fn mine_locker_block(context: Arc<Mutex<Context>>) {
|
||||||
let mut context = context.lock().unwrap();
|
let mut context = context.lock().unwrap();
|
||||||
if let Some(block) = context.chain.last_block() {
|
if let Some(block) = context.chain.last_block() {
|
||||||
if block.index < context.chain.max_height() {
|
if let Some(keystore) = &context.keystore {
|
||||||
info!("No locker mining while syncing");
|
if block.index < context.chain.max_height() {
|
||||||
return;
|
info!("No locker mining while syncing");
|
||||||
}
|
return;
|
||||||
let lockers: HashSet<Bytes> = context.chain.get_block_lockers(&block).into_iter().collect();
|
}
|
||||||
if lockers.contains(&context.keystore.get_public()) {
|
let lockers: HashSet<Bytes> = context.chain.get_block_lockers(&block).into_iter().collect();
|
||||||
info!("We have an honor to mine locker block!");
|
if lockers.contains(&keystore.get_public()) {
|
||||||
context.bus.post(crate::event::Event::ActionMineLocker { index: block.index + 1, hash: block.hash });
|
info!("We have an honor to mine locker block!");
|
||||||
} else if !lockers.is_empty() {
|
let keystore = Box::new(keystore.clone());
|
||||||
info!("Locker block must be mined by other nodes");
|
context.bus.post(crate::event::Event::ActionMineLocker { index: block.index + 1, hash: block.hash, keystore });
|
||||||
|
} else if !lockers.is_empty() {
|
||||||
|
info!("Locker block must be mined by other nodes");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+54
-29
@@ -61,12 +61,13 @@ pub fn run_interface(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>) {
|
|||||||
.build()
|
.build()
|
||||||
.expect("Error building GUI");
|
.expect("Error building GUI");
|
||||||
|
|
||||||
run_interface_loop(&mut interface);
|
let mut context = Arc::clone(&context);
|
||||||
|
run_interface_loop(&mut context, &mut interface);
|
||||||
interface.exit();
|
interface.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indefinitely loops through WebView steps
|
/// Indefinitely loops through WebView steps
|
||||||
fn run_interface_loop(interface: &mut WebView<()>) {
|
fn run_interface_loop(context: &mut Arc<Mutex<Context>>, interface: &mut WebView<()>) {
|
||||||
// We use this ugly loop to lower CPU usage a lot.
|
// 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
|
// 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.
|
// to support 60FPS and uses more CPU than it should.
|
||||||
@@ -76,6 +77,8 @@ fn run_interface_loop(interface: &mut WebView<()>) {
|
|||||||
match interface.step() {
|
match interface.step() {
|
||||||
None => {
|
None => {
|
||||||
info!("Interface closed, exiting");
|
info!("Interface closed, exiting");
|
||||||
|
context.lock().unwrap().bus.post(Event::ActionQuit);
|
||||||
|
thread::sleep(Duration::from_millis(100));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Some(result) => {
|
Some(result) => {
|
||||||
@@ -101,8 +104,10 @@ fn action_check_zone(context: &Arc<Mutex<Context>>, web_view: &mut WebView<()>,
|
|||||||
web_view.eval("zoneAvailable(false)").expect("Error evaluating!");
|
web_view.eval("zoneAvailable(false)").expect("Error evaluating!");
|
||||||
} else {
|
} else {
|
||||||
let c = context.lock().unwrap();
|
let c = context.lock().unwrap();
|
||||||
let available = c.get_chain().is_domain_available(&name, &c.get_keystore());
|
if let Some(keystore) = c.get_keystore() {
|
||||||
web_view.eval(&format!("zoneAvailable({})", available)).expect("Error evaluating!");
|
let available = c.get_chain().is_domain_available(&name, &keystore);
|
||||||
|
web_view.eval(&format!("zoneAvailable({})", available)).expect("Error evaluating!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,24 +119,31 @@ fn action_check_record(web_view: &mut WebView<()>, data: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn action_check_domain(context: &Arc<Mutex<Context>>, web_view: &mut WebView<()>, name: String) {
|
fn action_check_domain(context: &Arc<Mutex<Context>>, web_view: &mut WebView<()>, name: String) {
|
||||||
let name = name.to_lowercase();
|
|
||||||
let c = context.lock().unwrap();
|
let c = context.lock().unwrap();
|
||||||
let available = c.get_chain().is_domain_available(&name, &c.get_keystore());
|
if let Some(keystore) = c.get_keystore() {
|
||||||
web_view.eval(&format!("domainAvailable({})", available)).expect("Error evaluating!");
|
let name = name.to_lowercase();
|
||||||
|
let available = c.get_chain().is_domain_available(&name, &keystore);
|
||||||
|
web_view.eval(&format!("domainAvailable({})", available)).expect("Error evaluating!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_save_key(context: &Arc<Mutex<Context>>) {
|
fn action_save_key(context: &Arc<Mutex<Context>>) {
|
||||||
|
if context.lock().unwrap().get_keystore().is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let result = tfd::save_file_dialog_with_filter("Save keys file", "", &["*.key"], "Key files (*.key)");
|
let result = tfd::save_file_dialog_with_filter("Save keys file", "", &["*.key"], "Key files (*.key)");
|
||||||
match result {
|
match result {
|
||||||
None => {}
|
None => {}
|
||||||
Some(new_path) => {
|
Some(new_path) => {
|
||||||
let mut context = context.lock().unwrap();
|
let mut context = context.lock().unwrap();
|
||||||
let path = new_path.clone();
|
let path = new_path.clone();
|
||||||
let public = context.keystore.get_public().to_string();
|
if let Some(mut keystore) = context.get_keystore() {
|
||||||
let hash = context.keystore.get_hash().to_string();
|
let public = keystore.get_public().to_string();
|
||||||
context.keystore.save(&new_path, "");
|
let hash = keystore.get_hash().to_string();
|
||||||
info!("Key file saved to {}", &path);
|
keystore.save(&new_path, "");
|
||||||
context.bus.post(Event::KeySaved { path, public, hash });
|
info!("Key file saved to {}", &path);
|
||||||
|
context.bus.post(Event::KeySaved { path, public, hash });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,7 +164,7 @@ fn action_load_key(context: &Arc<Mutex<Context>>, _web_view: &mut WebView<()>) {
|
|||||||
let public = keystore.get_public().to_string();
|
let public = keystore.get_public().to_string();
|
||||||
let hash = keystore.get_hash().to_string();
|
let hash = keystore.get_hash().to_string();
|
||||||
c.bus.post(Event::KeyLoaded { path, public, hash });
|
c.bus.post(Event::KeyLoaded { path, public, hash });
|
||||||
c.set_keystore(keystore);
|
c.set_keystore(Some(keystore));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,23 +225,32 @@ fn action_loaded(context: &Arc<Mutex<Context>>, web_view: &mut WebView<()>) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if !eval.is_empty() {
|
if !eval.is_empty() {
|
||||||
//debug!("Evaluating {}", &eval);
|
|
||||||
handle.dispatch(move |web_view| {
|
handle.dispatch(move |web_view| {
|
||||||
web_view.eval(&eval.replace("\\", "\\\\"))
|
web_view.eval(&eval.replace("\\", "\\\\"))
|
||||||
}).expect("Error dispatching!");
|
}).expect("Error dispatching!");
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
});
|
});
|
||||||
let eval = format!("keystoreChanged('{}', '{}', '{}');", c.keystore.get_path(), &c.keystore.get_public().to_string(), &c.keystore.get_hash().to_string());
|
if let Some(keystore) = c.get_keystore() {
|
||||||
debug!("Evaluating {}", &eval);
|
let eval = format!("keystoreChanged('{}', '{}', '{}');",
|
||||||
web_view.eval(&eval.replace("\\", "\\\\")).expect("Error evaluating!");
|
keystore.get_path(),
|
||||||
|
&keystore.get_public().to_string(),
|
||||||
|
&keystore.get_hash().to_string());
|
||||||
|
//debug!("Evaluating {}", &eval);
|
||||||
|
web_view.eval(&eval.replace("\\", "\\\\")).expect("Error evaluating!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_create_domain(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, web_view: &mut WebView<()>, name: String, records: &String) {
|
fn action_create_domain(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, web_view: &mut WebView<()>, name: String, records: &String) {
|
||||||
debug!("Creating domain with records: {}", records);
|
debug!("Creating domain with records: {}", records);
|
||||||
let c = Arc::clone(&context);
|
let c = Arc::clone(&context);
|
||||||
let context = context.lock().unwrap();
|
let context = context.lock().unwrap();
|
||||||
let pub_key = context.keystore.get_public();
|
if context.get_keystore().is_none() {
|
||||||
|
show_warning(web_view, "You don't have keys loaded!\nLoad or mine the keys and try again.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let keystore = context.get_keystore().unwrap();
|
||||||
|
let pub_key = keystore.get_public();
|
||||||
match context.chain.can_mine_domain(&name, &records, &pub_key) {
|
match context.chain.can_mine_domain(&name, &records, &pub_key) {
|
||||||
MineResult::Fine => {
|
MineResult::Fine => {
|
||||||
let zone = get_domain_zone(&name);
|
let zone = get_domain_zone(&name);
|
||||||
@@ -237,7 +258,6 @@ fn action_create_domain(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>,
|
|||||||
if let Ok(records) = serde_json::from_str::<Vec<DnsRecord>>(&records) {
|
if let Ok(records) = serde_json::from_str::<Vec<DnsRecord>>(&records) {
|
||||||
let data = DomainData::new(zone.clone(), records);
|
let data = DomainData::new(zone.clone(), records);
|
||||||
let data = serde_json::to_string(&data).unwrap();
|
let data = serde_json::to_string(&data).unwrap();
|
||||||
let keystore = context.keystore.clone();
|
|
||||||
std::mem::drop(context);
|
std::mem::drop(context);
|
||||||
create_domain(c, miner, &name, &data, difficulty, &keystore);
|
create_domain(c, miner, &name, &data, difficulty, &keystore);
|
||||||
let _ = web_view.eval("domainMiningStarted()");
|
let _ = web_view.eval("domainMiningStarted()");
|
||||||
@@ -271,18 +291,23 @@ fn action_create_zone(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, we
|
|||||||
let context = context.lock().unwrap();
|
let context = context.lock().unwrap();
|
||||||
(context.get_keystore(), context.chain.get_domain_transaction(&name))
|
(context.get_keystore(), context.chain.get_domain_transaction(&name))
|
||||||
};
|
};
|
||||||
match transaction {
|
if let Some(keystore) = keystore {
|
||||||
None => {
|
match transaction {
|
||||||
create_domain(Arc::clone(&context), miner.clone(), &name, &data, ZONE_DIFFICULTY, &keystore);
|
None => {
|
||||||
}
|
|
||||||
Some(transaction) => {
|
|
||||||
if transaction.pub_key == keystore.get_public() {
|
|
||||||
create_domain(Arc::clone(&context), miner.clone(), &name, &data, ZONE_DIFFICULTY, &keystore);
|
create_domain(Arc::clone(&context), miner.clone(), &name, &data, ZONE_DIFFICULTY, &keystore);
|
||||||
} else {
|
}
|
||||||
warn!("Tried to mine not owned domain!");
|
Some(transaction) => {
|
||||||
show_warning(web_view, "You cannot change domain that you don't own!");
|
if transaction.pub_key == keystore.get_public() {
|
||||||
|
create_domain(Arc::clone(&context), miner.clone(), &name, &data, ZONE_DIFFICULTY, &keystore);
|
||||||
|
} else {
|
||||||
|
warn!("Tried to mine not owned domain!");
|
||||||
|
show_warning(web_view, "You cannot change domain that you don't own!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Can not mine without keys!");
|
||||||
|
show_warning(web_view, "You don't have keys loaded!\nLoad or mine the keys and try again.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,7 +328,7 @@ fn create_domain(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, name: &
|
|||||||
//let tags_vector: Vec<String> = tags.into().trim().split(",").map(|s| s.trim()).map(String::from).collect();
|
//let tags_vector: Vec<String> = tags.into().trim().split(",").map(|s| s.trim()).map(String::from).collect();
|
||||||
let transaction = Transaction::from_str(name, "dns".to_owned(), data.to_owned(), keystore.get_public().clone());
|
let transaction = Transaction::from_str(name, "dns".to_owned(), data.to_owned(), keystore.get_public().clone());
|
||||||
let block = Block::new(Some(transaction), keystore.get_public(), Bytes::default(), difficulty);
|
let block = Block::new(Some(transaction), keystore.get_public(), Bytes::default(), difficulty);
|
||||||
miner.lock().unwrap().add_block(block);
|
miner.lock().unwrap().add_block(block, keystore.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|||||||
+13
-13
@@ -154,7 +154,7 @@
|
|||||||
<div class="content is-hidden" id="key_load">
|
<div class="content is-hidden" id="key_load">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Key path</label>
|
<label class="label">Key path</label>
|
||||||
<p id="key_file_name">Key not saved</p>
|
<p id="key_file_name">Not loaded</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
@@ -169,16 +169,16 @@
|
|||||||
|
|
||||||
<br>
|
<br>
|
||||||
<div class="field is-grouped">
|
<div class="field is-grouped">
|
||||||
<div class="control">
|
|
||||||
<button class="button is-success" onclick="loadKey();">Load key</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<button class="button is-warning" onclick="createKey();">Mine new key</button>
|
<button class="button is-warning" onclick="createKey();">Mine new key</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<button class="button is-primary" onclick="saveKey();">Save key</button>
|
<button class="button is-light" onclick="loadKey();">Load key</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control">
|
||||||
|
<button id="save_key" class="button is-light" onclick="saveKey();" disabled>Save key</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -190,7 +190,7 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Domain name</label>
|
<label class="label">Domain name</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input class="input" type="text" placeholder="example.ygg" id="new_domain" oninput="onDomainChange(this)">
|
<input class="input" type="text" placeholder="example.ygg" id="new_domain" oninput="onDomainChange(this)" disabled>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -198,7 +198,7 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Domain tags (will be used for search)</label>
|
<label class="label">Domain tags (will be used for search)</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input class="input" type="text" placeholder="blog, community, friendship" id="new_domain_tags">
|
<input class="input" type="text" placeholder="blog, community, friendship" id="new_domain_tags" disabled>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -210,10 +210,10 @@
|
|||||||
|
|
||||||
<div class="field is-grouped">
|
<div class="field is-grouped">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<button class="button is-success" id="add_record_button" onclick="showNewRecordDialog();">Add record</button>
|
<button id="new_domain_button" class="button is-warning" onclick="createDomain();" disabled>Mine domain</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<button class="button is-link" id="new_domain_button" onclick="createDomain();" disabled>Mine domain</button>
|
<button id="add_record_button" class="button is-light" onclick="showNewRecordDialog();" disabled>Add record</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -225,7 +225,7 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Zone name</label>
|
<label class="label">Zone name</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input class="input" type="text" placeholder="ygg" id="new_zone" oninput="onZoneChange()">
|
<input class="input" type="text" placeholder="ygg" id="new_zone" oninput="onZoneChange()" disabled>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -233,7 +233,7 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Difficulty (for all domains in zone)</label>
|
<label class="label">Difficulty (for all domains in zone)</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input class="input" type="number" placeholder="20" id="new_zone_difficulty" oninput="onZoneChange()">
|
<input class="input" type="number" placeholder="20" id="new_zone_difficulty" oninput="onZoneChange()" disabled>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -241,7 +241,7 @@
|
|||||||
|
|
||||||
<div class="field is-grouped">
|
<div class="field is-grouped">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<button class="button is-link" id="new_zone_button" onclick="createZone();" disabled>Mine zone</button>
|
<button class="button is-warning" id="new_zone_button" onclick="createZone();" disabled>Mine zone</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -189,12 +189,15 @@ function onDomainChange(element) {
|
|||||||
function domainAvailable(available) {
|
function domainAvailable(available) {
|
||||||
input = document.getElementById("new_domain");
|
input = document.getElementById("new_domain");
|
||||||
button = document.getElementById("new_domain_button");
|
button = document.getElementById("new_domain_button");
|
||||||
|
button2 = document.getElementById("add_record_button");
|
||||||
if (available) {
|
if (available) {
|
||||||
input.className = "input";
|
input.className = "input";
|
||||||
button.disabled = false
|
button.disabled = false
|
||||||
|
button2.disabled = false
|
||||||
} else {
|
} else {
|
||||||
input.className = "input is-danger";
|
input.className = "input is-danger";
|
||||||
button.disabled = true
|
button.disabled = true
|
||||||
|
button2.disabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,4 +312,15 @@ function keystoreChanged(path, pub_key, hash) {
|
|||||||
key_public_key.innerHTML = pub_key;
|
key_public_key.innerHTML = pub_key;
|
||||||
var key_public_hash = document.getElementById("key_public_hash");
|
var key_public_hash = document.getElementById("key_public_hash");
|
||||||
key_public_hash.innerHTML = hash;
|
key_public_hash.innerHTML = hash;
|
||||||
|
var save_key = document.getElementById("save_key");
|
||||||
|
save_key.disabled = false;
|
||||||
|
|
||||||
|
var new_domain = document.getElementById("new_domain");
|
||||||
|
new_domain.disabled = false;
|
||||||
|
var new_domain_tags = document.getElementById("new_domain_tags");
|
||||||
|
new_domain_tags.disabled = false;
|
||||||
|
var new_zone = document.getElementById("new_zone");
|
||||||
|
new_zone.disabled = false;
|
||||||
|
var new_zone_difficulty = document.getElementById("new_zone_difficulty");
|
||||||
|
new_zone_difficulty.disabled = false;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user