Blockchain App with Ruby

   Back to list

At the end of 2017, the world got mad about cryptocurrencies. The value of Bitcoin (BTC) or Ether (ETH) multiplied and reached an astronomical amount of money. People who had never invested, now compulsively bought and traded coins or tokens in the exchange.

But for me the biggest discovery related to cryptocurrencies is blockchain – technology on which Bitcoins, Ether and other ones depend, a technology which could change our finances, politics (eg. voting), and of course, IT systems. Blockchain allows us to create applications which do not depend on the centralized resource.

This tutorial shows how to work with blockchain using Ruby and how to create a decentralized application based on Ruby on Rails. It doesn’t cover deep knowledge of blockchain, only a basic introduction that helps to understand how it works and how to design dApp architecture.

Decentralized application

Before we start implementing our application we need to understand the differences between a common, centralized application and designing a blockchain-based system. In a standard case, the main part is a server which holds the code of the app and connects to a database which stores all necessary information. User interacts with the application via UI which is a part of (or strictly connected with) the server.

In a decentralized app, we don’t have server side because we don’t want to store any data on unsafe servers. The code is stored in a blockchain and users connect directly to it via UI, so all actions, like creating and signing transaction should be executed client side. The interface only allows calls with smart contract methods. The client-side application needs to know the address of smart contract in blockchain.

Ok… so why does this tutorial show how to connect blockchain with Ruby on Rails? Didn’t I just mention that decentralized apps don’t include server-backend-side? Yes, it’s true. Why we do it? Because we can 🙂 It’s all about safety and safety in the Blockchain world is mostly about keeping your private key safe.

When you only use a UI to make transactions with blockchain, you don’t send your private key anywhere, with a server you need to pass it using a form. The same is true when you log in to your bank, so why did I say it’s unsafe? The only difference is that bank is a trusted institution, it adds a sense of responsibility to your transactions. When we’re dealing with dApp you don’t really know what will happen to the key you’re sending.

Creating an application

In this tutorial, we will create a lunch voting application. Let’s consider the requirements:

  • Administrator should be able to create voting, choose which users could vote and meal types to vote for.
  • Users can only vote (once) and see results. Each user is obliged to have an account on blockchain (a wallet).

First, we need to make some preparations before we start coding. To test and set up our application, we start node on our machine. I chose Parity because it is supported by ethereum.rb – gem which we will use and also it’s got a built-in UI to interact with blockchain. At the ethereum.rb GitHub repository you can find detailed instruction on how to install parity:

https://github.com/EthWorks/ethereum.rb/blob/master/PREREQUISITES.md#installing-prerequisites

To run dev (local) blockchain use command parity --chain=dev. Then visit localhost:8180 you should see the parity interface. We will need two wallets for both admin and user account. You can create it using the parity interface. To get your private key go to account manager, choose the account and click Export. You’ll get your wallet in the form of a JSON file.

To encrypt the key, use the rails console and run the following command:

decrypted_key = Eth::Key.decrypt File.read('./some/path.json'), 'account_password'.

Please store your private key in a safe place! Yes, I know it’s the DEV instance and there is no risk, but good habits are hard to create and I’d rather see you paranoid than complaining that you shared your real private key by mistake.

After setting up the environment, create a smart contract which is the core of every decentralized, blockchain application. I don’t want to focus on creating smart contracts as there are lots of articles, tutorials on the web about it. Also you can find documentation for Solidity with examples on official Ethereum site.

pragma solidity ^0.4.18;

contract VotingContract {

  // amount votes for meal types
  mapping (bytes32 => uint8) public votes;
  // which users've already voted
  mapping (address => bool) public usersCompleted;

  // list of meal types available in voting
  bytes32[] public mealTypesList;
  // list of users who are permitted to vote
  address[] public usersList;

  // checks if has user already voted
  modifier checkIfVoted {
    require(!usersCompleted[msg.sender]);
    _;
  }

  // checks if user is permitted to vote
  modifier validUser {
    for(uint i = 0; i < usersList.length; i++) {
      if (usersList[i] == msg.sender) {
        _;
      }
    }
  }

  // constructor, set permitted users and available meal types
  function VotingContract(bytes32[] mealTypes, address[] users) public {
    mealTypesList = mealTypes;
    usersList = users;
  }

  // returns amount of votes for meal type
  function totalVotesFor(bytes32 mealType) view public returns (uint8) {
    return votes[mealType];
  }

  // votes for meal type, it also sets that user's voted
  function voteForMealType(bytes32[] choices) public checkIfVoted validUser {
    usersCompleted[msg.sender] = true;
    for(uint i = 0; i < choices.length; i++) {
      votes[choices[i]] += 1;
    }
  }
}

Our contract, VotingContract, stores two mappings (is like hashes in Ruby):

  • votes for meals – incrementable counters
  • boolean value if the user has already voted (we don’t allow to vote more than once).

It also contains a list of available meals and user, both are set in a constructor.

