Building a Minimal Blockchain: Genesis Blocks, Integrity, and Chain Validation
You're still here! That means I haven't bored you, or you just really want to make a blockchain crypto. If this isn't the first post you're reading, you should know it's actually part 3 of my Blockchain crypto series. Check out the first post, Build Your Own Blockchain: The First Step to Building a Decentralised Future.
In our previous post, Breaking Blocks: How Hashing Holds the Blockchain Together, we took a closer look at what information needs to be in a block and created our own class. We also examined hashing functions, their use, and what makes a good hash function. We also looked at how hashes are used to cryptographically link blocks together. In this post, we're going to expand on linking blocks together because a blockchain is not a blockchain without a block chain.
What is a Blockchain Chain?
In our last post, we built individual blocks, but Blockchain is more than isolated blocks. It is a sequence where each block points to its predecessor's hash. This ensures that any alteration in a block invalidates all subsequent blocks.
The first block, the genesis block, is unique, with no predecessor. Every subsequent block references the previous block's hash, forming an unbreakable sequence.
Developing a Minimal Blockchain in TypeScript
Create a file called block-chain.ts
. Expanding on our Block class, here's how to build a Blockchain class:
import { Block } from './block';
export class Blockchain {
private chain: Block[];
constructor() {
this.chain = [this.createGenesisBlock()];
}
private createGenesisBlock(): Block {
return new Block(0, Date.now(), 'Genesis Block', '0');
}
public getLatestBlock(): Block {
return this.chain[this.chain.length - 1];
}
public addBlock(newBlock: Block): void {
newBlock.previousHash = this.getLatestBlock().hash;
newBlock.hash = newBlock.calculateHash();
this.chain.push(newBlock);
}
public isChainValid(): boolean {
for (let i = 1; i < this.chain.length; i++) {
const currentBlock = this.chain[i];
const previousBlock = this.chain[i - 1];
if (currentBlock.hash !== currentBlock.calculateHash() || currentBlock.previousHash !== previousBlock.hash) {
return false;
}
}
return true;
}
}
This Blockchain class initialises the Blockchain with a genesis block, allows the addition of new blocks, and validates the entire chain.
Step-by-Step Practical Explanation
- Genesis Block: The first block is created manually with a `previousHash` of 0.
- Adding Blocks: New blocks link to the previous block through hashes.
- Chain Validation: Each block's hash is recalculated to ensure integrity.
Blockchain in Action
We now want to add some blocks to our chain, inspect them, and verify the chain is valid.
Let's modify our `index.ts` file to use the new `Blockchain` class and add some blocks.
import { Block } from "./block";
import { Blockchain } from "./block-chain";
const blockchain = new Blockchain();
blockchain.addBlock(
new Block(1, Date.now(), { to: "Leo", from: "Miles", amount: 10 }),
);
blockchain.addBlock(
new Block(2, Date.now(), { to: "Miles", from: "David", amount: 5 }),
);
blockchain.addBlock(
new Block(3, Date.now(), { to: "David", from: "Alice", amount: 2 }),
);
blockchain.addBlock(
new Block(4, Date.now(), { to: "Alice", from: "Lina", amount: 1 }),
);
console.log(JSON.stringify({
blockChain: blockchain,
isValid: blockchain.isChainValid(),
}, null, 2));
Now, let's run our program; remember to transpile the code before you run it.
➜ minimal-blockchain npm run build && npm start
> minimal-blockchain@1.0.0 build
> tsc
> minimal-blockchain@1.0.0 start
> node dist/index.js
{
"blockChain": {
"chain": [
{
"index": 0,
"timestamp": 1740739233225,
"data": "Genesis Block",
"previousHash": "0",
"hash": "4f999a611732b538654dfbdaab8a7d2dcb193c92be23a48795185cacf927caec"
},
{
"index": 1,
"timestamp": 1740739233225,
"data": {
"to": "Leo",
"from": "Miles",
"amount": 10
},
"previousHash": "4f999a611732b538654dfbdaab8a7d2dcb193c92be23a48795185cacf927caec",
"hash": "824cd653b966fb7314ac0e963569316ba2f5363d854e0f6b5666b6d9bcf58064"
},
{
"index": 2,
"timestamp": 1740739233225,
"data": {
"to": "Miles",
"from": "David",
"amount": 5
},
"previousHash": "824cd653b966fb7314ac0e963569316ba2f5363d854e0f6b5666b6d9bcf58064",
"hash": "e465af0af1374e8a7161a15e34bdf0d60ea23d7c775cd32cd5a2c657a2802b11"
},
{
"index": 3,
"timestamp": 1740739233225,
"data": {
"to": "David",
"from": "Alice",
"amount": 2
},
"previousHash": "e465af0af1374e8a7161a15e34bdf0d60ea23d7c775cd32cd5a2c657a2802b11",
"hash": "427bea865286e01be5f0ddcf22addd61be5e6c45faf58484e537e314b58f8d9d"
},
{
"index": 4,
"timestamp": 1740739233225,
"data": {
"to": "Alice",
"from": "Lina",
"amount": 1
},
"previousHash": "427bea865286e01be5f0ddcf22addd61be5e6c45faf58484e537e314b58f8d9d",
"hash": "73c632660f9f0a450d1bf7a3bf74ca1d0163c861815c96614fb6dfda389dec50"
}
]
},
"isValid": true
}
If we look at the output, we can see that each block in the chain holds the hash from the previous, and as we have rehashed each block with this information, we create linkage. We can also see the output of the `isChainValid()` function, showing that we do, in fact, have a valid chain.
Let's see what happens if we modify the values in one of the blocks earlier in the chain.
To do this, I need to update a block already in the chain after the fact. I've gotten a little ahead of myself by making the `chain` property in the class private. Let's weaken the class and make this public.
import { Block } from './block';
export class Blockchain {
public chain: Block[]; // Change property from private to public
constructor() {
this.chain = [this.createGenesisBlock()];
}
... rest of the code
}
And now, let's tamper with the data. We'll change the amount in the block at index position 2 to 100.
import { Block } from "./block";
import { Blockchain } from "./block-chain";
const blockchain = new Blockchain();
blockchain.addBlock(
new Block(1, Date.now(), { to: "Leo", from: "Miles", amount: 10 }),
);
blockchain.addBlock(
new Block(2, Date.now(), { to: "Miles", from: "David", amount: 5 }),
);
blockchain.addBlock(
new Block(3, Date.now(), { to: "David", from: "Alice", amount: 2 }),
);
blockchain.addBlock(
new Block(4, Date.now(), { to: "Alice", from: "Lina", amount: 1 }),
);
// Tampering with the data
blockchain.chain[2].data.amount = 100;
console.log(JSON.stringify({
blockChain: blockchain,
isValid: blockchain.isChainValid(),
}, null, 2));
Build and run the code, and we will get the following output:
➜ minimal-blockchain npm run build && npm start
> minimal-blockchain@1.0.0 build
> tsc
> minimal-blockchain@1.0.0 start
> node dist/index.js
{
"blockChain": {
"chain": [
{
"index": 0,
"timestamp": 1740739396125,
"data": "Genesis Block",
"previousHash": "0",
"hash": "4dfe79a47450bea12ac7d1b65a1df7b08637860beec763e6a1de2485fe5ed0c3"
},
{
"index": 1,
"timestamp": 1740739396125,
"data": {
"to": "Leo",
"from": "Miles",
"amount": 10
},
"previousHash": "4dfe79a47450bea12ac7d1b65a1df7b08637860beec763e6a1de2485fe5ed0c3",
"hash": "98d728da276bd789d8fa93320219afe90d37a79a8b287d5ba87a347ad8dc0b98"
},
{
"index": 2,
"timestamp": 1740739396125,
"data": {
"to": "Miles",
"from": "David",
"amount": 100
},
"previousHash": "98d728da276bd789d8fa93320219afe90d37a79a8b287d5ba87a347ad8dc0b98",
"hash": "46b4b90bc0aaa6622615a545ed093bb3e080a2b4e9f000b2a644f3c7946ce12d"
},
{
"index": 3,
"timestamp": 1740739396125,
"data": {
"to": "David",
"from": "Alice",
"amount": 2
},
"previousHash": "46b4b90bc0aaa6622615a545ed093bb3e080a2b4e9f000b2a644f3c7946ce12d",
"hash": "3709fcd860585ddb0baac01a20e1b0ec2b74cfc44ee2417dbadb76547ce3e159"
},
{
"index": 4,
"timestamp": 1740739396125,
"data": {
"to": "Alice",
"from": "Lina",
"amount": 1
},
"previousHash": "3709fcd860585ddb0baac01a20e1b0ec2b74cfc44ee2417dbadb76547ce3e159",
"hash": "9d51e31e0ca80266cd60d5e561d79358b9e579ad802b48992cb031e44651d7f9"
}
]
},
"isValid": false
}
So, we can see that modifying parts of the chain invalidates it. To truly modify the chain, we would have to update every hash in each block after the change.
You may be asking why we need to verify the chain when I can just leave the `chain` property in the `Blockchain` class as private. Well, the answer is that because the chain is decentralised, I don't get to control how other people add blocks to it. It is a language feature that allows me to enforce the property can not be access outside of the class but another user could simply disregard that in their implementation.
Consensus
If you've got your black hat (or grey hat) on, you may be thinking, "Well, if I want to tamper with a block's data, I can just amend the whole chain like you said. It's not hard to make a new hash function for each one."
If you thought that, you'd be right. At the moment, our Blockchain implementation lacks a consensus mechanism. Even if a chain is valid, we have no way of agreeing on which chain presented is correct in a decentralised fashion.
Trust in Our Minimal Blockchain
While our minimal Blockchain is a simplified model, it demonstrates crucial trust points:
- Trust is established via cryptographic hashes linking blocks.
- Integrity checks protect the chain from tampering.
- The absence of consensus mechanisms means no distributed trust, leaving it vulnerable.
Conclusion and Next Steps
Next, we'll cover cryptographic foundations, including public/private keys and digital signatures, essential for securing transactions in Blockchain.