Multi-Node Private Ethereum Network using Docker


Bhaskar S 01/12/2018


Overview

In the Introduction to Ethereum series, we demonstrated the simple vehicle buying use-case (involving the buyer, the dealer, and the dmv) using a single node on a private Ethereum network.

In reality, if one were to setup a private Ethereum network in any Enterprise, it would involve more than one node in the network.

In this article, we will lay out the steps to setup a multi-node private Ethereum network (one for each of the entities - the bank, the buyer, the dealer, and the dmv).

In addition, we will leverage the official Docker images for the Ethereum client and the Solidity compiler, rather than the approach of installing the necessary software components as we did in Part 1 of Introduction to Ethereum series.

Special callout to Sudhaker Raj for helping me sort out some issues with running an Ethereum node using Docker.

Installation

The setup will be on a Ubuntu 16.04 LTS based Linux desktop.

Ensure Docker is installed and setup. Else, refer to the article Introduction to Docker.

Assuming that we are logged in as polarsparc and the current working directory is the home directory /home/polarsparc, we will setup a directory structure by executing the following commands:

$ mkdir -p Ethereum/conf

$ mkdir -p Ethereum/data/bank

$ mkdir -p Ethereum/data/buyer

$ mkdir -p Ethereum/data/dealer

$ mkdir -p Ethereum/data/dmv

Now, change the current working directory to the directory /home/polarsparc/Ethereum.

For our exploration, we will be downloading and using the official docker images ethereum/client-go:alltools-v1.7.3 for Ethereum and ethereum/solc:nightly for Solidity.

To pull and download the docker image for Ethereum, execute the following command:

docker pull ethereum/client-go:alltools-v1.7.3

The following should be the typical output:

Output.1

alltools-v1.7.3: Pulling from ethereum/client-go
  b56ae66c2937: Pull complete 
  cd6eff31471d: Pull complete 
  c97a5da0e9b5: Pull complete 
  Digest: sha256:aa3abfdb1a124c7b38b98935f2afc8cd9bd23cdddb76d90272e956f646793ad7
  Status: Downloaded newer image for ethereum/client-go:alltools-v1.7.3

Similarly, to pull and download the docker image for Ethereum, execute the following command:

docker pull ethereum/solc:nightly

The following should be the typical output:

Output.2

nightly: Pulling from ethereum/solc
  ff3a5c916c92: Pull complete 
  fd8ae676bb8c: Pull complete 
  d9f17870f49d: Pull complete 
  cd6046ce4cc7: Pull complete 
  db1eebcf0946: Pull complete 
  Digest: sha256:416ac7d45d1fcd9bf5537de03009892b5e0921bc3a1bbd6e3dff260c865a77cb
  Status: Downloaded newer image for ethereum/solc:nightly

Once the installation complete, execute the following command to check everything was ok with the image for Ethereum:

$ docker run --rm --name geth ethereum/client-go:alltools-v1.7.3 geth version

The following would be the typical output:

Output.3

Geth
  Version: 1.7.3-stable
  Architecture: amd64
  Protocol Versions: [63 62]
  Network Id: 1
  Go Version: go1.9.2
  Operating System: linux
  GOPATH=
  GOROOT=/usr/local/go

Similarly, execute the following command to check everything was ok with the image for Solidity:

$ docker run --rm --name geth ethereum/client-go:alltools-v1.7.3 geth version

The following would be the typical output:

Output.4

solc, the solidity compiler commandline interface
  Version: 0.4.20-nightly.2018.1.11+commit.0c20b6da.Linux.g++

Multi-Node Setup

For this multi-node setup, we will create 4 accounts, one for each of the entities - the bank, the buyer, the dealer, and the dmv. Before we proceed further, we will create text files (.txt) for each of the entities (bank, buyer, dealer, and dmv), in their respective directories, containing their account password. To create the password text files, execute the following commands:

$ echo 'bank' data/bank/bank.txt

$ echo 'buyer' data/buyer/buyer.txt

$ echo 'dealer' data/dealer/dealer.txt

$ echo 'dmv' data/dmv/dmv.txt

In a multi-node setup, each of the nodes will need to have the same accounts and their corresponding keystores. Hence, we will first create the accounts in the bank directory and copy the keystore directory to each of the other directories - buyer, dealer, and dmv.

To create an account for the bank entity, execute the following command:

