Dapp Development on Ethereum

In this blog entry we take a look at how to develop a Dapp (‘decentralized app’) on the Ethereum blockchain network and enhance our development process through Gitlab’s continuous integration (CI) services. It is part of the examination for the lecture “Software Development for Cloud Computing”.

First, all the necessary technologies to develop a Dapp are shown and explained, followed by an example of a crowd funding smart contract. Finally, a possible CI setup for this kind of application is shown.

Blockchain

A blockchain is an open, distributed ledger that can record transactions between two parties efficiently and in a verifiable and permanent way. All these transactions are stored by each participant and are synchronized between them. The ledger itself consists of a chain of blocks (therefore the name Blockchain), and each block contains several transactions. New transactions are added to the most recent block, and after this block is “mined”, it is linked to the chain and a new block of transactions is started.

The process of “mining” involves finding a proof-of-work for the latest block, in order to guarantee that considerable effort was made. Since every participant is working on the longest end of this generated chain, manipulations in earlier blocks are only possible if one has more computational power than the rest of the network. As an incentive to have as many competitors for block generation as possible, the one who finds the correct proof-of-work first receives a small amount of coins, called blockreward.

Ethereum

Ethereum

The Ethereum project was started around 2014 as a platform for smart contract development. It is built on blockchain technology, representing a distributed, secure-by-design database. This global infrastructure allows us to represent ownership and move property between network participants.

Ethereum can be seen as a second generation blockchain platform, if we would count Bitcoin as the first. The biggest step forward was the introduction of smart contracts. Instead of having each transaction only carry a certain amount of “coins”, now you can also store code, which is supposed to interact with the data in the blockchain.

Another improvement was a new proof-of-work concept. Bitcoin’s proof-of-work has the inherent problem that specialized hardware (so-called ASIC) is much better at mining than regular hardware. Thus, the amount of possible competitors for the generation of the next block decreased a lot over time, since often the power consumption costs were higher than the value gained through mining. In Ethereum, the proof-of-work was changed so that regular GPU’s are already indirectly optimized for this kind of task.

Smart Contracts

Smart contracts allow us to exchange property, or anything of value in that matter, in a secure and conflict-free way without the need for a middle man. They not only define rules and penalties of an agreement like in a regular contract, but also enforce those obligations automatically. Smart contracts could be compared to a vending machine: You fulfill some requirement (e.g. putting a coin in the vending machine), and you automatically earn the expected results.

In Ethereum, smart contracts received their own scripting language called Solidity, which targets the Ethereum Virtual Machine (EVM).  With it we can develop similar to other programming languages, besides some smaller quirks due to the unique nature of a Blockchain.

contract MyToken {
    /* This creates an array with all balances */
    mapping (address => uint256) public balanceOf;

    /* Initializes contract with initial supply tokens to the creator of the contract */
    function MyToken(uint256 initialSupply) {
        balanceOf[msg.sender] = initialSupply;              // Give the creator all initial tokens
    }

    /* Send coins */
    function transfer(address _to, uint256 _value) {
        if (balanceOf[msg.sender] < _value) throw;           // Check if the sender has enough
        if (balanceOf[_to] + _value < balanceOf[_to]) throw; // Check for overflows
        balanceOf[msg.sender] -= _value;                     // Subtract from the sender
        balanceOf[_to] += _value;                            // Add the same to the recipient
    }
}

The idea now is that every contract also has an address, similar to regular participants in the network. This means that they can be the recipient of a transaction, allowing the usual transfer of funds, and also of instructions, i.e. calls to the contract’s member functions, through our supplied code in the transaction.

Since the data is stored in a tamper-free way, it can, for instance, be used for verification purposes. One could implement a smart contract imitating the traditional land register, without the need for additional notary services. Or a company could use this technology for its supply chain management, to prove the point of origin for each of their products.

