Blockchain in Elixir

Groking blockchains in your favorite language

by Justus Eapen 🖋 ✒️ @justuseapen

Why are we here?

To blockchains, one of the technologies underlying cryptocurrencies like Bitcoin.

blockchain

a continuously growing list of records, called blocks, which are linked and secured using cryptography.

Crypto-what?

Cryptographic Hash Functions

  1. input can be any string.
  2. produces a fixed size output.(256-bit usually)
  3. is efficiently computable.

Native to Erlang Crypto Module


            :md5
            :ripemd160
            :sha (SHA-1)
            :sha224 (SHA-2)
            :sha256 (SHA-2)
            :sha384 (SHA-2)
            :sha512 (SHA-2)
          
Erlang Crypto Module Documentation

Show me the blockchain 🤑🤑🤑

A Couple'a Blocks


            0: {
              data: “genesis block”,
              previous_block_hash: “”,
              chain_hash: “0x12456”
            }
          

            1: {
              data: “anything”,
              previous_block_hash: hash(block[0]),
              chain_hash: hash(all_blocks)
            }
          

block.ex


            defmodule Blockchain.Block do
              @moduledoc "Provides Block struct and related block operations"

              alias Blockchain.{Block, Chain, BlockData, Crypto}

              @derive [Poison.Encoder]
              defstruct [
                :index,
                :previous_hash,
                :timestamp,
                :data, # must follow the Blockchain.BlockData protocol
                :nounce,
                :hash
              ]

              def genesis_block do
                %Block{
                  index: 0,
                  previous_hash: "0",
                  timestamp: 1_465_154_705,
                  data: "genesis block",
                  nounce: 35_679,
                  hash: "0000DA3553676AC53CC20564D8E956D03A08F7747823439FDE74ABF8E7EADF60"
                }
              end

              def generate_next_block(data) do
                generate_next_block(data, Chain.latest_block)
              end

              def generate_next_block(data, %Block{} = latest_block) do
                b = %Block {
                  index: latest_block.index + 1,
                  previous_hash: latest_block.hash,
                  timestamp: System.system_time(:second),
                  data: data
                }
                hash = compute_hash(b)
                %{b | hash: hash}
              end

              def compute_hash(%Block{index: i, previous_hash: h, timestamp: timestamp, data: data, nounce: nounce}) do
                "#{i}#{h}#{timestamp}#{BlockData.hash(data)}#{nounce}"
                |> Crypto.hash(:sha256)
                |> Base.encode16()
              end
            end
          

chain.ex


            defmodule Blockchain.Chain do
              @moduledoc """
                GenServer that stores the blockchain. Chain is stored in reverse order
                (oldest block last)
              """

              use GenServer

              alias Blockchain.{Block, BlockData}

              def start_link do
                GenServer.start_link(__MODULE__, nil, name: __MODULE__)
              end

              def init(_) do
                {:ok, [Block.genesis_block()]}
              end

              def latest_block do
                GenServer.call(__MODULE__, :latest_block)
              end

              def all_blocks do
                GenServer.call(__MODULE__, :all_blocks)
              end

              def add_block(%Block{} = b) do
                GenServer.call(__MODULE__, {:add_block, b})
              end

              def replace_chain(chain) do
                GenServer.call(__MODULE__, {:replace_chain, chain})
              end

              def handle_call(:latest_block, _from, chain) do
                [h | _] = chain
                {:reply, h, chain}
              end

              def handle_call(:all_blocks, _from, chain) do
                {:reply, chain, chain}
              end

              def handle_call({:add_block, %Block{} = b}, _from, chain) do
                [previous_block | _] = chain
                case validate_block(previous_block, b, chain) do
                  {:error, reason} ->
                    {:reply, {:error, reason}, chain}
                  :ok ->
                    {:reply, :ok, [b | chain]}
                end
              end

              def handle_call({:replace_chain, new_chain}, _from, chain) do
                case validate_chain(new_chain) do
                  :ok -> {:reply, :ok, new_chain}
                  {:error, _} = error -> {:reply, error, chain}
                end
              end

              defp validate_block(previous_block, block, chain) do
                cond do
                  previous_block.index + 1 != block.index ->
                    {:error, "invalid index"}
                  previous_block.hash != block.previous_hash ->
                    {:error, "invalid previous hash"}
                  proof_of_work().verify(block.hash) == false ->
                    {:error, "proof of work not verified"}
                  block.hash != Block.compute_hash(block) ->
                    {:error, "invalid block hash"}
                  true ->
                    validate_block_data(block, chain)
                end
              end

              defp validate_block_data(%Block{data: data}, chain), do: BlockData.verify(data, chain)

              def validate_chain(blockchain) when length(blockchain) == 0, do: {:error, "empty chain"}
              def validate_chain([genesis_block | _] = blockchain) when length(blockchain) == 1 do
                if genesis_block == Block.genesis_block() do
                  :ok
                else
                  {:error, "chain doesn't start with genesis block"}
                end
              end
              def validate_chain([block | [previous_block | rest] = chain]) do
                case validate_block(previous_block, block, chain) do
                  {:error, _} = error -> error
                  _ -> validate_chain([previous_block | rest])
                end
              end

              defp proof_of_work, do: Application.fetch_env!(:blockchain, :proof_of_work)
            end
          

Blockchains are not enough.

  • P2P - Protocol for interacting with nodes, sharing state, and reaching consensus
  • Proof of Work - How to verify blocks being added to the network as valid
  • Tokens - Useful abstractions, what is our data supposed to represent? What is a cryptocurrency?

THE END