Chapter 12. Putting it all together: Building a complete voting Dapp

This chapter covers

  • Designing and implementing a voting contract showcasing most Solidity features, such as modifiers and events
  • Integrating the voting contract in Truffle, for integrated compilation, testing, and deployment
  • Implementing an asynchronous web UI seamlessly connected to the contract through the truffle-contract JavaScript library
  • Deploying onto the public test network from Truffle

In the previous chapter, you started to enjoy the benefits of using Truffle to improve the development lifecycle:

  • Contract compilation became as easy as executing a simple truffle compile command. You didn’t have to instruct the solc compiler explicitly to push its output to specific files to be reused later for deployment.
  • Contract deployment became much easier than you were used to, thanks to Truffle’s migrations functionality based on minimalistic configuration. Truffle did all the hard work of packaging the compilation output and feeding the contract ABI and bytecode to the deployment transaction behind the curtains. No more manual copying and pasting of long text!
  • Also, testing became much simpler than when you performed it through Mocha. You didn’t need complicated initialization to deploy the contract at each test, and you could keep testing logic by writing asynchronous JavaScript based on async/await.

I decided to introduce Truffle through SimpleCoin to focus exclusively on the functionality of the tool and avoid being distracted by the presentation of new concepts a new contract would have introduced. Also, by having you rewrite in Truffle the same SimpleCoin unit tests you had written in Mocha, I could compare more explicitly the advantages and disadvantages of one framework versus the other.

In this chapter, we’ll go one step further. Now that you’re relatively familiar with Truffle, I believe you’re ready to take advantage of this framework to build an entirely new Dapp from scratch, including a smart contract, some unit tests, and a web UI. We’ll start gently: I’ll give you some background on what functionality the voting contract should provide, and I’ll help you design and implement it. After you’ve completed the contract, I’ll guide you through the usual steps of deploying it within the development environment and unit testing it. Then I’ll show how you can simplify the web UI of an Ethereum Dapp by importing the ABI from a file generated during Truffle compilation—no more copying and pasting ABI and addresses here either!

This chapter is long, but I hope to keep you engaged until the end, especially if you’re eager to learn more. After completing this chapter, you’ll know most of the tool set you need to develop a Dapp from start to end. Now it’s time to start!

12.1. Defining the requirements of a voting Dapp

A voting Dapp can be simple or complex, depending on the requirements of the elections you wish to support. Voting can be on a small number of preselected proposals (or candidates), or on a potentially large number of proposals suggested dynamically by the voters themselves. The electorate can be made up of a small number of individuals, all known to the organization coordinating the elections, or it can include all the residents of a certain administrative area or of an entire country. In the latter case, a registration process might be necessary to run the election process efficiently and transparently. The outcome might be decided by simple majority: the proposal that gets more votes wins. Or it might require a qualified majority (or quorum): a proposal is passed only if it gets a minimum predefined percentage of votes. The ballot can be secret or open. The vote can be delegated to other individuals or kept exclusively direct. This is only a subset of the options and variants a voting process can include. With technology, the voting process can become even more sophisticated. For example, a vote could be split by the voter into fractions, such that each is assigned to a different proposal, or it could be split similarly, but with each fraction delegated to a different individual.

You might be ambitious and try to design an ultrageneric application that could cater to all possibilities. For our purposes, and to keep the length of this chapter reasonable, I’ll constraint the voting application to a limited set of requirements:

  • You’ll write the Voting Dapp within a small organization. Voters, all of whom the organization knows, are white-listed through their Ethereum address, can submit new proposals during a proposal registration session, and can vote on the proposals during the voting session.
  • The vote isn’t secret; every voter is able to see other people’s votes.
  • The winner is determined by simple majority; the proposal that gets more votes wins.

As you might remember from chapter 1, even such a simple voting Dapp has a major advantage over a centralized one: a Dapp decentralizes vote processing and storing and consequently makes tampering much less likely than if voting was running through a centralized application.

Figure 12.1 shows the workflow of the entire voting process. Let’s walk quickly through it:

  1. The voting administrator registers a white list of voters identified by their Ethereum addresses.
  2. The voting administrator starts the proposal registration session.
  3. Registered voters are entitled to register their proposals while the registration session is active.
  4. The voting administrator ends the proposal registration session.
  5. The voting administrator starts the voting session.
  6. Registered voters cast their votes for their favorite proposals.
  7. The voting administrator ends the voting session.
  8. The voting administrator tallies the votes.
  9. Anyone can check the final details of the winning proposal.

Figure 12.1. The workflow of the voting process. Some steps are performed by the administrator, other steps by voters.

If you’re eager to develop a more generic voting Dapp, at the end of the chapter I’ll give you a few pointers to help you step into cutting-edge e-voting territory. For now, though, we’ll stick with the constraints I outlined.

12.2. The development plan

Before you start to get your hands dirty, I’ll give you an idea of all the steps you’ll be going through to build your voting Dapp. You can get a more visual idea of the development cycle in figure 12.2, but here’s a list of the steps:

  1. Create a new Truffle project.
  2. Design and implement SimpleVoting, the voting contract, according to the initial requirements.
  3. Compile and deploy SimpleVoting on Ganache.
  4. Write and execute unit tests for SimpleVoting.
  5. Create a web UI that connects to the voting contract by reading the ABI and contract address from Truffle’s output.
  6. Run the voting workflow through the web UI.
  7. Deploy the Dapp onto a public test network.

Figure 12.2. The Dapp development plan, including all the steps, from creation of a Truffle project to final deployment onto a public test network

12.3. Starting the Truffle project

Enough talking—it’s time to get started! Create a new directory within your Ethereum project folder—C:Ethereum ruffleSimpleVoting—then open an OS shell and initialize a Truffle project:

C:Ethereum	ruffleSimpleVoting>truffle init 

As usual, you should get output like that shown in the screenshot in figure 12.3.

Figure 12.3. Truffle project initialization

If you’re working on Windows, remember to delete truffle.js; otherwise, delete truffle-config.js. Initially, you’ll be deploying on Ganache, as you did in the previous chapter, so make sure truffle.js (or truffle-config.js) is configured as follows:

module.exports = {
  networks: {
    development: {
      host: "localhost",
      port: 8545,
      network_id: "*" // Match any network id
    }
  }
};

Now you’re ready to create the voting contract!

12.4. Implementing the voting contract

I believe you could attempt to implement the contract by yourself, based on the requirements outlined earlier. Why don’t you give it a go and then come back here later?

Are you already back? Read on. Arguably, the most important entities involved in an election are the proposal, the voter, and the voting workflow. Let’s look at how to model them.

12.4.1. Modeling proposal, voter, and workflow

A registered voter creates a proposal and adds it dynamically to the list of existing proposals during a proposal registration session. The Proposal type should expose a (string) description that its author provides and the (uint) number of votes cast against it. You don’t want to capture the author because they might want to remain anonymous:

struct Proposal {
    string description;   
    uint voteCount; 
}

What data would you like to capture about a voter? How about whether the voter has been registered (only white-listed accounts will be allowed to vote), whether they’ve already voted (to prevent double voting), and, if they’ve voted, which proposal they’ve voted for? Try this:

struct Voter {
    bool isRegistered;
    bool hasVoted;  
    uint votedProposalId;   
}

You can easily represent the voting workflow I described earlier in this section with the following enumeration:

enum WorkflowStatus {
    RegisteringVoters, 
    ProposalsRegistrationStarted,
    ProposalsRegistrationEnded,
    VotingSessionStarted,

    VotingSessionEnded,
    VotesTallied
}

12.4.2. Contract state

As for most contracts, you’re interested in assigning a management role to the Ethereum address that submits the contract deployment transaction. This will become (at construction) the voting administrator:

address public administrator;

Two main processes take place from the start of the election: the proposal registration session, during which any registered voter is entitled to submit a proposal, and the voting session, which starts immediately afterwards. The voting administrator is responsible for starting and ending each session, presumably on the basis of an agreed start and end time. You’ll capture the status of the voting workflow with this state variable:

WorkflowStatus public workflowStatus;

Obviously, the most important state of the contract has to do with voters and proposals:

  1. A Voter mapping associates the voter address, which is their identifier, with the voter object:
    mapping(address => Voter) public voters;
  2. A Proposal array captures the registered proposals
    Proposal[] public proposals;

The proposal ID’s array index implicitly represents it.

Once votes have been tallied, you want to capture the ID of the winning proposal:

uint private winningProposalId;

