Changed origin block index to 1. Added "locker" blocks - mining, exchange etc. Removed unnecesarry creation of 'zones' directory on startup. Changed bind port of DNS-UDP socket to random (fixes inability to start several copies of Alfis). Sped up block exchange by sending additional pings when we have more blocks than other peers. Fixed unnecesarry double requests of blocks. Totally reworked block checking on arrival. Added target tags for logging in main. Added a commandline flag to list all blocks in DB and exit.

This commit is contained in:
Revertron
2021-03-06 21:28:06 +01:00
parent 59df68d7c7
commit b0e78edb3d
12 changed files with 465 additions and 147 deletions
+1 -1
View File
@@ -64,7 +64,7 @@ impl Block {
}
pub fn is_genesis(&self) -> bool {
self.index == 0 && self.transaction.is_none() && self.prev_block_hash == Bytes::default()
self.index == 1 && self.transaction.is_none() && self.prev_block_hash == Bytes::default()
}
pub fn as_bytes(&self) -> Vec<u8> {
+209 -83
View File
@@ -9,18 +9,43 @@ use std::cell::RefCell;
use chrono::Utc;
use crate::blockchain::transaction::hash_identity;
use crate::blockchain::blockchain::BlockQuality::*;
use crate::blockchain::{BLOCK_DIFFICULTY, CHAIN_VERSION};
use crate::blockchain::{BLOCK_DIFFICULTY, CHAIN_VERSION, LOCKER_BLOCK_START, LOCKER_DIFFICULTY, LOCKER_BLOCK_COUNT, LOCKER_BLOCK_INTERVAL};
const DB_NAME: &str = "blockchain.db";
const SQL_CREATE_TABLES: &str = "CREATE TABLE blocks (
'id' BIGINT NOT NULL PRIMARY KEY,
'timestamp' BIGINT NOT NULL,
'version' INT,
'difficulty' INTEGER,
'random' INTEGER,
'nonce' INTEGER,
'transaction' TEXT,
'prev_block_hash' BINARY,
'hash' BINARY,
'pub_key' BINARY,
'signature' BINARY);
CREATE INDEX block_index ON blocks (id);
CREATE TABLE transactions (id INTEGER PRIMARY KEY AUTOINCREMENT, identity BINARY, confirmation BINARY, method TEXT, data TEXT, pub_key BINARY);
CREATE INDEX ids ON transactions (identity);";
const SQL_ADD_BLOCK: &str = "INSERT INTO blocks (id, timestamp, version, difficulty, random, nonce, 'transaction',\
prev_block_hash, hash, pub_key, signature) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);";
const SQL_GET_LAST_BLOCK: &str = "SELECT * FROM blocks ORDER BY id DESC LIMIT 1;";
const SQL_ADD_TRANSACTION: &str = "INSERT INTO transactions (identity, confirmation, method, data, pub_key) VALUES (?, ?, ?, ?, ?)";
const SQL_GET_BLOCK_BY_ID: &str = "SELECT * FROM blocks WHERE id=? LIMIT 1;";
const SQL_GET_LAST_FULL_BLOCK: &str = "SELECT * FROM blocks WHERE `transaction`<>'' ORDER BY id DESC LIMIT 1;";
const SQL_GET_PUBLIC_KEY_BY_ID: &str = "SELECT pub_key FROM transactions WHERE identity = ? ORDER BY id DESC LIMIT 1;";
const SQL_GET_ID_BY_ID: &str = "SELECT identity FROM transactions WHERE identity = ? ORDER BY id DESC LIMIT 1;";
const SQL_GET_TRANSACTION_BY_ID: &str = "SELECT * FROM transactions WHERE identity = ? ORDER BY id DESC LIMIT 1;";
pub struct Blockchain {
origin: Bytes,
pub version: u32,
pub blocks: Vec<Block>,
last_block: Option<Block>,
last_full_block: Option<Block>,
max_height: u64,
db: Connection,
zones: RefCell<HashSet<String>>
zones: RefCell<HashSet<String>>,
}
impl Blockchain {
@@ -28,99 +53,120 @@ impl Blockchain {
let origin = settings.get_origin();
let db = sqlite::open(DB_NAME).expect("Unable to open blockchain DB");
let mut blockchain = Blockchain{ origin, version: CHAIN_VERSION, blocks: Vec::new(), last_block: None, max_height: 0, db, zones: RefCell::new(HashSet::new()) };
let mut blockchain = Blockchain {
origin,
version: CHAIN_VERSION,
blocks: Vec::new(),
last_block: None,
last_full_block: None,
max_height: 0,
db,
zones: RefCell::new(HashSet::new()),
};
blockchain.init_db();
blockchain
}
/// Reads options from DB or initializes and writes them to DB if not found
fn init_db(&mut self) {
match self.db.prepare("SELECT * FROM blocks ORDER BY id DESC LIMIT 1;") {
// Trying to get last block from DB to check its version
let block: Option<Block> = match self.db.prepare(SQL_GET_LAST_BLOCK) {
Ok(mut statement) => {
let mut result = None;
while statement.next().unwrap() == State::Row {
match Self::get_block_from_statement(&mut statement) {
None => { error!("Something wrong with block in DB!"); }
None => {
error!("Something wrong with block in DB!");
panic!();
}
Some(block) => {
info!("Loaded last block: {:?}", &block);
self.version = block.version;
self.last_block = Some(block);
debug!("Loaded last block: {:?}", &block);
result = Some(block);
break;
}
}
debug!("Blockchain version from DB = {}", self.version);
}
result
}
Err(_) => {
info!("No blockchain database found. Creating new.");
self.db.execute("
CREATE TABLE blocks (
'id' BIGINT,
'timestamp' BIGINT,
'version' TEXT,
'difficulty' INTEGER,
'random' INTEGER,
'nonce' INTEGER,
'transaction' TEXT,
'prev_block_hash' BINARY,
'hash' BINARY,
'pub_key' BINARY,
'signature' BINARY
);
CREATE INDEX block_index ON blocks (id);
CREATE TABLE transactions (id INTEGER PRIMARY KEY AUTOINCREMENT, identity BINARY, confirmation BINARY, method TEXT, data TEXT, pub_key BINARY);
CREATE INDEX ids ON transactions (identity);"
).expect("Error creating blocks table");
self.db.execute(SQL_CREATE_TABLES).expect("Error creating blocks table");
None
}
};
// If some block loaded we check its version and determine if we need some migration
if let Some(block) = block {
self.max_height = block.index;
if self.version > block.version {
self.migrate_db(block.version, self.version);
} else if self.version < block.version {
error!("Version downgrade {}->{} is not supported!", block.version, self.version);
panic!();
}
// Cache some info
self.last_block = Some(block.clone());
if block.transaction.is_some() {
self.last_full_block = Some(block);
} else {
self.last_full_block = self.get_last_full_block();
}
}
}
fn migrate_db(&mut self, from: u32, to: u32) {
debug!("Migrating DB from {} to {}", from, to);
}
pub fn add_block(&mut self, block: Block) {
info!("Adding block:\n{:?}", &block);
self.blocks.push(block.clone());
self.last_block = Some(block.clone());
let transaction = block.transaction.clone();
{
// Adding block to DB
let mut statement = self.db.prepare("INSERT INTO blocks (\
id, timestamp, version, difficulty, random, nonce, 'transaction',\
prev_block_hash, hash, pub_key, signature)\
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);").unwrap();
statement.bind(1, block.index as i64).expect("Error in bind");
statement.bind(2, block.timestamp as i64).expect("Error in bind");
statement.bind(3, block.version as i64).expect("Error in bind");
statement.bind(4, block.difficulty as i64).expect("Error in bind");
statement.bind(5, block.random as i64).expect("Error in bind");
statement.bind(6, block.nonce as i64).expect("Error in bind");
match &transaction {
None => { statement.bind(7, "").expect("Error in bind"); }
Some(transaction) => {
statement.bind(7, transaction.to_string().as_ref() as &str).expect("Error in bind");
}
}
statement.bind(8, block.prev_block_hash.as_bytes()).expect("Error in bind");
statement.bind(9, block.hash.as_bytes()).expect("Error in bind");
statement.bind(10, block.pub_key.as_bytes()).expect("Error in bind");
statement.bind(11, block.signature.as_bytes()).expect("Error in bind");
statement.next().expect("Error adding block to DB");
if block.transaction.is_some() {
self.last_full_block = Some(block.clone());
}
if let Some(transaction) = transaction {
self.add_transaction(&transaction);
let transaction = block.transaction.clone();
if self.add_block_to_table(block).is_ok() {
if let Some(transaction) = transaction {
self.add_transaction_to_table(&transaction).expect("Error adding transaction");
}
}
}
fn add_transaction(&mut self, t: &Transaction) {
let mut statement = self.db.prepare("INSERT INTO transactions (identity, confirmation, method, data, pub_key) VALUES (?, ?, ?, ?, ?)").unwrap();
statement.bind(1, t.identity.as_bytes()).expect("Error in bind");
statement.bind(2, t.confirmation.as_bytes()).expect("Error in bind");
statement.bind(3, t.method.as_ref() as &str).expect("Error in bind");
statement.bind(4, t.data.as_ref() as &str).expect("Error in bind");
statement.bind(5, t.pub_key.as_bytes()).expect("Error in bind");
statement.next().expect("Error adding transaction to DB");
/// Adds block to blocks table
fn add_block_to_table(&mut self, block: Block) -> sqlite::Result<State> {
let mut statement = self.db.prepare(SQL_ADD_BLOCK)?;
statement.bind(1, block.index as i64)?;
statement.bind(2, block.timestamp as i64)?;
statement.bind(3, block.version as i64)?;
statement.bind(4, block.difficulty as i64)?;
statement.bind(5, block.random as i64)?;
statement.bind(6, block.nonce as i64)?;
match &block.transaction {
None => { statement.bind(7, "")?; }
Some(transaction) => {
statement.bind(7, transaction.to_string().as_str())?;
}
}
statement.bind(8, block.prev_block_hash.as_bytes())?;
statement.bind(9, block.hash.as_bytes())?;
statement.bind(10, block.pub_key.as_bytes())?;
statement.bind(11, block.signature.as_bytes())?;
statement.next()
}
/// Adds transaction to transactions table
fn add_transaction_to_table(&mut self, t: &Transaction) -> sqlite::Result<State> {
let mut statement = self.db.prepare(SQL_ADD_TRANSACTION)?;
statement.bind(1, t.identity.as_bytes())?;
statement.bind(2, t.confirmation.as_bytes())?;
statement.bind(3, t.method.as_ref() as &str)?;
statement.bind(4, t.data.as_ref() as &str)?;
statement.bind(5, t.pub_key.as_bytes())?;
statement.next()
}
pub fn get_block(&self, index: u64) -> Option<Block> {
match self.db.prepare("SELECT * FROM blocks WHERE id=? LIMIT 1;") {
match self.db.prepare(SQL_GET_BLOCK_BY_ID) {
Ok(mut statement) => {
statement.bind(1, index as i64).expect("Error in bind");
while statement.next().unwrap() == State::Row {
@@ -130,10 +176,10 @@ impl Blockchain {
None
}
Some(block) => {
debug!("Loaded block: {:?}", &block);
trace!("Loaded block: {:?}", &block);
Some(block)
}
}
};
}
None
}
@@ -144,12 +190,38 @@ impl Blockchain {
}
}
/// Gets last block that has a Transaction within
pub fn get_last_full_block(&self) -> Option<Block> {
match self.db.prepare(SQL_GET_LAST_FULL_BLOCK) {
Ok(mut statement) => {
while statement.next().unwrap() == State::Row {
return match Self::get_block_from_statement(&mut statement) {
None => {
error!("Something wrong with block in DB!");
None
}
Some(block) => {
trace!("Got last full block: {:?}", &block);
Some(block)
}
};
}
None
}
Err(e) => {
warn!("Can't find any full blocks: {}", e);
None
}
}
}
/// Checks if any domain is available to mine for this client (pub_key)
pub fn is_domain_available(&self, domain: &str, keystore: &Keystore) -> bool {
if domain.is_empty() {
return false;
}
let identity_hash = hash_identity(domain, None);
let mut statement = self.db.prepare("SELECT pub_key FROM transactions WHERE identity = ? ORDER BY id DESC LIMIT 1;").unwrap();
let mut statement = self.db.prepare(SQL_GET_PUBLIC_KEY_BY_ID).unwrap();
statement.bind(1, identity_hash.as_bytes()).expect("Error in bind");
while let State::Row = statement.next().unwrap() {
let pub_key = Bytes::from_bytes(statement.read::<Vec<u8>>(0).unwrap().as_slice());
@@ -170,6 +242,7 @@ impl Blockchain {
true
}
/// Checks if some zone exists in our blockchain
pub fn is_zone_in_blockchain(&self, zone: &str) -> bool {
if self.zones.borrow().contains(zone) {
return true;
@@ -177,7 +250,7 @@ impl Blockchain {
// Checking for existing zone in DB
let identity_hash = hash_identity(zone, None);
let mut statement = self.db.prepare("SELECT identity FROM transactions WHERE identity = ? ORDER BY id DESC LIMIT 1;").unwrap();
let mut statement = self.db.prepare(SQL_GET_ID_BY_ID).unwrap();
statement.bind(1, identity_hash.as_bytes()).expect("Error in bind");
while let State::Row = statement.next().unwrap() {
// If there is such a zone
@@ -187,13 +260,14 @@ impl Blockchain {
false
}
/// Gets full Transaction info for any domain. Used by DNS part.
pub fn get_domain_transaction(&self, domain: &str) -> Option<Transaction> {
if domain.is_empty() {
return None;
}
let identity_hash = hash_identity(domain, None);
let mut statement = self.db.prepare("SELECT * FROM transactions WHERE identity = ? ORDER BY id DESC LIMIT 1;").unwrap();
let mut statement = self.db.prepare(SQL_GET_TRANSACTION_BY_ID).unwrap();
statement.bind(1, identity_hash.as_bytes()).expect("Error in bind");
while let State::Row = statement.next().unwrap() {
let identity = Bytes::from_bytes(statement.read::<Vec<u8>>(1).unwrap().as_slice());
@@ -225,7 +299,7 @@ impl Blockchain {
match self.last_block {
None => { 0u64 }
Some(ref block) => {
block.index + 1
block.index
}
}
}
@@ -247,14 +321,19 @@ impl Blockchain {
}
}
/// Check if this block can be added to our blockchain
pub fn check_new_block(&self, block: &Block) -> BlockQuality {
let timestamp = Utc::now().timestamp();
if block.timestamp > timestamp {
warn!("Ignoring block from the future:\n{:?}", &block);
return Bad;
}
if !hash_is_good(block.hash.as_bytes(), BLOCK_DIFFICULTY as usize) {
warn!("Ignoring block with low difficulty:\n{:?}", &block);
let difficulty = match block.transaction {
None => { LOCKER_DIFFICULTY }
Some(_) => { BLOCK_DIFFICULTY }
};
if block.difficulty < difficulty {
warn!("Block difficulty is lower than needed");
return Bad;
}
if !hash_is_good(block.hash.as_bytes(), block.difficulty as usize) {
@@ -264,6 +343,7 @@ impl Blockchain {
match &self.last_block {
None => {
if !block.is_genesis() {
warn!("Block is from the future, how is this possible?");
return Future;
}
if !self.origin.is_zero() && block.hash != self.origin {
@@ -277,16 +357,31 @@ impl Blockchain {
return Bad;
}
if last_block.index + 1 < block.index {
warn!("Got block from the future");
warn!("Block is from the future, how is this possible?");
return Future;
}
if last_block.index >= block.index && last_block.hash == block.hash {
warn!("Ignoring block {}, we already have it", block.index);
return Twin;
if block.index <= last_block.index {
if last_block.hash == block.hash {
warn!("Ignoring block {}, we already have it", block.index);
return Twin;
}
if let Some(my_block) = self.get_block(block.index) {
return if my_block.hash != block.hash {
warn!("Got forked block {} with hash {:?} instead of {:?}", block.index, block.hash, last_block.hash);
Fork
} else {
warn!("Ignoring block {}, we already have it", block.index);
Twin
};
}
}
if last_block.index == block.index && last_block.hash != block.hash {
warn!("Got forked block {} with hash {:?} instead of {:?}", block.index, block.hash, last_block.hash);
return Fork;
if block.transaction.is_none() {
if let Some(locker) = self.get_block_locker(&last_block, block.timestamp) {
if locker != block.pub_key {
warn!("Ignoring block {}, as wrong locker", block.index);
return Bad;
}
}
}
}
}
@@ -302,6 +397,38 @@ impl Blockchain {
Good
}
/// Gets a public key of a node that needs to mine "locker" block above this block
pub fn get_block_locker(&self, block: &Block, timestamp: i64) -> Option<Bytes> {
if block.hash.is_empty() || block.hash.is_zero() {
return None;
}
if block.index < LOCKER_BLOCK_START {
return None;
}
match self.get_last_full_block() {
Some(b) => {
if b.index + LOCKER_BLOCK_COUNT <= block.index {
trace!("Block {} is locked enough", b.index);
return None;
}
}
None => {}
}
// How many 5 min intervals have passed since this block?
let intervals = ((timestamp - block.timestamp) / LOCKER_BLOCK_INTERVAL) as u64;
let tail = block.hash.get_tail_u64();
let start_index = 1 + ((tail + tail * intervals) % (block.index - 2));
for index in start_index..block.index {
if let Some(b) = self.get_block(index) {
if b.pub_key != block.pub_key {
trace!("Locker block for block {} must be mined by owner of block {} block_hash: {:?}", block.index, b.index, block.hash);
return Some(b.pub_key);
}
}
}
None
}
fn get_block_from_statement(statement: &mut Statement) -> Option<Block> {
let index = statement.read::<i64>(0).unwrap() as u64;
let timestamp = statement.read::<i64>(1).unwrap();
@@ -324,7 +451,7 @@ pub enum BlockQuality {
Twin,
Future,
Bad,
Fork
Fork,
}
pub fn check_block_hash(block: &Block) -> bool {
@@ -337,7 +464,6 @@ pub fn check_block_hash(block: &Block) -> bool {
pub fn check_block_signature(block: &Block) -> bool {
let mut copy = block.clone();
copy.signature = Bytes::zero64();
let data = serde_json::to_string(&copy).unwrap();
Keystore::check(data.as_bytes(), copy.pub_key.as_bytes(), block.signature.as_bytes())
copy.signature = Bytes::default();
Keystore::check(&copy.as_bytes(), copy.pub_key.as_bytes(), block.signature.as_bytes())
}
+5 -1
View File
@@ -1,2 +1,6 @@
pub const BLOCK_DIFFICULTY: u32 = 24;
pub const BLOCK_DIFFICULTY: u32 = 20;
pub const LOCKER_DIFFICULTY: u32 = 16;
pub const CHAIN_VERSION: u32 = 1;
pub const LOCKER_BLOCK_START: u64 = 5;
pub const LOCKER_BLOCK_COUNT: u64 = 3;
pub const LOCKER_BLOCK_INTERVAL: i64 = 300;