Serge Koba

Serge Koba

Web, blockchain, mobile and IoT developer

Web Team Lead at MobiDev, who likes learning new stuff non-stop. Main languages are: PHP and Ruby. Doesn't hesitate playing with blockchain, mobile and IoT. We live until we learn something.

Local environment for Ethereum Smart Contracts development using Docker

19th of January 11:20

Not long time ago I had a chance to attend series of events about blockchain. Quickly I became interested in the technical side of the trendy idea. After building simple Bitcoin wallet I decided to try something more advanced. In this article I will share a toolset for building, deploying and testing locally your Ethereum Smart Contracts using Docker.

Illustration by Anna Koba.

Local Ethereum with Docker

First of all there are several options you should consider before start. To test stuff on Ethereum blockchain you could use the next tools

  1. Use Ethereum Testnet
  2. Truffle and Ganache - your personal local Ethereum like blockchain and full toolset for smart contracts. Best for quick start.
  3. Setup your own local copy of Ethereum. Closest to real world.

As for me I've chosen the last one, because I wanted to be close to real world Ethereum Blockchain and still have the possibility to configure all tools myself to get additional knowledge about how it works.

Quickly I understood that I need to setup several Ethereum nodes locally and some tool to make sure they are functioning properly. When it comes to trying new software on my machine I prefer Docker. Nothing makes me so happy than running dozens of services without any knowledge how to setup them :D Quick search gave me the result I wanted: already existing Docker Compose based Ethereum solution, which allows to bring up as many Ethereum nodes as you wish, has predefined test users with some ETH on the balance and Netstat to monitor the nodes. Let's take a look at docker-compose.yml file for the project (I skipped volumes and some other stuff)

version: '2'
services:
bootstrap:
build: monitored-geth-client
#...
links:
- netstats
command: '--datadir=~/.ethereum/devchain --nodekeyhex=091bd6067cb4612df85d9c1ff85cc47f259ced4d4cd99816b14f35650f59c322 --rpcapi "db,personal,eth,net,web3" --rpccorsdomain="*" --networkid=456719 --rpc --rpcaddr="0.0.0.0"'
#...
ports:
- "30303:30303"
- "30303:30303/udp"
- "8545:8545"
eth:
build: monitored-geth-client
links:
- bootstrap
- netstats
#...
command: '--datadir=~/.ethereum/devchain --rpccorsdomain="*" --networkid=456719 --rpc --bootnodes="enode://288b97262895b1c7ec61cf314c2e2004407d0a5dc77566877aad1f2a36659c8b698f4b56fd06c4a0c0bf007b4cfb3e7122d907da3b005fa90e724441902eb19e@XXX:30303"'
netstats:
build: eth-netstats
#...
ports:
- "3000:3000"

Bootstrap container acts as the node you can use for RPC and setups initial users. To make it close to real world there is second node called eth, which participates in validating transactions. Both of them are linked to Netsat for monitoring purposes.

All you need to launch your local copy of Ethereum is to run the next command inside the project folder

docker-compose up -d

You can also run as many nodes as you need by running this command (brings up 3 eth nodes)

docker-compose scale eth=3

After successful start you should be able to go to http://localhost/3000 (Netstat) and see your nodes there

IMPORTANT! to deploy your Smart Contract and confirm transactions you need to make at least 1 node mining. You can do that with the next commands

docker exec -it bootstrap geth --datadir=~/.ethereum/devchain attach

miner.start()
exit

Go to Netstat again to validate mining has started. So now we have local Ethereum network fully functioning and RPC is available on http://localhost:8545.

Building Smart Contracts

Always wanted to learn Go, so inspired by this article I decided to use Go as a basic language for interaction with contracts (deploy, fetch info, update info).

But first of all let's create a service for building smart contracts. For that we need solc (Solidity compiler), Go and Go-Ethereum (official implementation of Ethereum protocol using Go). Docker image for such a service could look like this

# ./builder/Dockerfile
FROM ethereum/solc:stable

