blockchain

Build A Blockchain In Elixir Part-2

So we are building a blockchain in Elixir, in case you have jumped here directly please make sure you have read the part-1.

I did some refactoring before we proceed to write code for the modules to make a bit cleaner. So you can make changes accordingly.

defmodule ExChain.Blockchain.BlockTest do
  @moduledoc """
  This module contains test related to a block
  """

  use ExUnit.Case
  alias ExChain.Blockchain.Block

  describe "block" do
    test "genesis is valid" do
      assert %Block{
               data: "genesis data",
               hash: "F277BF9150CD035D55BA5B48CB5BCBE8E564B134E5AD0D56E439DD04A1528D3B",
               last_hash: "-",
               timestamp: 1_599_909_623_805_627
             } == Block.genesis()
    end

    test "mine block returns new block" do
      %Block{hash: hash} = genesis_block = Block.genesis()

      assert %Block{
               data: "this is mined data",
               last_hash: ^hash
             } = Block.mine_block(genesis_block, "this is mined data")
    end

    test "new give a new block when we pass the parameters" do
      # setup the data
      timestamp = DateTime.utc_now() |> DateTime.to_unix(1_000_000)
      last_hash = "random_hash"
      data = "this is new block data"

      # Perform assertions
      assert %Block{timestamp: ^timestamp, hash: _hash, last_hash: ^last_hash, data: ^data} =
               Block.new(timestamp, last_hash, data)
    end
  end
endCode language: Elixir (elixir)
defmodule ExChain.Blockchain.Block do
  @moduledoc """
  This module is the single block struct in a blockchain
  """

  alias __MODULE__

  @type t :: %Block{
          timestamp: pos_integer(),
          last_hash: String.t(),
          hash: String.t(),
          data: any()
        }

  defstruct ~w(timestamp last_hash hash data)a

  @spec new(pos_integer(), String.t(), any()) :: Block.t()
  def new(timestamp, last_hash, data) do
    %__MODULE__{}
    |> add_timestamp(timestamp)
    |> add_last_hash(last_hash)
    |> add_data(data)
    |> add_hash()
  end

  @spec get_str(Block.t()) :: String.t()
  def get_str(block = %__MODULE__{}) do
    """
    Block
    timestamp: #{block.timestamp}
    last_hash: #{block.last_hash}
    hash: #{block.hash}
    data: #{block.data}
    """
  end

  @spec genesis() :: Block.t()
  def genesis() do
    __MODULE__.new(1_599_909_623_805_627, "-", "genesis data")
  end

  def mine_block(%__MODULE__{hash: last_hash}, data) do
    __MODULE__.new(get_timestamp(), last_hash, data)
  end

  # private functions
  defp add_timestamp(%__MODULE__{} = block, timestamp), do: %{block | timestamp: timestamp}

  defp add_data(%__MODULE__{} = block, data), do: %{block | data: data}

  defp add_last_hash(%__MODULE__{} = block, last_hash), do: %{block | last_hash: last_hash}

  defp add_hash(%__MODULE__{timestamp: timestamp, last_hash: last_hash, data: data} = block) do
    %{block | hash: hash(timestamp, last_hash, data)}
  end

  defp get_timestamp(), do: DateTime.utc_now() |> DateTime.to_unix(1_000_000)

  defp hash(timestamp, last_hash, data) do
    data = "#{timestamp}:#{last_hash}:#{Jason.encode!(data)}"
    Base.encode16(:crypto.hash(:sha256, data))
  end
endCode language: Elixir (elixir)

In Part-1 we created a block now let’s focus on connecting those block into a chain so let’s create a new file lib/blockchain.ex and test/blockchain_test.exs

Let’s write the test for blockchain_test.exs then we will write code to make our test cases pass, in our tests, we will test 2 functionalities first block should be genesis block in a new blockchain , a new block should added into the blockchain when we mine a block

defmodule ExChain.BlockchainTest do
  @moduledoc """
  This module contains test related to a blockchain
  """

  use ExUnit.Case

  alias ExChain.Blockchain
  alias ExChain.Blockchain.Block

  describe "Blockchain" do
    setup [:initialize_blockchain]

    test "should start with the genesis block", %{blockchain: blockchain} do
      assert %Block{
               data: "genesis data",
               hash: "F277BF9150CD035D55BA5B48CB5BCBE8E564B134E5AD0D56E439DD04A1528D3B",
               last_hash: "-",
               timestamp: 1_599_909_623_805_627
             } == hd(blockchain.chain)
    end

    test "adds a new block", %{blockchain: blockchain} do
      data = "foo"
      blockchain = Blockchain.add_block(blockchain, data)
      [_, block] = blockchain.chain
      assert block.data == data
    end
  end

  defp initialize_blockchain(context), do: Map.put(context, :blockchain, Blockchain.new())
endCode language: Elixir (elixir)

Here in the test above we have taken the genesis block and we are comparing the first block in the blockchain with the genesis block, and the second test we have written so validate the block addition into the blockchain.

Let’s take our tests for a spin and both tests should fail.

mix test

And obviously, you’ll see that your tests are failing so let’s start writing code to make both of them pass.

defmodule ExChain.Blockchain do
  @moduledoc """
  This module contains the blockchain related functions
  """
  alias __MODULE__
  alias ExChain.Blockchain.Block

  defstruct ~w(chain)a

  @type t :: %Blockchain{
          chain: [Block.t({})]
        }

  @spec new :: Blockchain.t()
  def new() do
    %__MODULE__{}
    |> add_genesis()
  end

  defp add_genesis(blockchain = %__MODULE__{}) do
    %{blockchain | chain: [Block.genesis()]}
  end
endCode language: Elixir (elixir)

Here in blockchain.ex we have added a function to initialize our blockchain with the genesis block.

Let’s run our tests again and now our first test should pass, which validates our blockchain can be initialized nicely and it adds the genesis block in the blockchain properly.

mix testCode language: Elixir (elixir)

Only 1 test should be failing right now, so let’s write code to add a block into our blockchain.

defmodule ExChain.Blockchain do
  @moduledoc """
  This module contains the blockchain related functions
  """

....

  @spec add_block(BlockChain.t(), any) :: BlockChain.t()
  def add_block(blockchain = %__MODULE__{chain: chain}, data) do
    {last_block, _} = List.pop_at(chain, -1)

    %{blockchain | chain: chain ++ [Block.mine_block(last_block, data)]}
  end

...

endCode language: Elixir (elixir)

This function adds the block into our blockchain and now we can run our tests and check the happily all tests should be passing. You should get this nice green happy message that our all tests are passed 🙂

blockchain

Now let’s check how our blockchain looks when we run it. Start the application with iex -S mix

When you run the application and try with the following command you should able to see our nice blockchain.

Cool! we have built our first basic blockchain, we will stop here & in the next post, we will discuss the validation of the blockchain that how to know our chain & data is valid. Long way to go but we are proceeding towards building our own blockchain step by step.

Third Post in this series post-3

I hope you have enjoyed this article. If you liked it, consider buying a coffee here … Thank You

Leave a Comment

Your email address will not be published. Required fields are marked *