circleci-ethereum icon indicating copy to clipboard operation
circleci-ethereum copied to clipboard

CI/CD for Ethereum Smart Contracts with CircleCI and Truffle

CI/CD for Ethereum Smart Contracts with CircleCI and Truffle

CI/CD walk-through of a simple demo app (Metacoin by Truffle) using CircleCI and docker.

If you are interested in doing CI on a windows agent with Visual Studio Team Services instead, take a look at David Burela's article

Configuring the agent requirements

First, we need to request an agent with the docker service enable, because we will be using Geth and testrpc in containers.
We also want the latest nodejs version.

In circleci.yml:

machine:
  services:
    - docker
  node:
    version: 7.4.0

Installing the dependencies

We will need truffle to test and migrate our smart contracts, and web3 to have a JavaScript API for Ethereum. The easiest way to install them is to use npm.
Specify this dependencies in your package.json:

  "dependencies": {
    "truffle": "^2.1.1",
    "web3": "^0.18.2"
  }

By default, CircleCI will detect your package.json file and automatically run npm install, so you don't need to add anything in your circleci.yml at this point.

Testing the Contract

CircleCI will also run npm test just after finishing npm install, so we need to modify our package.json to correctly launch our tests.
The easiest way to test a smart contract, is by deploying on an in-memory simulated blockchain. testrpc allows us to do just that. For convenience, we are going to use launch testrpc inside a container, that way we don't have to deal with installing the depencies.
You can either build the image yourself from the Dockerfile or use an existing one: wbuchwalter/testrpc

If not already done, you need to configure truffle with the correct RPC endpoint and network id (truffle.js).

Here is what our package.json script section will look like:

"scripts": {
    "test": "docker run -d -p 8545:8545 --name testrpc wbuchwalter/testrpc  && truffle test",
    "posttest": "docker stop testrpc"
  }

We are starting the testrpc container in the background (-d) and exposing the 8545 port on the same port on localhost so that truffle can connect. The posttest script will be executed after the test script automatically, and ensures we stop our testrpc container so that the port 8545 is freed.

Deploying/Migrating Contracts

Now that our tests are successful you might want to deploy it on a test environment, or directly on your production blockchain. To keep this example simple, we are going to do the former.

Again, we will be using Geth in a container as well. The official image for Geth is ethereum/client-go.

Here is what circleci.yml will look like:

deployment:
  production:
    branch: master
    commands:
      - docker run -it -v $(pwd):/app ethereum/client-go --datadir /app/geth/data init /app/genesis.json
      - docker run -d -p 8545:8545 -v $(pwd):/app ethereum/client-go --datadir /app/geth/data --networkid 20170123 --rpc --unlock 0x322ba17d251afdb6d84fd288b5aef518208cccb9 --password /app/password --rpcaddr "0.0.0.0" --verbosity 5
      - node scripts/waitForEthSync.js
      - truffle migrate

The first commands starts Geth, and call init. We need to mount the current directory into the container (-v $(pwd):/app) so that Geth can access geth/data and genesis.json.

Once this is done we actually start the Geth node:

docker run -d \ #Run in background
-p 8545:8545 \ #Expose port 8545 so that truffle can connect
-v $(pwd):/app \ #Mount the working directory inside the container 
ethereum/client-go \ #image name
--datadir /app/geth/data \
--networkid 20170123 \ 
--rpc \
--unlock 0x322ba17d251afdb6d84fd288b5aef518208cccb9 \ #unlock an account from the keystore that can submit transactions
--password /app/password \ #file containing the password to unlock the account
--rpcaddr "0.0.0.0" \ #allow localholst connections
--verbosity 5 \ #easier debugging

For simplicity, the password is stored in a file in the git repository. This is obviously not ideal. Instead you should use something like CircleCI's environment variables.

This command will start the syncrhonization of the Geth node with the blockchain. This can take a while (you could also use the --fast flag), so we need to wait for it to finish before calling truffle migrate. For this purpose, I created a node script(./scripts/waitForEthSync.js), that will connect to the node, and block the deployment pipeline until the syncrhonization is complete:

attemptConnection('http://0.0.0.0:8545').then(waitForSync).catch(console.log)

Once the synchronization completed we can submit our migration with truffle migrate

You should now have a pipeline similar to this one: wbuchwalter/circleci-ethereum/48