CommonLounge Archive

Full Stack Hello World Voting Ethereum Dapp Tutorial — Part 2

November 15, 2017

In Part 1 of this tutorial, we built a simple voting application in our development environment using testrpc. Now, let’s get this application on the real blockchain. Ethereum has 2 public blockchains.

  1. Testnet (also called Ropsten): This is a test blockchain. Think of this as a QA or a staging server, it is used for testing purposes only. All the Ether you use on this network is fake.
  2. Mainnet (also called Homestead): This is the blockchain which the entire world transacts on for real. There is real value to the Ether you use on this network.

In this tutorial, we will accomplish the following:

  1. Install geth — the client software used to download the blockchain and run the Ethereum node on your local machine.
  2. Install the Ethereum dapp framework called Truffle which will be used for compiling and deploying our contract.
  3. Make small updates to our Voting application to make it work using truffle.
  4. Compile and deploy the contract to the Ropsten testnet.
  5. Interact with the contract through truffle console and then through a webpage.

1. Install geth and sync the blockchain

We have installed and tested everything on MacOS and Ubuntu. Installation is pretty straightforward:

On Mac:

mahesh@projectblockchain:~$ brew tap ethereum/ethereum
mahesh@projectblockchain:~$ brew install ethereum

On Ubuntu:

mahesh@projectblockchain:~$ sudo apt-get install software-properties-common
mahesh@projectblockchain:~$ sudo add-apt-repository -y ppa:ethereum/ethereum
mahesh@projectblockchain:~$ sudo apt-get update
mahesh@projectblockchain:~$ sudo apt-get install ethereum

You can find installation instructions for various platforms here: https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum

Once you have installed geth, run the below command in your command line console:

mahesh@projectblockchain:~$ geth --testnet --syncmode "fast" --rpc --rpcapi db,eth,net,web3,personal --cache=1024  --rpcport 8545 --rpcaddr 127.0.0.1 --rpccorsdomain "*" --bootnodes "enode://20c9ad97c081d63397d7b685a412227a40e23c8bdc6688c6f37e97cfbc22d2b4d1db1510d8f61e6a8866ad7f0e17c02b14182d37ea7c3c8b9c2683aeb6b733a1@52.169.14.227:30303,enode://6ce05930c72abc632c58e2e4324f7c7ea478cec0ed4fa2528982cf34483094e9cbc9216e7aa349691242576d552a2a56aaeae426c5303ded677ce455ba1acd9d@13.84.180.240:30303"

This will start the Ethereum node, connect to other peer nodes and start downloading the blockchain. The time it takes to download the blockchain depends on various factors like your internet connection speed, RAM on your computer, type of hard drive etc. It took me 10–15 minutes on a machine which has 8GB RAM and 50Mbps connection.

In the console where you have geth running, you will see the output like below. Look for the block number which is in bold. When your blockchain is fully sync’d, the block number you see will be close to the block number on this page: https://ropsten.etherscan.io/

I0130 22:18:15.116332 core/blockchain.go:1064] imported   32 blocks,    49 txs (  6.256 Mg) in 185.716ms (33.688 Mg/s). #445097 [e1199364… / bce20913…]
I0130 22:18:20.267142 core/blockchain.go:1064] imported    1 blocks,     1 txs (  0.239 Mg) in  11.379ms (20.963 Mg/s). #445097 [b4d77c46…]
I0130 22:18:21.059414 core/blockchain.go:1064] imported    1 blocks,     0 txs (  0.000 Mg) in   7.807ms ( 0.000 Mg/s). #445098 [f990e694…]
I0130 22:18:34.367485 core/blockchain.go:1064] imported    1 blocks,     0 txs (  0.000 Mg) in   4.599ms ( 0.000 Mg/s). #445099 [86b4f29a…]
I0130 22:18:42.953523 core/blockchain.go:1064] imported    1 blocks,     2 txs (  0.294 Mg) in   9.149ms (32.136 Mg/s). #445100 [3572f223…]

2. Install the Truffle Framework

Install truffle using npm. The truffle version being used in this tutorial is 3.1.1.

npm install -g truffle

*Depending on your system setup, you might have to add a sudo at the beginning.

3. Set up the voting contract

First step is to set up the truffle project:

mahesh@projectblockchain:~$ mkdir voting
mahesh@projectblockchain:~$ cd voting
mahesh@projectblockchain:~/voting$ npm install -g webpack
mahesh@projectblockchain:~/voting$ truffle unbox webpack
mahesh@projectblockchain:~/voting$ ls
README.md               contracts               node_modules            test                    webpack.config.js       truffle.js
app                     migrations              package.json            
mahesh@projectblockchain:~/voting$ ls app/
index.html  javascripts  stylesheets
mahesh@projectblockchain:~/voting$ ls contracts/
ConvertLib.sol  MetaCoin.sol  Migrations.sol
mahesh@projectblockchain:~/voting$ ls migrations/
1_initial_migration.js  2_deploy_contracts.js