# Install Go
RUN apk update && apk add curl git bash mercurial build-base linux-headers bzr 'go=1.8.4-r0' && rm -rf /var/cache/apk/*
ENV GOROOT /usr/lib/go
ENV GOPATH /gopath
ENV GOBIN /gopath/bin
ENV PATH $PATH:$GOROOT/bin:$GOPATH/bin
# Install go-ethereum
RUN go get github.com/ethereum/go-ethereum &&\
cd $GOPATH/src/github.com/ethereum/go-ethereum/ &&\
make &&\
make devtools # Override default solc entrypoint
ENTRYPOINT []

A service for this Dockerfile is very simple

# ./docker-compose.yml
# ...
builder:
build: builder
container_name: builder
links:
- bootstrap
volumes:
- ./contracts:/contracts

Pay attention I've mounted local ./contracts folder to /contracts folder inside container. Now we are ready to create our first Smart Contract. This article is not about Solidity language so as an example I will use basic Hello World (Greeter) Smart Contract from official tutorial

/* ./contracts/samples/greeter/greeter.sol */
contract mortal {
/* Define variable owner of the type address */
address owner;
/* This function is executed at initialization and sets the owner of the contract */
function mortal() { owner = msg.sender; }
/* Function to recover the funds on the contract */
function kill() { if (msg.sender == owner) selfdestruct(owner); }
}
contract greeter is mortal {
/* Define variable greeting of the type string */
string greeting;
/* This runs when the contract is executed */
function greeter(string _greeting) public {
greeting = _greeting;
}
/* Main function */
function greet() constant returns (string) {
return greeting;
}
}

To interact with Ethereum blockchain we need to use its RPC.

However, writing the boilerplate code that translates decent Go language constructs into RPC calls and back is extremely time consuming and also extremely brittle: implementation bugs can only be detected during runtime and it's almost impossible to evolve a contract as even a tiny change in Solidity can be painful to port over to Go.

There is a tool in go-ethereum, which allows us to generate Go bindings for contract. Generated Go file has predefined methods for deploying and interacting with Smart Contracts. The next command will generate go bindings for /contracts/samples/greeter/greeter.sol and save them to /contracts/samples/greeter/greeter.go

docker-compose run builder abigen --sol=/contracts/samples/greeter/greeter.sol --pkg=main --out=/contracts/samples/greeter/greeter.go

Take a look at generated file. Most of all we are interested in DeployGreeterNewGreeter and Greet methods. They allow us to deploy contract, bind to deployed contract and call greet method accordingly.

Deploy Smart Contract

To deploy a smart contract we will create a small Go program.

/* ./contracts/samples/greeter/deploy_greeter.go */
package main
import (
"os"
"fmt"
"strings"
"log"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
)
const key = "{\"address\":\"007ccffb7916f37f7aeef05e8096ecfbe55afc2f\",\"Crypto\":{\"cipher\":\"aes-128-ctr\",\"ciphertext\":\"44600de3be08153de243f0de344b9841c774a2fcd5b7f3406b9ea9a42c8be97b\",\"cipherparams\":{\"iv\":\"9ca680df904bc0138d2d52f9c432a797\"},\"kdf\":\"scrypt\",\"kdfparams\":{\"dklen\":32,\"n\":262144,\"p\":1,\"r\":8,\"salt\":\"a6524c4ef60060476f2e47dea7f5945aac8159019d3bf5275a683c1970d03c40\"},\"mac\":\"4daf73bc6da7f75beb9a8ea28896c5f1f074fb8cd54880fa9a4e34700dc0ee00\"},\"id\":\"2615dd6a-6d73-4b05-a02d-257b0fd28d3a\",\"version\":3}"
func main() {
conn, err := ethclient.Dial("http://bootstrap:8545")
if err != nil {
fmt.Printf("Cannot connect to node %v", err)
os.Exit(1)
}
auth, errAuth := bind.NewTransactor(strings.NewReader(key), "")
if errAuth != nil {
log.Fatalf("Failed to create new transactor : %v", errAuth)
}
action := os.Args[1]
fmt.Printf("Executing action: %v\n", action)
switch action {
case "deploy":
DoDeploy(auth, conn)
}
}
func DoDeploy(auth *bind.TransactOpts, conn bind.ContractBackend) (common.Address, *Greeter, error) {
addr, _, contract, err := DeployGreeter(auth, conn, "Hello I am your first deployed SC!")
if err != nil {
log.Fatalf("could not deploy contract: %v", err)
}
fmt.Printf("Contract address: %v\n", addr.Hex())
return addr, contract, err
}