The possibilities are seemingly endless and it is obvious that many current systems could be improved with this kind of technology. However, there are, of course, also drawbacks. For one, the concept of “proof-of-work” to guarantee the integrity of the network requires a lot of computational power and therefore comes with a high power consumption. There are some new techniques in development (-> proof-of-stake) to remedy this issue, but currently it still has to be taken into account. Also, for some applications the openness of a Blockchain may be problematic. Nevertheless, we should keep in mind that blockchain technology in general is also just another tool among others in our software development toolbox, which may be good for certain applications, but has limitations regarding others.

Structure of a Dapp

A decentralized app (or Dapp) is an application that makes use of Ethereum smart contracts in the background. Therefore each Dapp requires access to the Ethereum network somehow. Ethereum offers several, open-source clients, the most popular ones being:

  • Geth (Implementation in Go)
  • Eth (Implementation in C++)
  • Pyethapp (Implementation in Python)

Through them we are able to read from or write to the blockchain. They all offer more or less the same functionality, however Ethereum didn’t want to limit themselves to one client only for security purposes. By default, those clients offer a console environment to pass commands. In the case of a Dapp, though, we need this interface in our (web) application. Therefore we can set the client to act as a so-called JSON-RPC endpoint to supply a JavaScript interface – called web3 – on an arbitrary port. In our application, we only need to load the web3 library, and connect it to the RPC endpoint. Now we are able to access all important features of the Ethereum client in our JavaScript application.

MetaMask

MetaMask

MetaMask is a useful browser plugin to manage identities of a blockchain. If we consider that we are a visitor on a Dapp website, for instance related to crowd funding, then of course we need a way to verify our Ethereum account (i.e. a simple SSH-key) in order to be able to transfer funds from it to the Dapp. In the most simple way, we could just copy their contract address (and probably it’s interface defined in JSON) and make the transaction locally on our Ethereum client. However, this approach is rather unintuitive and uncomfortable to use, and we would obviously also need the fully downloaded blockchain (multiple gigabytes of data for official networks) first. There are also various public RPC endpoints available, that provide the web3 interface of a certain network, but the issue of account management still remains. We could now read data from the chain without having it downloaded locally, but writing still requires an identity in the network.

MetaMask now solves this issue by injecting the accounts managed by the plugin itself into the source code of the web application, while offering easy RPC connections to external networks. Now writing to the external blockchain is possible, provided that we have enough funds in our account on that network to afford the transaction fees. One thing to note, though, is that the accounts managed by the Ethereum client directly cannot be accessed if MetaMask is used, since they will be replaced by MetaMask’s accounts.

Truffle

Truffle

We need some way to deploy our smart contracts on the Ethereum network. Initially they are written in Solidity, then they need to be compiled into byte-code, and finally this byte-code is added to a transaction and deployed on the network. On the other hand, we probably want to interact with this contract once it is deployed from our web application, so we need some JavaScript abstraction of it. We could do all this manually by using the web3 interface, it is however a rather exhausting and error-prone approach. Instead, we can use Truffle, a development framework for Ethereum, which allows us to easily develop, test, and deploy Ethereum smart contracts.

After some quick initial configurations, the interaction between contract and application becomes a breeze compared to using web3, while many more included features make our developer life much easier. From the perspective of our web application, we now receive a JavaScript interface for each contract, and one function call will get translated to a transaction on the blockchain on the fly.

Truffle also allows us test contracts either directly in Solidity, or in JavaScript. The first uses libraries of Truffle for contract assertions, which are, in essence, comprised of other smart contracts. The latter uses the Mocha testing framework for automated testing and Chai for assertions.

