Skip to content

Commit

Permalink
fifth video - transactions and outputs
Browse files Browse the repository at this point in the history
  • Loading branch information
encody committed Feb 21, 2019
1 parent e23aac3 commit bc663ec
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 56 deletions.
23 changes: 14 additions & 9 deletions src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ use super::*;
pub struct Block {
pub index: u32,
pub timestamp: u128,
pub hash: BlockHash,
pub prev_block_hash: BlockHash,
pub hash: Hash,
pub prev_block_hash: Hash,
pub nonce: u64,
pub payload: String,
pub transactions: Vec<Transaction>,
pub difficulty: u128,
}

Expand All @@ -17,21 +17,21 @@ impl Debug for Block {
&self.index,
&hex::encode(&self.hash),
&self.timestamp,
&self.payload,
&self.transactions.len(),
&self.nonce,
)
}
}

impl Block {
pub fn new (index: u32, timestamp: u128, prev_block_hash: BlockHash, nonce: u64, payload: String, difficulty: u128) -> Self {
pub fn new (index: u32, timestamp: u128, prev_block_hash: Hash, transactions: Vec<Transaction>, difficulty: u128) -> Self {
Block {
index,
timestamp,
hash: vec![0; 32],
prev_block_hash,
nonce,
payload,
nonce: 0,
transactions,
difficulty,
}
}
Expand All @@ -56,13 +56,18 @@ impl Hashable for Block {
bytes.extend(&u128_bytes(&self.timestamp));
bytes.extend(&self.prev_block_hash);
bytes.extend(&u64_bytes(&self.nonce));
bytes.extend(self.payload.as_bytes());
bytes.extend(
self.transactions
.iter()
.flat_map(|transaction| transaction.bytes())
.collect::<Vec<u8>>()
);
bytes.extend(&u128_bytes(&self.difficulty));

bytes
}
}

pub fn check_difficulty (hash: &BlockHash, difficulty: u128) -> bool {
pub fn check_difficulty (hash: &Hash, difficulty: u128) -> bool {
difficulty > difficulty_bytes_as_u128(&hash)
}
111 changes: 85 additions & 26 deletions src/blockchain.rs
Original file line number Diff line number Diff line change
@@ -1,40 +1,99 @@
use super::*;
use std::collections::HashSet;

#[derive(Debug)]
pub enum BlockValidationErr {
MismatchedIndex,
InvalidHash,
AchronologicalTimestamp,
MismatchedPreviousHash,
InvalidGenesisBlockFormat,
InvalidInput,
InsufficientInputValue,
InvalidCoinbaseTransaction,
}

pub struct Blockchain {
pub blocks: Vec<Block>,
unspent_outputs: HashSet<Hash>,
}

impl Blockchain {
pub fn verify (&self) -> bool {
for (i, block) in self.blocks.iter().enumerate() {
if block.index != i as u32 {
println!("Index mismatch {} != {}",
&block.index,
&i,
);
return false;
} else if !block::check_difficulty(&block.hash(), block.difficulty) {
println!("Difficulty fail");
return false;
} else if i != 0 {
// Not genesis block
let prev_block = &self.blocks[i - 1];
if block.timestamp <= prev_block.timestamp {
println!("Time did not increase");
return false;
} else if block.prev_block_hash != prev_block.hash {
println!("Hash mismatch");
return false;
pub fn new () -> Self {
Blockchain {
blocks: vec![],
unspent_outputs: HashSet::new(),
}
}

pub fn update_with_block (&mut self, block: Block) -> Result<(), BlockValidationErr> {
let i = self.blocks.len();

if block.index != i as u32 {
return Err(BlockValidationErr::MismatchedIndex);
} else if !block::check_difficulty(&block.hash(), block.difficulty) {
return Err(BlockValidationErr::InvalidHash);
} else if i != 0 {
// Not genesis block
let prev_block = &self.blocks[i - 1];
if block.timestamp <= prev_block.timestamp {
return Err(BlockValidationErr::AchronologicalTimestamp);
} else if block.prev_block_hash != prev_block.hash {
return Err(BlockValidationErr::MismatchedPreviousHash);
}
} else {
// Genesis block
if block.prev_block_hash != vec![0; 32] {
return Err(BlockValidationErr::InvalidGenesisBlockFormat);
}
}

if let Some((coinbase, transactions)) = block.transactions.split_first() {
if !coinbase.is_coinbase() {
return Err(BlockValidationErr::InvalidCoinbaseTransaction);
}

let mut block_spent: HashSet<Hash> = HashSet::new();
let mut block_created: HashSet<Hash> = HashSet::new();
let mut total_fee = 0;

for transaction in transactions {
let input_hashes = transaction.input_hashes();

if
!(&input_hashes - &self.unspent_outputs).is_empty() ||
!(&input_hashes & &block_spent).is_empty()
{
return Err(BlockValidationErr::InvalidInput);
}
} else {
// Genesis block
if block.prev_block_hash != vec![0; 32] {
println!("Genesis block prev_block_hash invalid");
return false;

let input_value = transaction.input_value();
let output_value = transaction.output_value();

if output_value > input_value {
return Err(BlockValidationErr::InsufficientInputValue);
}

let fee = input_value - output_value;

total_fee += fee;

block_spent.extend(input_hashes);
block_created.extend(transaction.output_hashes());
}

if coinbase.output_value() < total_fee {
return Err(BlockValidationErr::InvalidCoinbaseTransaction);
} else {
block_created.extend(coinbase.output_hashes());
}

self.unspent_outputs.retain(|output| !block_spent.contains(output));
self.unspent_outputs.extend(block_created);
}

true
self.blocks.push(block);

Ok(())
}
}
4 changes: 3 additions & 1 deletion src/hashable.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use super::*;

pub trait Hashable {
fn bytes (&self) -> Vec<u8>;

fn hash (&self) -> Vec<u8> {
fn hash (&self) -> Hash {
crypto_hash::digest(crypto_hash::Algorithm::SHA256, &self.bytes())
}
}
5 changes: 4 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
type BlockHash = Vec<u8>;
type Hash = Vec<u8>;
type Address = String;

// Credit: https://stackoverflow.com/a/44378174/2773837
use std::time::{ SystemTime, UNIX_EPOCH };
Expand Down Expand Up @@ -84,3 +85,5 @@ mod hashable;
pub use crate::hashable::Hashable;
mod blockchain;
pub use crate::blockchain::Blockchain;
pub mod transaction;
pub use crate::transaction::Transaction;
69 changes: 50 additions & 19 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,64 @@ use blockchainlib::*;
fn main () {
let difficulty = 0x000fffffffffffffffffffffffffffff;

let mut block = Block::new(0, now(), vec![0; 32], 0, "Genesis block!".to_owned(), difficulty);
let mut genesis_block = Block::new(0, now(), vec![0; 32], vec![
Transaction {
inputs: vec![ ],
outputs: vec![
transaction::Output {
to_addr: "Alice".to_owned(),
value: 50,
},
transaction::Output {
to_addr: "Bob".to_owned(),
value: 7,
},
],
},
], difficulty);

block.mine();
println!("Mined genesis block {:?}", &block);

let mut last_hash = block.hash.clone();
genesis_block.mine();

let mut blockchain = Blockchain {
blocks: vec![block],
};
println!("Mined genesis block {:?}", &genesis_block);

println!("Verify: {}", &blockchain.verify());
let mut last_hash = genesis_block.hash.clone();

for i in 1..=10 {
let mut block = Block::new(i, now(), last_hash, 0, "Another block".to_owned(), difficulty);
let mut blockchain = Blockchain::new();

block.mine();
println!("Mined block {:?}", &block);
blockchain.update_with_block(genesis_block).expect("Failed to add genesis block");

last_hash = block.hash.clone();
let mut block = Block::new(1, now(), last_hash, vec![
Transaction {
inputs: vec![ ],
outputs: vec![
transaction::Output {
to_addr: "Chris".to_owned(),
value: 536,
},
],
},
Transaction {
inputs: vec![
blockchain.blocks[0].transactions[0].outputs[0].clone(),
],
outputs: vec![
transaction::Output {
to_addr: "Alice".to_owned(),
value: 360,
},
transaction::Output {
to_addr: "Bob".to_owned(),
value: 12,
},
],
},
], difficulty);

blockchain.blocks.push(block);
block.mine();

println!("Verify: {}", &blockchain.verify());
}
println!("Mined block {:?}", &block);

blockchain.blocks[3].prev_block_hash[18] = 8;
last_hash = block.hash.clone();

println!("Verify: {}", &blockchain.verify());
blockchain.update_with_block(block).expect("Failed to add block");
}
80 changes: 80 additions & 0 deletions src/transaction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use super::*;
use std::collections::HashSet;

#[derive(Clone)]
pub struct Output {
pub to_addr: Address,
pub value: u64,
}

impl Hashable for Output {
fn bytes (&self) -> Vec<u8> {
let mut bytes = vec![];

bytes.extend(self.to_addr.as_bytes());
bytes.extend(&u64_bytes(&self.value));

bytes
}
}

pub struct Transaction {
pub inputs: Vec<Output>,
pub outputs: Vec<Output>,
}

impl Transaction {
pub fn input_value (&self) -> u64 {
self.inputs
.iter()
.map(|input| input.value)
.sum()
}

pub fn output_value (&self) -> u64 {
self.outputs
.iter()
.map(|output| output.value)
.sum()
}

pub fn input_hashes (&self) -> HashSet<Hash> {
self.inputs
.iter()
.map(|input| input.hash())
.collect::<HashSet<Hash>>()
}

pub fn output_hashes (&self) -> HashSet<Hash> {
self.outputs
.iter()
.map(|output| output.hash())
.collect::<HashSet<Hash>>()
}

pub fn is_coinbase (&self) -> bool {
self.inputs.len() == 0
}
}

impl Hashable for Transaction {
fn bytes (&self) -> Vec<u8> {
let mut bytes = vec![];

bytes.extend(
self.inputs
.iter()
.flat_map(|input| input.bytes())
.collect::<Vec<u8>>()
);

bytes.extend(
self.outputs
.iter()
.flat_map(|output| output.bytes())
.collect::<Vec<u8>>()
);

bytes
}
}

0 comments on commit bc663ec

Please sign in to comment.