Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
229: Building a Simple Blockchain in Rust
A few years ago, I worked with a developer named Marcus who was building a high-stakes audit log for a legacy banking system. He initially used a standard SQL table, thinking that restrictive permissions would keep the data safe. Then, during a security audit, the team discovered that a database administrator had manually updated a few transaction records to cover up a series of errors. Because the records were just rows in a table, there was no way to prove the data had been altered after the fact. Marcus realized that he didn't need a database; he needed a way to make the data computationally expensive to fake. That's when he dove into the world of cryptographic chaining.
At its core, a blockchain is just a distributed ledger, but for our purposes today, think of it as a linked list where the "link" is a cryptographic hash of the previous element. If you change a single bit in block one, its hash changes. Because block two contains that hash, block two's hash now changes, and so on, creating a domino effect that makes tampering immediately obvious.
Defining the Immutable Block Structure
In Rust, we want our block to be a self-contained unit of data. We need to track the index, a timestamp, the actual payload (the transaction or log), the hash of the previous block, and the hash of the current block. I usually recommend using the sha2 crate for this because it's the industry standard for SHA-256 hashing.
use sha2::{Sha256, Digest};
use std::time::SystemTime;
#[derive(Debug, Clone)]
pub struct Block {
pub index: u32,
pub timestamp: u64,
pub data: String,
pub previous_hash: String,
pub hash: String,
}
impl Block {
pub fn new(index: u32, data: String, previous_hash: String) -> Self {
let timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("Time went backwards")
.as_secs();
let mut block = Block {
index,
timestamp,
data,
previous_hash,
hash: String::new(),
};
block.hash = block.calculate_hash();
block
}
pub fn calculate_hash(&self) -> String {
let input = format!("{}{}{}{}", self.index, self.timestamp, self.data, self.previous_hash);
let mut hasher = Sha256::new();
hasher.update(input);
format!("{:x}", hasher.finalize())
}
}
Notice that we calculate the hash inside the new method. In a real-world scenario, you'd likely have a "mining" process here to find a hash with specific properties, but for a simple blockchain, a direct hash of the contents is enough to establish the chain of trust.
Linking Blocks into a Verifiable Chain
Now we need a way to manage these blocks. The Blockchain struct is essentially a wrapper around a Vec<Block>. The most critical part here is the "Genesis Block"—the very first block in the chain. Since there's no previous block to reference, we have to hardcode its previous_hash. It's the anchor for everything that follows.
pub struct Blockchain {
pub chain: Vec<Block>,
}
impl Blockchain {
pub fn new() -> Self {
let genesis_block = Block::new(0, "Genesis Block".to_string(), "0".to_string());
Blockchain {
chain: vec![genesis_block],
}
}
pub fn add_block(&mut self, data: String) {
let previous_hash = self.chain.last().unwrap().hash.clone();
let new_block = Block::new(
self.chain.len() as u32,
data,
previous_hash
);
self.chain.push(new_block);
}
}
I like the simplicity of this approach. By pulling the hash from the last() element of the vector, we ensure that each new block is mathematically tied to the one before it. If you were to change the data in chain[0], the previous_hash stored in chain[1] would no longer match the recalculated hash of chain[0].
Verifying Chain Integrity
The real power of this structure isn't in storing the data, but in verifying it. To check if the chain is still valid, we need to iterate through the blocks and ensure two things: first, that the block's stored hash actually matches its recalculated hash, and second, that the block's previous_hash matches the hash of the block before it.
If either of these checks fails, you know the data has been tampered with. This is the "trustless" part of the system; you don't have to trust the person holding the data—you just trust the math.
impl Blockchain {
pub fn is_chain_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() {
return false;
}
if current.previous_hash != previous.hash {
return false;
}
}
true
}
}📋 Practical Task
Exercise: Implementing a Blockchain Tamper-Detector
Your task is to write a test suite that proves the blockchain's integrity checks actually work. You will need to:
- Initialize a
Blockchainand add at least three blocks of data (e.g., "Payment 1: $10", "Payment 2: $20", "Payment 3: $30"). - Verify that
is_chain_valid()returnstruefor the freshly created chain. - Manually mutate the
datafield of the second block in the chain (e.g., change "$20" to "$2000"). - Verify that
is_chain_valid()now returnsfalse. - Attempt to "fix" the chain by recalculating the hash of the tampered block, and then verify that the chain is still invalid because the subsequent block's
previous_hashno longer matches.
Ensure you include the sha2 crate in your Cargo.toml to handle the hashing logic.
There are no comments for now.