This state variable shouldn’t be exposed directly to the Dapp users because its value should be revealed only after votes have been tallied, through getter functions.

12.4.3. Function modifiers

You need the state variables you’ve defined to determine the identity of a function caller and the status of the voting workflow. As you know, you can create function modifiers to check the conditions under which a function should be called. You could create a function modifier to check if the caller is the voting administrator

modifier onlyAdministrator() {
    require(msg.sender == administrator, 
      "the caller of this function must be the administrator");
    _;
 }

or they’re a registered voter:

 modifier onlyRegisteredVoter() {
    require(voters[msg.sender].isRegistered, 
      "the caller of this function must be a registered voter");
    _;
  }

You could create other function modifiers to verify whether voters are being registered

 modifier onlyDuringVotersRegistration() {
    require(workflowStatus == WorkflowStatus.RegisteringVoters, 
      "this function can be called only before proposals registration has 
 started");
    _;
 }    

or whether the proposals registration session is active:

modifier onlyDuringProposalsRegistration() {
   require(workflowStatus == WorkflowStatus.ProposalsRegistrationStarted, 
     "this function can be called only during proposals registration");
   _;
}

Similarly, you can implement modifiers to verify whether the proposals registration has ended (onlyAfterProposalsRegistration), whether the voting session is active (onlyDuringVotingSession) or has ended (onlyAfterVotingSession), or whether vote tallying has already taken place (onlyAfterVotesTallied). You’ll find all of these modifiers useful for simplifying the logic of the functions you’ll implement shortly.

12.4.4. Events

Because blockchain operations take a few seconds to complete from the moment they’re triggered, it’s useful from a user point of view to get notified as soon as their execution has taken place. Therefore, at the end of each action, the voting contract will publish an event that alters the state of the contract. This will also happen at every workflow status change so that clients, such as a UI, can update their screens accordingly. The contract will publish these events:

event VoterRegisteredEvent (address voterAddress); 
event ProposalsRegistrationStartedEvent ();
event ProposalsRegistrationEndedEvent ();
event ProposalRegisteredEvent(uint proposalId);
event VotingSessionStartedEvent ();
event VotingSessionEndedEvent ();
event VotedEvent (address voter, uint proposalId);
event VotesTalliedEvent ();

event WorkflowStatusChangeEvent (
   WorkflowStatus previousStatus,
   WorkflowStatus newStatus
);

12.4.5. Constructor

At contract construction, you should identify the sender of the contract transaction and make them the voting administrator. You should also set the initial workflow status:

constructor() public {
    administrator = msg.sender;
    workflowStatus = WorkflowStatus.RegisteringVoters;
}

12.4.6. Functions

If you look back at the workflow of the voting process in figure 12.1, you’ll see that the voting administrator is responsible for registering voters:

function registerVoter(address _voterAddress) 
    public onlyAdministrator onlyDuringVotersRegistration {
        
    require(!voters[_voterAddress].isRegistered, 
       "the voter is already registered");
        
    voters[_voterAddress].isRegistered = true;
    voters[_voterAddress].hasVoted = false;
    voters[_voterAddress].votedProposalId = 0;

    emit VoterRegisteredEvent(_voterAddress);
}

The voting administrator is also responsible for starting and ending the proposal registration session (and, similarly, starting and ending the voting session):

function startProposalsRegistration() 
    public onlyAdministrator onlyDuringVotersRegistration {
    workflowStatus = WorkflowStatus. ProposalsRegistrationStarted;
        
    emit ProposalsRegistrationStartedEvent();
    emit WorkflowStatusChangeEvent(
        WorkflowStatus. RegisteringVoters, workflowStatus);
}
    
function endProposalsRegistration() 
    public onlyAdministrator onlyDuringProposalsRegistration {
    workflowStatus = WorkflowStatus.ProposalsRegistrationEnded;

    emit ProposalsRegistrationEndedEvent();        
    emit WorkflowStatusChangeEvent(
        WorkflowStatus.ProposalsRegistrationStarted, workflowStatus);
}

As you can see, function modifiers you defined earlier have simplified the code so much that it has been reduced to only one line and the publishing of a couple of events.

While the proposal registration session is active, registered voters submit proposals with this function:

function registerProposal(string proposalDescription) 
   public onlyRegisteredVoter 
      onlyDuringProposalsRegistration {           1
   proposals.push(Proposal({                      2
        description: proposalDescription,
        voteCount: 0
    }));

   emit ProposalRegisteredEvent(proposals.length - 1);
}

  • 1 Checks if the caller is a registered user and whether the proposal registration session is active
  • 2 Creates the proposal and adds it to the proposals array

Also in this case, function modifiers perform the hard work of checking whether the caller is a registered voter and whether the proposal registration session is active. The logic of the function itself is minimal: it creates a new proposal with the provided description and adds it to the proposals array.

Once the voting session has been started, a registered voter can cast their vote with this function:

function vote(uint proposalId) 
    onlyRegisteredVoter 
    onlyDuringVotingSession public {                   1
    require(!voters[msg.sender].hasVoted, 
      "the caller has already voted");                 2
        
    voters[msg.sender].hasVoted = true;                3
    voters[msg.sender].votedProposalId = proposalId;   3
        
    proposals[proposalId].voteCount += 1;              4

    emit VotedEvent(msg.sender, proposalId);
}

  • 1 Checks that the caller is a registered voter and the voting session is open
  • 2 Checks that the caller hasn’t voted yet
  • 3 Flags that the caller has voted and records their vote
  • 4 Assigns the vote to the chosen proposal. (Note that Ethereum has no concurrency issue because transactions are processed sequentially on the miner’s EVM when a new block is being created.)

After the administrator has ended the voting session, they can tally the vote with the following function, which finds the ID of the winning proposal and assigns it to the corresponding state variable you defined earlier:

function tallyVotes() 
    onlyAdministrator 
    onlyAfterVotingSession 
    onlyBeforeVotesTallied public {                           1
    uint winningVoteCount = 0;
    uint winningProposalIndex = 0;

        
    for (uint i = 0; i < proposals.length; i++) {             2
        if (proposals[i].voteCount > winningVoteCount) {
           winningVoteCount = proposals[i].voteCount;
           winningProposalIndex = i;                          3
        }
    }
        
    winningProposalId = winningProposalIndex;                 4
    workflowStatus = WorkflowStatus.VotesTallied;             5

    emit VotesTalliedEvent();
    emit WorkflowStatusChangeEvent(
        WorkflowStatus. VotingSessionEnded, workflowStatus);   6
}

  • 1 Verifies the administrator has called the function only after the voting session has ended and it hasn’t already been called
  • 2 Iterates over the proposals to find the one with the higher vote count
  • 3 Records the array index of the winning proposal so far
  • 4 Assigns the array index of the winning proposal to the corresponding state variable
  • 5 Flags votes that have been tallied
  • 6 Publishes the change of workflow status event

It’s convenient to create a set of views—read-only functions that return a slice of the current contract state—for consumption by UI and other clients. For example, during and after the proposal registration session, it would be possible to check the number of proposals and their descriptions with these read-only functions:

function getProposalsNumber() public view
    returns (uint) {
        return proposals.length;
}

function getProposalDescription(uint index) public view 
    returns (string) {
        return proposals[index].description;
}  

After votes have been tallied, it would be possible to retrieve the final results (winning proposal ID, description, and vote count) through the following functions:

function getWinningProposalId() onlyAfterVotesTallied 
    public view
        returns (uint) {
        return winningProposalId;
}
    
function getWinningProposalDescription() onlyAfterVotesTallied 
    public view
        returns (string) {
        return proposals[winningProposalId].description;
}  

function getWinningProposalVoteCounts() onlyAfterVotesTallied 
    public view
        returns (uint) {
        return proposals[winningProposalId].voteCount;
}

Anyone should be entitled to check the winning proposal, not only the administrator or voters, so no modifiers should be present.

It’s also convenient to write other functions to check the identity of the caller. For example, you could write a function that checks whether an address is a registered voter

function isRegisteredVoter(address _voterAddress) public view
    returns (bool) {
    return voters[_voterAddress].isRegistered;
}

or the administrator:

function isAdministrator(address _address) public view 
    returns (bool){
    return _address == administrator;
}

And a view of the current voting workflow status could also be useful:

function getWorkflowStatus() public view
    returns (WorkflowStatus) {
    return workflowStatus;       
}

12.4.7. The full voting contract

You can see the full contract in listing D.1 in appendix D. I encourage you to enter it in Remix and play with it before placing it under Truffle.

12.5. Compiling and deploying SimpleVoting

Place the code in listing D.1 (from the book website) in a file named SimpleVoting.sol in the following folder: C:Ethereum ruffleSimpleVotingcontracts. Now, go to the migrations folder: C:Ethereum ruffleSimpleVotingmigrations. Create a new file called 2_deploy_contracts.js and place in it the contents of the following listing, which I’ve adapted from the migration configuration you wrote earlier for SimpleCoin.

Listing 12.1. 2_deploy_contracts.js: migration configuration for SimpleVoting
var SimpleVoting = artifacts.require("SimpleVoting");

module.exports = function(deployer) {
  deployer.deploy(SimpleVoting);
};

You can now kick the compilation

C:EthereumTruffleSimpleVoting>truffle compile

and you’ll see output similar to this:

Compiling .contractsSimpleVoting.sol...
Writing artifacts to .uildcontracts
Note

If you’re experiencing compilation issues, have a look at chapter 11, section 11.2.3, “Troubleshooting truffle compile errors.”

After a successful compilation, start up Ganache in a new OS console:

C:Ethereum	ruffleSimpleVoting>ganache-cli

Then go back to the OS console you were using to execute Truffle commands and perform a migration:

C:Ethereum	ruffleSimpleVoting>truffle migrate

You’ll see the following output:

Using network 'development'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0xf3ab00eef045a34e2208f6f4dfa218950f7d1a0d30f3f32982febdff55db6a92
  Migrations: 0x2f90a477697c9a0144acf862ab7dd98372dd7b33
Saving successful migration to network...
  ... 0x7af1903172cab2baac3c05f7ff721c8c54aa0f9f34ad64c3a26495bb591c36c9
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Deploying SimpleVoting...
  ... 0x37ecb76d6cd6051c5d122df53db0063d1336e5b3298150b50d62e8ae845e9bbb
  SimpleVoting: 0xaf18e4e373b90709cc08a231ce24015a0da4f8cc
Saving successful migration to network...
  ... 0x19ac99d629c4053bf699c34c83c47288666afe6fa8994b90438d0e78ec204c96
Saving artifacts...

12.6. Writing unit tests

By now, you should know how to write unit tests in Truffle. Bear in mind, though, that SimpleVoting’s workflow is more complex than SimpleCoin’s, as described in section 12.1. For example, actions such as registering a proposal or voting can happen only after the administrator has opened the related session, so you should aim some of your tests at verifying that these constraints are being respected. Table 12.1 shows a small sample of what your tests should cover.

Table 12.1. Sample of unit tests you should cover against SimpleVoting

Test case

Only the administrator can register voters.
You aren’t allowed to register the same voter twice.
Only the administrator can start the proposal registration session.
Only the administrator can end the proposal registration session, after starting it.
Only a registered voter can submit a proposal.
A registered voter can submit a proposal only after the administrator has started the proposal registration session.
A registered voter can’t vote before the voting session has started.

Each of these test cases generally requires that you write at least a negative test that proves an exception is thrown if the underlying conditions aren’t met, and a positive test that verifies the functionality works as expected when all constraints have been met. As an example, if you want to test that “Only the administrator can end the proposal registration session, after starting it,” you should write these tests (with the code shown in listing 12.2):

  • A negative test that verifies an exception is thrown if a voter other than the administrator tries to end the proposal registration session
  • A negative test that verifies the administrator isn’t allowed to end the proposal registration session if it hasn’t started yet
  • A positive test that verifies the administrator can successfully end the proposal registration session after it has started
Listing 12.2. testSimpleVoting.js: testing ending the proposal registration session
const SimpleVoting = artifacts.require("./SimpleVoting.sol");

contract('SimpleVoting', function(accounts) {     
  contract('SimpleVoting.endProposalRegistration - 
    onlyAdministrator modifier ', 
    function(accounts) {
     it("The voting administrator should be able to end the proposal 
 registration session only after it has started", 
       async function() {                                              1
       //arrange 
       let simpleVotingInstance = await SimpleVoting.deployed();
       let votingAdministrator = await simpleVotingInstance.administrator();
                    
       let nonVotingAdministrator = web3.eth.accounts[1];     
                                           
       try {
            //act
            await simpleVotingInstance.endProposalsRegistration(

              {from: nonVotingAdministrator});
            assert.isTrue(false);                                      2
       }
       catch(e) {
            //assert
            assert.isTrue(votingAdministrator != nonVotingAdministrator);
            assert.equal(e, "Error: VM Exception while processing 
 transaction: revert - the caller of this function 
 must be the administrator");                                        3
       }
     });
  });
     
  contract('SimpleVoting.endProposalRegistration - 
    onlyDuringProposalsRegistration modifier', 
    function(accounts) {
     it("An account that is not the voting administrator must not be able 
 to end the proposal registration session", 
       async function() {                                              4
       //arrange 
       let simpleVotingInstance = await SimpleVoting.deployed();
       let votingAdministrator = await simpleVotingInstance.administrator();

       try {
         //act
            await simpleVotingInstance.endProposalsRegistration(
              {from: votingAdministrator});
            assert.isTrue(false);                                      5
       }
       catch(e) {
            //assert
            assert.equal(e, "Error: VM Exception while processing 
 transaction: revert - this function can be called only 
 during proposals registration");                                    6
       }
     });                                   
  });

  contract('SimpleVoting.endProposalRegistration - successful', 
    function(accounts) {
     it("An account that is not the voting administrator must 
 not be able to end the proposal registration session", 
       async function() {                                              7
       //arrange 
       let simpleVotingInstance = await SimpleVoting.deployed();
       let votingAdministrator = await simpleVotingInstance.administrator();

       await simpleVotingInstance.startProposalsRegistration(
          {from: votingAdministrator});
       let workflowStatus = await simpleVotingInstance.getWorkflowStatus();
       let expectedWorkflowStatus = 1;

       assert.equal(workflowStatus.valueOf(), expectedWorkflowStatus, 
          "The current workflow status does not correspond 
 to proposal registration session started"); 

       //act
       await simpleVotingInstance.endProposalsRegistration(
         {from: votingAdministrator});
       let newWorkflowStatus = await simpleVotingInstance
         .getWorkflowStatus();
       let newExpectedWorkflowStatus = 2;
                    
       //assert
       assert.equal(newWorkflowStatus.valueOf(), newExpectedWorkflowStatus,
         "The current workflow status does not correspond 
            to proposal registration session ended");                8

       });
     });
});

  • 1 First negative test
  • 2 If this line is reached, it means no exception has been thrown, and the test should fail.
  • 3 The test passes if the expected exception has been thrown.
  • 4 Second negative test
  • 5 If this line is reached, it means no exception has been thrown, and the test should fail.
  • 6 The test passes if the expected exception has been thrown.
  • 7 Positive test, verifying successful outcome
  • 8 The test passes if the workflow status changes to the expected value.

If you want to run these unit tests, copy the code from listing 12.2 into a file called testSimpleVoting.js in the test folder: C:Ethereum ruffleSimpleVoting est. (I recommend you use the file from the book website.) Then you can execute them as usual

C:Ethereum	ruffleSimpleVoting>truffle test

and you’ll see familiar-looking output, as shown in the screenshot in figure 12.4.

Figure 12.4. Output of SimpleVoting unit tests on ending the proposal registration session

12.7. Creating a web UI

The voting website needs two web pages: one for the voting admin, and one for the voters. You’ll create the website by going through these steps:

  1. Prepare the dependencies, such as required JavaScript libraries and the smart-contract ABI json file.
  2. Set up a web server so you can read local JSON files from your web page.
  3. Write the admin page HTML.
  4. Write the admin page JavaScript.
  5. Write the voter page HTML.
  6. Write the voter page JavaScript.
  7. Run the admin and voter web pages.

12.7.1. Preparing the UI dependencies

First of all, open a new OS command shell and create a new directory for the voting web UI, say: C:EthereumSimpleVotingWebUI. In case you didn’t install Bower globally back in chapter 8, install it locally in this directory:

C:EthereumSimpleVotingWebUI>npm install bower

Now import the Web3.js and JQuery libraries into the current directory, as you did in chapter 8. This time, you’ll also install truffle-contract, a library that allows you to import the ABI and contract address seamlessly from the output of truffle compile:

C:EthereumSimpleVotingWebUI>bower install web3#0.20.6

C:EthereumSimpleVotingWebUI>bower install jquery

C:EthereumSimpleVotingWebUI>bower install truffle-contract

As you know, Bower will download these libraries into respective directories within the bower_components folder:

bower_components
   |-- web3
   |-- jquery
   |-- truffle-contract

Now you’ll be able to reference these local copies of Web3.js, jQuery, and truffle-contract from your JavaScript code.

To call the SimpleVoting contract from the web UI, you need to reference its ABI. Truffle generates this during contract compilation in a file called SimpleVoting.json, which is located in this folder: C:Ethereum ruffleSimpleVotinguildcontracts. Create a folder called contracts within your web UI directory: C:EthereumSimpleVoting-WebUIcontracts. Now copy SimpleVoting.json into it.

You might remember that when you created SimpleCoin’s web UI, you copied the ABI manually into the page JavaScript:

var abi = "[{"constant":false,"inputs":[{"name":"_to...
var SimpleCoinFactory = web3.eth.contract(JSON.parse(abi));
var simpleCoinInstance = SimpleCoinFactory.at('0x773dc5fc8fc3e...');

Now you’re trying to improve the process of building a web UI, and ideally you’d like to import the contract ABI directly from SimpleVoting.json. But web browsers don’t allow you to read JSON files from your hard drive, so the following JavaScript will fail to execute when loaded directly from your hard drive:

window.onload = function() {
     $.getJSON("./contracts/SimpleVoting.json", function(json) {
         SimpleVoting = TruffleContract( json );         
       ...

This same JavaScript will work as expected, though, if it’s served to the browser through a web server. Bear in mind, Dapp web UIs are served through conventional web servers, so you must set one up to prepare for a realistic deployment.

Warning

If you make modifications to your contract (for example, you add a new function), after recompiling it (with truffle compile) and remigrating it (with truffle migrate), you have to copy SimpleVoting.json again from Truffle’s buildcontracts folder to your web project contracts folder. If you shut down Ganache, when you restart it, the deployed instance of Simple-Voting won’t be present, so it’s best to force a new clean migration with truffle migrate --reset and then copy the SimpleVoting.json file across to your web project.

12.7.2. Setting up a minimalistic web server with Node.js

You can easily set up a simple web server, handling only static pages including plain HTML and JavaScript, using Connect and ServerStatic, two Node.js packages, as described in the Stack Overflow article “Using Node.js as a simple web server” (http://mng.bz/oNR2).

First of all, install them into your web UI directory:

C:EthereumSimpleVotingWebUI>npm install connect serve-static

Then, create a file called webserver.js with this code in it:

var connect = require('connect');
var serveStatic = require('serve-static');
connect().use(serveStatic(__dirname)).listen(8080, function(){
    console.log('Web server running on 8080...');
});

Finally, start up the website:

C:EthereumSimpleVotingWebUI>node webserver.js

You should see an initialization message:

Server running on 8080...

You’ll now be able to browse any HTML page located in the SimpleVotingWebUI folder through an HTTP connection to localhost on port 8080. For example, create a dummy page called test.html with this markup:

<html>
<table><tr><td><b>test</b></td></tr></table>
</html>

Then access it through your browser: http://localhost:8080/test.html.

Now that you’ve set up a web server, you’re ready to create the SimpleVoting website. You’ll start from the admin page.

12.7.3. Writing the admin page HTML

The admin web page should authenticate the user through an Ethereum address. Only the voting administrator address will be allowed to use the page.

Note

When running against Ganache, any password will be valid. But when you move to a public test network, only the valid password associated with the administrator public address will be accepted to execute the contract functionality.

The voting administrator will use this page to

  • register voters
  • start and end the proposal registration session
  • start and end the voting session
  • tally the votes

You can get an idea of the layout of this page from figure 12.5.

Although you can look at the entire HTML of this web page on the book website, and I invite you to download and experiment with it (SimpleVotingWebUI folder), I’ll highlight the most frequently recurring elements that you’ll find across both admin and voter pages:

  • JavaScript include files
  • Table row showing current voting workflow status
  • Administrator address and password fields
  • Input fields, such as address for voter registration
  • Buttons triggering smart contract function calls
  • Feedback message cells, to show error messages that the JavaScript validating the input produces or the contract returns

Figure 12.5. The admin web page. The voting administrator will use this page to register voters, start and end the proposal registration session, start and end the voting session, and tally the votes

Javascript Includes

These are the JavaScript files that the admin.html page references:

<head>   
     <script src="bower_components/web3/dist/web3.min.js"></script>
     <script src="bower_components/jquery/dist/jquery.min.js"></script>
     <script src="bower_components/truffle-contract/dist/truffle-contract.js"></script>     
     <script src="./simplevoting.js"></script>
</head>

As you can see, the admin page references Web3.js, jQuery, and truffle-contract through their respective Bower download folders. For simplicity, and to avoid code duplication, I’ve decided to place all the JavaScript needed by the admin and voter web pages in a single file called simplevoting.js. I’ll present it to you in the next section.

Text Displaying Current Workflow Status

At the top of the page, a row displays the current status of the voting workflow; for example, “Registering Voters” or “Proposal Registration Session Open:”

<table border="0" cellpadding="0" id='currentStatus'> 
     <tr>
            <td><b>Current status:</b></td>

                        <td id='currentWorkflowStatusMessage'></td>
            <td></td>
            <td></td>
     </tr>     
</table>

This is useful from two points of view:

  • It reminds the user which processing step they’re in, so it prevents them from attempting incorrect operations.
  • From a technical point of view, the workflow description is retrieved from the voting contract at page load, so correct rendering confirms the contract is running as expected.
Administrator address and password fields

The page captures the administrator address and password through input fields:

<table border="0" cellpadding="0" id='user'> 
     <tr>
         <td><b>Admin address:</b></td>
         <td><input type="text" id="adminAddress" width="400" /></td>
         <td><b>password:</b></td>
         <td><input type="text" id="adminPassword" width="400" /></td>
         <td><button onclick="unlockAdmin()">Unlock account</button></td>
         <td id='adminMessage'></td>
      </tr>     
</table>

This input is needed to

  • unlock the administrator account by clicking Unlock Account (If the password is correct, the specified account will be unlocked, and the user will be able to perform operations on this page for three minutes, before the account gets locked again.)
  • authenticate the account as the administrator so the user is allowed to access the web page functionality
  • attach the address as the sender of the contract function calls associated with this web page, which are all restricted to the administrator account, so they can get authorized
Note

When connecting to Ganache, it isn’t necessary to unlock the administrator account to perform operations on the web page, but when you connect to the test network, you’ll have to do so.

Other input fields

Input fields also capture input specific to the functionality provided by the page, such as the registration of voters; for example, the field for the voter address:

<td>Voter address:</td>
<td><input type="text" id="voterAddress" width="400" /></td>
Buttons triggering smart contract function call

Some buttons, such as the one associated with the voter registration, trigger a Java-Script function (in this case registerVoter()), which gathers the associated input (in this case the voter address) and includes it in the contract call together with the administrator address:

<td><button onclick="registerVoter()">Register</button></td>

Other buttons, such as the one that starts the voting session, trigger a JavaScript function (startVotingSession()) that doesn’t take any input apart from the administrator address before creating and submitting the related contract call:

<table border="0" cellpadding="0" id='proposalsRegistration'> 
     <tr>
         <td><button onclick="startProposalsRegistration()">Start</button></td>
         <td><button onclick="endProposalsRegistration()">End</button></td>
         <td id='proposalsRegistrationMessage'></td>
      </tr>     
</table>
Feedback message cells

Next to most buttons is a cell to show error or success messages that the JavaScript validating the input or the contract call return:

<td id='voterRegistrationMessage'></td>

As I mentioned previously, you can find the full HTML of the admin.html page on the book website. I encourage you to examine the code before running it.

12.7.4. Writing the admin page JavaScript

Before starting this section, I’d like to make it clear I’ll be presenting JavaScript code based on asynchronous calls for two reasons:

  • It’s best practice to write asynchronous JavaScript in web applications.
  • If you want to use Web3 providers recommended from a security point of view, such as Mist or MetaMask, they only support asynchronous calls to an Ethereum contract.
Note

I believe that even if you aren’t familiar with asynchronous JavaScript, you should be able to follow this section. But if you’re struggling, I’ve also implemented a synchronous version of the code I’ll be presenting, which you can find on the book website.

I’ll present only the JavaScript around the registration of a voter address by the administrator. This is an interesting use case because it requires common functionality needed by most administrator operations, such as JavaScript code that

  • connects to the contract on page load
  • displays the voting workflow status on page load
  • unlocks the administrator account
  • validates user input; for example, checking whether the administrator address is valid or whether the operation the administrator is attempting is compatible with the current workflow status
  • executes a contract transaction
  • handles contract events

Once you’ve understood this use case, you should be able to implement the rest of the administrator functionality by yourself. I encourage you to do so and then compare your code with mine from the book website. Let’s go step by step.

Connecting to the voting contract

The JavaScript code connecting the web page to the voting contract is similar to the code you wrote for connecting to SimpleCoin back in chapter 8. But, thanks to truffle-contract

  • The contract ABI is read directly from SimpleVoting.json, a file that truffle compile generates, with no need of hardcoding.
  • The contract address isn’t hardcoded anymore, and it’s also read from Simple-Voting.json, specifically from its networks dictionary; a new network entry is added every time ganache is restarted, whereas the contract address is updated when executing truffle migrate:
    "networks": {
        "1526138147842": {      "events": {},
          "links": {},
          "address": "0xaf18e4e373b90709cc08a231ce24015a0da4f8cc",
          "transactionHash":
           "0x37ecb76d6cd6051c5d122df53db0063d1336e5b3298150b50d62e8ae845e9bbb"
        },
       ...
        "1526683622193": {                                    1
          "events": {},
          "links": {},
          "address": 
             "0xdaef7d6211bc0c0639178f442c68f468494b7ea2",    2
          "transactionHash":
           "0xea66f69e35ccc77845405dbc183fc0c3ce831cd977f74c5152d6f97a55ebd8af"
    }

    • 1 Network entry generated at Ganache restart
    • 2 Contract address updated when migrating (deploying) the contract

Here’s how the code looks:

var SimpleVoting;                                    1

window.onload = function() {

     $.getJSON("./contracts/SimpleVoting.json", 
         function(json) {                            2
         SimpleVoting = TruffleContract( json );     3
         
            SimpleVoting.setProvider(
               new Web3.providers.HttpProvider(
                 "http://localhost:8545"));          4

  • 1 Variable to store a reference to the deployed voting contract instance
  • 2 Reads SimpleVoting.json
  • 3 References SimpleVoting through truffle-contract, which imports the contract ABI from SimpleVoting.json
  • 4 Sets up Web3 to point to the local Ethereum client (currently Ganache)

As I mentioned earlier in section 12.7.1, the jQuery instruction to read SimpleVoting.json will fail if you try to browse on the admin.html page directly from disk, but it will work if you browse through the website you created earlier on:

http//localhost:8080/admin.html
Displaying the voting workflow status

The voting workflow status (“Registering Voters,” “Proposals Registration Started,” and so on) is displayed at page load and refreshed at each status change with this JavaScript function:

function refreshWorkflowStatus()
{          
    SimpleVoting.deployed()                                1
    .then(instance => instance.getWorkflowStatus())        2
    .then(workflowStatus => {
       var workflowStatusDescription;
          
       switch(workflowStatus.toString())                   3
       {
          case '0':
             workflowStatusDescription = "Registering Voters";
             break;
          case '1':
             workflowStatusDescription = "Proposals Registration Started";
             break;
          ...     
          default:
               workflowStatusDescription = "Unknown Status";
      }
                    
         
    $("#currentWorkflowStatusMessage").html(
     workflowStatusDescription);                           4
     });
}

  • 1 References the deployed contract
  • 2 Gets the workflow status ID from the contract
  • 3 Determines the related status description
  • 4 Binds the status description to the HTML table cell where it should be displayed

As you know, because the contract function getWorkflowStatus() is a read-only view, its invocation is considered a plain call and doesn’t generate a blockchain transaction. As a result, you don’t need to specify the caller and set other transaction details, such as the gas limit.

You’ll call the workflow status function in two places. The first one is at the end of the code block setting the SimpleVoting reference to the instance of the voting contract, which happens at page load:

$.getJSON("./contracts/SimpleVoting.json", function(json) { ...

    ...
    refreshWorkflowStatus();
});

The other one is in the event handler associated with the WorkflowStatusEvent event that the voting contract publishes at every status change. We’ll examine that later.

Unlocking the administrator account

As you might remember, to invoke any contract function that alters contract state and consequently generates a blockchain transaction, you need to unlock the account of the caller (who becomes the transaction sender). Before performing any state-changing operation on this web page, such as registering a voter or starting the voting session by clicking on the corresponding button, you need to unlock the administrator account. This unlocking takes place through the following function:

function unlockAdmin()
{
     var adminAddress = $("#adminAddress").val();
     var adminPassword = $("#adminPassword").val();

     var result = web3.personal.unlockAccount(
        adminAddress, adminPassword, 180);               1
     if (result)
            $("#adminMessage").html('The account has been unlocked');
     else
            $("#adminMessage").html('The account has NOT been unlocked');
}

  • 1 Unlocks the account for three minutes

As you might recall, I’ve registered the execution of this function to the click event of the Unlock Account button:

<td><button onclick="unlockAdmin()">Unlock account</button></td>

As a result, you must click this button before performing any state-changing operation. But if you prefer, you can get rid of the button and call this function just before any contract call that generates a transaction. For example, you could place it just before the contract call instance.registerVoter() within your JavaScript registerVoter() function:

function registerVoter() 
{
...
unlockAdmin(adminAddress, adminPassword);
SimpleVoting.deployed()                         
.then(instance => instance.registerVoter(voterToRegister, ...
Validating user input

As for centralized applications, it’s good practice to validate user input before passing it to an external function call, to avoid having bad input generate an exception. To implement that, the first few lines of your registerVoter() JavaScript function are dedicated to capturing user input from the HTML and checking it:

function registerVoter() {

   $("#voterRegistrationMessage").html('');
   var adminAddress = 
      $("#adminAddress").val();                             1
   var voterToRegister = 
      $("#voterAddress").val();                             1

   SimpleVoting.deployed()
   .then(instance => instance.isAdministrator(   
       adminAddress))                                       2
   .then(isAdministrator =>  {          
       if (isAdministrator)
       {
          return SimpleVoting.deployed()
          .then(instance => 
                 instance.isRegisteredVoter(
                       voterToRegister))                    3
          .then(isRegisteredVoter => {
               if (isRegisteredVoter)
                 $("#voterRegistrationMessage")
                    .html(
                    'The voter is already registered');     4
               else
               {
                ...
               }
            });
       }
       else
       {
          $("#voterRegistrationMessage")
               .html(
               'The given address does not correspond 
                  to the administrator');                   4
       }

   });
}

  • 1 Gets admin and voter addresses from the HTML
  • 2 Checks if the specified address belongs to the administrator
  • 3 Checks if the specified address belongs to a registered voter
  • 4 Shows validation error message

As you can see, you can call some contract read-only view functions to validate the input you’re going to then submit to a transaction-generating function. You might also want to validate if the JavaScript registerVoter() function is being called during the correct workflow step (voter registration) to avoid contract-side exceptions you’ll receive if you attempt to register a voter when the proposal registration session has already started; for example:

return SimpleVoting.deployed()
          .then(instance => instance.getWorkflowStatus())
          .then(workflowStatus => {
                 if (workflowStatus > 0)
                    $("#voterRegistrationMessage")
                    .html('Voters registration has already ended');
                 else
          {
          ...

You’ve performed substantial validation. Now you can call the contract registerVoter() with more confidence that it won’t throw an exception.

Note

You might wonder whether a user might be able to hack the Dapp if they can modify the JavaScript and bypass the validation. This would be pointless. The purpose of JavaScript input validation isn’t to provide a layer of security but to avoid unnecessary transaction costs the user will incur when their transaction is reverted by contract-side validation code. For example, trying to register a voter from a nonadministrator account by bypassing the JavaScript performing the isAdministrator() check will end in an exception being thrown by the contract-side onlyAdministrator function modifier.

Calling a transaction generating contract function

While you’re here, I’ll tell you more about JavaScript code around the execution of a contract transaction such as instance.registerVoter(). As you know, when calling a function that alters contract state, you’re generating a blockchain transaction. In this case, you must supply

  • the transaction sender
  • transaction details, such as at least the gas limit
  • a callback to handle errors in case of unsuccessful completion, or results in case of successful completion

You can see all these details in the registerVoter() call:

SimpleVoting.deployed()                         1
.then(instance => instance.registerVoter(
   voterToRegister,                             2
       {from:adminAddress, gas:200000}))        3
.catch(e => $("#voterRegistrationMessage")
   .html(e));                                   4

  • 1 References deployed contract
  • 2 Calls registerVoter function
  • 3 Specifies transaction sender and gas limit. (Generally you set it to the lowest amount that allows transaction completion consistently, and you can find the value during testing.)
  • 4 Handles errors from the contract call
Handling contract events

Before leaving this section on the JavaScript of the admin page, I’d like to show you how to handle workflow status change events that the contract publishes. This would allow you to, for example, update the current workflow status description at the top of the page and show registration confirmations for each voter that the administrator adds.

First of all, you must declare a variable to reference the contract WorkflowStatusChangeEvent event type:

var workflowStatusChangeEvent;

You instantiate this variable at page load, after having set the reference to the contract:

window.onload = function() {
    $.getJSON("./contracts/SimpleVoting.json", function(json) {
    ...
    SimpleVoting.deployed()
    .then(instance => instance
       .WorkflowStatusChangeEvent())                     1
    .then(workflowStatusChangeEventSubscription => {
             workflowStatusChangeEvent = 
                workflowStatusChangeEventSubscription;

       workflowStatusChangeEvent
          .watch(function(error, result){                2
          if (!error)
              refreshWorkflowStatus();                   3
          else
              console.log(error);
       });                
    });     
   ...

  • 1 References contract WorkflowStatusChangeEvent event
  • 2 Registers handler with the contract event
  • 3 When the event is handled, invoke client-side function to refresh UI

As you can see, every time the workflow status changes, the function refreshing its description is invoked. The contract event that notifies a voter registration confirmation, VoterRegisteredEvent(), is handled in exactly the same way, with the only difference being that the UI is refreshed inline and not through a client-side function:

SimpleVoting.deployed()
.then(instance => instance.VoterRegisteredEvent())
.then(voterRegisteredEventSubscription => {
       voterRegisteredEvent = voterRegisteredEventSubscription;     

       voterRegisteredEvent.watch(function(error, result) {
       if (!error)
           $("#voterRegistrationMessage")
           .html('Voter successfully 
              registered');                 1
       else
          console.log(error);
       });                 
});

  • 1 Refreshes voterRegistrationMessage label

12.7.5. Writing the voter page HTML

The voter page, which you can see in figure 12.6, shares many elements in common with the admin page. As I said earlier, the main purpose of the voter page is to register proposals and to vote on them. It’s worthwhile to highlight the proposal table, which is updated dynamically every time a new proposal is added by a voter:

<tr><table border="0" cellpadding="0" width="600" id='proposalsTable'> </table></tr>
Figure 12.6. Screenshot of the voter page

12.7.6. Writing the voter page JavaScript

Let’s see how the proposal table update takes place. If you have a look at the register-Proposal() function in simplevoting.js, you’ll notice many similarities to registerVoter() that we examined earlier. The main difference is that the event handler associated with the event that the contract fires upon proposal registration, ProposalRegisteredEvent(), doesn’t only confirm that the registration has taken place by refreshing the related HTML label. In addition, it calls a function that generates a dynamic HTML table listing of all the proposals added so far:

...
proposalRegisteredEvent.watch(function(error, result) {
    if (!error)
    {
       $("#proposalRegistrationMessage")
         .html('The proposal has been 
            registered successfully');      1
       loadProposalsTable();                2
    }
    else
       console.log(error);
});

  • 1 Refreshes proposal registration confirmation label
  • 2 Refreshes table listing all proposals registered so far

Here’s how the proposals table is refreshed dynamically:

function loadProposalsTable() {
     SimpleVoting.deployed()
     .then(instance => instance.getProposalsNumber())
     .then(proposalsNumber => {
            
          var innerHtml = "<tr><td><b>Proposal ID</b></td><td><b>Description</b></td></tr>";
            
          j = 0;
          for (var i = 0; i < proposalsNumber; i++) {
             getProposalDescription(i)
             .then(description => {
                 innerHtml = innerHtml + "<tr><td>" + (j++) + 
                     "</td><td>" + description + "</td></tr>";
                 $("#proposalsTable").html(innerHtml);
         });
         }
    });          
}

function getProposalDescription(proposalId)
{
    return SimpleVoting.deployed()
       .then(instance => instance.getProposalDescription(proposalId));
}

12.7.7. Running the admin and voter web pages

Now that you understand the code for both web pages, it’s time to run them. I’ll guide you through the entire voting workflow so you’ll see how the application works from start to end.

Preliminaries

Before starting, make sure Ganache is running in a console. I suggest you start it with an instruction to redirect the output to a log file, which will come in handy later:

C:Ethereum	ruffleSimpleVoting>ganache-cli > c:	empganache.log

Because you’ve restarted Ganache, you must redeploy SimpleVoting (in a separate console):

C:Ethereum	ruffleSimpleVoting>truffle migrate --reset

Next, recopy SimpleVoting.json from Truffle’s buildcontracts folder to the website’s contracts folder. Now you can restart the website (in a separate console):

C:EthereumSimpleVotingWebUI>node webserver.js
Server running on 8080...
Registering voters

Browse the admin webpage

http://localhost:8080/admin.html

and you’ll see the screen I showed you earlier in figure 12.5. You might have noticed that the initial current status is “Registering Voters,” as expected, as you can see in figure 12.7.

Figure 12.7. The initial workflow status shown on the admin web page at startup

Unless your truffle.js is configured to run the migrations from a specific account, they’ll be executed against accounts[0], which, as you know, will become the voting administrator. Because you’re deploying on Ganache, look at the ganache.log file you’re redirecting the output to:

Ganache CLI v6.1.0 (ganache-core: 2.1.0)

Available Accounts
==================
(0) 0xda53708da879dced568439272eb9a7fab05bd14a

(1) 0xf0d14c6f6a185aaaa74010d510b975ca4caa1cad
(2) 0x5d6449a4313a5d2dbf7ec326cb8ad204c97413ae
...

Copy the address corresponding to account (0) into the Admin Address text box and, because you’re running against Ganache, leave the password field empty, as shown in figure 12.8. You can then register the voters’ addresses.

Figure 12.8. Copying the administrator’s account into the corresponding text box

Because shortly you’ll be running some voter functionality, I suggest you register (1), (2), (3), ... from Ganache’s startup screen as voters. When registering account (1), you enter the corresponding address in the Voter Address text box, as shown in figure 12.9.

Figure 12.9. Entering the voter address

Before submitting the voter registration transaction, you must unlock the admin account, so click Unlock Account. You’ll get a confirmation that the account has been unlocked. Then click Register. If everything has gone well, you should get a confirmation message next to the Register button; otherwise, you’ll get an error message.

Register a few voters using the other account addresses from Ganache’s log file. If you do so within the next three minutes, you don’t need to unlock the administrator account again before registering the accounts.

Note

As I said earlier, when running on Ganache, you don’t need to enter any administrator password, and you don’t even need to unlock the corresponding account, as accounts are all indefinitely unlocked. But it’s best to get used to the account unlock operation for a smoother transition to the public test network later.

If you want to verify that these accounts are indeed registered, you can use the Check Registration verification area. For example, to verify that account (1) is a registered voter, enter the corresponding address in the Address text box and click Check Registration. You should see a confirmation message like the one shown in figure 12.10.

Figure 12.10. Checking whether an account has been registered as a voter

Before we move to the next workflow step, I suggest you see what happens if you try to

  • register a voter by specifying in the admin address text box a different account; for example, account (4)
  • perform an operation that shouldn’t happen at this stage; for example, you click the Tally votes button

You should receive corresponding error messages.

Starting the proposal registration session

Once you’ve registered a few voters, you can start the proposal registration session by clicking Start in the related area of the screen. This will call the JavaScript start-Proposals-Registration() function, which in turn will call the start-Proposals-Registration() contract function. The contract function will raise the WorkflowStatusChangeEvent event, which the client-side JavaScript refreshWorkflow-Status() function that’s in charge of refreshing the workflow status label to “Proposals Registration Started” will handle, as you can see in figure 12.11.

Figure 12.11. After you start the proposals registration session, the Current Status label will be refreshed with the corresponding status description.

Registering proposals

At this point, voters are allowed to register proposals. Open the voter web page in a separate browser tab: http://localhost:8080/voter.html.

You’ll see a screen similar to the one shown in figure 12.6, with a small difference: the current status will be “Proposals Registration Started” as on the Admin page. This confirms the page is connected correctly to the voting contract.

Now register a proposal under Ganache’s account (1), who is a valid registered user. Enter the corresponding address in the Voter Address text box, and leave the password blank, as you did for the administrator. Then enter a proposal description, for example, “Proposal Zero.” If you want, before clicking Register, you can check what happens if you submit a vote at this stage (say against proposal ID = 0). You should get an error message.

Now click Register. You’ll get a confirmation message next to the button. You’ll also see at the top of the screen the proposal you’ve entered together with the related ID, returned from the contract, as shown in figure 12.12.

Keep registering more proposals, using all the accounts you’ve registered as voters. If you want, you can register more than one proposal against the same account, as you didn’t set any constraints on this. You can also check what happens if you try to register a proposal against the administrator account or against some account that you didn’t register as a user.

Figure 12.12. Registering a new proposal

Ending the proposal registration session

After registering a few proposals, you can end the proposal registration session. Go back to the admin page and, if the administrator account address is still in the related text box, click End in the Proposal Registration Session area. You should get a confirmation message next to the button and the new workflow status in the Current Status area, as shown in figure 12.13.

Figure 12.13. Ending the proposal registration session

As usual, you can try to attempt actions that aren’t supposed to take place at this stage. For example, try ending the voting session.

Starting the Voting session

Time to start the voting session! Click Start corresponding to the voting session, and the Current Status description at the top of the screen will change accordingly to “Voting Session Started.”

Voting

Go back to the voter’s screen. You’ll see that the status at the top of the screen has also changed to “Voting Session Started.” This confirms that it has been refreshed by the WorkflowStatusChangeEvent event that the contract published at the end of the startVotingSession() function and the voter’s page JavaScript handled. At this point, you’re allowed to vote.

Enter the address of one of the voter accounts in the Voter Address text box, and then put one of the Proposal IDs you see at the top of the screen in the Proposal ID text box. Then click Vote. You’ll see a confirmation message next to the Vote button. If you try to vote again, you’ll see an exception from the voting contract that’s preventing you from voting twice. Continue by casting various votes from the accounts you registered earlier.

Ending the voting session

After you’ve voted from various registered accounts, you can end the voting session the same way you started it: go back to the admin page, move the mouse in the Voting Registration area, and click End. As usual, you’ll see a confirmation message and a change of state at the top of the screen. If you now try to go back to the voter page and cast a vote through a registered account that you didn’t use, you’ll be prevented from voting.

Tallying the votes

It’s the moment of truth: time to tally the votes! On the admin page, click Tally Votes. Apart from the usual confirmation message and status description stage, you’ll see a new table appearing at the bottom of the screen summarizing the vote results. The handler of the WorkflowStatusChangeEvent event has generated this table, with a specific check on the status ID corresponding to votes tallied.

If you flip back to the voter page, you’ll see the same results table at the bottom of the screen, as you can verify in figure 12.14. The same table appears because both pages were listening to the same event.

Figure 12.14. The voter page after the votes have been tallied

Congratulations! You’ve designed, implemented, tested, and run a full voting Dapp. You should consider this an important achievement. It’s the first time (in the book) you’ve built a complete Dapp from scratch, covering all the layers from the smart contract to the web UI. You’ve come a long way from chapter 1, when you started to learn about new concepts, such as blockchain and Dapp, and experimented with a simple embryonic version of SimpleCoin. Now you should have a good understanding of smart contracts, how to write them in Solidity, how to communicate with them with Web3.js, and how to create a simple web UI to interact with them in an easier way. Before leaving the chapter, you’ll deploy the Dapp into the public test network and check that everything is still working as expected.

12.7.8. Deploying to the public network

Here are the steps you must take to deploy SimpleVoting onto Ropsten, the public test network:

  1. Stop Ganache.
  2. Start geth, configured to point to Ropsten.
  3. Configure Truffle to point to the Ropsten network and deploy from one of your Ropsten accounts.
  4. Unlock the Ropsten account you’ll use for deployment.
  5. Perform a Truffle migration to Ropsten.
  6. Check that the UI still works correctly.
Stopping Ganache

You should be able to do everything pretty much by yourself but, just in case, let me walk through these steps with you. Start with an easy one: press CTRL+C or close down the Ganache window.

Starting geth on Ropsten

You’ve done this a couple of times already, but I’ll save you some page flipping. You can see in the following code the full command to connect to Ropsten through some seed nodes, including the option to perform a fast synchronization, in case you haven’t been connected to the test network for a while. Also, I’ve highlighted the last two parameters, which open geth’s communication through RPC on port 8545 and allow cross-origin resource sharing (CORS) so that the JavaScript of your web page will be allowed to communicate directly with geth:

C:Program Filesgeth>geth --testnet –-bootnodes
"enode://145a93c5b1151911f1a232e04dd1a76708dd12694f952b8a180ced40e8c4d25a908
a292bed3521b98bdd843147116a52ddb645d34fa51ae7668c39b4d1070845@188.166.147
.175:30303,enode://2609b7ee28b51f2f493374fee6a2ab12deaf886c5daec948f122bc837
16aca27840253d191b9e63a6e7ec77643e69ae0d74182d6bb64fd30421d45aba82c13bd@13
.84.180.240:30303,enode://94c15d1b9e2fe7ce56e458b9a3b672ef11894ddedd0c6f247e
0f1d3487f52b66208fb4aeb8179fce6e3a749ea93ed147c37976d67af557508d199d9594c35f
[email protected]:30303" 
--verbosity=4 --syncmode "fast" --cache=1024 --rpc --rpcport 8545
--rpccorsdomain "http://localhost:8080" --rpcapi "eth,net,web3,personal"
Note

If your geth client doesn’t seem to synchronize with the public test network, attach a console to geth and manually add the Ropsten peers specified on this GitHub page: http://mng.bz/6jAZ.

Configuring Truffle to point to Ropsten

In the previous chapter, I explained how to point Truffle to a test or live network. Modify your truffle.js (or truffle-config.js) file as follows:

module.exports = {
  networks: {
    development: {
      host: "localhost",
      port: 8545,
      network_id: "*" // Match any network id
    },
    ropsten: {
      host: "localhost", 
      port: 8545,                                    1
      network_id: 3,    
      from: 
 "0x70e36be8ab8f6cf66c0c953cf9c63ab63f3fef02",     2
      gas: 4000000                                   3
    }      
  }
};

  • 1 Matches the port open on geth for RPC communication (as noted previously)
  • 2 Replace this account with the Ropsten account you want to use to deploy.
  • 3 This should be enough gas to deploy SimpleVoting.

Make sure the Ropsten account you choose to deploy SimpleVoting has some (test) Ether in it!

Unlocking the account used for deployment

To unlock the Ropsten account you’ve specified for deployment, start by opening a new console and attaching it to geth:

C:Program Filesgeth>geth attach ipc:\.pipegeth.ipc

Once the geth JavaScript console has started, unlock the deployer address (for 300 seconds) as follows:

> web3.personal.unlockAccount("0x70e36be8ab8f6cf66c0c953cf9c63ab63f3fef02", "YOUR_PASSWORD", 300);
true
Performing a Truffle migration to Ropsten

To perform a migration on a network other than the development one, you must provide it explicitly through the --network option (on a separate console):

C:Ethereum	ruffleSimpleVoting>truffle migrate --network ropsten
Warning

Make sure your geth client is fully synchronized with Ropsten before attempting this operation. You can check the latest Ropsten block against https://ropsten.etherscan.io/ and compare it with what you see on your client’s console.

If your geth client is actively connected to Ropsten and you’ve configured everything correctly, you should see some output confirming that deployment has taken place:

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0x8ec9c0b3e1d996dcd2f6f8b0ca07f8ce5e5d15bd0cc2eea3142f351f53070499
  Migrations: 0xeaf67f4ce8a26aa432c106c2d59b7dbeaa3108d8
Saving successful migration to network...
  ... 0xadffddd1afe362a802fc6a80d9c60dc2e4a85f9e850ff3c252307ec9255988af
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Deploying SimpleVoting...
  ... 0xbe1ca60296cc8fb94a6e97c74bf6c20547b2ed7705e8db84d51e17f38904fa09
  SimpleVoting: 0x3a437f22d092b6ae37d543dcb765de4366c00ecc
Saving successful migration to network...
  ... 0x37f90733539ba00f946ef18b456b5f7d6b362f93e639583d789acd36c033e5d2
Saving artifacts...
Checking the UI still works

If you look back at the first lines of simplevoting.js, the initialization looks at a web3 provider coming from localhost:8545. Geth is exposing to RPC the same port number that Ganache was previously exposing, so everything should still work, right? Let’s find out! First of all, copy the new SimpleVoting.json from Truffle’s buildcontracts to the website contracts folder. Then try to browse on admin.html as you did previously. (Make sure the website is still running; otherwise, restart it with: node webserver.js.) Type the following:

http://localhost:8080/admin.html

Yes! The page loads correctly, and it shows the initial “Registering Voters” status as expected. You can now go through the whole workflow, from voters’ registration to vote tallying, as you did when running on Ganache. Before doing so, make sure the TESTNET admin and voter accounts have enough Ether to perform the related operations.

Note

Bear in mind that when you perform operations on TESTNET, you must unlock the related account at each operation. Also, every time you perform an operation, from either the admin account or a voter account, you’ll have to wait for the related transaction to be mined, so it will take longer (around 15 seconds) to receive confirmations.

Warning

If your UI seems unresponsive and you don’t get a confirmation that the operation you attempted completed, check potential errors on the console of your browser’s development tools. For example, on Chrome, you can click F12 and then select Console from the top menu. If the issue is related to account unlocking, it might be due to changes in Web3 around this area. If that’s the case, you can unlock the administrator or the voter account from an attached Geth console, as you’ve seen in previous chapters, rather than using the Unlock Account button.

12.8. Food for thought

I hope you’ve found this chapter useful for tying together all you learned in the previous chapters. At this point, you should feel confident you can start building your own Dapp, and you should be geared up to take on a new challenge. If you’re eager to practice your newly acquired skills a little more before moving on to the next chapter on advanced topics in contract design and security, I’ve prepared for you a little plan you can use as a springboard for improving SimpleVoting. I have two sets of improvements in mind:

  • Technical improvements
  • Functional improvements

12.8.1. Possible technical improvements

I designed SimpleVoting with the main objective of presenting the full Ethereum Dapp development lifecycle in one shot, so I decided to keep it as simple as possible to focus mainly on the big picture. Also, I understand that not all readers have the same background and experience in web development and continuous integration, so I avoided introducing relatively advanced techniques in either of these areas. If you’d like to improve the web UI, there’s room for improvement in various respects, including these two:

  • Using a standard Web3 provider
  • Automating build and deployment with a web build tool
Using a standard Web3 provider

Your application shouldn’t be served through a plain web browser, but through a standard Web3 provider, such as Mist or MetaMask, that provides enhanced security. These providers require you to call contract functions asynchronously, though, so reimplementing your SimpleVoting.js using asynchronous features would also be beneficial for this reason. After you’ve reimplemented your JavaScript code accordingly, you can detect a standard provider by replacing this line

SimpleVoting.setProvider(
  new Web3.providers.HttpProvider(
  "http://localhost:8545"));

with this:

var web3Provider;
if(typeof web3 != 'undefined')
     web3Provider = new Web3(web3.currentProvider);     1
else
     web3Provider = new Web3(
       new Web3.providers.HttpProvider(
       "http://localhost:8545"));                       2

SimpleVoting.setProvider(web3Provider);

  • 1 Uses standard provider (such as Metamask or Mist) if available
  • 2 Instantiates default provider if none is available

How do you supply a standard provider? First, remember to log in. Then, assuming you installed the MetaMask browser plugin, as explained in chapter 3, make my suggested code change at the top of SimpleVoting.js, and finally browse on the admin or voter page. By doing this, the Web3 provider will be set to MetaMask.

Automating build and deployment with a web build tool

If you have experience in using web build tools, you might have found it a bit annoying to copy SimpleVoting.json from Truffle’s buildcontracts to the web project contracts folder every time you made a change. You might be wondering whether Truffle supports a build pipeline. It does, and, in fact, you can plug in your favorite build tool, such as Webpack or Grunt.

The Truffle documentation website also includes a Webpack Truffle Box (https://truffleframework.com/boxes/webpack), which is a template project developed with Webpack support. You can use it as a base to build your Webpack integration, or at least to understand how it works.

12.8.2. Possible functional improvements

The current voting application is intentionally limited from a functional point of view. After the admin registers the voters (one by one), voters can register (potentially many) proposals and then vote on one of them. The winning proposal is decided based on simple majority: the one that gets the most votes is chosen. Also, you’re recording the proposal that each voter chooses, so there’s no anonymity. Here are some ways you could enrich the voting Dapp functionality:

  • Allow the admin to register voters in bulk.
  • Allow a voter to delegate their vote to another registered voter.
  • Decide the winning proposal with a qualified majority.
  • Implement token-based voting.
  • Implement anonymous voting.
Registering voters in bulk

The admin could register voters in a single bulk registration, rather than one by one, saving on administrative time and transaction costs. You could implement this enhancement with a new function taking in an array of addresses.

Delegating votes

A voter could delegate their vote to another registered voter. Then the delegator wouldn’t be able to vote, and the person delegated would cast as many votes as they’ve been delegated to cast by as many voters.

These are some hints on how to start the implementation:

  • You could change the voter struct as follows:
    Voter
    {
        HasVoted: boolean,
        CanVote: Boolean,
        NumVotes: int
    }
  • You could add a new function so that the caller can delegate their vote to another registered voter. This would be similar to SimpleCoin’s authorize function to allocate an allowance to another account:
    delegateVote(address delegatedVoter)
  • The function’s implementation should alter the state of both delegator (CanVote = false) and delegated (NumVotes +=1).
  • To check easily whether a voter is still entitled to vote, you could implement a canvote modifier around the CanVote property.
Deciding the winning proposal with a qualified majority

You could introduce the concept of a qualified majority. To win, a proposal should reach a predefined quorum: a minimum percentage of the cast votes.

  • You’d introduce a new variable defining the quorum, initialized in the constructor:
    uint quorum
  • You’d modify the tallyVotes() function so the winning proposal would set the winner only if the quorum was reached.
Implementing token-based voting

The current voting Dapp allows a voter account to increment the vote count of one of the proposals. This creates strong coupling between the voter and the chosen proposal, which can be slightly softened through vote delegation. In addition, the delegated voter will assign the entire vote of the delegator to a single proposal.

An interesting concept that has appeared in the crypto space is that of token-based voting. It works like this:

  • Each voter is assigned a voting coin or token, similar to our dear SimpleCoin.
  • The voter then casts a vote by transferring the voting token to the proposal address.

Because a vote has to be “spent,” this would prevent double voting. Things could get more sophisticated. A voter could, for example, do one of the following:

  • Transfer the voting token entirely or partially to one or many other voters through a transfer operation similar to that you’ve seen in SimpleCoin. (You could look at this as fractional delegation of the original vote.)
  • Vote for one or many proposals, by assigning them specific fractions of the initial token.

You could build a token-based voting Dapp by implementing a voting token (along the lines of SimpleCoin) and then integrating it into the voting contract in the same way you integrated the crowdsale token in the crowdsale contract.

Anonymous voting

As you might remember, you’re recording the proposal ID that each voter chooses in the votedProposalId property of the voter struct. If you think you could implement anonymous voting by not recording the proposal chosen by the voter and by incrementing its vote count, you’re wrong. As you know, votes are cast through transactions, which are all recorded in the blockchain and can be inspected, so it would be easy to find out who voted for which proposal.

Anonymizing voting is possible, but it’s complicated because there’s no out-of-the-box functionality for anonymizing transactions. This is a complex topic based on advanced cryptographic concepts such as zero-knowledge proof, which you might have heard associated with popular cryptocurrencies such as Zcoin, Zcash, and Monero.

Some researchers at Newcastle University, UK, have implemented an Ethereum anonymous voting Dapp based on a protocol called Open Vote Network. They’ve made both their paper and their code publicly available on GitHub,[1] together with a video tutorial and many academic references on the topic. I recommend you review this material!

1

See “Open Vote Network” on GitHub at http://mng.bz/nQze for more information.

Summary

  • The SimpleVoting Dapp smart contract shows an effective implementation of predefined requirements, based on events and modifiers.
  • You can build a web UI based on truffle-contract, a JavaScript library that easily connects a web page to a contract that has been previously deployed using Truffle.
  • The truffle-contract library reads the contract ABI and address from a JSON file generated during Truffle contract compilation.
  • To read the contract ABI and address from a JSON file, you must render a web page through a web server. You can set up a minimal web server through Connect and ServerStatic, two Node.js packages.
  • Once you’ve implemented a Dapp, unit tested it, deployed it, and run it successfully against Ganache, a mock Ethereum client, you can deploy it on a public test network.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset