A Toy Blockchain in 100 Lines or Less
Tags: rust, blockchain, tutorial
Published: 2022-02-14 18:40:43

### Introduction
As the popularity and mainstream awareness of NFTs and blockchains proliferate, I was pretty confused about how they worked - I kept hearing about "mining" and "blocks", and so I figured the best way to learn about the technology was to create a small model of it to see how the internals functioned (or at least a small subset of it).

I also took the opportunity to use Rust to build it, as I've had my eye on it for a while, but haven't yet found the opportunity to use it for anything substantial.

### First Principles
As with any new technology, let's start with the basics. For the blockchain, I know that we must be able to have some sort of data structure that contains a chain of blocks. Since we'll be adding to the chain, we know that our chain will need to be some sort of variable-length container.

For our purposes, we'll choose a `Vec` that holds `Block`s in Rust and declare it like so:

```(rust)
struct BlockChain {
    chain: Vec<Block>
}
```

Now that we have a chain that holds `Block`s we'll need to define the `Block` itself:

```(rust)
struct Block {
    index: usize,
    timestamp: i64,
    data: Vec<BlockData>,
    hash: String,
    previous_hash: String,
    proof: i64
} 
```

For most blockchains (and this one included), the block contains pretty standard fields:

1. index: the index of the block on the chain
2. timestamp: when this block was mined
3. data: the data of the block itself - this is usually the list of transactions that this block is holding (normally this is all previous transactions + one additional transaction)
4. hash: the SHA256 hash of this block
5. previous_hash: the SHA256 hash of the block that came before
6. proof: the number required for the Proof of Work algorithm (more on this later)

We'll also define the `BlockData` as the following:
```(rust)
struct BlockData {
    sender: String,
    recipient: String,
    amount: f64
}
```

