Implemented P2P network protocol. Refactored project structure.
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
extern crate num_bigint;
|
||||
extern crate num_traits;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use chrono::Utc;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use num_bigint::BigUint;
|
||||
use num_traits::One;
|
||||
use crypto::sha2::Sha256;
|
||||
use crypto::digest::Digest;
|
||||
use crate::keys::Bytes;
|
||||
use crate::Transaction;
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
|
||||
pub struct Block {
|
||||
pub index: u64,
|
||||
pub timestamp: i64,
|
||||
pub chain_name: String,
|
||||
pub version_flags: u32,
|
||||
pub difficulty: usize,
|
||||
pub random: u32,
|
||||
pub nonce: u64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub transaction: Option<Transaction>,
|
||||
#[serde(default, skip_serializing_if = "Bytes::is_zero")]
|
||||
pub prev_block_hash: Bytes,
|
||||
#[serde(default, skip_serializing_if = "Bytes::is_zero")]
|
||||
pub hash: Bytes,
|
||||
}
|
||||
|
||||
impl Block {
|
||||
pub fn new(index: u64, timestamp: i64, chain_name: &str, version_flags: u32, prev_block_hash: Bytes, transaction: Option<Transaction>) -> Self {
|
||||
Block {
|
||||
index,
|
||||
timestamp,
|
||||
chain_name: chain_name.to_owned(),
|
||||
version_flags,
|
||||
// TODO make difficulty parameter
|
||||
difficulty: 20,
|
||||
random: 0,
|
||||
nonce: 0,
|
||||
transaction,
|
||||
prev_block_hash,
|
||||
hash: Bytes::default(),
|
||||
}
|
||||
}
|
||||
|
||||
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<Transaction>) -> 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();
|
||||
digest.input(data);
|
||||
digest.result(&mut buf);
|
||||
Bytes::new(buf.to_vec())
|
||||
}
|
||||
|
||||
pub fn is_genesis(&self) -> bool {
|
||||
self.index == 0 && self.transaction.is_none() && self.prev_block_hash == Bytes::default()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
use chrono::Utc;
|
||||
use sqlite::{Connection, Error, Readable, State, Statement};
|
||||
|
||||
use crate::{Block, Bytes, Keystore, Transaction};
|
||||
|
||||
const DB_NAME: &str = "blockchain.db";
|
||||
|
||||
pub struct Blockchain {
|
||||
pub chain_name: String,
|
||||
pub version_flags: u32,
|
||||
pub blocks: Vec<Block>,
|
||||
last_block: Option<Block>,
|
||||
db: Connection,
|
||||
}
|
||||
|
||||
impl Blockchain {
|
||||
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
|
||||
}
|
||||
|
||||
/// 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_from_statement(&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);
|
||||
CREATE TABLE transactions (id INTEGER PRIMARY KEY AUTOINCREMENT, identity BINARY, method TEXT, data TEXT, pub_key BINARY, signature BINARY);
|
||||
CREATE INDEX ids ON transactions (identity);"
|
||||
).expect("Error creating blocks table");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_block(&mut self, block: Block) {
|
||||
if self.check_block(&block, &self.last_block) {
|
||||
println!("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, 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.as_ref() as &str);
|
||||
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 &transaction {
|
||||
None => { statement.bind(8, ""); }
|
||||
Some(transaction) => {
|
||||
statement.bind(8, transaction.to_string().as_ref() as &str);
|
||||
}
|
||||
}
|
||||
statement.bind(9, block.prev_block_hash.as_bytes());
|
||||
statement.bind(10, block.hash.as_bytes());
|
||||
statement.next().expect("Error adding block to DB");
|
||||
}
|
||||
|
||||
match &transaction {
|
||||
None => {}
|
||||
Some(transaction) => {
|
||||
self.add_transaction(transaction);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("Bad block found, ignoring:\n{:?}", &block);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_transaction(&mut self, t: &Transaction) {
|
||||
let mut statement = self.db.prepare("INSERT INTO transactions (identity, method, data, pub_key, signature) VALUES (?, ?, ?, ?, ?)").unwrap();
|
||||
statement.bind(1, t.identity.as_bytes());
|
||||
statement.bind(2, t.method.as_ref() as &str);
|
||||
statement.bind(3, t.data.as_ref() as &str);
|
||||
statement.bind(4, t.pub_key.as_bytes());
|
||||
statement.bind(5, t.signature.as_bytes());
|
||||
statement.next().expect("Error adding transaction to DB");
|
||||
}
|
||||
|
||||
pub fn get_block(&self, index: u64) -> Option<Block> {
|
||||
match self.db.prepare("SELECT * FROM blocks WHERE id=? LIMIT 1;") {
|
||||
Ok(mut statement) => {
|
||||
statement.bind(1, index as i64);
|
||||
while statement.next().unwrap() == State::Row {
|
||||
return match Self::get_block_from_statement(&mut statement) {
|
||||
None => {
|
||||
println!("Something wrong with block in DB!");
|
||||
None
|
||||
}
|
||||
Some(block) => {
|
||||
println!("Loaded block: {:?}", &block);
|
||||
Some(block)
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
Err(_) => {
|
||||
println!("Can't find block {}", index);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_domain_available(&self, domain: &str, keystore: &Keystore) -> bool {
|
||||
if domain.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let identity_hash = Transaction::hash_identity(domain);
|
||||
let mut statement = self.db.prepare("SELECT pub_key FROM transactions WHERE identity = ? ORDER BY id DESC LIMIT 1;").unwrap();
|
||||
statement.bind(1, identity_hash.as_bytes());
|
||||
while let State::Row = statement.next().unwrap() {
|
||||
let pub_key = Bytes::from_bytes(statement.read::<Vec<u8>>(0).unwrap().as_slice());
|
||||
if !pub_key.eq(&keystore.get_public()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let parts: Vec<&str> = domain.rsplitn(2, ".").collect();
|
||||
if parts.len() > 1 {
|
||||
// We do not support third level domains
|
||||
if parts.last().unwrap().contains(".") {
|
||||
return false;
|
||||
}
|
||||
// Checking for available zone, for this domain
|
||||
let identity_hash = Transaction::hash_identity(parts.first().unwrap());
|
||||
let mut statement = self.db.prepare("SELECT identity FROM transactions WHERE identity = ? ORDER BY id DESC LIMIT 1;").unwrap();
|
||||
statement.bind(1, identity_hash.as_bytes());
|
||||
while let State::Row = statement.next().unwrap() {
|
||||
// If there is such a zone
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn last_block(&self) -> Option<Block> {
|
||||
self.last_block.clone()
|
||||
}
|
||||
|
||||
pub fn height(&self) -> u64 {
|
||||
match self.last_block {
|
||||
None => { 0u64 }
|
||||
Some(ref block) => {
|
||||
block.index
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*pub fn check(&self) -> bool {
|
||||
let mut prev_block = None;
|
||||
for block in self.blocks.iter() {
|
||||
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 {
|
||||
// TODO check if it is already stored, or its height is more than we need
|
||||
if !Self::check_block_hash(block) {
|
||||
return false;
|
||||
}
|
||||
if prev_block.is_none() {
|
||||
return true;
|
||||
}
|
||||
|
||||
return block.prev_block_hash == prev_block.as_ref().unwrap().hash;
|
||||
}
|
||||
|
||||
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();
|
||||
let chain_name = statement.read::<String>(2).unwrap();
|
||||
let version_flags = statement.read::<i64>(3).unwrap() as u32;
|
||||
let difficulty = statement.read::<i64>(4).unwrap() as usize;
|
||||
let random = statement.read::<i64>(5).unwrap() as u32;
|
||||
let nonce = statement.read::<i64>(6).unwrap() as u64;
|
||||
let transaction = Transaction::from_json(&statement.read::<String>(7).unwrap());
|
||||
let prev_block_hash = Bytes::from_bytes(statement.read::<Vec<u8>>(8).unwrap().as_slice());
|
||||
let hash = Bytes::from_bytes(statement.read::<Vec<u8>>(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 check_block_hash(block: &Block) -> bool {
|
||||
// We need to clear Hash value to rehash it without it for check :(
|
||||
let mut copy: Block = block.clone();
|
||||
copy.hash = Bytes::default();
|
||||
let data = serde_json::to_string(©).unwrap();
|
||||
Block::hash(data.as_bytes()) == block.hash
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
pub mod transaction;
|
||||
pub mod block;
|
||||
pub mod blockchain;
|
||||
|
||||
pub use transaction::Transaction;
|
||||
pub use block::Block;
|
||||
pub use blockchain::Blockchain;
|
||||
@@ -0,0 +1,84 @@
|
||||
use crate::keys::*;
|
||||
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
|
||||
use serde::{Serialize, Deserialize, Serializer};
|
||||
use serde::ser::SerializeStruct;
|
||||
use std::fmt;
|
||||
use crypto::sha2::Sha256;
|
||||
use crypto::digest::Digest;
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
pub struct Transaction {
|
||||
pub identity: Bytes,
|
||||
pub method: String,
|
||||
pub data: String,
|
||||
pub pub_key: Bytes,
|
||||
pub signature: Bytes,
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
pub fn from_str(identity: String, method: String, data: String, pub_key: Bytes) -> Self {
|
||||
let bytes = Self::hash_identity(&identity);
|
||||
return Self::new(bytes, method, data, pub_key);
|
||||
}
|
||||
|
||||
pub fn new(identity: Bytes, method: String, data: String, pub_key: Bytes) -> Self {
|
||||
Transaction { identity, method, data, pub_key, signature: Bytes::zero64() }
|
||||
}
|
||||
|
||||
pub fn from_json(json: &str) -> Option<Self> {
|
||||
match serde_json::from_str(json) {
|
||||
Ok(transaction) => Some(transaction),
|
||||
Err(_) => None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_signature(&mut self, hash: Bytes) {
|
||||
self.signature = hash;
|
||||
}
|
||||
|
||||
pub fn get_bytes(&self) -> Vec<u8> {
|
||||
// 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()
|
||||
}
|
||||
|
||||
pub fn hash_identity(identity: &str) -> Bytes {
|
||||
let mut buf: [u8; 32] = [0; 32];
|
||||
let mut digest = Sha256::new();
|
||||
digest.input_str(identity);
|
||||
digest.result(&mut buf);
|
||||
Bytes::from_bytes(&buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Transaction {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.debug_struct("Transaction")
|
||||
.field("identity", &self.identity)
|
||||
.field("method", &self.method)
|
||||
.field("data", &self.data)
|
||||
.field("pub", &&self.pub_key)
|
||||
.field("sign", &&self.signature)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Transaction {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> where
|
||||
S: Serializer {
|
||||
let mut structure = serializer.serialize_struct("Transaction", 3).unwrap();
|
||||
structure.serialize_field("identity", &self.identity);
|
||||
structure.serialize_field("method", &self.method);
|
||||
structure.serialize_field("data", &self.data);
|
||||
structure.serialize_field("pub_key", &self.pub_key);
|
||||
structure.serialize_field("signature", &self.signature);
|
||||
structure.end()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user