$ docker run -it --rm --name geth -u $(id -u $USER):$(id -g $USER) -v $HOME/Ethereum:/root ethereum/client-go:alltools-v1.7.3 geth --password /root/data/bank/bank.txt --datadir /root/data/bank account new

The following would be the typical output:

Output.5

Address: {46ba3a22f4a62333433df5bf5068055af5c33e53}

The following is the repetition of the above command to create the remaining 3 accounts for our setup.

Account buyer:

$ docker run -it --rm --name geth -u $(id -u $USER):$(id -g $USER) -v $HOME/Ethereum:/root ethereum/client-go:alltools-v1.7.3 geth --password /root/data/buyer/buyer.txt --datadir /root/data/bank account new

Account buyer output:

Output.6

Address: {fe88391515b1074c9d02c58504d0ff648b7170d3}

Account dealer:

$ docker run -it --rm --name geth -u $(id -u $USER):$(id -g $USER) -v $HOME/Ethereum:/root ethereum/client-go:alltools-v1.7.3 geth --password /root/data/dealer/dealer.txt --datadir /root/data/bank account new

Account dealer output:

Output.7

Address: {75f091276c19177122af6bd4750938852359bbe2}

Account dmv:

$ docker run -it --rm --name geth -u $(id -u $USER):$(id -g $USER) -v $HOME/Ethereum:/root ethereum/client-go:alltools-v1.7.3 geth --password /root/data/dmv/dmv.txt --datadir /root/data/bank account new

Account dmv output:

Output.8

Address: {00acfabed04ce7b28049fe2f7980f8d082a6ce2f}

Execute the following command from the directory /home/polarsparc to list all the account key files:

$ ls -l data/bank/keystore/

The following would be the typical output:

Output.9

total 16
-rw------- 1 polarsparc polarsparc 491 Jan 12 22:10 UTC--2018-01-13T03-10-58.639559319Z--46ba3a22f4a62333433df5bf5068055af5c33e53
-rw------- 1 polarsparc polarsparc 491 Jan 12 22:13 UTC--2018-01-13T03-13-15.373151600Z--fe88391515b1074c9d02c58504d0ff648b7170d3
-rw------- 1 polarsparc polarsparc 491 Jan 12 22:15 UTC--2018-01-13T03-15-41.939949920Z--75f091276c19177122af6bd4750938852359bbe2
-rw------- 1 polarsparc polarsparc 491 Jan 12 22:16 UTC--2018-01-13T03-16-44.818547819Z--00acfabed04ce7b28049fe2f7980f8d082a6ce2f

We need to copy the keystore directory to each of the other directories - buyer, dealer, and dmv.

Execute the following commands:

$ cp -R data/bank/keystore/ data/buyer

$ cp -R data/bank/keystore/ data/dealer

$ cp -R data/bank/keystore/ data/dmv

In order to setup a private Ethereum blockchain network, we need to initialize and create the Genesis block. The Genesis block is the first block of the blockchain that has no previous block.

We will create an initialization file called genesis.json that will be located in the directory conf, with the following contents:

genesis.json
{
    "config": {
        "chainId": 21,
        "homesteadBlock": 0,
        "eip155Block": 0,
        "eip158Block": 0
    },
    "nonce": "0x0000000000000000",
    "timestamp": "0x0",
    "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "extraData": "0x00",
    "gasLimit": "0x80000000",
    "difficulty": "0x400",
    "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "coinbase": "0x46ba3a22f4a62333433df5bf5068055af5c33e53",
    "alloc": {
        "46ba3a22f4a62333433df5bf5068055af5c33e53": {
            "balance": "20000000000000000000"
        },
        "fe88391515b1074c9d02c58504d0ff648b7170d3": {
            "balance": "10000000000000000000"
        },
        "75f091276c19177122af6bd4750938852359bbe2": {
            "balance": "5000000000000000000"
        },
        "00acfabed04ce7b28049fe2f7980f8d082a6ce2f": {
            "balance": "3000000000000000000"
        }        
    }
}

To initialize our private Ethereum network with the Genesis block, execute the following command for the bank node:

$ docker run -it --rm --name geth -u $(id -u $USER):$(id -g $USER) -v $HOME/Ethereum:/root ethereum/client-go:alltools-v1.7.3 geth --datadir /root/data/bank init /root/conf/genesis.json

