@ -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 ( ) ) ;
if block . transaction . is_some ( ) {
self . last_full_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" ) ;
}
if self . add_block_to_table ( block ) . is_ok ( ) {
if let Some ( transaction ) = transaction {
self . add_transaction_to_table ( & transaction ) . expect ( "Error adding transaction" ) ;
}
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 let Some ( transaction ) = transaction {
self . add_transaction ( & transaction ) ;
/// 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 ( )
}
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 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 = > { 0 u64 }
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 ( ) )
}