I consider myself a late adopter. Everyone talks about blockchain these days. Everyone tries to apply the technology everywhere, even when it does't make sense. So let's learn by doing and try to implement the simple blockchain from scratch. And let's do this in Ada!
Wikipedia defines blockchain as:
A blockchain, originally block chain, is a continuously growing list of records, called blocks, which are linked and secured using cryptography. Each block typically contains a cryptographic hash of the previous block, a timestamp, and transaction data. By design, a blockchain is resistant to modification of the data.
Twitter has a different opinion:
So it looks like we have to model:
- blocks: timestamped records that are able to store some kind of payload
- a chain of blocks, a.k.a., the blockchain
- a way to prove the validity of the whole blockchain
- a proof of work to implement the commit protocol.
Block can be implemented as a new Ada type,
with Ada.Calendar; use Ada.Calendar; with Ada.Strings.Unbounded; use Ada.Strings.Unbounded; package Simple_Blockchain.Block is type Object is private; -- public methods omitted for brevity private type Object is record -- cryptographic hash of the current block Hash : String (1 .. 64); -- cryptographic hash of the previous block Previous_Hash : String (1 .. 64); Timestamp : Time; -- transaction data payload Data : Unbounded_String; end record; end Simple_Blockchain.Block;
The complete, working code can be found at tomekw/simple_blockchain Github repository.
We will create new blocks with a
Make function acting as a constructor:
function Make (Previous_Hash : String, Data : String) return Object is Now : Time := Clock; begin return ( -- current hash is a product of new hash attributes -- and the whole history of hashes of previous blocks Hash => Calculate_Hash (Previous_Hash, Now, Data), Previous_Hash => Previous_Hash, Timestamp => Now, Data => To_Unbounded_String (Data) ); end Make;
How do we calculate a hash then?
function Calculate_Hash (Previous_Hash : String; Timestamp : Time; Data : String) return String is begin -- with GNAT.SHA256; use GNAT; return SHA256.Digest (Previous_Hash & Image (Timestamp) & Data); end Calculate_Hash;
We have the blocks but they don't mean anything without being linked in a chain. A simple blockchain can be stored in a growable vector. Luckily, Ada provides an excellent containers library:
with Ada.Containers.Vectors; use Ada.Containers; with Simple_Blockchain.Block; use Simple_Blockchain; package Simple_Blockchain.Blockchain is use type Block.Object; package Block_Vectors is new Vectors (Index_Type => Positive, Element_Type => Block.Object); type Object is private; -- public methods omitted for brevity private type Object is record Blocks : Block_Vectors.Vector; end record; end Simple_Blockchain.Blockchain;
Now, we are able to
Append new blocks to the chain:
The_Blockchain : Blockchain.Object := Blockchain.Make; -- Let's use a string of zeroes as a first hash First_Hash : String (1 .. 64) := (others => '0'); First_Block : Block.Object := Block.Make (Previous_Hash => First_Hash, Data => "First block"); Second_Block : Block.Object := Block.Make (Previous_Hash => Block.Get_Hash (First_Block), Data => "Second block"); Blockchain.Append (The_Blockchain, First_Block); Blockchain.Append (The_Blockchain, Second_Block);
How do we know the blockchain is valid?
- The stored block hash is the same as the calculated one
- The stored previous hash is the same as the hash of the preceding block.
These rules apply to all block in the chain.
function Is_Valid (This : Blockchain.Object) return Boolean is Current_Block : Block.Object; Next_Block : Block.Object; begin -- iterate over the blockchain in pairs for I in Get_Blocks (This).First_Index .. (Get_Blocks (This).Last_Index - 1) loop Current_Block := Block_Vectors.Element (Get_Blocks (This), I); Next_Block := Block_Vectors.Element (Get_Blocks (This), I + 1); -- confirm the stored block hash is the same as the calculated one if Block.Get_Hash (Next_Block) /= Block.Calculate_Hash (Block.Get_Previous_Hash (Next_Block), Block.Get_Timestamp (Next_Block), Block.Get_Data (Next_Block)) then return False; end if; -- confirm the stored previous hash is the same as the hash of the preceding block if Block.Get_Hash (Current_Block) /= Block.Get_Previous_Hash (Next_Block) then return False; end if; end loop; return True; end Is_Valid;
Our newly created blockchain should be valid:
Blockchain.Is_Valid (The_Blockchain); -- => TRUE
The last part to implement is the proof of work. In the blockchain terminology it's called mining. Instead of simply appending new blocks to the chain we will mine them. Proof of work will be satisfied as a number of leading zeroes in the block hash defined as a blockchain
procedure Mine_Block (This : in out Blokchain.Object; Data : String) is New_Block : Block.Object; Previous_Hash : String (1 .. 64); begin if Is_Empty (This) then Previous_Hash := (others => '0'); else Previous_Hash := Block.Get_Hash (Last_Block (This)); end if; New_Block := Block.Make (Previous_Hash => Previous_Hash, Data => Data); -- the important part: recalculate the hash until it starts -- with the expected number of zeroes while Block.Get_Hash (New_Block) (1 .. Get_Difficulty (This)) /= Expected_Hash_Prefix (This) loop Block.Recalculate_Hash (New_Block); end loop; Block_Vectors.Append (This.Blocks, New_Block); end Mine_Block;
Now, instead of appending blocks, it's possible to simply mine new ones:
Blockchain.Mine_Block (The_Blockchain, "Third block");
Is_Valid function has to be adjusted to take into account the expected hash prefix validation:
function Is_Valid (This : Blockchain.Object) return Boolean is Current_Block : Block.Object; Next_Block : Block.Object; begin -- code omitted if Block.Get_Hash (Next_Block) (1.. Get_Difficulty (This)) /= Expected_Hash_Prefix (This) then return False; end if; -- code omitted return True; end Is_Valid;
There is more to the blockchain technolgy than this: sending transactions, wallets, and signatures. However, this post should demistify the concept of blockchains to the reader.
The complete, working code, and a short demo, can be found at tomekw/simple_blockchain Github repository.
with Ada.Text_IO; use Ada.Text_IO; with Simple_Blockchain.Block; with Simple_Blockchain.Blockchain; use Simple_Blockchain; function Simple_Blockchain_Demo return Integer is The_Blockchain : Blockchain.Object := Blockchain.Make (Difficulty => 6); begin Put_Line ("Simple blockchain demo"); New_Line; Put_Line ("Mining first block..."); Blockchain.Mine_Block (The_Blockchain, Data => "First block"); Put_Line ("Block mined."); New_Line; Put_Line ("Mining second block..."); Blockchain.Mine_Block (The_Blockchain, Data => "Second block"); Put_Line ("Block mined."); New_Line; Put_Line ("Mining third block..."); Blockchain.Mine_Block (The_Blockchain, Data => "Third block"); Put_Line ("Block mined."); New_Line; Put_Line ("Is blockchain valid? " & Blockchain.Is_Valid (The_Blockchain)'Image); New_Line; Put_Line ("Printing blockchain..."); Put_Line (Blockchain.Image (The_Blockchain)); return 0; end Simple_Blockchain_Demo;
Simple blockchain demo Mining first block... Block mined. Mining second block... Block mined. Mining third block... Block mined. Is blockchain valid? TRUE Printing blockchain... Blockchain - difficulty: 6, blocks: 3 Hash: 000000f78298d2028737f802a82edb955e0e4cfdc06b514325da2a037f41b754, Previous hash: 0000000000000000000000000000000000000000000000000000000000000000, Timestamp: 2018-06-14 13:26:11, Nonce: 10942584, Data: First block Hash: 000000ddfddf305f15fefdada0939139ea33dc94bca1e57eda5f16d902fec55f, Previous hash: 000000f78298d2028737f802a82edb955e0e4cfdc06b514325da2a037f41b754, Timestamp: 2018-06-14 13:26:17, Nonce: 6008350, Data: Second block Hash: 000000e4530387b122e91de4fedc84b77406a819713c9601b09b29695cb6e336, Previous hash: 000000ddfddf305f15fefdada0939139ea33dc94bca1e57eda5f16d902fec55f, Timestamp: 2018-06-14 13:26:32, Nonce: 17905780, Data: Third block