The following would be the typical output:

Output.10

INFO [01-13|03:45:47] Allocated cache and file handles         database=/root/data/bank/geth/chaindata cache=16 handles=16
INFO [01-13|03:45:47] Writing custom genesis block 
INFO [01-13|03:45:47] Successfully wrote genesis state         database=chaindata                      hash=d40a5a…084710
INFO [01-13|03:45:47] Allocated cache and file handles         database=/root/data/bank/geth/lightchaindata cache=16 handles=16
INFO [01-13|03:45:47] Writing custom genesis block 
INFO [01-13|03:45:47] Successfully wrote genesis state         database=lightchaindata                      hash=d40a5a…084710

Repeat the following commands for each of the other nodes buyer, dealer, and dmv:

$ docker run -it --rm --name geth -u $(id -u $USER):$(id -g $USER) -v $HOME/Ethereum:/root ethereum/client-go:alltools-v1.7.3 geth --datadir /root/data/buyer init /root/conf/genesis.json

$ docker run -it --rm --name geth -u $(id -u $USER):$(id -g $USER) -v $HOME/Ethereum:/root ethereum/client-go:alltools-v1.7.3 geth --datadir /root/data/dealer init /root/conf/genesis.json

$ docker run -it --rm --name geth -u $(id -u $USER):$(id -g $USER) -v $HOME/Ethereum:/root ethereum/client-go:alltools-v1.7.3 geth --datadir /root/data/dmv init /root/conf/genesis.json

Open 4 terminal windows, each representing the 4 entities, namely, the bank, the buyer, the dealer, and the dmv. We will refer to these 4 windows corresponding to the entity they refer. For example, the first window will be referred to as the bank, the second window as the buyer, the third window as the dealer and the fourth window as the dmv.

In each of the 4 windows, change the current working directory to the directory /home/polarsparc/Ethereum.

In the bank terminal, start an Ethereum node by executing the following command:

$ docker run -it --rm --name bank -v $HOME/Ethereum:/root -u $(id -u $USER):$(id -g $USER) -p 8081:8081 -p 30001:30001 ethereum/client-go:alltools-v1.7.3 geth --password /root/data/bank/bank.txt --unlock 0 --identity "bank" --datadir /root/data/bank --ethash.dagdir /root/data/bank --networkid "21" --maxpeers 4 --nodiscover --ipcdisable --rpc --rpcaddr "0.0.0.0" --rpcport 8081 --rpccorsdomain "*" --port 30001 --verbosity 2 console

Notice the use of the value 4 for the option maxpeers to represent the 4 nodes in our private network.

Also, notice the use of the the option password pointing to the text file containing the account password and the option unlock specifying the index of the account.

CAUTION:

Without the password option, one will encounter an error when trying to unlock the account.
The exception would be: liner: function not supported in this terminal.

The following would be the typical output:

Output.11

WARN [01-13|17:04:06] Upgrading database to use lookup entries 
Welcome to the Geth JavaScript console!

instance: Geth/bank/v1.7.3-stable/linux-amd64/go1.9.2
coinbase: 0x46ba3a22f4a62333433df5bf5068055af5c33e53
at block: 0 (Thu, 01 Jan 1970 00:00:00 UTC)
 datadir: /root/data/bank
 modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

  >

In the buyer terminal, start an Ethereum node by executing the following command:

$ docker run -it --rm --name buyer -v $HOME/Ethereum:/root -u $(id -u $USER):$(id -g $USER) -p 8082:8082 -p 30002:30002 ethereum/client-go:alltools-v1.7.3 geth --password /root/data/buyer/buyer.txt --unlock 1 --identity "buyer" --datadir /root/data/buyer --ethash.dagdir /root/data/buyer --networkid "21" --maxpeers 4 --nodiscover --ipcdisable --rpc --rpcaddr "0.0.0.0" --rpcport 8082 --rpccorsdomain "*" --port 30002 --verbosity 2 console

The following would be the typical output:

Output.12

WARN [01-13|17:11:19] Upgrading database to use lookup entries 
Welcome to the Geth JavaScript console!

instance: Geth/buyer/v1.7.3-stable/linux-amd64/go1.9.2
coinbase: 0x46ba3a22f4a62333433df5bf5068055af5c33e53
at block: 0 (Thu, 01 Jan 1970 00:00:00 UTC)
 datadir: /root/data/buyer
 modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

  >

