CommonLounge Archive

Full Stack Hello World Voting Ethereum Dapp Tutorial — Part 1

November 15, 2017

In the last post, we explained the high level architecture of Ethereum platform comparing it to a web application. As a developer, the best way to learn any new technology is by diving in and building toy applications. In this post, let’s build a simple ‘Hello World!’ application which is a Voting application.

The application is extremely simple, all it does is initialize a set of contestants, let anyone vote for the candidates and display the total votes received by each candidate. The goal is not to just code an application but to learn the process of compiling, deploying and interacting with it.

We have deliberately avoided using any DApp frameworks to build this application because the frameworks abstract away lot of the details and you fail to understand the internals of the system. Also, when you do use a framework, you will have more appreciation for all the heavy lifting the framework does for you!

The goal of this exercise is to:

  1. Set up the development environment.
  2. Learn the process of writing a contract, compiling it and deploying it in your development environment.
  3. Interact with the contract on the blockchain through a nodejs console.
  4. Interact with the contract through a simple web page to display the vote counts and vote for candidates through the page.

The entire application set up and build was done on a fresh installation of ubuntu 16.04 xenial. We have set up and tested the application on MacOS as well.

This is how to visualize this application we are going to build.

1. Setting up the development environment

Instead of developing the app against the live blockchain, we will use an in- memory blockchain (think of it as a blockchain simulator) called testrpc. In Part 2 of the tutorial, we will interact with the real blockchain. Below are the steps to install testrpc, web3js and start the test blockchain on a linux operating system. The exact same instructions work on macos as well. For windows, you can follow the instructions here (Thanks Prateesh!).

Note: This tutorial currently works with web3js version 0.20.1. Instead of running npm install ethereumjs-testrpc web3 run npm install ethereumjs-testrpc web3@0.20.1 . We will update the tutorial once web3js 1.0 stable is released

Notice that the testrpc creates 10 test accounts to play with automatically. These accounts come preloaded with 100 (fake) ethers.

2. Simple voting contract

We are going to use the solidity programming language to write our contract. If you are familiar with object oriented programming, learning to write solidity contracts should be a breeze. We will write a contract (think of contract as a class in your favorite OOP language) called Voting with a constructor which initializes an array of candidates. We will write 2 methods, one to return the total votes a candidate has received and another method to increment vote count for a candidate.

Note: The constructor is invoked once and only once when you deploy the contract to the blockchain. Unlike in the web world where every deploy of your code overwrites the old code, deployed code in the blockchain is immutable. i.e, If you update your contract and deploy again, the old contract will still be in the blockchain untouched along with all the data stored in it, the new deployment will create a new instance of the contract.

Below is the voting contract code with inline comment explanation:

pragma solidity ^0.4.18;
// We have to specify what version of compiler this code will compile with
contract Voting {
  /* mapping field below is equivalent to an associative array or hash.
  The key of the mapping is candidate name stored as type bytes32 and value is
  an unsigned integer to store the vote count
  */
  mapping (bytes32 => uint8) public votesReceived;
  /* Solidity doesn't let you pass in an array of strings in the constructor (yet).
  We will use an array of bytes32 instead to store the list of candidates
  */
  bytes32[] public candidateList;
  /* This is the constructor which will be called once when you
  deploy the contract to the blockchain. When we deploy the contract,
  we will pass an array of candidates who will be contesting in the election
  */
  function Voting(bytes32[] candidateNames) public {
    candidateList = candidateNames;
  }
  // This function returns the total votes a candidate has received so far
  function totalVotesFor(bytes32 candidate) view public returns (uint8) {
    require(validCandidate(candidate));
    return votesReceived[candidate];
  }
  // This function increments the vote count for the specified candidate. This
  // is equivalent to casting a vote
  function voteForCandidate(bytes32 candidate) public {
    require(validCandidate(candidate));
    votesReceived[candidate] += 1;
  }
  function validCandidate(bytes32 candidate) view public returns (bool) {
    for(uint i = 0; i < candidateList.length; i++) {
      if (candidateList[i] == candidate) {
        return true;
      }
    }
    return false;
  }
}

Copy the above code to a file named Voting.sol in the helloworldvoting directory. Now let’s compile the code and deploy it to testrpc blockchain.

To compile the solidity code, we will first install npm module called solc

mahesh@projectblockchain:~/hello_world_voting$ npm install solc

