Trusted by millions

Blockchain Development part 6: Building Digital Coin

In previous lectures we have covered each step in depth which is involved in a blockchain network.

Here finally we are now building our crypto currency by combining all the previous lectures together. Here we are creating the following files for our system.

  1. Block.rb  (developed in part 5)
  2. Client.rb (developed in part 1)
  3. Howcoin.rb (developed in part 1)
  4. Helpers.rb (developed in part 2)
  5. Pki.rb (developed in part 3)
  6. names.txt (A simple text file contain names)

1- Block.rb

Here is our block.rb class code.


require 'colorize'
require 'digest'
require_relative 'pki'

class Block
 NUM_ZEROES = 4
 attr_reader :own_hash, :prev_block_hash, :txn

 def self.create_genesis_block(pub_key, priv_key)
   genesis_txn = Transaction.new(nil, pub_key, 500_000, priv_key)
   Block.new(nil, genesis_txn)
 end

 def initialize(prev_block, txn)
   raise TypeError unless txn.is_a?(Transaction)
   @txn = txn
   @prev_block_hash = prev_block.own_hash if prev_block
   mine_block!
 end

 def mine_block!
   @nonce = calc_nonce
   @own_hash = hash(full_block(@nonce))
 end

 def valid?
   is_valid_nonce?(@nonce) && @txn.is_valid_signature?
 end

 def to_s
   [
     "Previous hash: ".rjust(15) + @prev_block_hash.to_s.yellow,
     "Message: ".rjust(15) + @txn.to_s.green,
     "Nonce: ".rjust(15) + @nonce.light_blue,
     "Own hash: ".rjust(15) + @own_hash.yellow,
     "↓".rjust(40),
   ].join("\n")
 end

 private

 def hash(contents)
   Digest::SHA256.hexdigest(contents)
 end

 def calc_nonce
   nonce = "HELP I'M TRAPPED IN A NONCE FACTORY"
   count = 0
   until is_valid_nonce?(nonce)
     print "." if count % 100_000 == 0
     nonce = nonce.next
     count += 1
   end
   nonce
 end

 def is_valid_nonce?(nonce)
   hash(full_block(nonce)).start_with?("0" * NUM_ZEROES)
 end

 def full_block(nonce)
   [@txn.to_s, @prev_block_hash, nonce].compact.join
 end
end

class Transaction
 attr_reader :from, :to, :amount
 def initialize(from, to, amount, priv_key)
   @from = from
   @to = to
   @amount = amount
   @signature = PKI.sign(message, priv_key)
 end

 def is_valid_signature?
   return true if genesis_txn? # genesis transaction is always valid
   PKI.valid_signature?(message, @signature, from)
 end

 def genesis_txn?
   from.nil?
 end

 def message
   Digest::SHA256.hexdigest([@from, @to, @amount].join)
 end

 def to_s
   message
 end
end

class BlockChain
 attr_reader :blocks

 def initialize(originator_pub_key, originator_priv_key)
   @blocks = []
   @blocks << Block.create_genesis_block(originator_pub_key, originator_priv_key)
 end

 def length
   @blocks.length
 end

 def add_to_chain(txn)
   @blocks << Block.new(@blocks.last, txn)
 end

 def valid?
   @blocks.all? { |block| block.is_a?(Block) } &&
     @blocks.all?(&:valid?) &&
     @blocks.each_cons(2).all? { |a, b| a.own_hash == b.prev_block_hash } &&
     all_spends_valid?
 end

 def all_spends_valid?
   compute_balances do |balances, from, to|
     return false if balances.values_at(from, to).any? { |bal| bal < 0 }
   end
   true
 end

 def compute_balances
   genesis_txn = @blocks.first.txn
   balances = { genesis_txn.to => genesis_txn.amount }
   balances.default = 0 # New people automatically have balance of 0
   @blocks.drop(1).each do |block| # Ignore the main block
     from = block.txn.from
     to = block.txn.to
     amount = block.txn.amount

     balances[from] -= amount
     balances[to] += amount
     yield balances, from, to if block_given?
   end
   balances
 end

 def to_s
   @blocks.map(&:to_s).join("\n")
 end