In the dealer terminal, start an Ethereum node by executing the following command:

$ docker run -it --rm --name dealer -v $HOME/Ethereum:/root -u $(id -u $USER):$(id -g $USER) -p 8083:8083 -p 30003:30003 ethereum/client-go:alltools-v1.7.3 geth --password /root/data/dealer/dealer.txt --unlock 2 --identity "dealer" --datadir /root/data/dealer --ethash.dagdir /root/data/dealer --networkid "21" --maxpeers 4 --nodiscover --ipcdisable --rpc --rpcaddr "0.0.0.0" --rpcport 8083 --rpccorsdomain "*" --port 30003 --verbosity 2 console

The following would be the typical output:

Output.13

WARN [01-13|17:13:08] Upgrading database to use lookup entries 
Welcome to the Geth JavaScript console!

instance: Geth/dealer/v1.7.3-stable/linux-amd64/go1.9.2
coinbase: 0x46ba3a22f4a62333433df5bf5068055af5c33e53
at block: 0 (Thu, 01 Jan 1970 00:00:00 UTC)
 datadir: /root/data/dealer
 modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

  >

In the dmv terminal, start an Ethereum node by executing the following command:

$ docker run -it --rm --name dmv -v $HOME/Ethereum:/root -u $(id -u $USER):$(id -g $USER) -p 8084:8084 -p 30004:30004 ethereum/client-go:alltools-v1.7.3 geth --password /root/data/dmv/dmv.txt --unlock 3 --identity "dmv" --datadir /root/data/dmv --ethash.dagdir /root/data/dmv --networkid "21" --maxpeers 4 --nodiscover --ipcdisable --rpc --rpcaddr "0.0.0.0" --rpcport 8084 --rpccorsdomain "*" --port 30004 --verbosity 2 console

The following would be the typical output:

Output.14

WARN [01-13|17:14:57] Upgrading database to use lookup entries 
Welcome to the Geth JavaScript console!

instance: Geth/dmv/v1.7.3-stable/linux-amd64/go1.9.2
coinbase: 0x46ba3a22f4a62333433df5bf5068055af5c33e53
at block: 0 (Thu, 01 Jan 1970 00:00:00 UTC)
 datadir: /root/data/dmv
 modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

  >

CAUTION:

The values for the port and the rpcport have to be unique for every Ethereum node in the private network.

Every node in the private Ethereum network has a unique endpoint address. Note that we have started each of the 4 nodes with the nodiscover option. This means the 4 nodes in our private network have no knowledge of each other. We need to manually connect the nodes to each other so they are aware of their peers in our private network. In order to achieve this, we need the endpoint address.

For our private network, we will add the endpoint address of the bank as a peer to the nodes buyer, dealer, and dmv.

Next, we will add the endpoint address of the dmv as a peer to the nodes buyer and dealer.

Finally, we will add the endpoint address of the dealer as a peer to the node buyer.

The end result is a private network with nodes as shown in the illustration below:

Private Network
Our Private Network

To identify the endpoint address of the bank, execute the following command in the Javascript prompt of the bank terminal:

> admin.nodeInfo

The following would be the typical output:

Output.15

{
  enode: "enode://3313eeb95c5a0f19914efb972069726a23aa49e28f87472278a5de93cc0864f016461eb16872d444b8ed07ed0adddb42fde6174a8b2d0ce372c6550fc30617d0@[::]:30001?discport=0",
  id: "3313eeb95c5a0f19914efb972069726a23aa49e28f87472278a5de93cc0864f016461eb16872d444b8ed07ed0adddb42fde6174a8b2d0ce372c6550fc30617d0",
  ip: "::",
  listenAddr: "[::]:30001",
  name: "Geth/bank/v1.7.3-stable/linux-amd64/go1.9.2",
  ports: {
    discovery: 0,
    listener: 30001
  },
  protocols: {
    eth: {
      difficulty: 1024,
      genesis: "0xd40a5a7c746504437c3874bef26ac3a8e4aef07e8d4f910ce7aabe5850084710",
      head: "0xd40a5a7c746504437c3874bef26ac3a8e4aef07e8d4f910ce7aabe5850084710",
      network: 21
    }
  }
}

