Wie funktioniert eine Kryptowährung bzw. eine Blockchain mit einem Proof of Work Mechanismus (Mining).
Github: https://github.com/HNP-Christopher-Rohde/hnp-example-blockchain-with-miner
Blockchain
Die Blockchain in diesem System ist eine auf Blöcken basierende Kette, die kryptografisch gesichert ist. Jeder Block enthält folgende Informationen:
Index: Die Position des Blocks in der Kette.
Zeitstempel: Der Zeitpunkt, zu dem der Block erstellt wurde (in Unix-Zeit).
Daten: Die im Block gespeicherten Informationen (in binärer Form).
Vorheriger Hash: Der Hash des vorhergehenden Blocks, der die Verbindung zu diesem sicherstellt.
Hash: Der eigene Hash des Blocks, der durch den Hashing-Algorithmus SHA-256 berechnet wird.
Nonce: Eine Zahl, die während des Minings variiert wird, um den Hash zu finden, der den Anforderungen der Schwierigkeit entspricht.
Die Blockchain wird mit einem Genesis-Block initialisiert. Jeder neue Block wird durch einen Mining-Prozess erstellt, der den Hash so anpasst, dass er den Schwierigkeitsanforderungen (Difficulty) entspricht, d. h. der Hash muss mit einer bestimmten Anzahl von Nullen beginnen. Die Schwierigkeit passt sich dynamisch an, basierend auf der Zeit, die für die Erstellung der letzten Blöcke benötigt wurde.
Validität der Blockchain:
Jeder Block in der Kette wird überprüft, um sicherzustellen, dass der Hash korrekt ist und der Hash des vorherigen Blocks mit dem gespeicherten previous_hash übereinstimmt.
Es wird außerdem geprüft, ob der Block die Schwierigkeit (Difficulty) erfüllt, bevor er zur Kette hinzugefügt wird.
Die Blockchain speichert die Blöcke auf der Festplatte (z. B. in der Datei blockchain.json) und bietet APIs für den Empfang und das Senden neuer Blöcke zwischen Peers.
Blockchain:
use serde::{Serialize, Deserialize}; use std::collections::VecDeque; use std::fs; use sha2::Digest; use hex; use std::time::{SystemTime, UNIX_EPOCH}; use reqwest; use warp::Filter; use log::{info, error}; use std::env; use std::sync::{Arc, Mutex}; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Block { pub index: u64, pub timestamp: u64, pub data: Vec<u8>, pub previous_hash: String, pub hash: String, pub nonce: u64, } impl Block { pub fn new(index: u64, data: Vec<u8>, previous_hash: String) -> Self { let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); let mut block = Block { index, timestamp, data, previous_hash, hash: String::new(), nonce: 0, }; block.hash = block.calculate_hash(); info!("Created new block: {:?}", block); block } pub fn calculate_hash(&self) -> String { let mut hasher = sha2::Sha256::new(); hasher.update(format!("{}{}{}{}{}", self.index, self.timestamp, hex::encode(&self.data), self.previous_hash, self.nonce)); let result = hasher.finalize(); hex::encode(result) } } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Blockchain { pub chain: VecDeque<Block>, pub difficulty: u32, pub adjustment_interval: u64, pub target_block_time: u64, pub last_difficulty_update: u64, } impl Blockchain { pub fn new(difficulty: u32, adjustment_interval: u64, target_block_time: u64) -> Self { let mut chain = VecDeque::new(); chain.push_back(create_genesis_block()); info!("Blockchain initialized with genesis block"); Blockchain { chain, difficulty, adjustment_interval, target_block_time, last_difficulty_update: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(), } } pub fn add_block(&mut self, data: Vec<u8>) { let previous_block = self.chain.back().unwrap(); let new_block = mine_block(previous_block, data, self.difficulty); info!("Adding new block: {:?}", new_block); self.chain.push_back(new_block); // Update difficulty if needed if (self.chain.len() as u64) % self.adjustment_interval == 0 { self.update_difficulty_based_on_time(); } if let Err(e) = self.save_to_file("blockchain.json") { error!("Failed to save blockchain after adding block: {}", e); } } pub fn is_valid(&self) -> bool { for i in 1..self.chain.len() { let current = &self.chain[i]; let previous = &self.chain[i - 1]; if current.hash != current.calculate_hash() { error!("Block {} has invalid hash", current.index); return false; } if current.previous_hash != previous.hash { error!("Block {} has invalid previous hash", current.index); return false; } if !meets_difficulty(¤t.hash, self.difficulty) { error!("Block {} does not meet difficulty", current.index); return false; } } true } pub fn save_to_file(&self, filename: &str) -> std::io::Result<()> { let serialized = serde_json::to_string(self) .expect("Serialization failed"); fs::write(filename, serialized).map_err(|e| { error!("Failed to write blockchain to file: {}", e); e })?; info!("Blockchain saved to file: {}", filename); Ok(()) } pub fn load_from_file(filename: &str) -> std::io::Result<Self> { match fs::read_to_string(filename) { Ok(data) if !data.is_empty() => { match serde_json::from_str::<Blockchain>(&data) { Ok(blockchain) => { info!("Blockchain loaded from file: {}", filename); Ok(blockchain) } Err(e) => { info!("Failed to deserialize blockchain from file, creating a new one: {}", e); let (difficulty, adjustment_interval, target_block_time) = load_config(); Ok(Blockchain::new(difficulty, adjustment_interval, target_block_time)) } } } _ => { info!("Blockchain file is empty or not found. Creating a new blockchain."); let (difficulty, adjustment_interval, target_block_time) = load_config(); Ok(Blockchain::new(difficulty, adjustment_interval, target_block_time)) } } } pub async fn broadcast_new_block(&self, block: &Block, peers: &[&str]) -> reqwest::Result<()> { for peer in peers { let url = format!("{}/new-block", peer); info!("Broadcasting new block to peer: {}", url); let result = reqwest::Client::new() .post(&url) .json(&block) .send() .await; match result { Ok(_) => info!("Successfully sent block to {}", url), Err(e) => error!("Failed to send block to {}: {:?}", url, e), } } Ok(()) }
pub fn add_block_from_request(&mut self, block: Block) -> bool { let previous_block = self.chain.back().unwrap(); info!("Received block: {:?}", block); info!("Last block in chain: {:?}", previous_block); // Additional validation for the timestamp if block.timestamp <= previous_block.timestamp { error!("Block timestamp is not valid"); return false; } let is_valid = block.index == previous_block.index + 1 && block.previous_hash == previous_block.hash && block.hash == block.calculate_hash() && meets_difficulty(&block.hash, self.difficulty); if is_valid { info!("Valid block received and added to chain"); self.chain.push_back(block); if let Err(e) = self.save_to_file("blockchain.json") { error!("Failed to save blockchain after adding block: {}", e); } } else { error!("Invalid block received: \n\ Index: {} \n\ Previous Hash: {} \n\ Expected Previous Hash: {} \n\ Hash: {} \n\ Calculated Hash: {} \n\ Difficulty Met: {}", block.index, block.previous_hash, previous_block.hash, block.hash, block.calculate_hash(), meets_difficulty(&block.hash, self.difficulty)); } is_valid } pub fn update_difficulty_based_on_time(&mut self) { let length = self.chain.len() as u64; info!("Chain length: {}", length); if length >= self.adjustment_interval { let interval_start_block = &self.chain[self.chain.len() - self.adjustment_interval as usize]; let interval_end_block = self.chain.back().unwrap(); let time_taken = interval_end_block.timestamp - interval_start_block.timestamp; let expected_time = self.adjustment_interval * self.target_block_time; info!("Time taken for the last {} blocks: {} seconds", self.adjustment_interval, time_taken); info!("Expected time for the last {} blocks: {} seconds", self.adjustment_interval, expected_time); if time_taken < expected_time / 2 { self.difficulty += 1; info!("Difficulty increased to {}", self.difficulty); } else if time_taken > expected_time * 2 { if self.difficulty > 1 { self.difficulty -= 1; info!("Difficulty decreased to {}", self.difficulty); } } else { info!("Difficulty remains at {}", self.difficulty); } // Update the last difficulty update timestamp self.last_difficulty_update = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); } else { info!("Not enough blocks to adjust difficulty."); } } pub fn update_difficulty(&mut self, new_difficulty: u32) { self.difficulty = new_difficulty; info!("Difficulty updated to {}", new_difficulty); if let Err(e) = self.save_to_file("blockchain.json") { error!("Failed to save blockchain after updating difficulty: {}", e); } } } pub fn create_genesis_block() -> Block { Block::new(0, "Genesis Block".as_bytes().to_vec(), "0".to_string()) } fn meets_difficulty(hash: &str, difficulty: u32) -> bool { let target = vec![0u8; difficulty as usize]; let hash_bytes = hex::decode(hash).expect("Hex decode failed"); hash_bytes.len() >= target.len() && hash_bytes.starts_with(&target) } fn mine_block(previous_block: &Block, data: Vec<u8>, difficulty: u32) -> Block { let mut new_block = Block::new(previous_block.index + 1, data.clone(), previous_block.hash.clone()); let mut attempt = 0; while !meets_difficulty(&new_block.hash, difficulty) { new_block.nonce += 1; new_block.hash = new_block.calculate_hash(); attempt += 1; if attempt % 100 == 0 { println!("Attempt {}: Trying hash: {}", attempt, new_block.hash); } } new_block } fn load_config() -> (u32, u64, u64) { use serde::Deserialize; #[derive(Deserialize)] struct Config { initial_difficulty: u32, adjustment_interval: u64, target_block_time: u64, } let data = fs::read_to_string("config.json").expect("Unable to read config file"); let config: Config = serde_json::from_str(&data).expect("Unable to parse config file"); (config.initial_difficulty, config.adjustment_interval, config.target_block_time) } #[tokio::main] async fn main() { env::set_var("RUST_LOG", "info"); env_logger::init(); let (initial_difficulty, adjustment_interval, target_block_time) = load_config(); let blockchain = Arc::new(Mutex::new( Blockchain::load_from_file("blockchain.json").unwrap_or_else(|_| Blockchain::new(initial_difficulty, adjustment_interval, target_block_time)) )); let blockchain_filter = warp::any().map(move || blockchain.clone()); let new_block = warp::post() .and(warp::path("new-block")) .and(warp::body::json()) .and(blockchain_filter.clone()) .map(|block: Block, blockchain: Arc<Mutex<Blockchain>>| { let mut blockchain = blockchain.lock().unwrap(); let response = if blockchain.add_block_from_request(block) { "Block added successfully" } else { "Invalid block" }; response }); let difficulty = warp::get() .and(warp::path("difficulty")) .map(move || format!("Difficulty: {}", initial_difficulty)); let set_difficulty = warp::post() .and(warp::path("set-difficulty")) .and(warp::body::json()) .and(blockchain_filter.clone()) .map(|_new_difficulty: u32, _blockchain: Arc<Mutex<Blockchain>>| { // Ignoring difficulty change here, as it's handled externally format!("Difficulty updated to {}", _new_difficulty) }); let last_block = warp::get() .and(warp::path("last-block")) .and(blockchain_filter.clone()) .map(|blockchain: Arc<Mutex<Blockchain>>| { let blockchain = blockchain.lock().unwrap(); let last_block = blockchain.chain.back().cloned().unwrap_or_else(|| create_genesis_block()); warp::reply::json(&last_block) }); let routes = new_block.or(difficulty).or(set_difficulty).or(last_block); info!("Starting server at http://127.0.0.1:8000"); warp::serve(routes).run(([127, 0, 0, 1], 8000)).await; }
Miner
Der Miner ist für die Erstellung neuer Blöcke in der Blockchain verantwortlich. Er arbeitet nach einem Proof-of-Work-Mechanismus, bei dem der Miner eine nonce-Nummer anpasst, bis ein gültiger Hash gefunden wird, der den Schwierigkeitsanforderungen entspricht.
Ablauf des Mining-Prozesses:
Der Miner fragt den letzten Block der Blockchain von einem Server ab.
Der Miner fragt die aktuelle Schwierigkeit vom Server ab.
Ein neuer Block wird erstellt, indem die Daten des vorherigen Blocks verwendet werden, sowie neue Transaktionsdaten.
Der Miner berechnet den Hash des neuen Blocks durch Anpassung der nonce, bis der Hash die Schwierigkeit erfüllt (d. h., der Hash beginnt mit einer bestimmten Anzahl von Nullen).
Sobald ein gültiger Block gefunden wurde, wird er an den Server gesendet, der ihn zur Blockchain hinzufügt.
Der Mining-Prozess läuft in einem Loop, sodass kontinuierlich nach neuen Blöcken gesucht wird, die der Blockchain hinzugefügt werden können.
use serde::{Serialize, Deserialize}; use sha2::{Sha256, Digest}; use std::time::{SystemTime, UNIX_EPOCH}; use reqwest; use hex; use anyhow::{Result, Context}; use tokio::task; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Block { pub index: u64, pub timestamp: u64, pub data: Vec<u8>, pub previous_hash: String, pub hash: String, pub nonce: u64, } impl Block { pub fn new(index: u64, data: Vec<u8>, previous_hash: String) -> Self { let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); let mut block = Block { index, timestamp, data, previous_hash, hash: String::new(), nonce: 0, }; block.hash = block.calculate_hash(); block } pub fn calculate_hash(&self) -> String { let mut hasher = Sha256::new(); hasher.update(format!("{}{}{}{}{}", self.index, self.timestamp, hex::encode(&self.data), self.previous_hash, self.nonce)); let result = hasher.finalize(); hex::encode(result) } } fn meets_difficulty(hash: &str, difficulty: u32) -> bool { let target = vec![0u8; difficulty as usize]; let hash_bytes = hex::decode(hash).expect("Hex decode failed"); hash_bytes.starts_with(&target) } async fn mine_block(previous_block: &Block, data: Vec<u8>, difficulty: u32) -> Block { let mut new_block = Block::new(previous_block.index + 1, data.clone(), previous_block.hash.clone()); // Parallel mining using multiple threads let mining_task = task::spawn_blocking(move || { let mut attempt = 0; while !meets_difficulty(&new_block.hash, difficulty) { new_block.nonce += 1; new_block.hash = new_block.calculate_hash(); attempt += 1; if attempt % 100 == 0 { println!("Attempt {}: Trying hash: {}", attempt, new_block.hash); } } new_block }); mining_task.await.unwrap() } async fn get_last_block_from_server() -> Result<Block> { let client = reqwest::Client::new(); let url = "http://localhost:8000/last-block"; let res = client.get(url) .send() .await .context("Failed to send request to get last block")?; let last_block: Block = res.json().await .context("Failed to parse last block")?; Ok(last_block) } async fn send_block_to_server(block: &Block) -> Result<()> { let client = reqwest::Client::new(); let url = "http://localhost:8000/new-block"; println!("Sending block: {:?}", block); let res = client.post(url) .json(block) .send() .await .context("Failed to send request to post new block")?; let status = res.status(); let body = res.text().await .context("Failed to read response text")?; if status.is_success() { println!("Block successfully sent to server."); } else { println!("Failed to send block to server: {} - {}", status, body); } Ok(()) }
async fn get_difficulty_from_server() -> Result<u32> { let client = reqwest::Client::new(); let url = "http://localhost:8000/difficulty"; let res = client.get(url) .send() .await .context("Failed to send request to get difficulty")?; let difficulty_str = res.text().await .context("Failed to read difficulty response")?; let difficulty = difficulty_str.trim_start_matches("Difficulty: ") .parse::<u32>() .context("Failed to parse difficulty")?; Ok(difficulty) } async fn display_difficulty() -> Result<()> { let difficulty = get_difficulty_from_server().await .context("Error retrieving difficulty from server")?; println!("Current Difficulty: {}", difficulty); Ok(()) } #[tokio::main] async fn main() -> Result<()> { loop { // Anzeige der aktuellen Schwierigkeit display_difficulty().await .context("Error displaying difficulty")?; // Hol die Schwierigkeit vom Server let difficulty = get_difficulty_from_server().await .context("Error retrieving difficulty from server")?; // Hol den letzten Block vom Server let previous_block = get_last_block_from_server().await .context("Error retrieving last block from server")?; let data = b"Block data".to_vec(); let new_block = mine_block(&previous_block, data, difficulty).await; send_block_to_server(&new_block).await .context("Error sending block to server")?; // Warte eine gewisse Zeit, bevor der nächste Block erstellt wird tokio::time::sleep(tokio::time::Duration::from_secs(0)).await; // Wartezeit erhöht } }
↩ Zurück zur Blogübersicht