end

All the code is same we had learned in part 5. We have now only created a class named transaction. The purpose of transaction class is to add public and private keys whenever any transaction is made. In simple words we are now signing our message or transaction amount before sending it to our required person for verification process (Learnt in part 3). 

Additionally we also need to check before sending the amount that amount is valid or not. For this purpose we are adding additional verification in our valid method of Blockchain class.


def valid?
   @blocks.all? { |block| block.is_a?(Block) } &&
     @blocks.all?(&:valid?) &&
     @blocks.each_cons(2).all? { |a, b| a.own_hash == b.prev_block_hash } &&
     all_spends_valid?
 end

 def all_spends_valid?
   compute_balances do |balances, from, to|
     return false if balances.values_at(from, to).any? { |bal| bal < 0 }
   end
   true
 end

def compute_balances
   genesis_txn = @blocks.first.txn
   balances = { genesis_txn.to => genesis_txn.amount }
   balances.default = 0 # New people automatically have balance of 0
   @blocks.drop(1).each do |block| # Ignore the main block
     from = block.txn.from
     to = block.txn.to
     amount = block.txn.amount

     balances[from] -= amount
     balances[to] += amount
     yield balances, from, to if block_given?
   end
   balances
 end

In the above code we check that balance is not zero and also not greater than current balance.

B- Howcoin.rb

Howcoin.rb is the same class as we have developed in part 2 with some additional code.


require 'sinatra'
require 'colorize'
require 'active_support/time'
require 'yaml'
require_relative 'block'
require_relative 'client'
require_relative 'helpers'

PORT, PEER_PORT = ARGV.first(2)
set :port, PORT

$PEERS = ThreadSafe::Array.new([PORT])
PRIV_KEY, PUB_KEY = PKI.generate_key_pair

if PEER_PORT.nil?
 # You are the progenitor!
 $BLOCKCHAIN = BlockChain.new(PUB_KEY, PRIV_KEY)
else
 # You're just joining the network.
 $PEERS << PEER_PORT
end

every(3.seconds) do
 $PEERS.dup.each do |port|
   next if port == PORT

   puts "Gossiping about blockchain and peers with #{port.to_s.green}"
   gossip_with_peer(port)
 end
 render_state
end

# @param blockchain
# @param peers
post '/gossip' do
 their_blockchain = YAML.load(params['blockchain'])
 their_peers = YAML.load(params['peers'])
 update_blockchain(their_blockchain)
 update_peers(their_peers)
 YAML.dump('peers' => $PEERS, 'blockchain' => $BLOCKCHAIN)
end

# @param to (port_number)
# @param amount
post '/send_money' do
 to = Client.get_pub_key(params['to'])
 amount = params['amount'].to_i
 $BLOCKCHAIN.add_to_chain(Transaction.new(PUB_KEY, to, amount, PRIV_KEY))
 'OK. Block mined!'
end

get '/pub_key' do
 PUB_KEY
end

Here we have created public and private key pairs for newly created users and if there is first block creating this blockchain network, he will create new blockchain with this code


if PEER_PORT.nil?
 # You are the progenitor!
 $BLOCKCHAIN = BlockChain.new(PUB_KEY, PRIV_KEY)
else
 # You're just joining the network.
 $PEERS << PEER_PORT
end

C- Helper.rb

This class has all the helper methods for this network.


Thread.abort_on_exception = true

def every(seconds)
 Thread.new do
   loop do
     sleep seconds
     yield
   end
 end
end

HUMAN_READABLE_NAMES = File.readlines('names.txt').map(&:chomp)

def human_readable_name(pub_key)
 pk_hash = Digest::SHA256.hexdigest(pub_key).to_i(16)
 HUMAN_READABLE_NAMES[pk_hash % HUMAN_READABLE_NAMES.length]
end

def readable_balances
 return "" if $BLOCKCHAIN.nil?
 $BLOCKCHAIN.compute_balances.map do |pub_key, balance|
    "#{human_readable_name(pub_key).red} currently has #{balance.to_s.green}"
 end.join("\n")