The trimmed string value enode://3313ee...@[::]:30001?discport=0 is the endpoint address for the node bank.

Next, to identify the endpoint address of the dealer, execute the following command in the Javascript promt of the dealer terminal:

> admin.nodeInfo

The following would be the typical output:

Output.16

{
  enode: "enode://165314f25235b30a38c478ebc707b40ae45bd246f2f5db29064f9b04e0458904c567624e0c70093facfa08c959bb9ef7da9bba09805904352694a71f0b3a4039@[::]:30003?discport=0",
  id: "165314f25235b30a38c478ebc707b40ae45bd246f2f5db29064f9b04e0458904c567624e0c70093facfa08c959bb9ef7da9bba09805904352694a71f0b3a4039",
  ip: "::",
  listenAddr: "[::]:30003",
  name: "Geth/dealer/v1.7.3-stable/linux-amd64/go1.9.2",
  ports: {
    discovery: 0,
    listener: 30003
  },
  protocols: {
    eth: {
      difficulty: 2632704,
      genesis: "0x877ec0cc2c6d6ba485ce592a048891fca4e16dd2de2e8920f49f568a596b0840",
      head: "0x1f394452a230fb3260419abff6bb1fda7346495583b0d864738b896200a5f7a4",
      network: 21
    }
  }
}

The trimmed string value enode://165314...@[::]:30003?discport=0 is the endpoint address for the node dealer.

Finally, to identify the endpoint address of the dmv, execute the following command in the Javascript promt of the dmv terminal:

> admin.nodeInfo

The following would be the typical output:

Output.17

{
  enode: "enode://f715c5891229eba2d5a5c4297d5e2018c94115fbdb7042e71fb7415728231db8e636d5159a6cc9d7f04fa78693ddaf299a5fe84898b3a34747bf9234b19d1273@[::]:30004?discport=0",
  id: "f715c5891229eba2d5a5c4297d5e2018c94115fbdb7042e71fb7415728231db8e636d5159a6cc9d7f04fa78693ddaf299a5fe84898b3a34747bf9234b19d1273",
  ip: "::",
  listenAddr: "[::]:30004",
  name: "Geth/dmv/v1.7.3-stable/linux-amd64/go1.9.2",
  ports: {
    discovery: 0,
    listener: 30004
  },
  protocols: {
    eth: {
      difficulty: 1024,
      genesis: "0xd40a5a7c746504437c3874bef26ac3a8e4aef07e8d4f910ce7aabe5850084710",
      head: "0xd40a5a7c746504437c3874bef26ac3a8e4aef07e8d4f910ce7aabe5850084710",
      network: 21
    }
  }
}

The trimmed string value enode://f715c5...@[::]:30004?discport=0 is the endpoint address for the node dmv.

We also need the IP address of the Docker containers running the bank, dealer, and dmv nodes.

Open a new terminal window and execute the following command:

$ docker ps

The following would be the typical output:

Output.18

CONTAINER ID        IMAGE                                COMMAND                  CREATED             STATUS              PORTS                                                                                         NAMES
d6681f549a65        ethereum/client-go:alltools-v1.7.3   "geth --identity dmv..."   25 minutes ago      Up 25 minutes       0.0.0.0:8084->8084/tcp, 8545-8546/tcp, 0.0.0.0:30004->30004/tcp, 30303/tcp, 30303-30304/udp   dmv
7a5693915572        ethereum/client-go:alltools-v1.7.3   "geth --identity dea..."   25 minutes ago      Up 25 minutes       0.0.0.0:8083->8083/tcp, 8545-8546/tcp, 0.0.0.0:30003->30003/tcp, 30303/tcp, 30303-30304/udp   dealer
2fb9d78baf58        ethereum/client-go:alltools-v1.7.3   "geth --identity buy..."   25 minutes ago      Up 25 minutes       0.0.0.0:8082->8082/tcp, 8545-8546/tcp, 0.0.0.0:30002->30002/tcp, 30303/tcp, 30303-30304/udp   buyer
7b2ed3524730        ethereum/client-go:alltools-v1.7.3   "geth --identity ban..."   25 minutes ago      Up 25 minutes       0.0.0.0:8081->8081/tcp, 8545-8546/tcp, 0.0.0.0:30001->30001/tcp, 30303/tcp, 30303-30304/udp   bank

