Refactored the work with signing blocks.

pull/61/head
Revertron 3 years ago
parent ee9b73b190
commit 536515519a

@ -71,11 +71,8 @@ ALFIS это ALternative Free Identity System. Альтернативная бе
3. Начиная с 35-го блока включается режим подписей блоков.
Каждый блок, содержащий транзакцию, то есть создающий или меняющий какой-нибудь домен, должен быть подписан группой ~~лиц по предварительному сговору~~ узлов, обладающих блоками перед текущим блоком.
Выбираются до 50 последних блоков перед текущим (подписываемым) блоком, среди них вычисляются 7 публичных ключей, владельцы которых должны подписать блок.
Вычисление происходит исходя из последних 4 байт хэша подписываемого блока.
Вычисление происходит исходя из последних 8 байт подписи подписываемого блока.
Блок должен быть подписан минимум четырьмя валидаторами.
Этот алгоритм в скором времени будет расширен таким образом, что если блок не подписан нужным числом валидаторов, то через некоторое время количество валидаторов и требуемое количество подписей будет расти.
То есть, например, если через полчаса после блока нет четырёх нужных подписей, то каждый узел вычисляет валидаторов заново (по тому же алгоритму), но чуть больше, и требуется уже больше подписей.
Например, 5 из 9, потом 6 из 11. Таким образом любой злоумышленник, долго и упорно добивавшийся своего доминирования в сети, не сможет захватить 4 нужных подписи, и отказаться от подписывания, тем самым остановив систему.
## Дополнительные возможности
ALFIS содержит несколько особенностей, которых нет в обычном DNS.