In the end, our chain will eventually be a bunch of blocks holding data about who is sending the amount of our coin where. Other blockchains (such as Ethereum or Solana) will have their blockchain data strucutured differently to allow for things such as smart contracts or NFTs - you can view the Ethereum documentation to see what their data is structured like [here](https://ethereum.org/en/developers/docs/transactions/).

### Second Steps
So we've defined our chain and the basic data structures we'll be using - the next steps will be to create the chain itself.

In Rust, we'll be using `impl` to add methods to the `BlockChain` struct:

```
struct BlockChain {
    chain: Vec<Block>
}

impl BlockChain {
    // methods go here
}
```

The first method to add is `genesis_block` -- all this really does is create the very first block of the chain. Keep in mind that this is Rust - so we'll just be implementing functionality for the `BlockChain` type (and the various other types we have defined).

The `genesis_block` method is as follows:

```(rust)
fn genesis_block(&mut self) {
    let now = Utc::now();
    let mut new_block = Block {
        index: 0, 
        timestamp: now.timestamp(),
        data: [].to_vec(),
        hash: "".to_string(),
        previous_hash: "".to_string(),
        proof: 0
    };

    new_block.hash = new_block.hash();
    self.chain.push(new_block);
}
```

One thing to note is we are marking the `self` variable to be mutable - this is because we are modifying the chain itself (i.e. adding a block to the chain). You'll also notice that almost everything in this block is empty - there is no data, no hash, the proof is set to 0 - this is because blocks are meant to be built from the previous block in the chain - if there is no other block, we must have some data somewhere in order for this block to be valid. 

Note: We're using the `chrono` crate here (specifically `Utc` from `chrono`) so be sure to add the `chrono` package to your `Cargo.toml` file to have access to it.

One last thing to note is that we are calling a `hash` method on the `new_block` object - but we haven't defined one yet! So let's do that now:

```(rust)
impl Block {
    fn hash(self) -> String {
        let hashable_data = format!("{:?}{}{}{}{}{}", self.data, self.hash, self.index, self.previous_hash, self.proof, self.timestamp);
        return digest(hashable_data);
    }
}
```

The `hash` method looks simple, but we're doing a lot here:
1. the `digest` method is using the `sha256` crate, so that's installed via our `cargo.toml` file, and declared by `use sha256::digest` at the top of the file.
2. We'll need to also add the `serde` package that allows us to serialize and deserialize data so we can save the `BlockData` as a String format (using the `format!` macro). Be sure to install `serde` and also add the following traits to the `BlockData` struct: `#[derive(Serialize, Deserialize, Debug, Clone)]`. We'll want to add those traits to both the `Block` and `Blockchain` structs as well to allow us to print the contents to stdout.

In the end, your `BlockData` and `Block` structs should look something like this:

```(rust)
#[derive(Serialize, Deserialize, Debug, Clone)]
struct BlockData {
    sender: String,
    recipient: String,
    amount: i64
}

#[derive(Serialize, Deserialize, Debug, Clone)]
struct Block {
    index: usize,
    timestamp: i64,
    data: Vec<BlockData>,
    hash: String,
    previous_hash: String,
    proof: i64
}

#[derive(Serialize, Deserialize, Debug, Clone)]
struct BlockChain {
    chain: Vec<Block>
}

```

In our `main` function, if we do the following:
```
let mut chain = BlockChain {
        chain: [].to_vec()
    };

chain.genesis_block();

println!("{:#?}", chain);
```

The output should be similar to the following:

```
BlockChain {
    chain: [
        Block {
            index: 0,
            timestamp: 1644628637,
            data: [],
            hash: "e7bcf22ad4e5d6c4387626c6ab70db8d52ba5977e85ac73e6ddcc51517a1a2ba",
            previous_hash: "",
            proof: 0,
        },
    ],
}
```

And that's great! We have our toy block chain that contains `Blocks` and we have the ability to create an chain with the genesis block on it. Our next step is to give us the ability to add a block and then perform work!

### Why Work?
This is one of the defining characteristics of the blockchain - doing some sort of heavy computational work just to be able to add to the chain. It's part of the reason why verifying / adding blocks to the current Bitcoin chain is so slow (or any Blockchain network).

The question remains however - why do we do this? Why not just allow for the addition of blocks via just `chain.push` like we do for the genesis block - why perform this work?

The answer lies in the original Bitcoin protocol paper. In it, the author (Satoshi Nakamoto) proposes Proof of Work as a way of solving the double spend problem (i.e. multiple people can't push to the chain and say "Aaron got 1000 bucks") - and one way to ensure that is to have a central authority checking every transaction. 

As blockchain is meant to be distributed, we need a way for everybody using the chain to come to an agreement about which blocks are valid, and which ones are not. This is where Proof of Work comes in.

As transactions are broadcast to the blockchain network (Bitcoin or otherwise), miners collect them and validate them based on the _previous_ block. For our toy blockchain, that means doing something like the following:

```
impl BlockChain {
    ...
    fn add_block(&mut self, sender: String, recipient: String, amount: i64) {
            // get the previous block
            let previous_block = self.chain.last().unwrap();

            // create new block data based on the new transaction
            let mut chain_data = previous_block.data.to_vec();
            let new_data = BlockData {
                sender: sender.to_string(),
                recipient: recipient.to_string(),
                amount: amount
            };
            chain_data.push(new_data);

            let now = Utc::now();
            // create a new block with the new transaction
            let mut new_block = Block {
                index: previous_block.index + 1, 
                timestamp: now.timestamp(),
                data: chain_data,
                hash: "".to_string(),
                previous_hash: previous_block.hash.to_string(),
                proof: 0
            };

            // do the proof of work 
            new_block.proof = new_block.mine(previous_block.proof);
            new_block.hash = new_block.hash();
            self.chain.push(new_block);
        }
}
```

You can see in the above `add_block` method we get the new transaction data, add it to the previous block's data and then perform the work in the `mine` method. The `mine` method looks like the following (it's based on the HashCash algorithm mentioned in the Bitcoin whitpaper):

```
impl Block {
    // other methods
    fn mine(&self, previous_proof: i64) -> i64 {
        let mut hashed_string = "".to_string();
        let mut proof = 0;
        while !hashed_string.starts_with("00000") {
            let work_string = format!("f{}{}", previous_proof, proof);
            hashed_string = digest(work_string);
            proof += 1;
        }
        return proof;
    }
}
```

By using the _previous_ block's proof, we've established a dependency on the previous block - assuming it is valid - there is no way for the current validator (or miner) to alter the blockchain without going through the whole chain and rehashing the proof for _every_ block on the chain. 

Even with small chains, this can prove to be prohibitively expensvie - although with this approach, just maintaining and adding new blocks can get more and more expensive over time - you can see this with Bitcoin, where the current energy expenditure of the Bitcoin network is greater than than [Thailand](https://digiconomist.net/bitcoin-energy-consumption/).

The most important part of the above function is the `starts_with("00000")` part - that determines the "difficulty" of adding a block to the chain - i.e. how long it will take (on average) to add a block to the chain. This particular chain, on my particular computer will take roughly 8 seconds to finish the work for and add a block to the chain. Bitcoin takes roughly 10 minutes to add a block. To increase the difficulty of the mine, just add more `0`s - to reduce, remove `0`s.

### What does this look like?
We now have a working instance of a `Blockchain`, consisting of multiple `Blocks` that can be mined and added to the `BlockChain` via our Proof of Work algorithm. 

For our toy blockchain this looks like the following:

```(rust)
fn main() {
    let mut chain = BlockChain {
        chain: [].to_vec()
    };

    chain.genesis_block();
    chain.add_block("mr.magoo".to_string(), "mr.magee".to_string(), 1000);

    println!("{:#?}", chain);
}
```

With the following output:
```(rust)
BlockChain {
    chain: [
        Block {
            index: 0,
            timestamp: 1644709286,
            data: [],
            hash: "223f430ce60c464d57fb13cae30c843b82260287dc329fc6be59cfc183fc48fb",
            previous_hash: "",
            proof: 0,
        },
        Block {
            index: 1,
            timestamp: 1644709286,
            data: [
                BlockData {
                    sender: "mr.magoo",
                    recipient: "mr.magee",
                    amount: 1000,
                },
            ],
            hash: "f315332a38dfcce0ce696f5371cf8a4de33db76d59f094322d0ddf68a7601ab6",
            previous_hash: "223f430ce60c464d57fb13cae30c843b82260287dc329fc6be59cfc183fc48fb",
            proof: 375214,
        },
    ],
}
```

### What is missing
We have a very basic blockchain implementation here, but there's a bunch of things missing which I'll list out in no particular order:
1. **Peer-to-peer network**: our blockchain currently is only aware of itself, and will not mine blocks automatically - in fact, new blocks can only be added by hardcoding them into the `main` function. 
2. **Chain verification**: Our blockchain process is currently not validating the chain - this is usually accomplished by only accepting the chain that is longest - right now we don't do that

Even with those two big things missing (the whole decentralized concept really), to see a basic blockchain in action is super interesting - and the concepts between this blockchain and others isn't much different. 

You can view our completed source code [here](https://github.com/gitguudd/toychain/blob/main/src/main.rs)