As you can see above, truffle creates the necessary files and directories required to run a full stack dapp. Truffle also creates a sample application to get you started (we won’t be using it in this tutorial). You can safely delete the ConvertLib.sol and MetaCoin.sol files in the contracts directory for this project.

It is important to understand the contents of the migrations directory. These migration files are used to deploy the contracts to the blockchain. (If you remember, in the previous post, we used VotingContract.new to deploy the contract to the blockchain, we don’t need to do that anymore). The very first migration 1_initial_migration.js deploys a contract named Migrations to the blockchain and is used to store the latest contract you have deployed. Every time you run the migration, truffle queries the blockchain to get the last contract that has been deployed and then deploys any contracts which haven’t been deployed yet. It then updates the lastcompletedmigration field in the Migrations contract to indicate the latest contract deployed. You can simply think of it as a database table called Migration with a column named lastcompletedmigration which is kept up to date always. You can find more details on the truffle documentation page.

Let’s now update the project with all the code we wrote in the previous tutorial with few changes explained below.

First, copy over the Voting.sol from the previous tutorial to the contracts directory (there are no changes to this file).

Voting.sol

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;
  }
}
mahesh@projectblockchain:~/voting$ ls contracts/
Migrations.sol  Voting.sol

Next, replace the contents of 2deploycontracts.js in the migrations directory with the following:

var Voting = artifacts.require("./Voting.sol");
module.exports = function(deployer) {
  deployer.deploy(Voting, ['Rama', 'Nick', 'Jose'], {gas: 6700000});
};
/* As you can see above, the deployer expects the first argument to   be the name of the contract followed by constructor arguments. In our case, there is only one argument which is an array of
candidates. The third argument is a hash where we specify the gas required to deploy our code. The gas amount varies depending on the size of your contract.
*/

You can also set the gas value as a global setting in truffle.js. Go ahead and add the gas option like below so in the future if you forget to set the gas in specific migration file, it will by default use the global value.

require('babel-register')
module.exports = {
  networks: {
    development: {
      host: 'localhost',
      port: 8545,
      network_id: '*',
      gas: 470000
    }
  }
}

Replace the contents of app/javascripts/app.js with the contents below.

// Import the page's CSS. Webpack will know what to do with it.
import "../stylesheets/app.css";
// Import libraries we need.
import { default as Web3} from 'web3';
import { default as contract } from 'truffle-contract'
/*
 * When you compile and deploy your Voting contract,
 * truffle stores the abi and deployed address in a json
 * file in the build directory. We will use this information
 * to setup a Voting abstraction. We will use this abstraction
 * later to create an instance of the Voting contract.
 * Compare this against the index.js from our previous tutorial to see the difference
 * https://gist.github.com/maheshmurthy/f6e96d6b3fff4cd4fa7f892de8a1a1b4#file-index-js
 */
import voting_artifacts from '../../build/contracts/Voting.json'
var Voting = contract(voting_artifacts);
let candidates = {"Rama": "candidate-1", "Nick": "candidate-2", "Jose": "candidate-3"}
window.voteForCandidate = function(candidate) {
  let candidateName = $("#candidate").val();
  try {
    $("#msg").html("Vote has been submitted. The vote count will increment as soon as the vote is recorded on the blockchain. Please wait.")
    $("#candidate").val("");
    /* Voting.deployed() returns an instance of the contract. Every call
     * in Truffle returns a promise which is why we have used then()
     * everywhere we have a transaction call
     */
    Voting.deployed().then(function(contractInstance) {
      contractInstance.voteForCandidate(candidateName, {gas: 140000, from: web3.eth.accounts[0]}).then(function() {
        let div_id = candidates[candidateName];
        return contractInstance.totalVotesFor.call(candidateName).then(function(v) {
          $("#" + div_id).html(v.toString());
          $("#msg").html("");
        });
      });
    });
  } catch (err) {
    console.log(err);
  }
}
$( document ).ready(function() {
  if (typeof web3 !== 'undefined') {
    console.warn("Using web3 detected from external source like Metamask")
    // Use Mist/MetaMask's provider
    window.web3 = new Web3(web3.currentProvider);
  } else {
    console.warn("No web3 detected. Falling back to http://localhost:8545. You should remove this fallback when you deploy live, as it's inherently insecure. Consider switching to Metamask for development. More info here: http://truffleframework.com/tutorials/truffle-and-metamask");
    // fallback - use your fallback strategy (local node / hosted node + in-dapp id mgmt / fail)
    window.web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
  }
  Voting.setProvider(web3.currentProvider);
  let candidateNames = Object.keys(candidates);
  for (var i = 0; i < candidateNames.length; i++) {
    let name = candidateNames[i];
    Voting.deployed().then(function(contractInstance) {
      contractInstance.totalVotesFor.call(name).then(function(v) {
        $("#" + candidates[name]).html(v.toString());
      });
    })
  }
});