From the above Output.18, the Docker instances for the bank, the dealer, and the dmv nodes are 7b2ed3524730, 2fb9d78baf58, and d6681f549a65 respectively.

To extract the IP address of the bank node, execute the following command:

$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 7b2ed3524730

The following would be the typical output:

Output.19

172.17.0.2

Next, to extract the IP address of the dealer node, execute the following command:

$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 2fb9d78baf58

The following would be the typical output:

Output.20

172.17.0.4

Finally, to extract the IP address of the dmv node, execute the following command:

$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' d6681f549a65

The following would be the typical output:

Output.21

172.17.0.5

From the above Output.19, Output.20, and Output.21, the IP address for the bank, the dealer, and the dmv nodes are 172.17.0.2, 172.17.0.4, and 172.17.0.5 respectively.

To add the endpoint address of the bank as a peer to the nodes buyer, dealer, and dmv, execute the following command in the Javascript prompt of the corresponding terminals buyer, dealer, and dmv:

> admin.addPeer('enode://3313eeb95c5a0f19914efb972069726a23aa49e28f87472278a5de93cc0864f016461eb16872d444b8ed07 ed0adddb42fde6174a8b2d0ce372c6550fc30617d0@[172.17.0.2]:30001?discport=0')

The following would be the typical output:

Output.22

true

Next, add the endpoint address of the dealer as a peer to the node buyer by executing the following command in the Javascript prompt of the corresponding terminal buyer:

> admin.addPeer('enode://165314f25235b30a38c478ebc707b40ae45bd246f2f5db29064f9b04e0458904c567624e0c70093 facfa08c959bb9ef7da9bba09805904352694a71f0b3a4039@[172.17.0.4]:30003?discport=0')

Finally, add the endpoint address of the dmv as a peer to the nodes buyer and dealer by executing the following command in the Javascript prompt of the corresponding terminals buyer and dealer:

> admin.addPeer('enode://f715c5891229eba2d5a5c4297d5e2018c94115fbdb7042e71fb7415728231db8e636d5159a6cc9d7f04fa7 8693ddaf299a5fe84898b3a34747bf9234b19d1273@[172.17.0.5]:30004?discport=0')

To verify the peers connected to the buyer node, execute the following command in the Javascript promt of the buyer terminal:

> admin.peers

The following would be the typical output:

Output.23

[{
    caps: ["eth/63"],
    id: "165314f25235b30a38c478ebc707b40ae45bd246f2f5db29064f9b04e0458904c567624e0c70093facfa08c959bb9ef7da9bba09805904352694a71f0b3a4039",
    name: "Geth/dealer/v1.7.3-stable/linux-amd64/go1.9.2",
    network: {
      localAddress: "172.17.0.3:37710",
      remoteAddress: "172.17.0.4:30003"
    },
    protocols: {
      eth: {
        difficulty: 2632704,
        head: "0x1f394452a230fb3260419abff6bb1fda7346495583b0d864738b896200a5f7a4",
        version: 63
      }
    }
}, {
    caps: ["eth/63"],
    id: "3313eeb95c5a0f19914efb972069726a23aa49e28f87472278a5de93cc0864f016461eb16872d444b8ed07ed0adddb42fde6174a8b2d0ce372c6550fc30617d0",
    name: "Geth/bank/v1.7.3-stable/linux-amd64/go1.9.2",
    network: {
      localAddress: "172.17.0.3:48728",
      remoteAddress: "172.17.0.2:30001"
    },
    protocols: {
      eth: {
        difficulty: 2500544,
        head: "0xfd276f25b71d43ab0a85817300f71b7ccbde79672316555ffbbc6020622d3dc7",
        version: 63
      }
    }
}, {
    caps: ["eth/63"],
    id: "f715c5891229eba2d5a5c4297d5e2018c94115fbdb7042e71fb7415728231db8e636d5159a6cc9d7f04fa78693ddaf299a5fe84898b3a34747bf9234b19d1273",
    name: "Geth/dmv/v1.7.3-stable/linux-amd64/go1.9.2",
    network: {
      localAddress: "172.17.0.3:40688",
      remoteAddress: "172.17.0.5:30004"
    },
    protocols: {
      eth: {
        difficulty: 2368448,
        head: "0xfab57c13a643af2b82def77a629b58c0aadb629f0a90b93bfac347e8f562b7c2",
        version: 63
      }
    }
}]