@ -19,6 +19,7 @@ use std::cmp::{min, max};
use crate::blockchain::transaction::{ZoneData, DomainData};
use std::ops::Deref;
use crate::blockchain::types::MineResult::*;
use crate::event::Event;
const DB_NAME: &str = "blockchain.db";
const TEMP_DB_NAME: &str = "temp.db";
@ -28,6 +29,7 @@ const SQL_ADD_BLOCK: &str = "INSERT INTO blocks (id, timestamp, version, difficu
const SQL_REPLACE_BLOCK: &str = "UPDATE blocks SET timestamp = ?, version = ?, difficulty = ?, random = ?, nonce = ?, 'transaction' = ?,\
prev_block_hash = ?, hash = ?, pub_key = ?, signature = ? WHERE id = ?;";
const SQL_GET_LAST_BLOCK: &str = "SELECT * FROM blocks ORDER BY id DESC LIMIT 1;";
const SQL_GET_FIRST_BLOCK_FOR_KEY: &str = "SELECT id FROM blocks WHERE pub_key = ? LIMIT 1;";
const SQL_ADD_DOMAIN: &str = "INSERT INTO domains (id, timestamp, identity, confirmation, data, pub_key) VALUES (?, ?, ?, ?, ?, ?)";
const SQL_ADD_ZONE: &str = "INSERT INTO zones (id, timestamp, identity, confirmation, data, pub_key) VALUES (?, ?, ?, ?, ?, ?)";
const SQL_DELETE_DOMAIN: &str = "DELETE FROM domains WHERE id = ?";
@ -185,6 +187,51 @@ impl Chain {
Ok(())
}
pub fn update(&mut self, keystore: &Option<Keystore>) -> Option<Event> {
if self.height() < BLOCK_SIGNERS_START {
trace!("Too early to start block signings");
return None;
}
if keystore.is_none() {
trace!("We can't sign blocks without keys");
return None;
}
if self.height() < self.max_height() {
trace!("No signing while syncing");
return None;
}
let block = self.last_block().unwrap();
if block.transaction.is_none() {
trace!("No need to sign signing block");
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()) {
info!("We have an honor to mine signing block!");
let keystore = Box::new(keystore);
// 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);
return Some(Event::ActionMineLocker { start, index: block.index + 1, hash: block.hash, keystore });
} else if !signers.is_empty() {
info!("Signing block must be mined by other nodes");
}
None
}
pub fn update_sign_block_for_mining(&self, mut block: Block) -> Option<Block> {
if let Some(full_block) = &self.last_full_block {
let sign_count = self.height() - full_block.index;
if sign_count >= BLOCK_SIGNERS_MIN {
return None;
}
block.index = self.height() + 1;
block.prev_block_hash = self.last_block.clone().unwrap().hash;
}
None
}
fn delete_transaction(&mut self, index: u64) -> sqlite::Result<()> {
let mut statement = self.db.prepare(SQL_DELETE_DOMAIN)?;
statement.bind(1, index as i64)?;
@ -508,10 +555,10 @@ impl Chain {
match self.last_full_block {
None => { self.height() + 1 }
Some(ref block) => {
if block.index < LOCKER_BLOCK_START {
if block.index < BLOCK_SIGNERS_START {
self.height() + 1
} else {
max(block.index, self.height()) + LOCKER_BLOCK_SIGNS
max(block.index, self.height()) + BLOCK_SIGNERS_MIN
}
}
}
@ -622,25 +669,10 @@ impl Chain {
warn!("Block {} arrived too early.", block.index);
return Future;
}
if block.index >= LOCKER_BLOCK_START {
// If this block is locked part of blockchain
if let Some(full_block) = &self.last_full_block {
let locker_blocks = self.height() - full_block.index;
if locker_blocks < LOCKER_BLOCK_SIGNS {
// Last full block is not locked enough
if block.transaction.is_some() {
warn!("Not enough signing blocks over full {} block!", full_block.index);
return Bad;
} else {
if self.check_block_for_signing(&block, full_block) == Bad {
return Bad;
}
}
} else if locker_blocks < LOCKER_BLOCK_LOCKERS && block.transaction.is_none() {
if self.check_block_for_signing(&block, full_block) == Bad {
return Bad;
}
}
if block.index >= BLOCK_SIGNERS_START {
// If this block is main, signed part of blockchain
if !self.is_good_sign_block(&block) {
return Bad;
}
}
@ -666,6 +698,79 @@ impl Chain {
Good
}
/// Checks if this block is a good signature block
fn is_good_sign_block(&self, block: &Block) -> bool {
if let Some(full_block) = &self.last_full_block {
let sign_count = self.height() - full_block.index;
if sign_count < BLOCK_SIGNERS_MIN {
// Last full block is not locked enough
if block.transaction.is_some() {
warn!("Not enough signing blocks over full {} block!", full_block.index);
return false;
} else {
if !self.is_good_signer_for_block(&block, full_block, sign_count) {
return false;
}
}
} else if sign_count < BLOCK_SIGNERS_ALL && block.transaction.is_none() {
if !self.is_good_signer_for_block(&block, full_block, sign_count) {
return false;
}
}
}
true
}
/// Check if this block's owner is a good candidate to sign last full block
fn is_good_signer_for_block(&self, block: &Block, full_block: &Block, sign_count: u64) -> bool {
// If the time for chosen signers is up
if self.can_sign_by_pos(sign_count, full_block.timestamp, block.timestamp, &block.pub_key) {
return true;
}
// If we got a locker/signing block
let signers: HashSet<Bytes> = self.get_block_signers(full_block).into_iter().collect();
if !signers.contains(&block.pub_key) {
warn!("Ignoring block {} from '{:?}', as wrong signer!", block.index, &block.pub_key);
return false;
}
// If this signers' public key has already locked/signed that block we return error
for i in (full_block.index + 1)..block.index {
let signer = self.get_block(i).expect("Error in DB!");
if signer.pub_key == block.pub_key {
warn!("Ignoring block {} from '{:?}', already signed by this key", block.index, &block.pub_key);
return false;
}
}
true
}
/// Gets an id of first block of this public key
fn get_first_block_id_for_key(&self, key: &Bytes) -> u64 {
match self.db.prepare(SQL_GET_FIRST_BLOCK_FOR_KEY) {
Ok(mut statement) => {
statement.bind(1, &***key).expect("Error in bind");
while statement.next().unwrap() == State::Row {
return statement.read::<i64>(0).unwrap() as u64;
}
0
}
Err(_) => {
0
}
}
}
/// Check if an owner of this public key can sign full block by PoS scheme (be in first 1000 users)
fn can_sign_by_pos(&self, sign_count: u64, block_time: i64, now: i64, pub_key: &Bytes) -> bool {
if sign_count < BLOCK_SIGNERS_MIN && block_time - now > BLOCK_SIGNERS_TIME {
let index = self.get_first_block_id_for_key(&pub_key);
if index > 0 && index <= BLOCK_POS_SIGNERS {
return true;
}
}
false
}
fn get_difficulty_for_transaction(&self, transaction: &Transaction) -> u32 {
match transaction.class.as_ref() {
"domain" => {
@ -689,38 +794,20 @@ impl Chain {
}
}
fn check_block_for_signing(&self, block: &Block, full_block: &Block) -> BlockQuality {
// If we got a locker/signing block
let signers: HashSet<Bytes> = self.get_block_signers(full_block).into_iter().collect();
if !signers.contains(&block.pub_key) {
warn!("Ignoring block {} from '{:?}', as wrong signer!", block.index, &block.pub_key);
return Bad;
}
// If this signers' public key has already locked/signed that block we return error
for i in (full_block.index + 1)..block.index {
let signer = self.get_block(i).expect("Error in DB!");
if signer.pub_key == block.pub_key {
warn!("Ignoring block {} from '{:?}', already signed by this key", block.index, &block.pub_key);
return Bad;
}
}
Good
}
/// Gets public keys of a node that needs to mine "signature" block above this block
/// block - last full block
pub fn get_block_signers(&self, block: &Block) -> Vec<Bytes> {
let mut result = Vec::new();
if block.index < LOCKER_BLOCK_START {
if block.index < BLOCK_SIGNERS_START {
return result;
}
let mut set = HashSet::new();
let tail = block.hash.get_tail_u64();
let interval = min(block.index, LOCKER_BLOCK_INTERVAL) - 1;
let tail = block.signature.get_tail_u64();
let interval = min(block.index, BLOCK_SIGNERS_WINDOW) - 1;
let start_index = block.index - interval;
let mut count = 1;
while set.len() < LOCKER_BLOCK_LOCKERS as usize {
let index = start_index + ((tail * count) % LOCKER_BLOCK_INTERVAL);
while set.len() < BLOCK_SIGNERS_ALL as usize {
let index = start_index + ((tail * count) % BLOCK_SIGNERS_WINDOW);
if let Some(b) = self.get_block(index) {
if b.pub_key != block.pub_key && !set.contains(&b.pub_key) {
result.push(b.pub_key.clone());

@ -6,11 +6,26 @@ pub const ZONE_MIN_DIFFICULTY: u32 = 22;
pub const LOCKER_DIFFICULTY: u32 = 16;
pub const KEYSTORE_DIFFICULTY: u32 = 23;
pub const LOCKER_BLOCK_START: u64 = 35;
pub const LOCKER_BLOCK_LOCKERS: u64 = 7;
pub const LOCKER_BLOCK_SIGNS: u64 = 4;
pub const LOCKER_BLOCK_TIME: i64 = 300;
pub const LOCKER_BLOCK_INTERVAL: u64 = 50;
/// Blocks start to be signed starting from this index
pub const BLOCK_SIGNERS_START: u64 = 35;
/// How many signers are chosen for signing
pub const BLOCK_SIGNERS_ALL: u64 = 7;
/// Minimal signatures needed
pub const BLOCK_SIGNERS_MIN: u64 = 4;
/// Last number of blocks from which we select signers
pub const BLOCK_SIGNERS_WINDOW: u64 = 50;
/// Signers have 30 minutes to sign, after that time any owner of first 1000 block can add needed signature
pub const BLOCK_SIGNERS_TIME: i64 = 1800;
/// PoS signers, that sign blocks when chosen signers didn't sign
pub const BLOCK_POS_SIGNERS: u64 = 1000;
/// We start mining signing blocks after random delay, this is the max delay
pub const BLOCK_SIGNERS_START_RANDOM: i64 = 180;
pub const NEW_DOMAINS_INTERVAL: i64 = 86400; // One day in seconds
pub const DOMAIN_LIFETIME: i64 = 86400 * 365; // One year

@ -13,7 +13,7 @@ pub enum Event {
NewBlockReceived,
BlockchainChanged { index: u64 },
ActionStopMining,
ActionMineLocker { index: u64, hash: Bytes, keystore: Box<Keystore> },
ActionMineLocker { start: i64, index: u64, hash: Bytes, keystore: Box<Keystore> },
ActionQuit,
NetworkStatus { nodes: usize, blocks: u64 },
Syncing { have: u64, height: u64 },

@ -37,7 +37,7 @@ impl Miner {
}
pub fn add_block(&mut self, block: Block, keystore: Keystore) {
self.jobs.lock().unwrap().push(MineJob { block, keystore });
self.jobs.lock().unwrap().push(MineJob { start: 0, block, keystore });
self.cond_var.notify_one();
}
@ -55,21 +55,28 @@ impl Miner {
let cond_var = self.cond_var.clone();
thread::spawn(move || {
running.store(true, Ordering::SeqCst);
let delay = Duration::from_millis(1000);
while running.load(Ordering::SeqCst) {
// If some transaction is being mined now, we yield
if mining.load(Ordering::SeqCst) {
thread::sleep(Duration::from_millis(1000));
thread::sleep(delay);
continue;
}
let mut lock = blocks.lock().unwrap();
if lock.len() > 0 {
info!("Got new block to mine");
let block = lock.remove(0);
mining.store(true, Ordering::SeqCst);
Miner::mine_internal(Arc::clone(&context), block, mining.clone());
let mut jobs = blocks.lock().unwrap();
if jobs.len() > 0 {
debug!("Got new job to mine");
let job = jobs.remove(0);
if job.start == 0 || job.start < Utc::now().timestamp() {
mining.store(true, Ordering::SeqCst);
Miner::mine_internal(Arc::clone(&context), job, mining.clone());
} else {
debug!("This job will wait for now");
thread::sleep(delay);
jobs.push(job);
}
} else {
let _ = cond_var.wait(lock).expect("Error in wait lock!");
let _ = cond_var.wait(jobs).expect("Error in wait lock!");
}
}
});
@ -83,11 +90,11 @@ impl Miner {
Event::ActionStopMining => {
mining.store(false, Ordering::SeqCst);
}
Event::ActionMineLocker { index, hash, keystore } => {
Event::ActionMineLocker { start, index, hash, keystore } => {
if !mining.load(Ordering::SeqCst) {
let mut block = Block::new(None, Bytes::default(), hash, LOCKER_DIFFICULTY);
block.index = index;
blocks.lock().unwrap().push(MineJob { block, keystore: keystore.deref().clone() });
blocks.lock().unwrap().push(MineJob { start, block, keystore: keystore.deref().clone() });
cond_var.notify_all();
info!("Added a locker block to mine");
}
@ -117,17 +124,15 @@ impl Miner {
mining.store(false, Ordering::SeqCst);
return;
}
match context.lock().unwrap().chain.last_block() {
None => {}
Some(last_block) => {
debug!("Last block found");
// If we were doing something else and got new block before we could mine this block
if last_block.index > job.block.index || last_block.hash != job.block.prev_block_hash {
warn!("We missed block to lock");
context.lock().unwrap().bus.post(Event::MinerStopped { success: false, full: false });
mining.store(false, Ordering::SeqCst);
return;
}
match context.lock().unwrap().chain.update_sign_block_for_mining(job.block) {
None => {
warn!("We missed block to lock");
context.lock().unwrap().bus.post(Event::MinerStopped { success: false, full: false });
mining.store(false, Ordering::SeqCst);
return;
}
Some(block) => {
job.block = block;
}
}
} else {
@ -185,6 +190,8 @@ impl Miner {
context.settings.origin = block.hash.to_string();
}
context.chain.add_block(block);
let option = Some(job.keystore);
context.chain.update(&option);
success = true;
}
context.bus.post(Event::MinerStopped { success, full });
@ -199,6 +206,7 @@ impl Miner {
#[derive(Clone)]
pub struct MineJob {
start: i64,
block: Block,
keystore: Keystore
}

@ -14,12 +14,10 @@ use mio::net::{TcpListener, TcpStream};
use log::{trace, debug, info, warn, error};
use std::net::{SocketAddr, IpAddr, SocketAddrV4, Shutdown};
use std::collections::HashSet;
use crate::{Context, Block, p2p::Message, p2p::State, p2p::Peer, p2p::Peers, Bytes, is_yggdrasil};
use crate::{Context, Block, p2p::Message, p2p::State, p2p::Peer, p2p::Peers, is_yggdrasil};
use crate::blockchain::types::BlockQuality;
use crate::commons::CHAIN_VERSION;
use std::sync::atomic::{AtomicBool, Ordering};
use chrono::Utc;
const SERVER: Token = Token(0);
const POLL_TIMEOUT: Option<Duration> = Some(Duration::from_millis(3000));
@ -144,7 +142,6 @@ impl Network {
}
(height, context.chain.last_hash())
};
mine_signing_block(Arc::clone(&context));
peers.send_pings(poll.registry(), height, hash);
peers.connect_new_peers(poll.registry(), &mut unique_token, yggdrasil_only);
peers_timer = Instant::now();
@ -461,6 +458,8 @@ fn handle_message(context: Arc<Mutex<Context>>, message: Message, peers: &mut Pe
match context.chain.check_new_block(&block) {
BlockQuality::Good => {
context.chain.add_block(block);
let keystore = context.keystore.clone();
context.chain.update(&keystore);
let my_height = context.chain.height();
context.bus.post(crate::event::Event::BlockchainChanged { index: my_height });
// If it was the last block to sync
@ -482,6 +481,10 @@ fn handle_message(context: Arc<Mutex<Context>>, message: Message, peers: &mut Pe
let last_block = context.chain.last_block().unwrap();
if block.is_better_than(&last_block) {
context.chain.replace_block(block.index, block).expect("Error replacing block with fork");
let keystore = context.keystore.clone();
context.chain.update(&keystore);
let index = context.chain.height();
context.bus.post(crate::event::Event::BlockchainChanged { index });
}
//let peer = peers.get_mut_peer(token).unwrap();
//deal_with_fork(context, peer, block);
@ -494,30 +497,6 @@ fn handle_message(context: Arc<Mutex<Context>>, message: Message, peers: &mut Pe
answer
}
/// Sends an Event to miner to start mining locker block if "locker" is our public key
fn mine_signing_block(context: Arc<Mutex<Context>>) {
let mut context = context.lock().unwrap();
if let Some(block) = context.chain.get_last_full_block(None) {
if block.timestamp + 60 > Utc::now().timestamp() {
return;
}
if let Some(keystore) = &context.keystore {
if block.index < context.chain.max_height() {
trace!("No signing while syncing");
return;
}
let signers: HashSet<Bytes> = context.chain.get_block_signers(&block).into_iter().collect();
if signers.contains(&keystore.get_public()) {
info!("We have an honor to mine signing block!");
let keystore = Box::new(keystore.clone());
context.bus.post(crate::event::Event::ActionMineLocker { index: block.index + 1, hash: block.hash, keystore });
} else if !signers.is_empty() {
info!("Signing block must be mined by other nodes");
}
}
}
}
#[allow(dead_code)]
fn deal_with_fork(context: MutexGuard<Context>, peer: &mut Peer, block: Block) {
peer.add_fork_block(block);

Loading…
Cancel
Save