Multi-Node Private Ethereum Network using Docker
Bhaskar S | *UPDATED*10/21/2022 |
Overview
In Introduction to Ethereum - Part 1, 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).
Installation
The setup will be on a Ubuntu 22.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 user 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 image ethereum/client-go:alltools-v1.10.25 for Ethereum Go Client.
To pull and download the docker image for Ethereum Go Client, execute the following command:
docker pull ethereum/client-go:alltools-v1.10.25
The following should be the typical output:
alltools-v1.10.25: Pulling from ethereum/client-go 213ec9aee27d: Pull complete 2963a9ae9217: Pull complete 811f758345ec: Pull complete Digest: sha256:22a9874c0cf02914693023ec5fe5fb50a9861d13fd213ffa6492302454484554 Status: Downloaded newer image for ethereum/client-go:alltools-v1.10.25 docker.io/ethereum/client-go:alltools-v1.10.25
To check everything was ok with the Ethereum Go Client docker image, execute the following command:
$ docker run --rm --name geth ethereum/client-go:alltools-v1.10.25 geth version
The following would be the typical output:
Geth Version: 1.10.25-stable Git Commit: 69568c554880b3567bace64f8848ff1be27d084d Architecture: amd64 Go Version: go1.18.6 Operating System: linux GOPATH= GOROOT=go
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
The next step in a multi-node private network setup, is to create accounts corresponding to each of the peers, namely, the bank, the buyer, the dealer, and the dmv.
To create an account for the bank entity, execute the following command:
$ docker run -it --rm --name bank -u $(id -u $USER):$(id -g $USER) -v $HOME/Ethereum:/root ethereum/client-go:alltools-v1.10.25 geth --password /root/data/bank/bank.txt --datadir /root/data/bank account new
The following would be the typical output:
INFO [10-20|22:23:14.050] Maximum peer count ETH=50 LES=0 total=50 INFO [10-20|22:23:14.050] Smartcard socket not found, disabling err="stat /run/pcscd/pcscd.comm: no such file or directory" Your new key was generated Public address of the key: 0xc95eD0386316E2A985B8ad11178eb83757999D3b Path of the secret key file: /root/data/bank/keystore/UTC--2022-10-20T22-23-14.050914931Z--c95ed0386316e2a985b8ad11178eb83757999d3b - You can share your public address with anyone. Others need it to interact with you. - You must NEVER share the secret key with anyone! The key controls access to your funds! - You must BACKUP your key file! Without the key, it's impossible to access account funds! - You must REMEMBER your password! Without the password, it's impossible to decrypt the key!
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 buyer -u $(id -u $USER):$(id -g $USER) -v $HOME/Ethereum:/root ethereum/client-go:alltools-v1.10.25 geth --password /root/data/buyer/buyer.txt --datadir /root/data/buyer account new
Account buyer output:
INFO [10-20|22:23:39.943] Maximum peer count ETH=50 LES=0 total=50 INFO [10-20|22:23:39.944] Smartcard socket not found, disabling err="stat /run/pcscd/pcscd.comm: no such file or directory" Your new key was generated Public address of the key: 0x694D784017852fd6e84a8EbD27cA2162966C40c4 Path of the secret key file: /root/data/buyer/keystore/UTC--2022-10-20T22-23-39.944789223Z--694d784017852fd6e84a8ebd27ca2162966c40c4 - You can share your public address with anyone. Others need it to interact with you. - You must NEVER share the secret key with anyone! The key controls access to your funds! - You must BACKUP your key file! Without the key, it's impossible to access account funds! - You must REMEMBER your password! Without the password, it's impossible to decrypt the key!
Account dealer:
$ docker run -it --rm --name dealer -u $(id -u $USER):$(id -g $USER) -v $HOME/Ethereum:/root ethereum/client-go:alltools-v1.10.25 geth --password /root/data/dealer/dealer.txt --datadir /root/data/dealer account new
Account dealer output:
INFO [10-20|22:24:07.580] Maximum peer count ETH=50 LES=0 total=50 INFO [10-20|22:24:07.581] Smartcard socket not found, disabling err="stat /run/pcscd/pcscd.comm: no such file or directory" Your new key was generated Public address of the key: 0xad7430C568AF808D785B7E0B6BAB057cf7EE3e24 Path of the secret key file: /root/data/dealer/keystore/UTC--2022-10-20T22-24-07.581815941Z--ad7430c568af808d785b7e0b6bab057cf7ee3e24 - You can share your public address with anyone. Others need it to interact with you. - You must NEVER share the secret key with anyone! The key controls access to your funds! - You must BACKUP your key file! Without the key, it's impossible to access account funds! - You must REMEMBER your password! Without the password, it's impossible to decrypt the key!
Account dmv:
$ docker run -it --rm --name dmv -u $(id -u $USER):$(id -g $USER) -v $HOME/Ethereum:/root ethereum/client-go:alltools-v1.10.25 geth --password /root/data/dmv/dmv.txt --datadir /root/data/dmv account new
Account dmv output:
INFO [10-20|22:24:28.986] Maximum peer count ETH=50 LES=0 total=50 INFO [10-20|22:24:28.986] Smartcard socket not found, disabling err="stat /run/pcscd/pcscd.comm: no such file or directory" Your new key was generated Public address of the key: 0x1cF2Da205D16f1d9cf485e46ADEd945E841946fa Path of the secret key file: /root/data/dmv/keystore/UTC--2022-10-20T22-24-28.986842822Z--1cf2da205d16f1d9cf485e46aded945e841946fa - You can share your public address with anyone. Others need it to interact with you. - You must NEVER share the secret key with anyone! The key controls access to your funds! - You must BACKUP your key file! Without the key, it's impossible to access account funds! - You must REMEMBER your password! Without the password, it's impossible to decrypt the key!
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 multi-genesis.json that will be located in the directory conf, with the following contents:
{ "config": { "chainId": 21, "homesteadBlock": 0, "eip150Block": 0, "eip155Block": 0, "eip158Block": 0, "byzantiumBlock": 0, "constantinopleBlock": 0, "petersburgBlock": 0, "istanbulBlock": 0, "berlinBlock": 0, "clique": { "period": 10, "epoch": 30000 } }, "difficulty": "0x1", "gasLimit": "0xa00000", "extradata": "0x0000000000000000000000000000000000000000000000000000000000000000c95ed0386316e2a985b8ad11178eb83757999d3b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "alloc": { "c95ed0386316e2a985b8ad11178eb83757999d3b": { "balance": "20000000000000000000" }, "694d784017852fd6e84a8ebd27ca2162966c40c4": { "balance": "10000000000000000000" }, "ad7430c568af808d785b7e0b6bab057cf7ee3e24": { "balance": "5000000000000000000" }, "1cf2da205d16f1d9cf485e46aded945e841946fa": { "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 bank -u $(id -u $USER):$(id -g $USER) -v $HOME/Ethereum:/root ethereum/client-go:alltools-v1.10.25 geth --datadir /root/data/bank init /root/conf/multi-genesis.json
The following would be the typical output:
INFO [10-20|22:26:09.709] Maximum peer count ETH=50 LES=0 total=50 INFO [10-20|22:26:09.710] Smartcard socket not found, disabling err="stat /run/pcscd/pcscd.comm: no such file or directory" INFO [10-20|22:26:09.713] Set global gas cap cap=50,000,000 INFO [10-20|22:26:09.715] Allocated cache and file handles database=/root/data/bank/geth/chaindata cache=16.00MiB handles=16 INFO [10-20|22:26:09.732] Opened ancient database database=/root/data/bank/geth/chaindata/ancient/chain readonly=false INFO [10-20|22:26:09.732] Writing custom genesis block INFO [10-20|22:26:09.732] Persisted trie from memory database nodes=5 size=764.00B time="52.399µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B INFO [10-20|22:26:09.733] Successfully wrote genesis state database=chaindata hash=0ba7a6..9957ed INFO [10-20|22:26:09.733] Allocated cache and file handles database=/root/data/bank/geth/lightchaindata cache=16.00MiB handles=16 INFO [10-20|22:26:09.749] Opened ancient database database=/root/data/bank/geth/lightchaindata/ancient/chain readonly=false INFO [10-20|22:26:09.749] Writing custom genesis block INFO [10-20|22:26:09.749] Persisted trie from memory database nodes=5 size=764.00B time="46.037µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B INFO [10-20|22:26:09.750] Successfully wrote genesis state database=lightchaindata hash=0ba7a6..9957ed
Repeat the genesis block initialization for each of the remaining nodes buyer, dealer, and dmv as shown below:
For account buyer:
$ docker run -it --rm --name buyer -u $(id -u $USER):$(id -g $USER) -v $HOME/Ethereum:/root ethereum/client-go:alltools-v1.10.25 geth --datadir /root/data/buyer init /root/conf/multi-genesis.json
The following would be the typical output:
INFO [10-20|22:26:31.418] Maximum peer count ETH=50 LES=0 total=50 INFO [10-20|22:26:31.421] Smartcard socket not found, disabling err="stat /run/pcscd/pcscd.comm: no such file or directory" INFO [10-20|22:26:31.428] Set global gas cap cap=50,000,000 INFO [10-20|22:26:31.431] Allocated cache and file handles database=/root/data/buyer/geth/chaindata cache=16.00MiB handles=16 INFO [10-20|22:26:31.448] Opened ancient database database=/root/data/buyer/geth/chaindata/ancient/chain readonly=false INFO [10-20|22:26:31.449] Writing custom genesis block INFO [10-20|22:26:31.449] Persisted trie from memory database nodes=5 size=764.00B time="52.008µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B INFO [10-20|22:26:31.450] Successfully wrote genesis state database=chaindata hash=0ba7a6..9957ed INFO [10-20|22:26:31.450] Allocated cache and file handles database=/root/data/buyer/geth/lightchaindata cache=16.00MiB handles=16 INFO [10-20|22:26:31.473] Opened ancient database database=/root/data/buyer/geth/lightchaindata/ancient/chain readonly=false INFO [10-20|22:26:31.473] Writing custom genesis block INFO [10-20|22:26:31.474] Persisted trie from memory database nodes=5 size=764.00B time="81.414µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B INFO [10-20|22:26:31.474] Successfully wrote genesis state database=lightchaindata hash=0ba7a6..9957ed
For account dealer:
$ docker run -it --rm --name dealer -u $(id -u $USER):$(id -g $USER) -v $HOME/Ethereum:/root ethereum/client-go:alltools-v1.10.25 geth --datadir /root/data/dealer init /root/conf/multi-genesis.json
The following would be the typical output:
INFO [10-20|22:26:51.111] Maximum peer count ETH=50 LES=0 total=50 INFO [10-20|22:26:51.114] Smartcard socket not found, disabling err="stat /run/pcscd/pcscd.comm: no such file or directory" INFO [10-20|22:26:51.121] Set global gas cap cap=50,000,000 INFO [10-20|22:26:51.125] Allocated cache and file handles database=/root/data/dealer/geth/chaindata cache=16.00MiB handles=16 INFO [10-20|22:26:51.143] Opened ancient database database=/root/data/dealer/geth/chaindata/ancient/chain readonly=false INFO [10-20|22:26:51.143] Writing custom genesis block INFO [10-20|22:26:51.144] Persisted trie from memory database nodes=5 size=764.00B time="46.557µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B INFO [10-20|22:26:51.144] Successfully wrote genesis state database=chaindata hash=0ba7a6..9957ed INFO [10-20|22:26:51.144] Allocated cache and file handles database=/root/data/dealer/geth/lightchaindata cache=16.00MiB handles=16 INFO [10-20|22:26:51.159] Opened ancient database database=/root/data/dealer/geth/lightchaindata/ancient/chain readonly=false INFO [10-20|22:26:51.159] Writing custom genesis block INFO [10-20|22:26:51.160] Persisted trie from memory database nodes=5 size=764.00B time="425.713µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B INFO [10-20|22:26:51.161] Successfully wrote genesis state database=lightchaindata hash=0ba7a6..9957ed
For account dmv:
$ docker run -it --rm --name dmv -u $(id -u $USER):$(id -g $USER) -v $HOME/Ethereum:/root ethereum/client-go:alltools-v1.10.25 geth --datadir /root/data/dmv init /root/conf/multi-genesis.json
The following would be the typical output:
INFO [10-20|22:27:14.874] Maximum peer count ETH=50 LES=0 total=50 INFO [10-20|22:27:14.876] Smartcard socket not found, disabling err="stat /run/pcscd/pcscd.comm: no such file or directory" INFO [10-20|22:27:14.880] Set global gas cap cap=50,000,000 INFO [10-20|22:27:14.881] Allocated cache and file handles database=/root/data/dmv/geth/chaindata cache=16.00MiB handles=16 INFO [10-20|22:27:14.900] Opened ancient database database=/root/data/dmv/geth/chaindata/ancient/chain readonly=false INFO [10-20|22:27:14.900] Writing custom genesis block INFO [10-20|22:27:14.901] Persisted trie from memory database nodes=5 size=764.00B time="54.593µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B INFO [10-20|22:27:14.901] Successfully wrote genesis state database=chaindata hash=0ba7a6..9957ed INFO [10-20|22:27:14.901] Allocated cache and file handles database=/root/data/dmv/geth/lightchaindata cache=16.00MiB handles=16 INFO [10-20|22:27:14.916] Opened ancient database database=/root/data/dmv/geth/lightchaindata/ancient/chain readonly=false INFO [10-20|22:27:14.917] Writing custom genesis block INFO [10-20|22:27:14.918] Persisted trie from memory database nodes=5 size=764.00B time="311.758µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B INFO [10-20|22:27:14.919] Successfully wrote genesis state database=lightchaindata hash=0ba7a6..9957ed
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 terminal windows corresponding to the entity they refer. For example, the first terminal will be referred to as the bank, the second terminal as the buyer, the third terminal as the dealer and the fourth terminal as the dmv.
In each of the 4 terminal windows, change the working directory to /home/polarsparc/Ethereum.
In the bank terminal, start an Ethereum node by executing the following command:
$ docker run -it --rm --name bank -u $(id -u $USER):$(id -g $USER) -p 8081:8081 -p 30001:30001 -v $HOME/Ethereum:/root ethereum/client-go:alltools-v1.10.25 geth --networkid "21" --identity "bank" --datadir /root/data/bank --syncmode "full" --ipcdisable --nodiscover --allow-insecure-unlock --unlock 0x2929cc22c9203107509392ab77da1bc17f652c3c --password /root/data/bank/bank.txt --http --http.addr "0.0.0.0" --http.port 8081 --http.corsdomain "*" --port 30001 --verbosity 2 console
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.
The --http* related options enable API access via the bank node.
The following would be the typical output:
WARN [10-20|22:30:51.895] Failed to load snapshot, regenerating err="missing or corrupted snapshot" WARN [10-20|22:30:51.896] Error reading unclean shutdown markers error="leveldb: not found" WARN [10-20|22:30:51.896] Engine API enabled protocol=eth WARN [10-20|22:30:51.896] Engine API started but chain not configured for merge yet Welcome to the Geth JavaScript console! instance: Geth/bank/v1.10.25-stable-69568c55/linux-amd64/go1.18.6 coinbase: 0xc95ed0386316e2a985b8ad11178eb83757999d3b at block: 0 (Thu Jan 01 1970 00:00:00 GMT+0000 (UTC)) datadir: /root/data/bank modules: admin:1.0 clique:1.0 debug:1.0 engine:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0 To exit, press ctrl-d or type exit >
In the buyer terminal, start an Ethereum node by executing the following command:
$ docker run -it --rm --name buyer -u $(id -u $USER):$(id -g $USER) -p 30002:30002 -v $HOME/Ethereum:/root ethereum/client-go:alltools-v1.10.25 geth --networkid "21" --identity "buyer" --datadir /root/data/buyer --syncmode "full" --ipcdisable --nodiscover --unlock 0x9a4f8e15ec721adbdf8af75be091279dff159523 --password /root/data/buyer/buyer.txt --port 30002 --verbosity 2 console
The following would be the typical output:
WARN [10-20|22:31:35.288] Failed to load snapshot, regenerating err="missing or corrupted snapshot" WARN [10-20|22:31:35.289] Error reading unclean shutdown markers error="leveldb: not found" WARN [10-20|22:31:35.289] Engine API enabled protocol=eth WARN [10-20|22:31:35.289] Engine API started but chain not configured for merge yet Welcome to the Geth JavaScript console! instance: Geth/buyer/v1.10.25-stable-69568c55/linux-amd64/go1.18.6 coinbase: 0x694d784017852fd6e84a8ebd27ca2162966c40c4 at block: 0 (Thu Jan 01 1970 00:00:00 GMT+0000 (UTC)) datadir: /root/data/buyer modules: admin:1.0 clique:1.0 debug:1.0 engine:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0 To exit, press ctrl-d or type exit >
In the dealer terminal, start an Ethereum node by executing the following command:
$ docker run -it --rm --name dealer -u $(id -u $USER):$(id -g $USER) -p 30003:30003 -v $HOME/Ethereum:/root ethereum/client-go:alltools-v1.10.25 geth --networkid "21" --identity "dealer" --datadir /root/data/dealer --syncmode "full" --ipcdisable --nodiscover --unlock 0xfd23c931300c331343696fc1df24cd99f4cf3191 --password /root/data/dealer/dealer.txt --port 30003 --verbosity 2 console
The following would be the typical output:
WARN [10-20|22:33:06.202] Failed to load snapshot, regenerating err="missing or corrupted snapshot" WARN [10-20|22:33:06.203] Error reading unclean shutdown markers error="leveldb: not found" WARN [10-20|22:33:06.203] Engine API enabled protocol=eth WARN [10-20|22:33:06.203] Engine API started but chain not configured for merge yet Welcome to the Geth JavaScript console! instance: Geth/dealer/v1.10.25-stable-69568c55/linux-amd64/go1.18.6 coinbase: 0xad7430c568af808d785b7e0b6bab057cf7ee3e24 at block: 0 (Thu Jan 01 1970 00:00:00 GMT+0000 (UTC)) datadir: /root/data/dealer modules: admin:1.0 clique:1.0 debug:1.0 engine:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0 To exit, press ctrl-d or type exit >
In the dmv terminal, start an Ethereum node by executing the following command:
$ docker run -it --rm --name dmv -u $(id -u $USER):$(id -g $USER) -p 30004:30004 -v $HOME/Ethereum:/root ethereum/client-go:alltools-v1.10.25 geth --networkid "21" --identity "dmv" --datadir /root/data/dmv --syncmode "full" --ipcdisable --nodiscover --unlock 0x8703b39acc4cad0cb9d0f7a5157ed85499a97486 --password /root/data/dmv/dmv.txt --port 30004 --verbosity 2 console
The following would be the typical output:
WARN [10-20|22:33:32.364] Failed to load snapshot, regenerating err="missing or corrupted snapshot" WARN [10-20|22:33:32.365] Error reading unclean shutdown markers error="leveldb: not found" WARN [10-20|22:33:32.365] Engine API enabled protocol=eth WARN [10-20|22:33:32.365] Engine API started but chain not configured for merge yet Welcome to the Geth JavaScript console! instance: Geth/dmv/v1.10.25-stable-69568c55/linux-amd64/go1.18.6 coinbase: 0x1cf2da205d16f1d9cf485e46aded945e841946fa at block: 0 (Thu Jan 01 1970 00:00:00 GMT+0000 (UTC)) datadir: /root/data/dmv modules: admin:1.0 clique:1.0 debug:1.0 engine:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0 To exit, press ctrl-d or type exit >
The values for the port has 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 the nodes bank, buyer, and dealer.
For our private network, we will add the endpoint address of the bank as a peer to the 3 nodes buyer, dealer, and dmv.
Next, we will add the endpoint address of the buyer as a peer to the nodes dealer and dmv.
Finally, we will add the endpoint address of the dealer as a peer to the node dmv .
The end result is a private network with nodes as shown in the illustration below:
Note that the node bank is also the miner (the transaction authorizer).
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:
{ enode: "enode://0fad6ce97abfb06faaec281068316c1cfe5301fe617a36e63c217700c8335a88003a8d5e4bc28245b12ab28d0a9898c68b8f66dd7d9644d2b04063021b885428@127.0.0.1:30001?discport=0", enr: "enr:-Jy4QI6Hghb5cPy_-AhIt_8gwYl1sv0VQWkwImp_kOmAYZnNQjTZCksluJR9G84la9cq7PaSS8ZjfWgZNFBOnCRTgqmGAYP3hKtqg2V0aMfGhEvaIuaAgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQIPrWzper-wb6rsKBBoMWwc_lMB_mF6NuY8IXcAyDNaiIRzbmFwwIN0Y3CCdTE", id: "1c3aa3c637a4af6b9cfaf0cf0903f54295e8b4a6ddd0ffdba7f317deb2c09f41", ip: "127.0.0.1", listenAddr: "[::]:30001", name: "Geth/bank/v1.10.25-stable-69568c55/linux-amd64/go1.18.6", ports: { discovery: 0, listener: 30001 }, protocols: { eth: { config: { berlinBlock: 0, byzantiumBlock: 0, chainId: 21, clique: {...}, constantinopleBlock: 0, eip150Block: 0, eip150Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", eip155Block: 0, eip158Block: 0, homesteadBlock: 0, istanbulBlock: 0, petersburgBlock: 0 }, difficulty: 1, genesis: "0x0ba7a661645bed437de13cc78f2c7e1dddd0af6240a9f1f0e5dcc839469957ed", head: "0x0ba7a661645bed437de13cc78f2c7e1dddd0af6240a9f1f0e5dcc839469957ed", network: 21 }, snap: {} } }
The trimmed string value enode://0fad6ce...@127.0.0.1:30001?discport=0 is the endpoint address for the node bank.
Next, to identify the endpoint address of the buyer, execute the following command in the Javascript promt of the buyer terminal:
> admin.nodeInfo
The following would be the typical output:
{ enode: "enode://13c5e8704608292825030e7d9fb4f0ef283fd533c7177a8d43d3d2415f93ecb7d350ea59998618adb76d1732682218b4b5226cf1875a95ebfdc287ef1eb83b98@127.0.0.1:30002?discport=0", enr: "enr:-Jy4QNE096uFK8lTZk4If-9a1bX9Hw6Oen4eihAi_127LFxzCoPWgryv-dfyDQxZJXnLl5Pw_HQdmILj-zNoL-krTliGAYP3hfaBg2V0aMfGhEvaIuaAgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQITxehwRggpKCUDDn2ftPDvKD_VM8cXeo1D09JBX5Pst4RzbmFwwIN0Y3CCdTI", id: "8512ec46f1a6d302cc9af09e7117235909d3f8d25d5d830915ade41aa2899a16", ip: "127.0.0.1", listenAddr: "[::]:30002", name: "Geth/buyer/v1.10.25-stable-69568c55/linux-amd64/go1.18.6", ports: { discovery: 0, listener: 30002 }, protocols: { eth: { config: { berlinBlock: 0, byzantiumBlock: 0, chainId: 21, clique: {...}, constantinopleBlock: 0, eip150Block: 0, eip150Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", eip155Block: 0, eip158Block: 0, homesteadBlock: 0, istanbulBlock: 0, petersburgBlock: 0 }, difficulty: 1, genesis: "0x0ba7a661645bed437de13cc78f2c7e1dddd0af6240a9f1f0e5dcc839469957ed", head: "0x0ba7a661645bed437de13cc78f2c7e1dddd0af6240a9f1f0e5dcc839469957ed", network: 21 }, snap: {} } }
The trimmed string value enode://13c5e87...@127.0.0.1:30002?discport=0 is the endpoint address for the node buyer.
Finally, 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:
{ enode: "enode://8cd44ef8f159ce3d267220476b376194fcec9b5adcdcbf4d5001c553ab7ddd4f73218da6494754f054ed6ccf21c20858b6b87bc1fb47726349e24fa7ede1ff52@127.0.0.1:30003?discport=0", enr: "enr:-Jy4QKjw0Fv2jTEfuGgBsSCPkU-oXI1dhEqQoYrN62RT5wj2EDggY0ZSGBFnTTahq_UqbGs3nb15qoH7iCFYzSR_X0aGAYP3h1mjg2V0aMfGhEvaIuaAgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQKM1E748VnOPSZyIEdrN2GU_OybWtzcv01QAcVTq33dT4RzbmFwwIN0Y3CCdTM", id: "fa0c135282b8b1b30c65863b98db67c98a1473d02ac262712a7627a3db9d07e9", ip: "127.0.0.1", listenAddr: "[::]:30003", name: "Geth/dealer/v1.10.25-stable-69568c55/linux-amd64/go1.18.6", ports: { discovery: 0, listener: 30003 }, protocols: { eth: { config: { berlinBlock: 0, byzantiumBlock: 0, chainId: 21, clique: {...}, constantinopleBlock: 0, eip150Block: 0, eip150Hash: "0x0000000000000000000000000000000000000000000000000000000000000000", eip155Block: 0, eip158Block: 0, homesteadBlock: 0, istanbulBlock: 0, petersburgBlock: 0 }, difficulty: 1, genesis: "0x0ba7a661645bed437de13cc78f2c7e1dddd0af6240a9f1f0e5dcc839469957ed", head: "0x0ba7a661645bed437de13cc78f2c7e1dddd0af6240a9f1f0e5dcc839469957ed", network: 21 }, snap: {} } }
The trimmed string value enode://8cd44e...@127.0.0.1:30003?discport=0 is the endpoint address for the node dealer.
We also need the IP address of the Docker containers running the bank, buyer, and dealer nodes.
Open a new terminal window and execute the following command:
$ docker ps -a
The following would be the typical output:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4c9c4a78c564 ethereum/client-go:alltools-v1.10.25 "geth --networkid 21…" About a minute ago Up About a minute 8545-8546/tcp, 30303/tcp, 0.0.0.0:30004->30004/tcp, :::30004->30004/tcp, 30303/udp dmv 54a2aaf5950a ethereum/client-go:alltools-v1.10.25 "geth --networkid 21…" About a minute ago Up About a minute 8545-8546/tcp, 30303/tcp, 0.0.0.0:30003->30003/tcp, :::30003->30003/tcp, 30303/udp dealer 14b3eb39ccba ethereum/client-go:alltools-v1.10.25 "geth --networkid 21…" 3 minutes ago Up 3 minutes 8545-8546/tcp, 30303/tcp, 0.0.0.0:30002->30002/tcp, :::30002->30002/tcp, 30303/udp buyer ea96fe103568 ethereum/client-go:alltools-v1.10.25 "geth --networkid 21…" 4 minutes ago Up 4 minutes 0.0.0.0:8081->8081/tcp, :::8081->8081/tcp, 8545-8546/tcp, 0.0.0.0:30001->30001/tcp, :::30001->30001/tcp, 30303/tcp, 30303/udp bank
From the above Output.18, the Docker instances for the bank, the buyer, and the dealer nodes are ea96fe103568, 14b3eb39ccba, and 54a2aaf5950a respectively.
To extract the IP address of the bank node, execute the following command:
$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ea96fe103568
The following would be the typical output:
172.17.0.2
Next, to extract the IP address of the buyer node, execute the following command:
$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 14b3eb39ccba
The following would be the typical output:
172.17.0.3
Finally, to extract the IP address of the dealer node, execute the following command:
$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 54a2aaf5950a
The following would be the typical output:
172.17.0.4
From the above Output.19, Output.20, and Output.21, the IP address for the bank, the buyer, and the dealer nodes are 172.17.0.2, 172.17.0.3, and 172.17.0.4 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://0fad6ce97abfb06faaec281068316c1cfe5301fe617a36e63c217700c8335a88003a8d5e4bc28245b12ab28d0a9898c68b8f66dd7d9644d2b0 4063021b885428@172.17.0.2:30001')
The following would be the typical output:
true
Next, add the endpoint address of the buyer as a peer to the nodes dealer and dmv by executing the following command in the Javascript prompt of the corresponding terminals dealer and dmv:
> admin.addPeer('enode://13c5e8704608292825030e7d9fb4f0ef283fd533c7177a8d43d3d2415f93ecb7d350ea59998618adb76d1732682218b4b5226cf1875a95ebfd c287ef1eb83b98@172.17.0.3:30002')
Finally, add the endpoint address of the dealer as a peer to the node dmv by executing the following command in the Javascript prompt of the corresponding terminal dmv:
> admin.addPeer('enode://8cd44ef8f159ce3d267220476b376194fcec9b5adcdcbf4d5001c553ab7ddd4f73218da6494754f054ed6ccf21c20858b6b87bc1fb47726349 e24fa7ede1ff52@172.17.0.4:30003')
To verify the peers connected to any of the nodes, execute the following command in the Javascript promt of the corresponding terminal:
> admin.peers
The following would be the typical output:
[{ caps: ["eth/66", "eth/67", "snap/1"], enode: "enode://0fad6ce97abfb06faaec281068316c1cfe5301fe617a36e63c217700c8335a88003a8d5e4bc28245b12ab28d0a9898c68b8f66dd7d9644d2b04063021b885428@172.17.0.2:30001", id: "1c3aa3c637a4af6b9cfaf0cf0903f54295e8b4a6ddd0ffdba7f317deb2c09f41", name: "Geth/bank/v1.10.25-stable-69568c55/linux-amd64/go1.18.6", network: { inbound: false, localAddress: "172.17.0.5:56192", remoteAddress: "172.17.0.2:30001", static: true, trusted: false }, protocols: { eth: { difficulty: 1, head: "0x0ba7a661645bed437de13cc78f2c7e1dddd0af6240a9f1f0e5dcc839469957ed", version: 67 }, snap: { version: 1 } } }, { caps: ["eth/66", "eth/67", "snap/1"], enode: "enode://13c5e8704608292825030e7d9fb4f0ef283fd533c7177a8d43d3d2415f93ecb7d350ea59998618adb76d1732682218b4b5226cf1875a95ebfdc287ef1eb83b98@172.17.0.3:30002", id: "8512ec46f1a6d302cc9af09e7117235909d3f8d25d5d830915ade41aa2899a16", name: "Geth/buyer/v1.10.25-stable-69568c55/linux-amd64/go1.18.6", network: { inbound: false, localAddress: "172.17.0.5:49414", remoteAddress: "172.17.0.3:30002", static: true, trusted: false }, protocols: { eth: { difficulty: 1, head: "0x0ba7a661645bed437de13cc78f2c7e1dddd0af6240a9f1f0e5dcc839469957ed", version: 67 }, snap: { version: 1 } } }, { caps: ["eth/66", "eth/67", "snap/1"], enode: "enode://8cd44ef8f159ce3d267220476b376194fcec9b5adcdcbf4d5001c553ab7ddd4f73218da6494754f054ed6ccf21c20858b6b87bc1fb47726349e24fa7ede1ff52@172.17.0.4:30003", id: "fa0c135282b8b1b30c65863b98db67c98a1473d02ac262712a7627a3db9d07e9", name: "Geth/dealer/v1.10.25-stable-69568c55/linux-amd64/go1.18.6", network: { inbound: false, localAddress: "172.17.0.5:43204", remoteAddress: "172.17.0.4:30003", static: true, trusted: false }, protocols: { eth: { difficulty: 1, head: "0x0ba7a661645bed437de13cc78f2c7e1dddd0af6240a9f1f0e5dcc839469957ed", version: 67 }, snap: { version: 1 } } }]
The following is a custom Javascript utility function to display account balances (in ethers):
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 code in the Javascript 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 each of the 4 terminals:
> showBalances()
In the bank terminal, the following would be the typical output:
---> eth.accounts[0]: 0xc95ed0386316e2a985b8ad11178eb83757999d3b balance: 20 ether undefined
In the buyer terminal, the following would be the typical output:
---> eth.accounts[0]: 0x694d784017852fd6e84a8ebd27ca2162966c40c4 balance: 10 ether undefined
In the dealer terminal, the following would be the typical output:
---> eth.accounts[0]: 0xad7430c568af808d785b7e0b6bab057cf7ee3e24 balance: 5 ether undefined
Finally, in the dmv terminal, the following would be the typical output:
---> eth.accounts[0]: 0x1cf2da205d16f1d9cf485e46aded945e841946fa balance: 3 ether undefined
It is *EXTREMELY IMPORTANT* that the miner is running before sending any transactions. Else, the transactions will be stuck in the pending state forever.
Given that the bank is the authorized signer (miner), start the miner (with one mining thread) by executing the following command in the Javascript shell prompt of the bank terminal:
> miner.start(1)
In the buyer terminal, assign the variables buyer and dealer with their corresponding account address using the Javascript shell as shown below:
> buyer = "0x694d784017852fd6e84a8ebd27ca2162966c40c4"
> dealer = "0xad7430c568af808d785b7e0b6bab057cf7ee3e24"
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:
"0xadb276931f378f2dfd21b5006e45a2e5b5f2ca99c964cc0bb9cd07bc7e261452"
To display all the pending transactions on this private network, execute the following command in the Javascript shell prompt of the buyer terminal:
> eth.pendingTransactions
The following would be the typical output:
[{ blockHash: null, blockNumber: null, chainId: "0x15", from: "0x694d784017852fd6e84a8ebd27ca2162966c40c4", gas: 21000, gasPrice: 1000000000, hash: "0xadb276931f378f2dfd21b5006e45a2e5b5f2ca99c964cc0bb9cd07bc7e261452", input: "0x", nonce: 0, r: "0x24c10bbb6453bd36be2b291d3b54c0128b9c232b8edbdf4bc5d49f8810ca14b3", s: "0x125bb21059c3dd271f7e6580105f8246a2dcb974d79041247c489683736b8570", to: "0xad7430c568af808d785b7e0b6bab057cf7ee3e24", transactionIndex: null, type: "0x0", v: "0x4d", value: 1000000000000000000 }]
Wait a few seconds and check the status of the sent transaction by executing the following command in the Javascript shell prompt of the buyer terminal:
> eth.pendingTransactions
The following would be the typical output:
[]
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, execute the following command in the Javascript shell prompt of each of the 4 terminals:
> showBalances()
In the bank terminal, the following would be the typical output:
---> eth.accounts[0]: 0xc95ed0386316e2a985b8ad11178eb83757999d3b balance: 20.000021 ether undefined
In the buyer terminal, the following would be the typical output:
---> eth.accounts[0]: 0x694d784017852fd6e84a8ebd27ca2162966c40c4 balance: 8.999979 ether undefined
In the dealer terminal, the following would be the typical output:
---> eth.accounts[0]: 0xad7430c568af808d785b7e0b6bab057cf7ee3e24 balance: 6 ether undefined
Finally, in the dmv terminal, the following would be the typical output:
---> eth.accounts[0]: 0x1cf2da205d16f1d9cf485e46aded945e841946fa balance: 3 ether undefined
As is evident from the Output.32 and Output.33 above, the account balance for the buyer has decreased, while the account balance for the dealer has increased.
Now, we will make a few API requests via the JSON-RPC HTTP interface using the curl command. Note that the bank exposes the JSON-RPC HTTP interface at the network endpoint localhost:8081.
In order to execute the curl commands, open a new terminal window.
To get the endpoint version, execute the following command:
$ curl --location --request POST 'localhost:8081/' \ --header 'Content-Type: application/json' \ --data-raw '{ "jsonrpc":"2.0", "method":"web3_clientVersion", "params":[], "id":1 }'
The following would be the typical output:
{"jsonrpc":"2.0","id":1,"result":"Geth/bank/v1.10.25-stable-69568c55/linux-amd64/go1.18.6"}
To get the list of accounts, execute the following command:
$ curl --location --request POST 'localhost:8081/' \ --header 'Content-Type: application/json' \ --data-raw '{ "jsonrpc":"2.0", "method":"eth_accounts", "params":[], "id":1 }'
The following would be the typical output:
{"jsonrpc":"2.0","id":1,"result":["0xc95ed0386316e2a985b8ad11178eb83757999d3b"]}
To get the balance of a specified account, execute the following command:
$ curl --location --request POST 'localhost:8081/' \ --header 'Content-Type: application/json' \ --data-raw '{ "jsonrpc":"2.0", "method":"eth_getBalance", "params":[ "0xc95ed0386316e2a985b8ad11178eb83757999d3b", "latest" ], "id":1 }'
The following would be the typical output:
{"jsonrpc":"2.0","id":1,"result":"0x1158e5922855a5000"}
HOORAY !!! At this point we have successfully demonstrated a 4-node private Ethereum network using Docker.
References