From 9e6a01847e363d3bc8c1695aa61045ee544c1a67 Mon Sep 17 00:00:00 2001 From: Revertron Date: Wed, 20 Jan 2021 19:23:41 +0100 Subject: [PATCH] Changed chain id to chain name (it will be a string). Implemented saving blockchain to sqlite DB. --- Cargo.toml | 1 + alfis.cfg | 4 +- src/block.rs | 25 ++++++++--- src/blockchain.rs | 108 ++++++++++++++++++++++++++++++++++++++------- src/context.rs | 4 +- src/main.rs | 46 +++---------------- src/miner.rs | 27 +++++------- src/transaction.rs | 12 +++++ 8 files changed, 146 insertions(+), 81 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f9e5836..5783d7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ groestl = "0.8.0" base64 = "0.11.0" chrono = "0.4.9" rand = "0.7.2" +sqlite = "0.25.3" eventbus = "0.5.1" [build-dependencies] diff --git a/alfis.cfg b/alfis.cfg index 6c6bd06..8f56749 100644 --- a/alfis.cfg +++ b/alfis.cfg @@ -1,5 +1,5 @@ { - "chain_id": 42, - "version": 0, + "chain_name": "test", + "version_flags": 0, "key_file": "default.key" } \ No newline at end of file diff --git a/src/block.rs b/src/block.rs index b01bcf6..aaae5c3 100644 --- a/src/block.rs +++ b/src/block.rs @@ -17,8 +17,8 @@ use crate::keys::Bytes; pub struct Block { pub index: u64, pub timestamp: i64, - pub chain_id: u32, - pub version: u32, + pub chain_name: String, + pub version_flags: u32, pub difficulty: usize, pub random: u32, pub nonce: u64, @@ -31,12 +31,12 @@ pub struct Block { } impl Block { - pub fn new(index: u64, timestamp: i64, chain_id: u32, version: u32, prev_block_hash: Bytes, transaction: Option) -> Self { + pub fn new(index: u64, timestamp: i64, chain_name: &str, version_flags: u32, prev_block_hash: Bytes, transaction: Option) -> Self { Block { index, timestamp, - chain_id, - version, + chain_name: chain_name.to_owned(), + version_flags, // TODO make difficulty parameter difficulty: 20, random: 0, @@ -47,6 +47,21 @@ impl Block { } } + pub fn from_all_params(index: u64, timestamp: i64, chain_name: &str, version_flags: u32, difficulty: usize, random: u32, nonce: u64, prev_block_hash: Bytes, hash: Bytes, transaction: Option) -> Self { + Block { + index, + timestamp, + chain_name: chain_name.to_owned(), + version_flags, + difficulty, + random, + nonce, + transaction, + prev_block_hash, + hash, + } + } + pub fn hash(data: &[u8]) -> Bytes { let mut buf: [u8; 32] = [0; 32]; let mut digest = Sha256::new(); diff --git a/src/blockchain.rs b/src/blockchain.rs index 76aac19..46ad187 100644 --- a/src/blockchain.rs +++ b/src/blockchain.rs @@ -1,46 +1,124 @@ use crate::{Block, Transaction, Bytes}; use chrono::Utc; +use sqlite::{Connection, State, Readable, Statement, Error}; + +const DB_NAME: &str = "blockchain.db"; pub struct Blockchain { - pub chain_id: u32, - pub version: u32, + pub chain_name: String, + pub version_flags: u32, pub blocks: Vec, + last_block: Option, + db: Connection, } impl Blockchain { - pub fn new(chain_id: u32, version: u32) -> Self { - let mut blockchain = Blockchain{chain_id, version, blocks: Vec::new()}; + pub fn new(chain_name: &str, version_flags: u32) -> Self { + let db = sqlite::open(DB_NAME).expect("Unable to open blockchain DB"); + let mut blockchain = Blockchain{ chain_name: chain_name.to_owned(), version_flags, blocks: Vec::new(), last_block: None, db}; + blockchain.init_db(); blockchain } - pub fn new_block(&self, transaction: Transaction) -> Block { - let prev_block = self.blocks.last().unwrap(); - let block = Block::new(prev_block.index + 1,Utc::now().timestamp(), self.chain_id, self.version, prev_block.hash.clone(), Some(transaction)); - block + /// 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;") { + Ok(mut statement) => { + while statement.next().unwrap() == State::Row { + match Self::get_block(&mut statement) { + None => { println!("Something wrong with block in DB!"); } + Some(block) => { + println!("Loaded last block: {:?}", &block); + self.chain_name = block.chain_name.clone(); + self.version_flags = block.version_flags; + self.last_block = Some(block); + } + } + println!("Loaded from DB: chain_name = {}, version_flags = {}", self.chain_name, self.version_flags); + } + } + Err(_) => { + println!("No blockchain database found. Creating new."); + self.db.execute(" + CREATE TABLE blocks ( + 'id' BIGINT, + 'timestamp' BIGINT, + 'chain_name' TEXT, + 'version_flags' TEXT, + 'difficulty' INTEGER, + 'random' INTEGER, + 'nonce' INTEGER, + 'transaction' TEXT, + 'prev_block_hash' BINARY, + 'hash' BINARY + ); + CREATE INDEX block_index ON blocks (id);" + ).expect("Error creating blocks table"); + } + } + } + + fn get_block(statement: &mut Statement) -> Option { + let index = statement.read::(0).unwrap() as u64; + let timestamp = statement.read::(1).unwrap(); + let chain_name = statement.read::(2).unwrap(); + let version_flags = statement.read::(3).unwrap() as u32; + let difficulty = statement.read::(4).unwrap() as usize; + let random = statement.read::(5).unwrap() as u32; + let nonce = statement.read::(6).unwrap() as u64; + let transaction = Transaction::from_json(&statement.read::(7).unwrap()); + let prev_block_hash = Bytes::from_bytes(statement.read::>(8).unwrap().as_slice()); + let hash = Bytes::from_bytes(statement.read::>(9).unwrap().as_slice()); + Some(Block::from_all_params(index, timestamp, &chain_name, version_flags, difficulty, random, nonce, prev_block_hash, hash, transaction)) } pub fn add_block(&mut self, block: Block) { - if self.check_block(&block, None) { + if self.check_block(&block, &self.last_block) { println!("Adding block:\n{:?}", &block); - self.blocks.push(block); + self.blocks.push(block.clone()); + self.last_block = Some(block.clone()); + + // Adding block to DB + let mut statement = self.db.prepare("INSERT INTO blocks (\ + id, timestamp, chain_name, version_flags, difficulty,\ + random, nonce, 'transaction', prev_block_hash, hash)\ + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").unwrap(); + statement.bind(1, block.index as i64); + statement.bind(2, block.timestamp as i64); + statement.bind(3, &*block.chain_name); + statement.bind(4, block.version_flags as i64); + statement.bind(5, block.difficulty as i64); + statement.bind(6, block.random as i64); + statement.bind(7, block.nonce as i64); + match block.transaction { + None => { statement.bind(8, ""); } + Some(transaction) => { statement.bind(8, &*transaction.to_string()); } + } + statement.bind(9, block.prev_block_hash.as_bytes()); + statement.bind(10, block.hash.as_bytes()); + statement.next().expect("Error adding block to DB"); } else { println!("Bad block found, ignoring:\n{:?}", &block); } } - pub fn check(&self) -> bool { + pub fn get_last_block(&self) -> Option { + self.last_block.clone() + } + + /*pub fn check(&self) -> bool { let mut prev_block = None; for block in self.blocks.iter() { - if !self.check_block(block, prev_block) { + if !self.check_block(block, &prev_block) { println!("Block {:?} is bad", block); return false; } prev_block = Some(block); } true - } + }*/ - fn check_block(&self, block: &Block, prev_block: Option<&Block>) -> bool { + fn check_block(&self, block: &Block, prev_block: &Option) -> bool { if !Self::check_block_hash(block) { return false; } @@ -48,7 +126,7 @@ impl Blockchain { return true; } - return block.prev_block_hash == prev_block.unwrap().hash; + return block.prev_block_hash == prev_block.as_ref().unwrap().hash; } pub fn check_block_hash(block: &Block) -> bool { diff --git a/src/context.rs b/src/context.rs index 3feeb09..1bbae95 100644 --- a/src/context.rs +++ b/src/context.rs @@ -46,8 +46,8 @@ impl Context { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Settings { - pub chain_id: u32, - pub version: u32, + pub chain_name: String, + pub version_flags: u32, pub key_file: String } diff --git a/src/main.rs b/src/main.rs index da876c6..3e2f315 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,7 +27,7 @@ fn main() { None => { generate_key(KEYSTORE_DIFFICULTY, Arc::new(AtomicBool::new(true))).expect("Could not load or generate keypair") } Some(keystore) => { keystore } }; - let blockchain: Blockchain = Blockchain::new(settings.chain_id, settings.version); + let blockchain: Blockchain = Blockchain::new(&settings.chain_name, settings.version_flags); let context: Arc> = Arc::new(Mutex::new(Context::new(settings, keystore, blockchain))); let mut miner_obj = Miner::new(context.clone()); @@ -40,19 +40,10 @@ fn main() { fn create_genesis_if_needed(context: &Arc>, miner: &Arc>) { // TODO check settings and if there is no mention of bootstrap nodes, generate genesis block - let keystore = { - // This code block makes it possible to contain quick lock here, and let the miner below work - let context_guard = context.lock().unwrap(); - if context_guard.get_blockchain().blocks.is_empty() { - // If blockchain is empty, we are going to mine a Genesis block - Some(context_guard.get_keystore()) - } else { - None - } - }; - - if keystore.is_some() { - create_genesis(miner.clone(), GENESIS_ZONE, &keystore.unwrap(), GENESIS_ZONE_DIFFICULTY); + let context_guard = context.lock().unwrap(); + if context_guard.get_blockchain().get_last_block().is_none() { + // If blockchain is empty, we are going to mine a Genesis block + create_genesis(miner.clone(), GENESIS_ZONE, &context_guard.get_keystore(), GENESIS_ZONE_DIFFICULTY); } } @@ -192,30 +183,3 @@ fn inline_style(s: &str) -> String { fn inline_script(s: &str) -> String { format!(r#""#, s) } - -fn test_blockchain() -> () { - let mut blockchain = Blockchain::new(42, 0); - println!("Blockchain with genesis block has been created"); - let keystore = Keystore::from_file("default.key", "").unwrap(); - - /*let mut block = create_transaction(&mut blockchain, keystore.clone(), "test.zz", vec!["AAAA IN 301:2925::1".to_owned()], vec!["testing".to_owned(), "example".to_owned()], 365); - - // Mining the nonce - block.mine(); - - // Our block is ready, we can print it and add to Blockchain - let s = serde_json::to_string(&block).unwrap(); - println!("Serialized block:\n{}", s); - blockchain.add_block(block); - println!("Second block added"); - - let block2: Block = serde_json::from_str(&s).unwrap(); - println!("DeSerialized block:\n{:?}", block2);*/ - - // Let's check if the blockchain is valid - if blockchain.check() { - println!("Blockchain is correct"); - } else { - println!("Blockchain is corrupted, aborting"); - } -} diff --git a/src/miner.rs b/src/miner.rs index 0e23e16..4f482b6 100644 --- a/src/miner.rs +++ b/src/miner.rs @@ -11,8 +11,8 @@ use num_cpus; pub struct Miner { context: Arc>, keystore: Keystore, - chain_id: u32, - version: u32, + chain_name: String, + version_flags: u32, transactions: Arc>>, last_block: Option, running: Arc, @@ -26,8 +26,8 @@ impl Miner { Miner { context: context.clone(), keystore: c.keystore.clone(), - chain_id: c.settings.chain_id, - version: c.settings.version, + chain_name: c.settings.chain_name.clone(), + version_flags: c.settings.version_flags, transactions: Arc::new(Mutex::new(Vec::new())), last_block: c.blockchain.blocks.last().cloned(), running: Arc::new(AtomicBool::new(false)), @@ -83,12 +83,12 @@ impl Miner { fn mine_internal(context: Arc>, transactions: Arc>>, mut transaction: Transaction, mining: Arc, cond_var: Arc) { let mut last_block_time = 0i64; - let mut chain_id = 0u32; - let mut version = 0u32; + let mut chain_name= String::new(); + let mut version_flags= 0u32; { let c = context.lock().unwrap(); - chain_id = c.settings.chain_id; - version = c.settings.version; + chain_name = c.settings.chain_name.clone(); + version_flags = c.settings.version_flags; } let block = { if transaction.signature.is_zero() { @@ -99,17 +99,17 @@ impl Miner { } // Get last block for mining - let last_block = { context.lock().unwrap().blockchain.blocks.last().cloned() }; + let last_block = { context.lock().unwrap().blockchain.get_last_block() }; match last_block { None => { println!("Mining genesis block"); // Creating a block with that signed transaction - Block::new(0, Utc::now().timestamp(), chain_id, version, Bytes::zero32(), Some(transaction.clone())) + Block::new(0, Utc::now().timestamp(), &chain_name, version_flags, Bytes::zero32(), Some(transaction.clone())) }, Some(block) => { last_block_time = block.timestamp; // Creating a block with that signed transaction - Block::new(block.index + 1, Utc::now().timestamp(), chain_id, version, block.hash.clone(), Some(transaction.clone())) + Block::new(block.index + 1, Utc::now().timestamp(), &chain_name, version_flags, block.hash.clone(), Some(transaction.clone())) }, } }; @@ -149,11 +149,6 @@ impl Miner { }); } } - - fn get_last_block(&self) -> Option { - let context = self.context.lock().unwrap(); - context.blockchain.blocks.last().cloned() - } } fn find_hash(digest: &mut dyn Digest, mut block: Block, prev_block_time: i64, running: Arc) -> Option { diff --git a/src/transaction.rs b/src/transaction.rs index bdd53d9..4bfb9eb 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -31,6 +31,13 @@ impl Transaction { Transaction { identity, method, data, pub_key, signature: Bytes::zero64() } } + pub fn from_json(json: &str) -> Option { + match serde_json::from_str(json) { + Ok(transaction) => Some(transaction), + Err(_) => None + } + } + pub fn set_signature(&mut self, hash: Bytes) { self.signature = hash; } @@ -39,6 +46,11 @@ impl Transaction { // Let it panic if something is not okay serde_json::to_vec(&self).unwrap() } + + pub fn to_string(&self) -> String { + // Let it panic if something is not okay + serde_json::to_string(&self).unwrap() + } } impl fmt::Debug for Transaction {