We have two main methods, one to vote and one to get results, as well as modifiers that are used to check if users could take part in voting and didn’t vote yet. Typically, the contract should be placed in the app/contracts/ directory in our application.

Let’s do some Ruby

Finally we can start with coding in Ruby! I did some research into Ruby & Ethereum gems and I chose ethereum.rb. It’s my personal choice, maybe I got attracted by the number of stars on GitHub 😉

This gem allows us to use all basic contract actions on blockchain. Additionally, we will use eth gem, which helps to build and sign ethereum transactions.

So add following lines to your Gemfile and run bundle install.

gem 'ethereum.rb'
gem 'eth'

Application skeleton

We need two models in our application: User and Voting.

For the first one, I recommend you create it by using devise and then add a wallet_address field which should be filled during registration. Remember to create an admin user, you could add the field admin to your model and set it as true in the console.

The voting model will store information about created votes: the address of deployed smart contract and the meals we can choose during voting. Between the User and Voting there is a many-to-many relation: many users can take part in many votings.

You may find the complete code of this application in this GitHub repository:

https://github.com/nopio/ethereum_voting_example

Play with blockchain

After we created application skeleton let’s consider actions which should be invoke to deploy and interact with contract.

Since the contract cannot be changed after it’s deployed to the blockchain, all its data needs to be set before that happens. Therefore, when an admin creates a vote, he needs to choose users and meals upfront. After the contract is created and deployed, we need to store its blockchain address in our local database. Storing this address is very important, it’s our pointer to the place in blockchain, with this we could make a transaction and fetch date stored in voting contract like amount of votes.

# creates and deploy contract with choosen meals and users. Contract is signed by users private key
def deploy_contract
  contract = Ethereum::Contract.create(file: "#{Dir.pwd}/app/contracts/VotingContract.sol")
  key = Eth::Key.new(priv: @private_key)
  contract.key = key
  # deploy_and_wait is waiting until transaction is mined and returns contract address
  contract.deploy_and_wait(@meals, @users)
end

To deploy a contract, we need to do a few things:

First, create Ethereum::Contract with parameters: file – path of our contract file (ethereum.rb allows to create contract from abi, so instead of contract file we pass path to abi file).

The next step to create a blockchain transaction and sign it with our (user which makes a transaction) private key.

Finally, we deploy our contract on blockchain. I use the deploy_and_wait function because it waits until the block will be mined. In this case, it’s recommended to use this function to get the contract address. Note that this process may take a long time when interacting with a real network, so in a real implementation, we’d like to monitor it while it’s running in the background.

We’ve just deployed the contract on blockchain – good job. We could now interact with it and make transactions. Calling smart contract methods (the view ones, when we fetch data) it’s very simple. All we need is to create an instance of the contract with the address of our deployed smart contract and call the function total_votes_for which returns the amount of votes for a meal. Note that in our smart contract definition there is the function totalVotesFor (camel case) and using the ethereum.rb gem we call it snake case.

# calls method total_votes_for on contract which is already stored in blockchain
def get_total_votes(meal)
  voting_contract = Ethereum::Contract.create(file: "#{Dir.pwd}/app/contracts/VotingContract.sol", address: @address)
  voting_contract.call.total_votes_for(meal)
end

The only difference between making transactions is that each transaction should be signed by a user. So similar to deploying the contract we create a contract instance and sign it. Then we could make the transaction and vote for the meal type.

# creates transaction vote_for_meal_type with meals for which votes. Transaction is called on contract which exists in 
# blockchain and it is signed by user private key
def vote
  voting_contract = Ethereum::Contract.create(file: "#{Dir.pwd}/app/contracts/VotingContract.sol", address: @address)
  key = Eth::Key.new(priv: @private_key)
  voting_contract.key = key
  # for create transaction use transact
  voting_contract.transact.vote_for_meal_type(@meals)
end

Summary

We created a simple voting application using ruby on rails and blockchain. It’s fine, works and looks good, but as I mentioned earlier is not perfect and not following the blockchain good practices. I warned you to create a secure application in ruby – or rather to avoid creating blockchain apps with server side processing! So why I did I make this tutorial? Why did someone create this gem?

I have at least two use cases for these kinds of apps. To be honest, in our work there is a lot of applications which we create internally for our company, friends etc. If you trust the institution which is responsible for the application it could be a good idea to create it in ruby – it’s easier. What’s more, you should use special wallets with a small amount of ETH, or maybe with tokens (special tokens only for your company, institution, some groups) than you won’t lose your life savings.

The other purpose is to build an admin interface for creating and deploying smart contracts, such as the admin in our application. In this approach, only one person has access to the application so he is responsible for the application and his private key. It is an easier way than using the command line especially when you need to parametrize each contract and the person who does it is not a very technical one.

I think you also have lot of ways to keep security in mind. I hope the knowledge you gained here will help you understand how blockchain and decentralized applications work.

Don’t hesitate to ask if you’ve got any questions about this tutorial.

Send this to a friend