We will use this library within a node console to compile our contract. Remember from the previous article, web3js is a library which lets you interact with the blockchain through RPC. We will use that library to deploy our application and interact with it.

To begin this process, initialize a new testrpc server by running this command:

node_modules/.bin/testrpc

Leave the process running and open a new terminal window to run the remaining command.

NOTE:Skipping this step will lead to the terminal throwing an error when you run “web3.eth.accounts”

Next, get in to the node console and initialize the solc and web3 objects.

node
mahesh@projectblockchain:~/hello_world_voting$ node
> Web3 = require('web3')
> web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));

To make sure web3 object is initialized and can communicate with the blockchain, let’s query all the accounts in the blockchain. You should see a result like below:

> web3.eth.accounts
['0x9c02f5c68e02390a3ab81f63341edc1ba5dbb39e',
'0x7d920be073e92a590dc47e4ccea2f28db3f218cc',
'0xf8a9c7c65c4d1c0c21b06c06ee5da80bd8f074a9',
'0x9d8ee8c3d4f8b1e08803da274bdaff80c2204fc6',
'0x26bb5d139aa7bdb1380af0e1e8f98147ef4c406a',
'0x622e557aad13c36459fac83240f25ae91882127c',
'0xbf8b1630d5640e272f33653e83092ce33d302fd2',
'0xe37a3157cb3081ea7a96ba9f9e942c72cf7ad87b',
'0x175dae81345f36775db285d368f0b1d49f61b2f8',
'0xc26bda5f3370bdd46e7c84bdb909aead4d8f35f3']

To compile the contract, load the code from Voting.sol in to a string variable and compile it.

> code = fs.readFileSync('Voting.sol').toString()
> solc = require('solc')
> compiledCode = solc.compile(code)

When you compile the code successfully and print the ‘contract’ object (just type compiledCode in the node console to see the contents), there are two important fields you will notice which are important to understand:

  1. compiledCode.contracts[‘:Voting’].bytecode: This is the bytecode you get when the source code in Voting.sol is compiled. This is the code which will be deployed to the blockchain.
  2. compiledCode.contracts[‘:Voting’].interface: This is an interface or template of the contract (called abi) which tells the contract user what methods are available in the contract. Whenever you have to interact with the contract in the future, you will need this abi definition. You can read more details about ABI here

Let’s now deploy the contract. You first create a contract object (VotingContract below) which is used to deploy and initiate contracts in the blockchain.

> abiDefinition = JSON.parse(compiledCode.contracts[':Voting'].interface)
> VotingContract = web3.eth.contract(abiDefinition)
> byteCode = compiledCode.contracts[':Voting'].bytecode
> deployedContract = VotingContract.new(['Rama','Nick','Jose'],{data: byteCode, from: web3.eth.accounts[0], gas: 4700000})
> deployedContract.address
> contractInstance = VotingContract.at(deployedContract.address)

VotingContract.new above deploys the contract to the blockchain. The first argument is an array of candidates who are competing in the election which is pretty straightforward. Let’s see what are all in the hash in the second argument:

  1. data: This is the compiled bytecode which we deploy to the blockchain.
  2. from: The blockchain has to keep track of who deployed the contract. In this case, we are just picking the first account we get back from calling web3.eth.accounts to be the owner of this contract (who will deploy it to the blockchain). Remember that web3.eth.accounts returns an array of 10 test accounts testrpc created when we started the test blockchain. In the live blockchain, you can not just use any account. You have to own that account and unlock it before transacting. You are asked for a passphrase while creating an account and that is what you use to prove your ownership of that account. Testrpc by default unlocks all the 10 accounts for convenience.
  3. gas: It costs money to interact with the blockchain. This money goes to miners who do all the work to include your code in the blockchain. You have to specify how much money you are willing to pay to get your code included in the blockchain and you do that by setting the value of ‘gas’. The ether balance in your ‘from’ account will be used to buy gas. The price of gas is set by the network.

We have now deployed the contract and have an instance of the contract (variable contractInstance above) which we can use to interact with the contract. There are hundreds of thousands of contracts deployed on the blockchain. So, how do you identify your contract in that blockchain? Answer: deployedContract.address. When you have to interact with your contract, you need this deployed address and abi definition we talked about earlier.

3. Interact with the contract in the nodejs console