Replace the contents of app/index.html with the following. Even this file is pretty much the same as last chapter except the js file included is app.js on line 41.

<!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 id="address"></div>
  <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 id="msg"></div>
  </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="app.js"></script>
</html>

4. Deploy the contract to Ropsten test network

Before we can deploy the contract, we will need an account and some ether. When we used the testrpc, it created 10 test accounts and came preloaded with 100 test ethers. But for testnet and mainnet we have to create the account and add some ether ourselves.

In your command line terminal, do the following:

mahesh@projectblockchain:~/voting$ truffle console
truffle(default)> web3.personal.newAccount('verystrongpassword')
'0x95a94979d86d9c32d1d2ab5ace2dcc8d1b446fa1'
truffle(default)> web3.eth.getBalance('0x95a94979d86d9c32d1d2ab5ace2dcc8d1b446fa1')
{ [String: '0'] s: 1, e: 0, c: [ 0 ] }
truffle(default)> web3.personal.unlockAccount('0x95a94979d86d9c32d1d2ab5ace2dcc8d1b446fa1', 'verystrongpassword', 15000)
// Replace 'verystrongpassword' with a good strong password.
// The account is locked by default, make sure to unlock it before using the account for deploying and interacting with the blockchain.

In the previous post, we started a node console and initialized the web3 object. When we execute truffle console, all of that is done for us and we get a web3 object ready to use. We now have an account with address ‘0x95a94979d86d9c32d1d2ab5ace2dcc8d1b446fa1’ (you will have a different address in your case) and the balance will be 0.

You can mine some ether by running your geth node by passing an additional option --mine. The easier option we would suggest for now is to get ether from the faucet. Try web3.eth.getBalance again to make sure you have ether.

Now that you have some ether, go ahead and compile and deploy the contract to the blockchain. Below is the command to run and the output you will see if everything goes well.

* Do not forget to unlock the account before deploying your contract.

mahesh@projectblockchain:~/voting$ truffle migrate
Compiling Migrations.sol...
Compiling Voting.sol...
Writing artifacts to ./build/contracts
Running migration: 1_initial_migration.js
Deploying Migrations...
Migrations: 0x3cee101c94f8a06d549334372181bc5a7b3a8bee
Saving successful migration to network...
Saving artifacts...
Running migration: 2_deploy_contracts.js
Deploying Voting...
Voting: 0xd24a32f0ee12f5e9d233a2ebab5a53d4d4986203
Saving successful migration to network...
Saving artifacts...
mahesh@projectblockchain:~/voting$

On my machine, it took about 70–80 seconds to deploy the contracts.

5. Interacting with the voting contract

If you were able to deploy the contract successfully, you should now be able to fetch the vote count and also vote through truffle console.

mahesh@projectblockchain:~/voting$ truffle console
truffle(default)> Voting.deployed().then(function(contractInstance) {contractInstance.voteForCandidate('Rama').then(function(v) {console.log(v)})})
// After a few seconds, you should see a transaction receipt like this:
receipt:
{ blockHash: '0x7229f668db0ac335cdd0c4c86e0394a35dd471a1095b8fafb52ebd7671433156',
blockNumber: 469628,
contractAddress: null,
....
....
truffle(default)> Voting.deployed().then(function(contractInstance) {contractInstance.totalVotesFor.call('Rama').then(function(v) {console.log(v)})})
{ [String: '1'] s: 1, e: 0, c: [ 1] }

If you are able to do this, it’s a success, your contract is live and functional! Now, go ahead and start the the server

mahesh@projectblockchain:~/voting$ npm run dev

You should see the voting page at localhost:8080 and be able to vote and see the vote counts of all the candidates. Since we are dealing with a real blockchain, every write to the blockchain (voteForCandidate) will take a few seconds (The miners have to include your transaction in a block and the block to the blockchain).

If you see this page and are able to vote, you have a built a full fledged Ethereum application on the public test network, congratulations!

Since all your transactions are public, you can look at them here: https://testnet.etherscan.io/. Just enter your account address and it will show you all your transactions with timestamp.

Hopefully you were able to follow along and get the application working. You can find the all the files in the github repo here.


© 2016-2022. All rights reserved.