Changed and refactored block and transaction structures. Moved signature from transaction scope to block.
This commit is contained in:
+27
-16
@@ -15,7 +15,7 @@ pub struct Block {
|
||||
pub index: u64,
|
||||
pub timestamp: i64,
|
||||
pub version: u32,
|
||||
pub difficulty: usize,
|
||||
pub difficulty: u32,
|
||||
pub random: u32,
|
||||
pub nonce: u64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -24,25 +24,30 @@ pub struct Block {
|
||||
pub prev_block_hash: Bytes,
|
||||
#[serde(default, skip_serializing_if = "Bytes::is_zero")]
|
||||
pub hash: Bytes,
|
||||
#[serde(default, skip_serializing_if = "Bytes::is_zero")]
|
||||
pub pub_key: Bytes,
|
||||
#[serde(default, skip_serializing_if = "Bytes::is_zero")]
|
||||
pub signature: Bytes,
|
||||
}
|
||||
|
||||
impl Block {
|
||||
pub fn new(index: u64, timestamp: i64, version: u32, prev_block_hash: Bytes, transaction: Option<Transaction>) -> Self {
|
||||
pub fn new(transaction: Option<Transaction>, pub_key: Bytes, prev_block_hash: Bytes) -> Self {
|
||||
Block {
|
||||
index,
|
||||
timestamp,
|
||||
version,
|
||||
// TODO make difficulty parameter
|
||||
difficulty: 20,
|
||||
index: 0,
|
||||
timestamp: 0,
|
||||
version: 0,
|
||||
difficulty: 0,
|
||||
random: 0,
|
||||
nonce: 0,
|
||||
transaction,
|
||||
prev_block_hash,
|
||||
hash: Bytes::default(),
|
||||
pub_key,
|
||||
signature: Bytes::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_all_params(index: u64, timestamp: i64, version: u32, difficulty: usize, random: u32, nonce: u64, prev_block_hash: Bytes, hash: Bytes, transaction: Option<Transaction>) -> Self {
|
||||
pub fn from_all_params(index: u64, timestamp: i64, version: u32, difficulty: u32, random: u32, nonce: u64, prev_block_hash: Bytes, hash: Bytes, pub_key: Bytes, signature: Bytes, transaction: Option<Transaction>) -> Self {
|
||||
Block {
|
||||
index,
|
||||
timestamp,
|
||||
@@ -53,18 +58,24 @@ impl Block {
|
||||
transaction,
|
||||
prev_block_hash,
|
||||
hash,
|
||||
pub_key,
|
||||
signature
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> Vec<u8> {
|
||||
Vec::from(serde_json::to_string(&self).unwrap().as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
use sqlite::{Connection, State, Statement};
|
||||
|
||||
use crate::{Block, Bytes, Keystore, Transaction};
|
||||
use crate::{Block, Bytes, Keystore, Transaction, hash_is_good};
|
||||
use crate::settings::Settings;
|
||||
#[allow(unused_imports)]
|
||||
use log::{trace, debug, info, warn, error};
|
||||
use std::collections::HashSet;
|
||||
use std::cell::RefCell;
|
||||
use chrono::Utc;
|
||||
use crate::blockchain::transaction::hash_identity;
|
||||
use crate::blockchain::blockchain::BlockQuality::*;
|
||||
use crate::blockchain::BLOCK_DIFFICULTY;
|
||||
|
||||
const DB_NAME: &str = "blockchain.db";
|
||||
|
||||
@@ -58,30 +62,19 @@ impl Blockchain {
|
||||
'nonce' INTEGER,
|
||||
'transaction' TEXT,
|
||||
'prev_block_hash' BINARY,
|
||||
'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, signature BINARY);
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_block(&mut self, block: Block) -> Result<(), &str> {
|
||||
match &self.last_block {
|
||||
None => {}
|
||||
Some(last_block) => {
|
||||
if last_block.index >= block.index && last_block.hash == block.hash {
|
||||
info!("Ignoring block {}, we already have it", block.index);
|
||||
return Err("Already have that block");
|
||||
}
|
||||
}
|
||||
}
|
||||
if !self.check_block(&block, &self.last_block) {
|
||||
warn!("Bad block found, ignoring:\n{:?}", &block);
|
||||
return Err("Bad block found, ignoring");
|
||||
}
|
||||
pub fn add_block(&mut self, block: Block) {
|
||||
info!("Adding block:\n{:?}", &block);
|
||||
self.blocks.push(block.clone());
|
||||
self.last_block = Some(block.clone());
|
||||
@@ -90,9 +83,9 @@ impl Blockchain {
|
||||
{
|
||||
// Adding block to DB
|
||||
let mut statement = self.db.prepare("INSERT INTO blocks (\
|
||||
id, timestamp, version, difficulty, random,\
|
||||
nonce, 'transaction', prev_block_hash, hash)\
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);").unwrap();
|
||||
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");
|
||||
@@ -107,28 +100,23 @@ impl Blockchain {
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
match &transaction {
|
||||
None => {
|
||||
Err("Error adding transaction!")
|
||||
}
|
||||
Some(transaction) => {
|
||||
self.add_transaction(transaction);
|
||||
Ok(())
|
||||
}
|
||||
if let Some(transaction) = transaction {
|
||||
self.add_transaction(&transaction);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_transaction(&mut self, t: &Transaction) {
|
||||
let mut statement = self.db.prepare("INSERT INTO transactions (identity, confirmation, method, data, pub_key, signature) VALUES (?, ?, ?, ?, ?, ?)").unwrap();
|
||||
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.bind(6, t.signature.as_bytes()).expect("Error in bind");
|
||||
statement.next().expect("Error adding transaction to DB");
|
||||
}
|
||||
|
||||
@@ -161,7 +149,7 @@ impl Blockchain {
|
||||
if domain.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let identity_hash = Transaction::hash_identity(domain);
|
||||
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();
|
||||
statement.bind(1, identity_hash.as_bytes()).expect("Error in bind");
|
||||
while let State::Row = statement.next().unwrap() {
|
||||
@@ -189,7 +177,7 @@ impl Blockchain {
|
||||
}
|
||||
|
||||
// Checking for existing zone in DB
|
||||
let identity_hash = Transaction::hash_identity(zone);
|
||||
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();
|
||||
statement.bind(1, identity_hash.as_bytes()).expect("Error in bind");
|
||||
while let State::Row = statement.next().unwrap() {
|
||||
@@ -204,7 +192,7 @@ impl Blockchain {
|
||||
if domain.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let identity_hash = Transaction::hash_identity(domain);
|
||||
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();
|
||||
statement.bind(1, identity_hash.as_bytes()).expect("Error in bind");
|
||||
@@ -214,10 +202,9 @@ impl Blockchain {
|
||||
let method = statement.read::<String>(3).unwrap();
|
||||
let data = statement.read::<String>(4).unwrap();
|
||||
let pub_key = Bytes::from_bytes(statement.read::<Vec<u8>>(5).unwrap().as_slice());
|
||||
let signature = Bytes::from_bytes(statement.read::<Vec<u8>>(6).unwrap().as_slice());
|
||||
let transaction = Transaction { identity, confirmation, method, data, pub_key, signature };
|
||||
let transaction = Transaction { identity, confirmation, method, data, pub_key };
|
||||
debug!("Found transaction for domain {}: {:?}", domain, &transaction);
|
||||
if transaction.check_for(domain) {
|
||||
if transaction.check_identity(domain) {
|
||||
return Some(transaction);
|
||||
}
|
||||
}
|
||||
@@ -254,75 +241,97 @@ impl Blockchain {
|
||||
}
|
||||
}
|
||||
|
||||
/*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);
|
||||
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;
|
||||
}
|
||||
true
|
||||
}*/
|
||||
|
||||
fn check_block(&self, block: &Block, prev_block: &Option<Block>) -> bool {
|
||||
if !check_block_hash(block) {
|
||||
warn!("{:?} has wrong hash! Ignoring!", &block);
|
||||
return false;
|
||||
if !hash_is_good(block.hash.as_bytes(), BLOCK_DIFFICULTY as usize) {
|
||||
warn!("Ignoring block with low difficulty:\n{:?}", &block);
|
||||
return Bad;
|
||||
}
|
||||
// TODO make transaction not Optional
|
||||
let transaction = block.transaction.as_ref().unwrap();
|
||||
if !check_transaction_signature(&transaction) {
|
||||
warn!("{:?} has wrong signature! Ignoring block!", &transaction);
|
||||
return false;
|
||||
if !hash_is_good(block.hash.as_bytes(), block.difficulty as usize) {
|
||||
warn!("Ignoring block with low difficulty:\n{:?}", &block);
|
||||
return Bad;
|
||||
}
|
||||
match prev_block {
|
||||
match &self.last_block {
|
||||
None => {
|
||||
if block.index != 0 {
|
||||
return false;
|
||||
if !block.is_genesis() {
|
||||
return Future;
|
||||
}
|
||||
|
||||
if self.origin.is_zero() && block.index > 0 {
|
||||
panic!("Error adding block {} without origin! Please, fill in origin in config!", block.index);
|
||||
if !self.origin.is_zero() && block.hash != self.origin {
|
||||
warn!("Mining gave us a bad block:\n{:?}", &block);
|
||||
return Bad;
|
||||
}
|
||||
|
||||
self.origin.is_zero() || block.hash.eq(&self.origin)
|
||||
}
|
||||
Some(prev) => {
|
||||
if block.index != prev.index + 1 {
|
||||
info!("Discarding block with index {} as not needed now", block.index);
|
||||
return false;
|
||||
Some(last_block) => {
|
||||
if block.timestamp < last_block.timestamp && block.index > last_block.index {
|
||||
warn!("Ignoring block with timestamp/index collision:\n{:?}", &block);
|
||||
return Bad;
|
||||
}
|
||||
if last_block.index + 1 < block.index {
|
||||
warn!("Got block from the future");
|
||||
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 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;
|
||||
}
|
||||
block.prev_block_hash.eq(&prev.hash)
|
||||
}
|
||||
}
|
||||
if !check_block_hash(block) {
|
||||
warn!("Block {:?} has wrong hash! Ignoring!", &block);
|
||||
return Bad;
|
||||
}
|
||||
if !check_block_signature(&block) {
|
||||
warn!("Block {:?} has wrong signature! Ignoring!", &block);
|
||||
return Bad;
|
||||
}
|
||||
|
||||
Good
|
||||
}
|
||||
|
||||
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 version = statement.read::<i64>(2).unwrap() as u32;
|
||||
let difficulty = statement.read::<i64>(3).unwrap() as usize;
|
||||
let difficulty = statement.read::<i64>(3).unwrap() as u32;
|
||||
let random = statement.read::<i64>(4).unwrap() as u32;
|
||||
let nonce = statement.read::<i64>(5).unwrap() as u64;
|
||||
let transaction = Transaction::from_json(&statement.read::<String>(6).unwrap());
|
||||
let prev_block_hash = Bytes::from_bytes(statement.read::<Vec<u8>>(7).unwrap().as_slice());
|
||||
let hash = Bytes::from_bytes(statement.read::<Vec<u8>>(8).unwrap().as_slice());
|
||||
Some(Block::from_all_params(index, timestamp, version, difficulty, random, nonce, prev_block_hash, hash, transaction))
|
||||
let pub_key = Bytes::from_bytes(statement.read::<Vec<u8>>(9).unwrap().as_slice());
|
||||
let signature = Bytes::from_bytes(statement.read::<Vec<u8>>(10).unwrap().as_slice());
|
||||
Some(Block::from_all_params(index, timestamp, version, difficulty, random, nonce, prev_block_hash, hash, pub_key, signature, transaction))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum BlockQuality {
|
||||
Good,
|
||||
Twin,
|
||||
Future,
|
||||
Bad,
|
||||
Fork
|
||||
}
|
||||
|
||||
pub fn check_block_hash(block: &Block) -> bool {
|
||||
let mut copy: Block = block.clone();
|
||||
copy.hash = Bytes::default();
|
||||
copy.signature = Bytes::default();
|
||||
let data = serde_json::to_string(©).unwrap();
|
||||
Block::hash(data.as_bytes()) == block.hash
|
||||
crate::blockchain::block::hash(data.as_bytes()) == block.hash
|
||||
}
|
||||
|
||||
pub fn check_transaction_signature(transaction: &Transaction) -> bool {
|
||||
let mut copy = transaction.clone();
|
||||
pub fn check_block_signature(block: &Block) -> bool {
|
||||
let mut copy = block.clone();
|
||||
copy.signature = Bytes::zero64();
|
||||
let data = copy.get_bytes();
|
||||
Keystore::check(data.as_slice(), copy.pub_key.as_bytes(), transaction.signature.as_bytes())
|
||||
let data = serde_json::to_string(©).unwrap();
|
||||
Keystore::check(data.as_bytes(), copy.pub_key.as_bytes(), block.signature.as_bytes())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
pub const BLOCK_DIFFICULTY: u32 = 24;
|
||||
pub const CHAIN_VERSION: u32 = 1;
|
||||
@@ -2,7 +2,9 @@ pub mod transaction;
|
||||
pub mod block;
|
||||
pub mod blockchain;
|
||||
pub mod filter;
|
||||
pub mod constants;
|
||||
|
||||
pub use transaction::Transaction;
|
||||
pub use block::Block;
|
||||
pub use blockchain::Blockchain;
|
||||
pub use blockchain::Blockchain;
|
||||
pub use constants::*;
|
||||
@@ -16,18 +16,17 @@ pub struct Transaction {
|
||||
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 hash = Self::hash_identity(&identity);
|
||||
let confirmation = Self::hash_with_key(&identity, &pub_key);
|
||||
let hash = hash_identity(&identity, None);
|
||||
let confirmation = hash_identity(&identity, Some(&pub_key));
|
||||
return Self::new(hash, confirmation, method, data, pub_key);
|
||||
}
|
||||
|
||||
pub fn new(identity: Bytes, confirmation: Bytes, method: String, data: String, pub_key: Bytes) -> Self {
|
||||
Transaction { identity, confirmation, method, data, pub_key, signature: Bytes::zero64() }
|
||||
Transaction { identity, confirmation, method, data, pub_key }
|
||||
}
|
||||
|
||||
pub fn from_json(json: &str) -> Option<Self> {
|
||||
@@ -37,10 +36,6 @@ impl Transaction {
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
@@ -51,26 +46,9 @@ impl Transaction {
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn hash_with_key(identity: &str, key: &Bytes) -> Bytes {
|
||||
let mut buf: [u8; 32] = [0; 32];
|
||||
let mut digest = Sha256::new();
|
||||
digest.input_str(identity);
|
||||
digest.input(key.as_bytes());
|
||||
digest.result(&mut buf);
|
||||
Bytes::from_bytes(&buf)
|
||||
}
|
||||
|
||||
pub fn check_for(&self, domain: &str) -> bool {
|
||||
let hash = Self::hash_identity(&domain);
|
||||
let confirmation = Self::hash_with_key(&domain, &self.pub_key);
|
||||
pub fn check_identity(&self, domain: &str) -> bool {
|
||||
let hash = hash_identity(&domain, None);
|
||||
let confirmation = hash_identity(&domain, Some(&self.pub_key));
|
||||
self.identity.eq(&hash) && self.confirmation.eq(&confirmation)
|
||||
}
|
||||
}
|
||||
@@ -83,20 +61,29 @@ impl fmt::Debug for Transaction {
|
||||
.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", 6).unwrap();
|
||||
let mut structure = serializer.serialize_struct("Transaction", 4).unwrap();
|
||||
structure.serialize_field("identity", &self.identity)?;
|
||||
structure.serialize_field("confirmation", &self.confirmation)?;
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hash_identity(identity: &str, key: Option<&Bytes>) -> Bytes {
|
||||
let mut buf: [u8; 32] = [0; 32];
|
||||
let mut digest = Sha256::new();
|
||||
digest.input_str(identity);
|
||||
if let Some(key) = key {
|
||||
digest.input(key.as_bytes());
|
||||
}
|
||||
digest.result(&mut buf);
|
||||
Bytes::from_bytes(&buf)
|
||||
}
|
||||
Reference in New Issue
Block a user