On line 12 we hardcoded one of the predefined Ethereum addresses. It will be used to deploy our contract and used as contract owner. Line 14 connects to Ethereum node in order to perform RPC calls. In our case we connect to the bootstrap node. The program takes 1 argument and if it is equal to "deploy" performs DoDeploy func, which actually calls DeployGreeter func from generated go file and deploys smart contract. Pay attention we pass a string "Hello I am your first deployed SC!", it will be stored under greeting variable in our smart contract. To deploy the contract run the next command

docker-compose run builder bash -c "cd /contracts/samples/greeter/ && go build . && ./greeter deploy"

Note the contract address from output, we will use it later to call smart contract functions.

Interact with Smart Contract

Calling Smart Contract function is a piece of cake using the generated bindings for Go. Let's create another function called DoGreet

/* ./contracts/samples/greeter/deploy_greeter.go */
/* ... */
func DoGreet(conn bind.ContractBackend, addr common.Address) (string, error) {
// Instantiate the contract and display its greeting
greeter, err := NewGreeter(addr, conn)
if err != nil {
log.Fatalf("Failed to instantiate a Greeter contract: %v", err)
}
greeting, err := greeter.Greet(nil)
if err != nil {
log.Fatalf("Failed to retrieve greeter greeting: %v", err)
}
fmt.Println("Contract Greeting:", greeting)
return greeting, err
}

We reuse contract address and connection on line 5 to initialize Greeter contract object. After that we can easily call any contract functions. For example get stored greeting using Greet func. Also let's modify the main program code to call DoGreet func

/* ./contracts/samples/greeter/deploy_greeter.go */ /* ... */ switch action { case "deploy": DoDeploy(auth, conn) case "greet": hexAddr := os.Args[2] DoGreet(conn, common.HexToAddress(hexAddr)) }

Run the program

docker-compose run builder bash -c "cd /contracts/samples/greeter/ && go build . && ./greeter greet 0xFF37a57B8D373518aBE222Db1077eD9A968a5FDf"

Replace 0xFF37a57B8D373518aBE222Db1077eD9A968a5FDf with your contract address. You should see "Hello I am your first deployed SC!" in the output. That's it! We have deployed our first smart contract and called a function!

Browse blocks and transactions

It's nice to have some simple analogue of Etherscan for your local chain browsing. It will be useful to examine transactions, balances, blocks and etc. It appeared that it is quite difficult to find an open-source good solution for geth. After several tries I found an acceptable solution called ETHExplorer V2. Clone it into explorer-v2 folder. To Dockerize it I had to make 2 changes. First create a Dockerfile

# ./explorer-v2/Dockerfile
FROM node:6

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY . /usr/src/app
RUN npm install && \
node_modules/.bin/bower install --allow-root

And change start script in package.json to "start": "http-server ./app -a 0.0.0.0 -p 8000 -c-1". This is required to allow connections to explorer from any IP (outside docker). Next we should create a service for explorer

# ./docker-compose.yml
# ... explorer: build: explorer-v2 container_name: explorer command: npm start ports: - "8000:8000"

Restart the project and go to http://localhost:8000. You should see the explorer and be able to browse your blocks, addresses and transactions! For example

Have fun!

REST API for Smart Contract

Remember we have Go bindings for Smart Contract? Nobody stops us from using them in conjunction with Go based web API. For testing purposes I've generated an API application using BeeGo. Of course I dockerized it and made a service. You can see the code here. One important thing to remember that you need go-ethereum packages to use generated smart contracts bindings.

Conclusion

 Again it was fun and easy for me to play with new technologies using Docker. As the result we have created the next services:

  • bootstrap and eth - local Ethereum blockchain nodes;
  • netstat - monitoring the nodes;
  • builder - building and deploying smart contracs;
  • explorer - exploring blocks, transactions and addresses of local bockchain.

Pros: It is close to real world, you can setup your own blockchains anywhere with Docker, you have full control over your development tools, easily extendable, no additional software setup except Docker and Docker Compose.

Cons: No visual interface for building and deploying smart contracts (you can develop it yourself or use some tool as a service), resource consuming during node mining.

All code source from this article is available here.

Back