[Ruby] How to build the simplest blockchain in Ruby

6 minute read

In this article, I’ll explore how to build a simple working blockchain demo using Ruby.

*This blog is a translation from the English version. You can check the original from here. Some machine translations are used. If you find any mistakes in the translation, we would appreciate it if you could point it out. *

Stage 1: Remittance

At this stage, balance confirmation and transfer will be carried out. Transfers are additions or subtractions that are performed based on your account balance.

The HTTP protocols GET and POST are the best ways to implement this functionality. GET gets data from the server and POST modifies the data on the server.

Here, the UI display does not require the HTML protocol. You can use Ruby’s web framework Sinatra to organize URLs and related methods, and use UE to view transfer information on the command line.

The client-side method and the server-side URL are very simple.

Client: client.rb

def create_user(name) …end

  def get_balance(user) …end

  def transfer(from, to, amount) ... end

Server: haseebcoin.rb

get "/balance" ... end

  post "/users" ... end

  post "/transfers" ... end

Knowledge required for this layer: ruby, HTTP GET, POST, Sinatra

Stage 2: Building a gossip network

Blockchain has a decentralized structure called “Gossip Protocol”. “Gossip” here is not a rumor, but information that is spread over a distributed network.

Build a gossip network where movie names are exchanged.

client.rb sends a message to the specified port.

def self.gossip(port, state)
  ...

  Faraday.post("#{URL}:#{port}/gossip", state: state).body
 
  ...

end

gossip.rb receives two parameters, source port and destination port. It exchanges information through specific ports on the source side, such as ports 1111 and 2222.

In a real distributed network, the two ports are essentially two network nodes. Exchanging information between different local ports represents communication between different nodes in the simulated network.

At each node Every 3 seconds tell the name of your favorite movie.

every(3.seconds) do

  …

  gossip_response = Client.gossip(port, JSON.dump(STATE))

  update_state(JSON.load(gossip_response))

  ...

end

Change your favorite movie name every 8 seconds.

every(8.seconds) do

  …

  update_state(PORT => [@favorite_movie, @version_number])

  ...

end

The server receives and processes the data.

post'/gossip' do

  …

  update_state(JSON.load(their_state))

  …

end

With a network of four

  1. Run gossip.rb 1111 on the first node. The first node speaks the name of its favorite movie on port 1111.
  2. Run gossip.rb 2222 1111. The second node speaks its favorite movie name on port 2222 to the first node (port 1111). 3, gossip.rb 3333 2222. The third node speaks the favorite movie name from port 3333 to the second node (port 2222). Run 4, gossip.rb 4444 3333. The fourth node speaks its favorite movie name from port 4444 to the third node (port 3333).

After running for a while, the four nodes finally get the peer end information, and the data keeps changing. This is a simple Gossip network.

Stage 3: Data encryption and decryption

Top-level encryption algorithms are the foundation of blockchain. At this layer, asymmetric cryptography is used to implement blockchain accounts. The RSA algorithm can generate public and private keys and enforce asymmetric encryption.

def generate_key_pair …end
def sign(plaintext, raw_private_key) ... end

Thanks to the OpenSSL module of the Ruby language, you can quickly implement asymmetric encryption and signature verification. On the blockchain, the public key is your account and your private key is your password. Each key pair will be one blockchain account.

Decrypt the ciphertext.

def plaintext(ciphertext, raw_public_key) …end

Check if the ciphertext is a message.

def valid_signature?(message, ciphertext, public_key) …end

Required knowledge of this layer: Asymmetric encryption algorithm

Stage 4: Data mining

At this stage, Proof of Work is implemented and blocks for the blockchain are generated. This is a time consuming and tedious process. Hash functions are lossy and have no conflicts. The calculation process is simple. The result is obtained only by performing a hash operation on the input.

The input is information relating to remittance, such as the remittance amount, the name of the remitter, and the name of the recipient. There are various algorithms for hash operations.

The SHA256 algorithm is used here.

def hash(message) …end

Hashing the same information gives different results each time. The calculation is continued until the obtained result satisfies the characteristics such as “starting from a number of 0’s”.

Check if the result starts with a few digits 0.

def is_valid_nonce?(nonce, message)

  hash(message + nonce).start_with?("0" * NUM_ZEROES)

end

It is not easy to perform the work to satisfy the above conditions. It consumes a lot of time. All such work is called mining.

def find_nonce(message)

  …

  until is_valid_nonce?(nonce, message)

  ...

end

The input contains the result of the previous hash operation. Therefore, each hash operation is affected by the previous hash operation. In other words, this is a chain structure. This is why it is called a blockchain.

Stage 5: Longest chain rule

At this stage, the first block is initialized and the blockchain structure is generated accordingly, forming the blockchain. Blockchains are stored in an Array structure. The block must be validated during storage.

Initialize the block.

def initialize(prev_block, msg)

  @msg = msg

  @prev_block_hash = prev_block.own_hash if prev_block
  
  mine_block!
    
end

The most rewarding task during mining is finding the nonce.

def mine_block!

  @nonce = calc_nonce

  @own_hash = hash(full_block(@nonce))

end

Complete blocks are compressed in this way.

def full_block(nonce)

  [@msg, @prev_block_hash, nonce].compact.join

end

Initialize the blockchain: class BlockChain

Just save using Array!

def initialize(msg)
  
  @blocks = []

  @blocks << Block.new(nil, msg)

end

Add blocks to the chain. The entire blockchain is continuously growing.

def add_to_chain(msg)

  @blocks << Block.new(@blocks.last, msg)

  puts @blocks.last

end

You need to rigorously verify that the block is healthy.

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}
    
end

Stage 6. Piece combination

Lastly, Blockchain works its magic through harmonious collaboration with all components in the network. In the first phase, the transfer is a transaction class and requires the use of a private key to sign the information.

@signature = PKI.sign(message, priv_key)

The miner’s reward for the first block is 500,000 silver coins.

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

Please check if the spending charged to your account is valid.

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

Add unknown node $PEERS to keep the network growing.

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

Data processing between the nodes reads and updates the blockchain and PEER.

# @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

The processing of the received block pays attention to whether the chain is long.

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

Update PEER until new.

def update_peers(their_peers)

  $PEERS = ($PEERS + their_peers).uniq

end

When sending money, we will get the recipient’s pub_key and send it via the sender’s pub_key.

# @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

Put the blockchain into the gossip network and assemble all functional components. This is completed. You have successfully created a blockchain.

More details about this demo can be found on Github: https://github.com/Haseeb-Qureshi/lets-build-a-blockchain

For more information on blockchain and other innovative technologies, please visit www.alibabacloud.com.

Alibaba Cloud is a No. 1 cloud infrastructure operator in the Asia Pacific region (2019 Gartner) with two data centers in Japan and more than 60 availability zones in the world. Click here for more information about Alibaba Cloud. Alibaba Cloud Japan Official Page