Furthermore, Truffle also takes care of network management. During smart contract development, we usually want to have multiple networks available to deploy on. At least, we want to have one for contract testing, and one for final contract deployment. This is because if we would only use a live network, we could probably not fully test our contracts without spending a lot of real money (remember: Even the contract deployment itself costs transaction fees), and possible bugs could ruin our project before it really began. Ethereum therefore offers dedicated development networks, in which the mining process is simplified and Ether (The currency used in Ethereum) is very easy to obtain. Still, for some test scenarios having to mine Ether first, even though it is rather quick, can be a nuisance, so our alternative would be to create our own blockchain network and give our accounts some starting funds.

Luckily for us, we can avoid having to go through this arduous process, as there is already a finished solution called Test-RPC, an Ethereum client dedicated to development and testing. It basically creates a whole blockchain with one console call and also has some more features specifically for testing.

Crowd Funding as a Smart Contract

Crowd funding is one of the model-applications of a smart contract. All the necessary constraints are in place, like having a fully transparent transaction process, a way to guarantee certain terms for both recipient and donator, and an equal treatment of all participants.

But first, we need to define some rules. These can be arbitrary and depend on personal preference. For instance:

  • Each crowd funded project has some funding goal and some deadline, and general information about the project itself and the backers so far
  • The Beneficiary is only allowed to withdraw the money once the deadline has passed and the funding goal was reached
  • The backers can withdraw their money again at any time before the deadline, afterwards only if the funding goal was not reached

We could of course expand this, but it should suffice for a simple demonstration. So our next step is to translate those rules into a smart contract, for instance called “Campaign”. Error handling or logging is not included for simplicity.

pragma solidity ^0.4.0;

contract Campaign {
    address public beneficiary;
    string public title;
    string public description;
    uint public deadline;
    uint public amountBacked;
    uint public fundingGoal;
    mapping(address => uint) public backers;
    address[] public backerAddresses;

    // Constructor of the contract
    function Campaign(address _beneficiary, string _title, string _description, uint durationInMinutes, uint _fundingGoal) {
        beneficiary = _beneficiary;
        title = _title;
        description = _description;
        deadline = now + durationInMinutes * 1 minutes; // "now" is a keyword in Solidity and returns the current block timestamp
        fundingGoal = _fundingGoal;
    }


    // Function is called if this contract is the recipient of a transaction
    function () public payable {
        if (now < deadline) {
            if (!isBacker(msg.sender)) {
              backerAddresses.push(msg.sender);
            }
            backers[msg.sender] += msg.value;
            amountBacked += msg.value;
        }
    }

    // Function to withdraw money from the contract
    function withdraw() {
        // After deadline
        if (now >= deadline) {
            // Goal was reached
            if (amountBacked >= fundingGoal) {
                // Function caller is beneficiary
                if (msg.sender == beneficiary) {
                    msg.sender.send(amountBacked);
                }
            } else {
                // Not enough money raised
                uint amount = backers[msg.sender];
                if (amount > 0) {
                    if (msg.sender.send(amount)) {
                        backers[msg.sender] = 0;
                    }
                }
            }
        }
        // Before deadline
        else {
            amount = backers[msg.sender];
            if (amount > 0) {
                if (msg.sender.send(amount)) {
                    backers[msg.sender] = 0;
                    amountBacked -= amount;
                }
            }
        }
    }

    // Function to check if backerAddress is a backer
    function isBacker(address backerAddress) public constant returns (bool) {
      for (uint i = 0; i < backerAddresses.length; ++i) {
        if (backerAddresses[i] == backerAddress) {
          return true;
        }
      }
      return false;
    }

}<br>

In our case, it is also useful to have a second contract “CampaignManager”, through which new campaigns are created.

pragma solidity ^0.4.0;

// Our Campaign contract
import "./Campaign.sol";

contract CampaignManager {
    function CampaignManager() {}

    // Function to create a new campaign
    function newCampaign(address beneficiary, string title, string description, uint durationInMinutes, uint fundingGoalInEther) {
        new Campaign(beneficiary, title, description, durationInMinutes, fundingGoalInEther);
    }
}

