Full Stack Decentralized Voting App using Ethereum
My name is Divya Jennifer DSouza. I am currently working as an Asst Professor at NMAM Institute of Technology, Nitte, India. In this article I would like to share the concepts of Blockchain, its working, usage and also demonstrate step by step guidance on building a full stack decentralized App using the Ethereum.
I would like to thank Mr Arun Sir from DLithe (www.dlithe.com) for conducting this internship on Blockchain Technology. I am also thankful to Mr Anubhav Chaturvedi Sir for being the resource person and rendering his best in delivering and sharing his knowledge along with handson web sessions.
I am writing this technical blog as a beginners reference on Blockchain. Some of the terms that you might come across while working with blockchain are italized for your understanding with its meaning. I have referred the below website and tried to recreate the voting scenario:
https://www.dappuniversity.com/articles/the-ultimate-ethereum-dapp-tutorial
Lets begin.
At a basic level Blockchain can be defined as a chain of blocks made of digital data (block) which is stored in a public database (chain) that are strung together. This forms the public ledger. In other words public ledger represents all the data in the blockchain.
Drawbacks of traditional approaches can be over come by using blockcahin based approaches. Few key factors of Ethereum block chain include Decentralization, immutable,transparency, accountablity.
SmartContract: Functionality to perform certain operations. We can say it as the business logic of our code.They are called so because they represent some kind of agreement.
Solidity: The programming language which is used to write smart contracts. So make sure you install this extension in your software tools while coding.Solidity is similar to java-script.
Case Study: Building a Voting Application on the Blockchain where anyone connected to the network can participate in the Election and polling the results.
Lets understand how it works:
All the data in the public ledger is secured by cryptographic hashing, and validated by a consensus algorithm. Nodes on the network participate to ensure that all copies of the data distributed across the network are the same. That’s one very important reason for building our voting application on the blockchain, because we want to ensure that our vote was counted, and that it did not change.
What would it look like for a user of our application to vote on the blockchain? Well, for starters, the user needs an account with a wallet address with some Ether, Ethereum’s cryptocurrency. Once they connect to the network they cast their vote and pay a small transaction fee to write this transaction to the blockchain. This transaction fee is called “gas”. Whenever the vote is cast, some of the nodes on the network, called miners, compete to complete this transaction. The miner who completes this transaction is awarded the Ether that we paid to vote.
Let us have a traditional front-end client that is written in HTML, CSS, and Javascript. Instead of talking to a back-end server, this client will connect to a local Ethereum blockchain. Let us code all the business logic about our dApp in an Election smart contract with the Solidity programming language. Let us deploy this smart contract to our local Etherum blockchain, and allow accounts to start voting.
Our final output would be looking like this:
Step By Step Guidance on Building Full Stack Ethereum DApp.
Step 1: Installing Dependencies
i) Node Package Manager(NPM)
You can see if you have node already installed by going to your terminal and typing:
$ node -v
ii) Truffle Framework
It allows us to build decentralized applications on the Ethereum blockchain.
Install Truffle with NPM in your command line like this:
$ npm install -g truffle
On successful Installation, you will be get the following:
iii) Ganache
The next dependency is Ganache, a local in-memory blockchain. You can install Ganache by downloading it from the Truffle Framework website. It will give us 10 external accounts with addresses on our local Ethereum blockchain. Each account is preloaded with 100 fake ether.
On succesful installtion, you should see the below:
iv) Metamask
The next dependency is the Metamask extension for Google Chrome. In order to use the blockchain, we must connect to it. We will have to install a special browser extension in order to use the Ethereum block chain. That’s where metamask comes in. We’ll be able to connect to our local Ethereum blockchain with our personal account, and interact with our smart contract.
You’ll see the fox icon in the top right hand side of your Chrome browser when it’s installed.
v) Syntax Highlighting
This dependency is optional, but recommended. We will be using Sublime Text, and I’ve downloaded the “Ethereum” package that provides nice syntax highlighting for Solidity.
Step 2: Smoke Test
i) Open Ganache. Now that Ganache has booted, you have a local blockchain running.
ii) Create Election folder
Create a project directory for our dApp in the command line like this:
$ mkdir election
$ cd election
Now that we’re inside our project, we can get up and running fast with a Truffle box. From within your project directory, install the pet shop box from the command line like this:
$ truffle unbox pet-shop
Let’s see what the pet shop box gave us:
Check the Election folder directory . You can see all files unboxes such as Contracts folder, migration folder, test folder so on.
iii) Writing Smart Contract
This smart contract will contain all the business logic of our dApp. It will be in charge reading from and write to the Ethereum blockchain. It will allow us to list the candidates that will run in the election, and keep track of all the votes and voters. It will also govern all of the rules of the election, like enforcing accounts to only vote once. From the root of your project, go ahead and create a new contract file in the contracts directory like this:
$ touch contracts/Election.sol
Open the file and type the following code:
pragma solidity 0.5.16;contract Election {
// Read/write candidate
string public candidate; // Constructor
constructor() public {
candidate = "Donald Trump";
}
}
We start by declaring the solidity version with the pragma solidity
statement. Next, we declare the smart contract with the "contract" keyword, followed by the contract name. Next, we declare a state variable that will store the value of the candidate name. State variables allow us to write data to the blockchain. We have declared that this variable will be a string, and we have set its visibility to public
.Because it is public, solidity will give us a getter function for free that will allow us to access this value outside of our contract.
Then, we create a constructor that will get called whenever we deploy the smart contract to the blockchain. This is where we’ll set the value of the candidate state variable that will get stored to the blockchain upon migration.
Now that we’ve created the foundation for the smart contract, let’s see if we can deploy it to the blockchain. In order to do this, we’ll need to create a new file in the migrations directory. From your project root, create a new file from the command line like this:
$ touch migrations/2_deploy_contracts.js
Notice that we number all of our files inside the migrations directory with numbers so that Truffle kno
ws which order to execute them in. Let’s create a new migration to deploy the contract like this:
var Election = artifacts.require("./Election.sol");module.exports = function(deployer) {
deployer.deploy(Election);
};
First, we require the contract we’ve created, and assign it to a variable called “Election”. Next, we add it to the manifest of deployed contracts to ensure that it gets deployed when we run the migrations. Now let’s run our migrations from the command line like this:
$ truffle migrate
if u make any changes to the code and then want to migrate use
$ truffle migrate --reset
On Successful Migration, You will get the following message:
Now that we have successfully migrated our smart contract to the local Ethereum blockchain, let’s open the console to interact with the smart contract. You can open the truffle console from the command line like this:
$ truffle console
Now that we’re inside the console, let’s get an instance of our deployed smart contract and see if we can read the candidate’s name from the contract. From the console, run this code:
Election.deployed().then(function(instance) { app = instance })
Here Election
is the name of the variable that we created in the migration file. We retrieved a deployed instance of the contract with the deployed()
function, and assigned it to an app
variable inside the promise's callback function.
Now we can read the value of the candidate variable like this:
app.candidate()
// => 'Donald Trump'
Step 3: Create List of Candidates
Now that everything is set up properly, let’s continue building out the smart contact by listing out the candidates that will run in the election. We need a way to store multiple candidates, and store multiple attributes about each candidate. We want to keep track of a candidate’s id, name, and vote count. Here is how we will model the candidate.
your complete contract code should look like this:
pragma solidity 0.5.16;contract Election {
// Model a Candidate
struct Candidate {
uint id;
string name;
uint voteCount;
} // Read/write candidates
mapping(uint => Candidate) public candidates; // Store Candidates Count
uint public candidatesCount; function Election () public {
addCandidate("Donald Trump");
addCandidate("Narendra Modi");
} function addCandidate (string memory _name) private {
candidatesCount ++;
candidates[candidatesCount] = Candidate(candidatesCount, _name, 0);
}}
Now let’s migrate our contract like this:
$ truffle migrate --reset
Step 4: Testing
Now let’s write some tests. Make sure you have Ganache running first. Then, create a new test file in the command line from the root of your project like this:
$ touch test/election.js
We’ll write all these tests in Javascript to simulate client-side interaction with our smart contract, much like we did in the console. Here is all the code for the tests:
var Election = artifacts.require("./Election.sol");contract("Election", function(accounts) {
var electionInstance; it("initializes with two candidates", function() {
return Election.deployed().then(function(instance) {
return instance.candidatesCount();
}).then(function(count) {
assert.equal(count, 2);
});
}); it("it initializes the candidates with the correct values", function() {
return Election.deployed().then(function(instance) {
electionInstance = instance;
return electionInstance.candidates(1);
}).then(function(candidate) {
assert.equal(candidate[0], 1, "contains the correct id");
assert.equal(candidate[1], "Donald Trump", "contains the correct name");
assert.equal(candidate[2], 0, "contains the correct votes count");
return electionInstance.candidates(2);
}).then(function(candidate) {
assert.equal(candidate[0], 2, "contains the correct id");
assert.equal(candidate[1], "Narendra Modi", "contains the correct name");
assert.equal(candidate[2], 0, "contains the correct votes count");
});
});
});
Now let’s run the tests from the command line like this:
$ truffle test
The first test checks that the contract was initialized with the correct number of candidates by checking the candidates count is equal to 2.
The next test inspects the values of each candidate in the election, ensuring that each candidate has the correct id, name, and vote count.
Step 5: Client Side Application
Let’s start building out the client-side application that will talk to our smart contract. We’ll do this by modifying the HTML and Javascript files that came with the Truffle Pet Shop box that we installed in the previous section.
Replace all of the content of your “index.html” file with this code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Election Results</title> <!-- Bootstrap -->
<link href="css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container" style="width: 650px;">
<div class="row">
<div class="col-lg-12">
<h1 class="text-center">Election Results</h1>
<hr/>
<br/>
<div id="loader">
<p class="text-center">Loading...</p>
</div>
<div id="content" style="display: none;">
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Votes</th>
</tr>
</thead>
<tbody id="candidatesResults">
</tbody>
</table>
<hr/>
<p id="accountAddress" class="text-center"></p>
</div>
</div>
</div>
</div> <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="js/bootstrap.min.js"></script>
<script src="js/web3.min.js"></script>
<script src="js/truffle-contract.js"></script>
<script src="js/app.js"></script>
</body>
</html>
Next, replace all of the content of your “app.js” file with this code:
App = {
web3Provider: null,
contracts: {},
account: '0x0', init: function() {
return App.initWeb3();
}, initWeb3: function() {
if (typeof web3 !== 'undefined') {
// If a web3 instance is already provided by Meta Mask.
window.ethereum.enable();
App.web3Provider = web3.currentProvider;
web3 = new Web3(web3.currentProvider);
} else {
// Specify default instance if no web3 instance provided
App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545');
web3 = new Web3(App.web3Provider);
}
return App.initContract();
}, initContract: function() {
$.getJSON("Election.json", function(election) {
// Instantiate a new truffle contract from the artifact
App.contracts.Election = TruffleContract(election);
// Connect provider to interact with contract
App.contracts.Election.setProvider(App.web3Provider); return App.render();
});
}, render: function() {
var electionInstance;
var loader = $("#loader");
var content = $("#content"); loader.show();
content.hide(); // Load account data
web3.eth.getCoinbase(function(err, account) {
if (err === null) {
App.account = account;
$("#accountAddress").html("Your Account: " + account);
}
}); // Load contract data
App.contracts.Election.deployed().then(function(instance) {
electionInstance = instance;
return electionInstance.candidatesCount();
}).then(function(candidatesCount) {
var candidatesResults = $("#candidatesResults");
candidatesResults.empty(); for (var i = 1; i <= candidatesCount; i++) {
electionInstance.candidates(i).then(function(candidate) {
var id = candidate[0];
var name = candidate[1];
var voteCount = candidate[2]; // Render candidate Result
var candidateTemplate = "<tr><th>" + id + "</th><td>" + name + "</td><td>" + voteCount + "</td></tr>"
candidatesResults.append(candidateTemplate);
});
} loader.hide();
content.show();
}).catch(function(error) {
console.warn(error);
});
}
};$(function() {
$(window).load(function() {
App.init();
});
});
web3.js is a javascript library that allows our client-side application to talk to the blockchain. It is a collection of libraries that allow you to interact with a local or remote ethereum node using HTTP, IPC or WebSocket. We configure web3 inside the “initWeb3” function.
App.js code does the following:
- Initialize contracts: We fetch the deployed instance of the smart contract inside this function and assign some values that will allow us to interact with it.
- Render function: The render function lays out all the content on the page with data from the smart contract. For now, we list the candidates we created inside the smart contract. We do this by looping through each candidate in the mapping, and rendering it to the table. We also fetch the current account that is connected to the blockchain inside this function and display it on the page
Now let’s view the client-side application in the browser. First, make sure that you’ve migrated your contracts like this:
$ truffle migrate --reset
Next, start your development server from the command line like this:
$ npm run dev
This should automatically open a new browser window with your client-side application.
Notice that your application says “Loading…”. That’s because we’re not logged in to the blockchain yet! In order to connect to the blockchain, we need to import one of the accounts from Ganache into Metamask.
In order for the Account number to get updated make sure your Injected Web component is set to true in one of the config files. The network should be set to localhost:7545 by choosing custom RPC and enter the private key of any address from the ganache.
Open Ganache and Click on any key(icon) to select the private key. The following would pop up.
Step 6: Cast Votes
Now let’s add the ability to cast votes in the election. Let’s define a “voters” mapping to the smart contract to keep track of the accounts that have voted in the election.
Complete contract code should look like this:
pragma solidity 0.5.16;contract Election {
// Model a Candidate
struct Candidate {
uint id;
string name;
uint voteCount;
} // Store accounts that have voted
mapping(address => bool) public voters;
// Read/write candidates
mapping(uint => Candidate) public candidates;
// Store Candidates Count
uint public candidatesCount; constructor () public {
addCandidate("Donald Trump");
addCandidate("Narendra Modi");
} function addCandidate (string memory _name) private {
candidatesCount ++;
candidates[candidatesCount] = Candidate(candidatesCount, _name, 0);
} function vote (uint _candidateId) public {
// require that they haven't voted before
require(!voters[msg.sender]); // require a valid candidate
require(_candidateId > 0 && _candidateId <= candidatesCount); // record that voter has voted
voters[msg.sender] = true; // update candidate vote Count
candidates[_candidateId].voteCount ++;
}
}
Step 7: Testing Voting Function
Let’s add a test to our “election.js” test file:
We want to test two things here:
- Test that the function increments the vote count for the candidate.
- Test that the voter is added to the mapping whenever they vote.
Test to ensure that our vote function throws an exception for double voting and invalid candidates.
All Tests Passed successfully!!
Step 8: Client Side Voting
Let’s add a form that allows accounts to vote below the table in our “index.html” file:
<form onSubmit="App.castVote(); return false;">
<div class="form-group">
<label for="candidatesSelect">Select Candidate</label>
<select class="form-control" id="candidatesSelect">
</select>
</div>
<button type="submit" class="btn btn-primary">Vote</button>
<hr />
</form>
Now let’s update our app.js file to handle both of those things. First we list all the candidates from the smart contract inside the form’s select element. Then we’ll hide the form on the page once the account has voted. We’ll update the render function to look like this:
render: function() {
var electionInstance;
var loader = $("#loader");
var content = $("#content"); loader.show();
content.hide(); // Load account data
web3.eth.getCoinbase(function(err, account) {
if (err === null) {
App.account = account;
$("#accountAddress").html("Your Account: " + account);
}
}); // Load contract data
App.contracts.Election.deployed().then(function(instance) {
electionInstance = instance;
return electionInstance.candidatesCount();
}).then(function(candidatesCount) {
var candidatesResults = $("#candidatesResults");
candidatesResults.empty(); var candidatesSelect = $('#candidatesSelect');
candidatesSelect.empty(); for (var i = 1; i <= candidatesCount; i++) {
electionInstance.candidates(i).then(function(candidate) {
var id = candidate[0];
var name = candidate[1];
var voteCount = candidate[2]; // Render candidate Result
var candidateTemplate = "<tr><th>" + id + "</th><td>" + name + "</td><td>" + voteCount + "</td></tr>"
candidatesResults.append(candidateTemplate); // Render candidate ballot option
var candidateOption = "<option value='" + id + "' >" + name + "</ option>"
candidatesSelect.append(candidateOption);
});
}
return electionInstance.voters(App.account);
}).then(function(hasVoted) {
// Do not allow a user to vote
if(hasVoted) {
$('form').hide();
}
loader.hide();
content.show();
}).catch(function(error) {
console.warn(error);
});
}
Next, we want to write a function that’s called whenever the form is submitted:
castVote: function() {
var candidateId = $('#candidatesSelect').val();
App.contracts.Election.deployed().then(function(instance) {
return instance.vote(candidateId, { from: App.account });
}).then(function(result) {
// Wait for votes to update
$("#content").hide();
$("#loader").show();
}).catch(function(err) {
console.error(err);
});
}
Now your front-end application should look like this:
Note: For latest Metamask users the Privacy Mode is default for Metamask They need to enter window.ethereum.enable() in the console of the browser or need to be included in the code. I have included this line in app.js code.Not including this line of code will result null in the Account as shown in above browser.
Let us see the how the voting app works:
As you can see in the above gif you can vote from one Account only once as per our test case. Double voting results in Failed vote. Also note that “gas” (amount) is deducted once you have voted(100ETH ->99.9987ETH ->99.9982ETH). Choose other Accounts from the imported list to cast more votes.
Once you have reached this step you have successfully built a full stack decentralized application on the Ethereum blockchain!