Implement blockchain with TypeScript
The blockchain technology, which could be part of a digital vehicle record e.g., was one of the main topics we dealt with at e.GO Mobile.
This time, I would like to show how to build a small blockchain using TypeScript, using a simple example focusing on data structure.
The example code can be found on GitHub.
How does a blockchain work?
A blockchain is essentially a container that holds a set of transaction records, along with a few additional pieces of data that serve to verify and secure each block, which are linked to the previous block, forming a chain of blocks that provides an immutable record of all the transactions that have occurred.
Beside the data itself, a block usually contains metadata such as a timestamp, an unique identifier, and a reference to the previous block in the chain.
Implement a block
A block object should implement an interface with the following information:
/**
* Describes a block in a blockchain.
*/
export interface IBlockChainBlock {
/**
* The data in Base64 format.
*/
readonly data: string;
/**
* The hash of this block in Base64 format.
*/
readonly hash: string;
/**
* The zero based index.
*/
readonly index: number;
/**
* The hash of this block in Base64 format.
*/
readonly previousHash: string;
/**
* The time of creation as UNIX timestamp (UTC).
*/
readonly timestamp: number;
}
Especially the hash
property could later be a computed value in the implementaion, that is created from the data of the other properties:
// ...
import crypto from "node:crypto";
// ...
get hash(): string {
const sha256 = crypto.createHash('sha256');
return sha256.update(`${this.index}\n${this.previousHash}\n${this.timestamp}\n${this.data}`)
.digest()
.toString('base64');
}
// ...
Implement the chain
For the chain itself we do not need too much requirements:
- it must be iterable, so we are later able to use it in a
for
loop which return the chain as stream of blocks - it must be work asynchronious, so we can decide later in the implementation, if we want to use Array as “backend” or a databse connection, like MongoDB
The interface could look like this:
/**
* An asynchronious iterable blockchain.
*/
export interface IBlockChain extends AsyncIterable<IBlockChainBlock> {
/**
* Adds a new block to chain.
*
* @param {string} data The data in Base64 format.
*
* @returns {Promise<IBlockChainBlock>} The promise with the new block.
*/
addBlock: (data: string) => Promise<IBlockChainBlock>;
}
Create and use a blockchain
For the case of creating an instance of an IBlockChain
object we can implement a factory function:
// ...
class ArrayBlockChain implements IBlockChain {
// ...
}
// ...
/**
* Creates a new instance of an `IBlockChain`.
*
* @returns {Promise<IBlockChain>} The promise with the new instance of a `IBlockChain`.
*/
export async function createNewBlockChain(): Promise<IBlockChain> {
return new ArrayBlockChain();
}
With all of this, we can now start using the new features:
// create instance
const myBlockchain = new createNewBlockChain();
// add blocks
await myBlockchain.addBlock(
Buffer.from('test1').toString('base64')
);
await myBlockchain.addBlock(
Buffer.from('test2').toString('base64')
);
// iterate from beginning to end over
// all blocks in the chain
for await (const block of myBlockchain) {
console.log(
'Block', block.index,
Buffer.from(block.data, 'base64').toString(),
block.previousHash, block.hash
);
}
Conclusion and outlook
This example only wants to give an idea how data in a blockchain depends on each other and how the whole chain could be validated later.
In one of the next posts, I will show how to use a database as storage instead of an array.
Have fun while trying it out! 🎉