> contractInstance.totalVotesFor.call('Rama')
{ [String: '0'] s: 1, e: 0, c: [ 0 ] }
> contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]})
'0xdedc7ae544c3dde74ab5a0b07422c5a51b5240603d31074f5b75c0ebc786bf53'
> contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]})
'0x02c054d238038d68b65d55770fabfca592a5cf6590229ab91bbe7cd72da46de9'
> contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]})
'0x3da069a09577514f2baaa11bc3015a16edf26aad28dffbcd126bde2e71f2b76f'
> contractInstance.totalVotesFor.call('Rama').toLocaleString()
'3'

Try the above commands in your node console and you should see the vote count increment. Every time you vote for a candidate, you get back a transaction id: Example: ‘0xdedc7ae544c3dde74ab5a0b07422c5a51b5240603d31074f5b75c0ebc786bf53’ above). This transaction id is the proof that this transaction occurred and you can refer back to this at any time in the future. This transaction is immutable. This immutability is one of the big advantages of blockchains such as Ethereum. In future tutorials, we will build applications leveraging this immutability.

4. Webpage to connect to the blockchain and vote

Now that most of the work is done, all we have to do now is create a simple html file with candidate names and invoke the voting commands (which we already tried and tested in the nodejs console) in a js file. Below you can find the html code and the js file. Drop both of them in the helloworldvoting directory and open the index.html in your browser.

index.html

<!DOCTYPE html>
<html>
<head>
  <title>Hello World DApp</title>
  <link href='https://fonts.googleapis.com/css?family=Open+Sans:400,700' rel='stylesheet' type='text/css'>
  <link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet' type='text/css'>
</head>
<body class="container">
  <h1>A Simple Hello World Voting Application</h1>
  <div class="table-responsive">
    <table class="table table-bordered">
      <thead>
        <tr>
          <th>Candidate</th>
          <th>Votes</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>Rama</td>
          <td id="candidate-1"></td>
        </tr>
        <tr>
          <td>Nick</td>
          <td id="candidate-2"></td>
        </tr>
        <tr>
          <td>Jose</td>
          <td id="candidate-3"></td>
        </tr>
      </tbody>
    </table>
  </div>
  <input type="text" id="candidate" />
  <a href="#" onclick="voteForCandidate()" class="btn btn-primary">Vote</a>
</body>
<script src="https://cdn.rawgit.com/ethereum/web3.js/develop/dist/web3.js"></script>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script src="./index.js"></script>
</html>

index.js

web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
abi = JSON.parse('[{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"totalVotesFor","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"validCandidate","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"votesReceived","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"x","type":"bytes32"}],"name":"bytes32ToString","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"candidateList","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"voteForCandidate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"contractOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"inputs":[{"name":"candidateNames","type":"bytes32[]"}],"payable":false,"type":"constructor"}]')
VotingContract = web3.eth.contract(abi);
// In your nodejs console, execute contractInstance.address to get the address at which the contract is deployed and change the line below to use your deployed address
contractInstance = VotingContract.at('0x2a9c1d265d06d47e8f7b00ffa987c9185aecf672');
candidates = {"Rama": "candidate-1", "Nick": "candidate-2", "Jose": "candidate-3"}
function voteForCandidate() {
  candidateName = $("#candidate").val();
  contractInstance.voteForCandidate(candidateName, {from: web3.eth.accounts[0]}, function() {
    let div_id = candidates[candidateName];
    $("#" + div_id).html(contractInstance.totalVotesFor.call(candidateName).toString());
  });
}
$(document).ready(function() {
  candidateNames = Object.keys(candidates);
  for (var i = 0; i < candidateNames.length; i++) {
    let name = candidateNames[i];
    let val = contractInstance.totalVotesFor.call(name).toString()
    $("#" + candidates[name]).html(val);
  }
});

If you remember, we said earlier we will need the abi and the address to interact with any contract. You can see above in the index.js file how they are used to interact with the contract.

This is what you should see when you open the index.html file in your browser.

If you are able to enter the candidate name in the text box and vote and see the vote count increment, you have successfully created your first application! Congratulations! To summarize, you set up your dev environment, coded a simple contract, compiled and deployed the contract on the blockchain and interacted with it via nodejs console and then through a webpage. Now would be a good time to pat yourself on the back if you haven’t already :)

In Part 2, we will deploy this contract to the public test network so the entire world can see it and vote for a candidate. We will also get more sophisticated and use the truffle framework for development (and not have to use the nodejs console to manage the entire process).


© 2016-2022. All rights reserved.