In all the 4 terminals representing the entities, assign the variables buyer and dealer with their corresponding account address using the Javascript shell as shown below:

> buyer = "0xfe88391515b1074c9d02c58504d0ff648b7170d3"

> dealer = "0x75f091276c19177122af6bd4750938852359bbe2"

To send a transaction to transfer 1 ether from the buyer to the dealer, execute the following command in the Javascript shell prompt of the buyer terminal:

> eth.sendTransaction({from: buyer, to: dealer, value: web3.toWei(1, "ether")})

The following would be the typical output:

Output.24

"0x808e451d65153cc731a761504f52cb8c2f99085be60fad919d3f71a4f9acec4a"

To display all the pending transactions on this private network, execute the following command in the Javascript shell prompt of any of the 4 terminals:

> eth.pendingTransactions

The following would be the typical output:

Output.25

[{
    blockHash: null,
    blockNumber: null,
    from: "0xfe88391515b1074c9d02c58504d0ff648b7170d3",
    gas: 90000,
    gasPrice: 18000000000,
    hash: "0x808e451d65153cc731a761504f52cb8c2f99085be60fad919d3f71a4f9acec4a",
    input: "0x",
    nonce: 0,
    r: "0xf3c7f2b02c45d4523008bf0e286d9a0febb4384abdc42714c4ae921300fb669c",
    s: "0x6be3265b94dfee52db10bc0f6cf4b058d2daee34fa9e0d29478b5c214eb38df3",
    to: "0x75f091276c19177122af6bd4750938852359bbe2",
    transactionIndex: 0,
    v: "0x4e",
    value: 1000000000000000000
}]

The following is a custom Javascript utility function to display account balances (in ethers):

showBalances
function showBalances() {
  var i = 0;
  eth.accounts.forEach(function(e) {
     console.log("---> eth.accounts["+i+"]: " + e + " \tbalance: " + web3.fromWei(eth.getBalance(e), "ether") + " ether");
     i++;
  })
};

Paste the above Javascript code in the shell prompt of all the 4 terminals.

To display account balances using the Javascript utility function showBalances, execute the following command in the Javascript shell prompt of any of the 4 terminals:

> showBalances()

The following would be the typical output:

Output.26

---> eth.accounts[0]: 0x46ba3a22f4a62333433df5bf5068055af5c33e53   balance: 20 ether
---> eth.accounts[1]: 0xfe88391515b1074c9d02c58504d0ff648b7170d3  balance: 10 ether
---> eth.accounts[2]: 0x75f091276c19177122af6bd4750938852359bbe2  balance: 5 ether
---> eth.accounts[3]: 0x00acfabed04ce7b28049fe2f7980f8d082a6ce2f  balance: 3 ether
undefined

Now we are ready to start a miner for this private network.

To start the miner with one mining thread, execute the following command in the Javascript shell prompt of the bank terminal:

> miner.start(1)

Wait a few seconds and check the status of the sent transaction by executing the following command in the Javascript shell prompt of any of the 4 terminals:

> eth.pendingTransactions

The following would be the typical output:

Output.27

[]

To stop the miner, execute the following command in the Javascript shell prompt of the bank terminal:

> miner.stop()

Now, we check the account balances using the Javascript utility function showBalances by executing the following command in the Javascript shell prompt of any of the 4 terminals:

> showBalances()

The following would be the typical output:

Output.28

---> eth.accounts[0]: 0x46ba3a22f4a62333433df5bf5068055af5c33e53   balance: 145.000378 ether
---> eth.accounts[1]: 0xfe88391515b1074c9d02c58504d0ff648b7170d3  balance: 8.999622 ether
---> eth.accounts[2]: 0x75f091276c19177122af6bd4750938852359bbe2  balance: 6 ether
---> eth.accounts[3]: 0x00acfabed04ce7b28049fe2f7980f8d082a6ce2f  balance: 3 ether
undefined

As is evident from the Output.28 above, the account balance for the buyer has decreased, while the account balance for the dealer has increased.

WALLA !!! We have successfully demonstrated a 4-node private Ethereum network using Docker.

References

Introduction to Ethereum - Part 3

Introduction to Ethereum - Part 2

Introduction to Ethereum - Part 1

Introduction to Blockchain

Introduction to Docker