Once we have our contracts implemented, we can use Truffle to do the rest. First, we need to call “truffle compile” in our project directory (make sure “truffle init” was used to create the project in the beginning) to generate the contract artifacts. Next, we need to call “truffle migrate <network>” (after correct migration configuration, best work on the truffle example project first and go through the truffle tutorials to better understand the inner workings) to deploy the contract (in our case the CampaignManager) on the blockchain. We do not want to create the Campaign contract just now, as this should be done through our web application. Make sure beforehand that an RPC endpoint is running and truffle has the correct address of it in the “truffle.js” config file. Now the contract is ready for use on the blockchain.

To interact with it on our javascript application side, the following needs to be done:

  • Import web3 and truffle-contract library
  • Import generated contract artifacts
  • Create javascript contract interface, e.g. “var Campaign = truffle-contract(campaign_artifacts)”
  • On load, set the web3 provider like “window.web3 = new Web3(web3.currentProvider);”, make sure MetaMask is running

Afterwards, we are free to interact with the contract as we please. An example of creating a new campaign in javascript:

// Get the variables from a form for example
var beneficiary = form.beneficiary.value;
var title = form.title.value;
var description = form.description.value;
var duration = form.duration.value;
var fundingGoal = form.fundingGoal.value;

CampaignManager.deployed().then(function(instance) {
      // Get the accounts before each contract interaction. This is necessary since the user might change the available accounts while the web application is running
      web3.eth.getAccounts(function (err, accounts) {
        if (accounts[0]) {
          // Gas price is depending on the size of the transaction, here it is probably too much
          instance.newCampaign(beneficiary, title, description, duration, fundingGoal, {from: accounts[0], gas: 2000000});
        }
      });
    });

Once everything is set up, it really is quite easy.

Gitlab Continuous Integration

Gitlab

To make the whole development process more smooth, a CI setup is recommended. In our crowd funding case, a three-step setup consisting of a build, test, and deploy stage is all we need to perform all necessary operations. As we have seen from before, we basically need to take care of two separate clients: One is the JSON-RPC endpoint, i.e. an Ethereum client, and the other is our web application. To keep things simple and clean, we use Docker for all clients. Assuming that we want to deploy our web application on a dedicated hosted server somewhere, we first need to setup the ssh connection in a “before_script” in our Gitlab CI yaml file. Then we start with the build stage, and perform the following:

  • Pull the Ethereum client docker image and the web app repo data on the remote server
  • Build docker image for our web app on the remote server, containing a truffle migrate command
  • Pull Test-RPC docker image and setup truffle in the Gitlab runner environment
  • Start Test-RPC docker container on the runner
  • Run truffle compile and migrate on the runner

Now we have two docker images on our remote server, and the running Test-RPC container in the runner environment. Next, we can continue with the test stage:

  • Run truffle test

If the truffle tests were successful, we continue with deploy:

  • Start RPC-Endpoint docker container on remote server
  • Start Web app docker container on remote server

On the remote server, we have to take into account that these two docker containers need to communicate with each other, and therefore need to be in the same docker network. Also, since we probably don’t want to download the entire blockchain after each restart, a docker volume is required to store the blockchain data independently of the docker containers on the remote server.

 

Conclusion

This project was rather full of new technologies for myself, as the whole Blockchain world was rather unknown to me before. Since it is still a rather recent technology, changes happen frequently and getting into it might prove difficult sometimes. Especially if one doesn’t know all the available methods and libraries to easen the Dapp development, it gets frustrating quickly. I hope that with this kind of collection of useful tools and a working example scenario others may find it easier to get into it. There is still a lot which is not covered in this blog entry, but atleast it helps to get onto the right track and not get lost in low-level details of developing on a blockchain. Once the general project setup with truffle, MetaMask, CI, and so on is done, further efforts can be entirely dedicated to the application itself without getting trouble from lower layers.

 

Leave a Reply

Your email address will not be published. Required fields are marked *