Building in Public

Sensible Solidity testing via Go Ethereum

by Dennis Stritzke


You have built a smart contract, tested it via unit tests, explored it via Metamask and a block explorer and now want to deploy to the Mainnet. Didn’t you forget something?

Until now you most likely tested your Solidity code via a local network like hardhat node, ganache-cli or the Ganache UI. Those are very convenient and fast, but are not the same thing as the real network. In this post we will explore how to test your code using the “official Go implementation of the Ethereum protocol” called geth, which enables us to be much closer to the Mainnet.

I will be using Hardhat on macOS within zsh in my descriptions, but similar workflows exist for other toolchains.

Start a local network via Geth

If you are looking for a very quick local setup and are fine with having only one account available, geth --dev is a solid option. The chain does not persist, if the command is terminated.

In most cases you will need more though: multiple accounts, account locking and unlocking, mining controls, timing changes, etc. All of these are possible through geth. Follow these steps to create your geth based local network.

  1. Install geth
  2. Create accounts
  3. Create the Genesis block configuration and initialise the network
  4. Start the network
  5. Cleanup

Install geth

Follow the extensive Installing geth instructions in the documentation.

Create accounts

geth handles encrypted private keys only. Within the local development environment we (oftentimes) don’t really care about key security. To handle passwords create a file named passwords containing the same password on each line. Create as many lines as you want to create accounts.

# passwords
secret
...
secret

Execute the geth account new command once for every account you need.

geth account new --datadir data --password passwords

Genesis Block configuration

Next we have to instruct geth how our chain should behave. This is done via the genesis block.

{
  "config": {
    "chainId": 15,
    "homesteadBlock": 0,
    "eip150Block": 0,
    "eip155Block": 0,
    "eip158Block": 0,
    "byzantiumBlock": 0,
    "constantinopleBlock": 0,
    "petersburgBlock": 0,
    "clique": {
      "period": 0,
      "epoch": 30000
    }
  },
  "difficulty": "1",
  "gasLimit": "8000000",
  "extradata": "0x0000000000000000000000000000000000000000000000000000000000000000<insert signer key without 0x prefix>0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  "alloc": {
    "c736af4e3dd4e0b463c3cbfecc9ab84705b69d8b": { "balance": "40000000000000000000" },
    ...
    "ba4b47ee3abdc81531304bb09d45a085fb59c6ce": { "balance": "40000000000000000000" }
  }
}

Notable properties:

  • Use the clique consensus algorithm, which is proof-of-authority and spares us to execute proof-of-work mining. Read more on choosing a consensus algorithm in the documentation.
  • The extradata property needs to contain the account used to sign blocks. I recommend to use the first account as the signing account. This selection works well with most framework assumptions. Insert the signing account public key without the 0x prefix in the padded string.
  • Add one entry per account in the alloc property to assign an initial balance to the respective account. The value is specified in wei. (I find it very useful to execute web3.toWei(10, "ether") to convert Ether to wei, which outputs 10000000000000000000)

Initialise the network by running geth init --datadir data genesis.json.

Start the network

Execute the following command to start the private network. Replace and insert all public keys of the accounts you want to unlock in the --unlock flag. Remember that your passwords file needs to have at least as many lines has you want to unlock accounts.

geth --datadir data --nodiscover --mine --fakepow \
  --http --http.addr 0.0.0.0 --http.vhosts "*" --http.api "eth,net,web3,txpool,debug"
  --allow-insecure-unlock --password data/passwords \
  --unlock 0xc736af4e3dd4e0b463c3cbfecc9ab84705b69d8b,...

Notable properties:

  • The --nodiscover flag makes sure that our node doesn’t search for peers to sync
  • The --mine flag enables block creation on our node. In combination with the --fakepow flag we disable proof-of-work mining.
  • The --http.* flags enables us to (1) run geth attach via the network and (2) access the chain via a Docker based Blockscout instance.

Cleanup

In the following commands we used a directory named data as the storage location. Just remove it once you are done.

Run your test suite

Now that the local geth network is running, the actual testing is straight forward. First, within the hardhat.config.js add a new network.

module.exports = {
  ...
  networks: {
    ...
    geth: {
      url: "http://127.0.0.1:8545"
    }
  }
};

Having done that, execute your test suite via npx hardhat test --network geth. Do your tests also work on the official Go Ethereum implementation? Yes, that is great. No? Good you caught that before deploying to Mainnet.

$ npx hardhat test --network geth

  Distribution Wallet
    deployment
      ✓ should set the correct owner

  1 passing (194ms)

If you need to analyse some issues or want to dig deeper, start the Geth JavaScript console via geth attach http://127.0.0.1:8545/, which allows you to execute the web3, eth and more commands.

$ geth attach http://127.0.0.1:8545/
Welcome to the Geth JavaScript console!

instance: Geth/v1.10.8-stable/darwin-amd64/go1.16.6
coinbase: 0xc736af4e3dd4e0b463c3cbfecc9ab84705b69d8b
at block: 9 (Tue Oct 12 2021 09:53:25 GMT+0200 (CEST))
 modules: debug:1.0 eth:1.0 net:1.0 rpc:1.0 txpool:1.0 web3:1.0

To exit, press ctrl-d
> web3.toWei(10, "ether")
"10000000000000000000"
> eth.accounts
["0xc736af4e3dd4e0b463c3cbfecc9ab84705b69d8b", "0xd8508d13c889728420103d6025761dfd98f43c36", "0x1a30d4f3db1d4781daf11398b55c62001cbef590", "0x3057d5b4f3ee1a6a56965af6708736fce858bc59", "0xba4b47ee3abdc81531304bb09d45a085fb59c6ce"]
> eth.getBalance(eth.accounts[1])
45999979000000000000
> web3.fromWei(eth.getBalance(eth.accounts[1]))
45.999979

Bonus: Block Explorer

In the post Using Block Explorers with Hardhat we tried different approaches to visually explore the local network. That worked reasonably well.

As our local network is now run through geth, compatibility is of no issue anymore. Fancy some Blockscout? Follow these steps:

  1. Make sure the geth network is running.

  2. Clone the Blockscout repository

  3. Change to the docker directory

  4. Execute the Makefile, which will build Blockscout on its first execution. (This took about 15 minutes on my machine)

    COIN=ETH \
    ETHEREUM_JSONRPC_VARIANT=geth \
    ETHEREUM_JSONRPC_HTTP_URL=http://host.docker.internal:8545 \
    make start
    
  5. Visit http://localhost:4000

Going forward

Looking at this post, I am quite surprised how lengthy it got. Maybe it is time to implement a script geth-dev that provides the start, stop, cleanup and accounts commands? Maybe something to integrate this easily into Mocha based tests? Send an email to me and tell me what you think!

Contact

Send an email and tell us about your project. Send mail
Book an appointment directly in my calendar. Book appointment