end

def render_state
 system 'clear'
 puts Time.now.to_s.split[1].light_blue
 puts "My blockchain: " + $BLOCKCHAIN.to_s
 puts "Blockchain length: " + ($BLOCKCHAIN || []).length.to_s
 puts "PORT: #{PORT}"
 puts "My human-readable name: " + human_readable_name(PUB_KEY).red
 puts "My peers: " + $PEERS.sort.join(", ").to_s.yellow
 puts readable_balances
end

def gossip_with_peer(port)
 gossip_response = Client.gossip(port, YAML.dump($PEERS), YAML.dump($BLOCKCHAIN))
 parsed_response = YAML.load(gossip_response)
 their_peers = parsed_response['peers']
 their_blockchain = parsed_response['blockchain']

 update_peers(their_peers)
 update_blockchain(their_blockchain)
rescue Faraday::ConnectionFailed => e
 $PEERS.delete(port)
end

def update_blockchain(their_blockchain)
 return if their_blockchain.nil?
 return if $BLOCKCHAIN && their_blockchain.length <= $BLOCKCHAIN.length
 return unless their_blockchain.valid?

 $BLOCKCHAIN = their_blockchain
end

def update_peers(their_peers)
 $PEERS = ($PEERS + their_peers).uniq
end



D- Pki.rb

This is exactly the same as we already developed.


require 'openssl'
require 'base64'

class PKI
 def self.generate_key_pair
   key_pair = OpenSSL::PKey::RSA.new(2048)
   private_key, public_key = key_pair.export, key_pair.public_key.export
 end

 def self.sign(plaintext, raw_private_key)
   private_key = OpenSSL::PKey::RSA.new(raw_private_key)
   Base64.encode64(private_key.private_encrypt(plaintext))
 end

 def self.plaintext(ciphertext, raw_public_key)
   public_key = OpenSSL::PKey::RSA.new(raw_public_key)
   public_key.public_decrypt(Base64.decode64(ciphertext))
 end

 def self.valid_signature?(message, ciphertext, public_key)
   message == plaintext(ciphertext, public_key)
 end
end

E- Client.rb


require 'faraday'

class Client
 URL = 'http://localhost'

 def self.gossip(port, peers, blockchain)
   begin
     Faraday.post("#{URL}:#{port}/gossip", peers: peers, blockchain: blockchain).body
   rescue Faraday::ConnectionFailed => e
     raise
   end
 end

 def self.get_pub_key(port)
   Faraday.get("#{URL}:#{port}/pub_key").body
 end

 def self.send_money(port, to, amount)
   Faraday.post("#{URL}:#{port}/send_money", to: to, amount: amount).body
 end
end

F- names.txt

This file contains random names who are creating blockchain. You can download it here.

Running The Code

Here we are now at the final stage and lets run our code with the following command.

ruby howcoin.rb 1111

You will get the following output after running this code

It shows that a blockchain is created for a person “Daloris”

Let’s open second window and run the following command

ruby howcoin.rb 2222 1111

You will connect two users on on different servers now

Perform Transactions

In this final step we are now performing transactions between these users.

For this purpose we need a public key from users to whom we need to perform transactions.

We are now loading client.rb to perform actions by following command

irb

load ‘client.rb’

To send money perform the following command

Client.send_money(1111, 2222, 5)

Here you have successfully sent money from “Daloris” to “Ninetta” of amount 5 coins. In this case one block is mined from “Daloris” towards “Ninetta”. 

Beauty of Crypto Currency

Let’s test it by sending fake money from “Ninetta” to “Daloris”. Assume Ninetta is sending 100 coins to Daloris see what happens now.

 

You can check that Ninetta balance goes to negative but no coin received by Daloris. No other person affects this fake transaction.

Conclusion

At the end Congrats We have now developed our first crypto currency simple system. Remember this is just a simple system there is a lot you need to learn in the crypto world. Sync with FirstHow to learn more in crypto world.

Here you can download complete source code of each step