PolarSPARC

Hyperledger Besu Private Network using Docker


Bhaskar S *UPDATED*10/22/2022


Overview

Hyperledger Besu is a Java-based, open source Ethereum client (formerly called Pantheon) that is designed for the Enterprise use-cases and can be run in both the public (permissionless) and the private (permissioned) scenarios.

Hyperledger Besu includes the following capabilities:

The following diagram illustrates the high-level architecture of Hyperledger Besu:


Besu Architecture
Hyperledger Besu Architecture

In the article Introduction to Ethereum - Part 1, we introduced some basic concepts of Ethereum, got our hands dirty in setting up a private network with 4 accounts (one for each of the entities - the bank, the buyer, the dealer, & the dmv), and demonstrated sending a sample transaction between two of the accounts.

In this article, we will demonstrate the same transaction behavior, but with a different setup using the official Docker image for Hyperledger Besu.

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 for help.

Also, ensure the Python programming language (version 3.7 or above) is installed.

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 in a terminal window:

$ mkdir -p HyperledgerBesu/conf

$ mkdir -p HyperledgerBesu/data/bank/keystore

$ mkdir -p HyperledgerBesu/data/buyer/keystore

$ mkdir -p HyperledgerBesu/data/dealer/keystore

$ mkdir -p HyperledgerBesu/data/dmv/keystore

Now, change the current working directory to the directory /home/polarsparc/HyperledgerBesu. In the following paragraphs we will refer to this location as $BESU_HOME.

Visit the site Hyperledger Besu to determine the current stable version. At the time of this article, the current stable version is 22.7.7.

For our exploration, we will be downloading and using the official docker image hyperledger/besu:22.7.7 for Hyperledger Besu.

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

docker pull hyperledger/besu:22.7.7

The following should be the typical output:

Output.1

22.7.7: Pulling from hyperledger/besu
fb0b3276a519: Already exists 
252b0b1a7bd8: Pull complete 
15f44a07bce1: Pull complete 
Digest: sha256:f76cee87b8a2024e9e031baf48396798e443dbf06d1f4382819f39d80f35ddd2
Status: Downloaded newer image for hyperledger/besu:22.7.7
docker.io/hyperledger/besu:22.7.7

Once the download is complete, execute the following command to check everything was ok with the image for Hyperledger Besu:

$ docker run --rm --name besu hyperledger/besu:22.7.7 --version

The following would be the typical output:

Output.2

besu/v22.7.7/linux-x86_64/openjdk-java-11
2022-10-22 14:24:38.396+00:00 | main | INFO  | Besu | Using jemalloc

Next step is to download and install the Python module for Web3. Execute the following command:

python -m pip install web3

This completes the installation part. Moving on to setup the private network.

Private Network Setup

For this demonstration, we will need 4 accounts, one for each of the entities - the bank, the buyer, the dealer, and the dmv.

We will create the 4 accounts using MyEtherWallet.

First, we will create an account for the entity bank. Click on Create a new wallet as shown in the illustration below:


Create New Wallet
Create a New Wallet

Next, click on Software as shown in the illustration below:


Software Wallet
Software Wallet

Next, click on Keystore File as shown in the illustration below:


Keystore File
Keystore File

Next, enter a secure password in the text box Password, re-enter the same password in the text box Confirm Password, and then click on Create Wallet as shown in the illustration below:


Create Wallet
Create Wallet

Next, click on Acknowledge & Download as shown in the illustration below:


Acknowledge & Download
Acknowledge & Download

This action will take a few seconds and the keystore file with the name in the form UTC--{timestamp}--{address} will be downloaded to the directory $HOME/Downloads.

Move the keystore file we just created for the bank to the appropriate directory by executing the following command:

$ mv $HOME/Downloads/UTC--* $BESU_HOME/data/bank/keystore

Perform the above steps for creating a new keystore for each of the remaining entities - the buyer, the dealer, and the dmv by clicking on Create Another Wallet each time as shown in the illustration below:.


Create Another Wallet
Create Another Wallet

Once the 4 keystore files are created and moved to the appropriate location, execute the following command at the location $BESU_HOME:

$ tree ./data

The following would be the typical output:

Output.3

./data
|-- bank
|   |-- keystore
|       |-- UTC--2022-10-22T13-02-38.299Z--f1da86e0f5288f0537865e7b51edd302089ae06f
|-- buyer
|   |-- keystore
|       |-- UTC--2022-10-22T13-13-58.581Z--760ee653f93dca288fe6f9a39b2ed54ed4be0226
|-- dealer
|   |-- keystore
|       |-- UTC--2022-10-22T13-15-26.669Z--658ba9305cb0de275e55ccfff396446bf7add077
|-- dmv
    |-- keystore
        |-- UTC--2022-10-22T13-16-24.965Z--41c6f1783161c92d4bdfe1f901bc2e9a1c80aa8d

8 directories, 8 files

From the Output.3 above, we have the 4 accounts and their respective addresses as follows:

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 use the following template to create the genesis file:


genesis-template.json
{
  "config":{
    "chainId": 21,
    "berlinBlock": 0,
    "clique":{
      "blockperiodseconds": 10,
      "epochlength": 30000
    }
  },
  "coinbase":"0x0000000000000000000000000000000000000000",
  "difficulty":"0x1",
  "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000bank-address0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  "gasLimit":"0xa00000",
  "mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
  "nonce":"0x0",
  "timestamp":"0x5c51a607",
  "alloc": {
      "bank-address": {
        "balance": "20000000000000000000"
      },
      "buyer-address": {
        "balance": "10000000000000000000"
      },
      "dealer-address": {
        "balance": "5000000000000000000"
      },
      "dmv-address": {
        "balance": "3000000000000000000"
      }
   }
}

Create a file called genesis.json with above contents in the directory $BESU_HOME/conf.

For this demonstration, we will be using the Clique algorithm, which is one of the supported Proof of Authority (PoA) consensus algorithm.

The PoA is most suitable for Enterprise scenarios where the participants know and trust each other. As a result, the PoA consensus algorithms have faster block creation times and higher transaction throughput.

Clique is a reputation-based algorithm in which only approved and trusted accounts (known as the Signers) can validate transactions and create blocks. If there are more than one Signers, they take turns to create the next block.

We will use the bank as the trusted Signers in our private Ethereum network. The address of the Signers is specified in the extraData field.

The following illustration shows the template for the extraData field:


Signer Template
Signer Template

Using the bank's address as the Signer (which is a 40 character hex string) f1da86e0f5288f0537865e7b51edd302089ae06f, the following illustration shows the final extraData field:


Bank as Signer
Bank as Signer

After modifying the contents of the file genesis.json (located in the directory $BESU_HOME/conf) with the Signer and the 4 account addresses, the following would be the contents of the genesis file:


genesis.json
{
  "config":{
    "chainId": 21,
    "berlinBlock": 0,
    "clique":{
      "blockperiodseconds": 10,
      "epochlength": 30000
    }
  },
  "coinbase":"0x0000000000000000000000000000000000000000",
  "difficulty":"0x1",
  "extraData":"0x0000000000000000000000000000000000000000000000000000000000000000f1da86e0f5288f0537865e7b51edd302089ae06f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  "gasLimit":"0xa00000",
  "mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
  "nonce":"0x0",
  "timestamp":"0x5c51a607",
  "alloc": {
      "f1da86e0f5288f0537865e7b51edd302089ae06f": {
        "balance": "20000000000000000000"
      },
      "760ee653f93dca288fe6f9a39b2ed54ed4be0226": {
        "balance": "10000000000000000000"
      },
      "658ba9305cb0de275e55ccfff396446bf7add077": {
        "balance": "5000000000000000000"
      },
      "41c6f1783161c92d4bdfe1f901bc2e9a1c80aa8d": {
        "balance": "3000000000000000000"
      }
   }
}

In the following section, we will explain some of the important parameters used in the genysis file shown above:

Moving along, the next step is to extract the private key for the 4 accounts. For this, we will leverage the web3 module we installed for Python.

Open a new terminal window, change the current directory to $BESU_HOME, and launch the Python interpreter.

To extract the private key for bank, execute the following commands at the Python interpreter prompt:

>>> from web3.auto import w3

>>> with open("./data/bank/keystore/UTC--2022-10-22T13-02-38.299Z--f1da86e0f5288f0537865e7b51edd302089ae06f") as key_file:

... encrypted_key = key_file.read()

... bank_priv_key = w3.eth.account.decrypt(encrypted_key, 'Bank_Secret')

>>> bank_priv_key

The following would be the typical output:

Output.4

HexBytes('0xa0d12aee64cecaf6af78a2594263f123b35ecf2fb3c76f88b662cbcb7207b348')

Copy the private key a0d12aee64cecaf6af78a2594263f123b35ecf2fb3c76f88b662cbcb7207b348 (without the 0x) and save it to a file called key in the directory $BESU_HOME/data/bank.

We will repeat the above steps for each of the other accounts buyer, dealer , and dmv.

To extract the private key for buyer, execute the following commands at the Python interpreter prompt:

>>> with open("./data/buyer/keystore/UTC--2022-10-22T13-13-58.581Z--760ee653f93dca288fe6f9a39b2ed54ed4be0226") as key_file:

... encrypted_key = key_file.read()

... buyer_priv_key = w3.eth.account.decrypt(encrypted_key, 'Buyer_Secret')

>>> buyer_priv_key

The following would be the typical output:

Output.5

HexBytes('0xabe351ed564b301bfbfc6e0e69a23720fc3bc40405f164188c88dbed548f60b1')

Copy the private key abe351ed564b301bfbfc6e0e69a23720fc3bc40405f164188c88dbed548f60b1 (without the 0x) and save it to a file called key in the directory $BESU_HOME/data/buyer.

To extract the private key for dealer, execute the following commands at the Python interpreter prompt:

>>> with open("./data/dealer/keystore/UTC--2022-10-22T13-15-26.669Z--658ba9305cb0de275e55ccfff396446bf7add077") as key_file:

... encrypted_key = key_file.read()

... dealer_priv_key = w3.eth.account.decrypt(encrypted_key, 'Dealer_Secret')

>>> dealer_priv_key

The following would be the typical output:

Output.6

HexBytes('0xa8fdf591c01c215140d1a3e614148e2d20ee162b10fe1dbce5db639f03024efc')

Copy the private key a8fdf591c01c215140d1a3e614148e2d20ee162b10fe1dbce5db639f03024efc (without the 0x) and save it to a file called key in the directory $BESU_HOME/data/dealer.

Finally, to extract the private key for dmv, execute the following commands at the Python interpreter prompt:

>>> with open("./data/dmv/keystore/UTC--2022-10-22T13-16-24.965Z--41c6f1783161c92d4bdfe1f901bc2e9a1c80aa8d") as key_file:

... encrypted_key = key_file.read()

... dmv_priv_key = w3.eth.account.decrypt(encrypted_key, 'Dmv_Secret')

>>> dmv_priv_key

The following would be the typical output:

Output.7

HexBytes('0x0a6b5fbe7c08d63b47ba7c051b27a5c773afd3d565660355355092ff14b8b1f3')

Copy the private key 0a6b5fbe7c08d63b47ba7c051b27a5c773afd3d565660355355092ff14b8b1f3 (without the 0x) and save it to a file called key in the directory $BESU_HOME/data/dmv.

The next step is to extract the public address of the bootnode and store it in a file named bootnode_pubkey that will be located at $BESU_HOME/data/bank. A bootnode is used for initial discovery of the nodes (peers) in the network. We will designate the node running the bank as the bootnode.

To extract the public address for bank, execute the following command in the terminal prompt:

$ docker run --rm --name bank -u $(id -u $USER):$(id -g $USER) -v $BESU_HOME/root hyperledger/besu:22.7.7 --data-path=/root/data/bank --node-private-key-file=/root/data/bank/key public-key export-address --to=/root/data/bank/bootnode_pubkey

The following would be the typical output:

Output.8

2022-10-22 18:33:31.630+00:00 | main | INFO  | KeyPairUtil | Loaded public key 0x8be283f1550a85b8ed46fb80527acce06b55985377b761bb7701b1b0241343937fc46bc08dd96a26ef20dcc37c6df8c79edf7a2a0e86828cc6ce1e07e5f5a963 from /root/data/bank/key

For simplicity, the nodes of our Hyperledger Besu based private Ethereum network will on localhost 127.0.0.1 on different ports.

The next step is to create a configuration file with various options for the bank node. The configuration file will be used by Hyperledger Besu to start the Ethereum node.

The following will be the contents of the configuration file bank-config.toml (located in the directory $BESU_HOME/conf):


bank-config.toml
logging="INFO"
identity="bank"
data-path="/opt/besu/data"
genesis-file="/config/genesis.json"
node-private-key-file="/opt/besu/keys/key"
nat-method="DOCKER"
host-whitelist=["*"]

# P2P related

p2p-host="127.0.0.1"
p2p-port=30303

# RPC related

rpc-http-enabled=true
rpc-http-host="127.0.0.1"
rpc-http-port=8545
rpc-http-api=["ADMIN", "CLIQUE", "ETH", "NET", "TXPOOL", "WEB3"]
rpc-http-cors-origins=["*"]

In the following section, we will explain some of the configuration options used above:

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 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 windows, change the current working directory to $BESU_HOME.

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

$ docker run --rm --name bank --network host -e BESU_OPTS="--add-opens java.base/jdk.internal.misc=ALL-UNNAMED -Dio.netty.tryReflectionSetAccessible=true" -u $(id -u $USER):$(id -g $USER) -v $BESU_HOME/data/bank:/opt/besu/data -v $BESU_HOME/data/bank:/opt/besu/keys -v $BESU_HOME/data/bank:/opt/besu/public-keys -v $BESU_HOME/conf/bank-config.toml:/config/config.toml -v $BESU_HOME/conf/genesis.json:/config/genesis.json hyperledger/besu:22.7.7 --config-file=/config/config.toml

The following would be the typical output:

Output.9

Setting logging level to INFO
2022-10-22 19:06:49.002+00:00 | main | INFO  | Besu | Using LibEthPairings native alt bn128
2022-10-22 19:06:49.004+00:00 | main | INFO  | Besu | Using the native implementation of the signature algorithm
2022-10-22 19:06:49.007+00:00 | main | INFO  | Besu | Using the native implementation of the blake2bf algorithm
2022-10-22 19:06:49.011+00:00 | main | INFO  | Besu | Starting Besu version: besu/bank/v22.7.7/linux-x86_64/openjdk-java-11
2022-10-22 19:06:49.455+00:00 | main | WARN  | Besu | --graphql-http-host has been ignored because --graphql-http-enabled was not defined on the command line.
2022-10-22 19:06:49.457+00:00 | main | WARN  | Besu | --rpc-ws-host has been ignored because --rpc-ws-enabled was not defined on the command line.
2022-10-22 19:06:49.459+00:00 | main | INFO  | Besu | Static Nodes file = /opt/besu/data/static-nodes.json
2022-10-22 19:06:49.460+00:00 | main | INFO  | StaticNodesParser | StaticNodes file /opt/besu/data/static-nodes.json does not exist, no static connections will be created.
2022-10-22 19:06:49.460+00:00 | main | INFO  | Besu | Connecting to 0 static nodes.
2022-10-22 19:06:49.463+00:00 | main | INFO  | Besu | Security Module: localfile
2022-10-22 19:06:49.479+00:00 | main | INFO  | RocksDBKeyValueStorageFactory | No existing database detected at /opt/besu/data. Using version 1
2022-10-22 19:06:49.931+00:00 | main | WARN  | Besu | --tx-pool-future-max-by-account has been deprecated, use --tx-pool-limit-by-account-percentage instead.
2022-10-22 19:06:49.950+00:00 | main | INFO  | KeyPairUtil | Loaded public key 0x8be283f1550a85b8ed46fb80527acce06b55985377b761bb7701b1b0241343937fc46bc08dd96a26ef20dcc37c6df8c79edf7a2a0e86828cc6ce1e07e5f5a963 from /opt/besu/keys/key
2022-10-22 19:06:50.077+00:00 | main | INFO  | ProtocolScheduleBuilder | Protocol schedule created with milestones: [Berlin: 0]
2022-10-22 19:06:50.154+00:00 | main | INFO  | TransactionPoolFactory | Enabling transaction pool
2022-10-22 19:06:50.166+00:00 | main | INFO  | BesuControllerBuilder | TTD difficulty is not present, creating initial sync phase for PoW
2022-10-22 19:06:50.190+00:00 | main | INFO  | RunnerBuilder | Detecting NAT service.
2022-10-22 19:06:50.297+00:00 | main | INFO  | Runner | Starting external services ... 
2022-10-22 19:06:50.297+00:00 | main | INFO  | JsonRpcHttpService | Starting JSON-RPC service on 0.0.0.0:8545
2022-10-22 19:06:50.417+00:00 | vert.x-eventloop-thread-1 | INFO  | JsonRpcHttpService | JSON-RPC service started and listening on 0.0.0.0:8545
2022-10-22 19:06:50.422+00:00 | main | INFO  | AutoTransactionLogBloomCachingService | Starting auto transaction log bloom caching service.
2022-10-22 19:06:50.423+00:00 | main | INFO  | LogBloomCacheMetadata | Lookup cache metadata file in data directory: /opt/besu/data/caches
2022-10-22 19:06:50.432+00:00 | main | INFO  | Runner | Starting Ethereum main loop ... 
2022-10-22 19:06:50.432+00:00 | main | INFO  | DockerNatManager | Starting docker NAT manager.
2022-10-22 19:06:50.441+00:00 | main | INFO  | NetworkRunner | Starting Network.
2022-10-22 19:06:50.456+00:00 | nioEventLoopGroup-2-1 | INFO  | RlpxAgent | P2P RLPx agent started and listening on /0:0:0:0:0:0:0:0:30303.
2022-10-22 19:06:50.457+00:00 | main | INFO  | PeerDiscoveryAgent | Starting peer discovery agent on host=0.0.0.0, port=30303
2022-10-22 19:06:50.490+00:00 | vert.x-eventloop-thread-1 | INFO  | VertxPeerDiscoveryAgent | Started peer discovery agent successfully, on effective host=0:0:0:0:0:0:0:0%0 and port=30303
2022-10-22 19:06:50.495+00:00 | vert.x-eventloop-thread-1 | INFO  | PeerDiscoveryAgent | P2P peer discovery agent started and listening on /0:0:0:0:0:0:0:0%0:30303
2022-10-22 19:06:50.589+00:00 | vert.x-eventloop-thread-1 | INFO  | PeerDiscoveryAgent | Writing node record to disk. NodeRecord{seq=1, publicKey=0x038be283f1550a85b8ed46fb80527acce06b55985377b761bb7701b1b024134393, udpAddress=Optional[/127.0.0.1:30303], tcpAddress=Optional[/127.0.0.1:30303], asBase64=-Je4QEk8ycQ0VBvQz1nvA3E3BtbGW132qt9DW_S07t6ZSaCqIY4TUJ8AsrV_czNMRAxPj3bhzDgrqBec6C530D6h2rYBg2V0aMfGhOOh5SSAgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQOL4oPxVQqFuO1G-4BSeszga1WYU3e3Ybt3AbGwJBNDk4N0Y3CCdl-DdWRwgnZf, nodeId=0x2ec606ee8985c1a3e070f8e5f1da86e0f5288f0537865e7b51edd302089ae06f, customFields={tcp=30303, udp=30303, ip=0x7f000001, eth=[[0xe3a1e524, 0x]], id=V4, secp256k1=0x038be283f1550a85b8ed46fb80527acce06b55985377b761bb7701b1b024134393}}
2022-10-22 19:06:50.611+00:00 | main | INFO  | DefaultP2PNetwork | Enode URL enode://8be283f1550a85b8ed46fb80527acce06b55985377b761bb7701b1b0241343937fc46bc08dd96a26ef20dcc37c6df8c79edf7a2a0e86828cc6ce1e07e5f5a963@127.0.0.1:30303
2022-10-22 19:06:50.612+00:00 | main | INFO  | DefaultP2PNetwork | Node address 0xf1da86e0f5288f0537865e7b51edd302089ae06f
2022-10-22 19:06:50.615+00:00 | main | INFO  | DefaultSynchronizer | Starting synchronizer.
2022-10-22 19:06:50.621+00:00 | main | INFO  | FullSyncDownloader | Starting full sync.
2022-10-22 19:06:50.622+00:00 | main | INFO  | FullSyncTargetManager | No sync target, waiting for peers. Current peers: 0
2022-10-22 19:06:50.641+00:00 | main | INFO  | Runner | Ethereum main loop is up.
2022-10-22 19:06:50.705+00:00 | pool-7-thread-1 | INFO  | BlockMiner | Produced #1 / 0 tx / 0 om / 0 (0.0%) gas / (0x8b55187027adb584f4304481a5c4891cfa3342aaa0af3c85edbc81ff91773bf7) in 0.047s
2022-10-22 19:06:55.637+00:00 | EthScheduler-Timer-0 | INFO  | FullSyncTargetManager | No sync target, waiting for peers. Current peers: 0

From the Output.8 above, we need to make a note of the bootnode address from the line that contains the sub-string Enode URL.

We will now create the configuration files (with various options including the address of the bootnode) for the remaining 3 entities.


* WARNING *

The docker option --network host was the *ONLY* way to get the Ethereum private network work, with all the nodes discovered and connected.

The following will be the contents of the configuration file buyer-config.toml (located in the directory $BESU_HOME/conf):


buyer-config.toml
logging="INFO"
identity="buyer"
data-path="/opt/besu/data"
genesis-file="/config/genesis.json"
node-private-key-file="/opt/besu/keys/key"
nat-method="DOCKER"
host-whitelist=["*"]
bootnodes=["enode://8be283f1550a85b8ed46fb80527acce06b55985377b761bb7701b1b0241343937fc46bc08dd96a26ef20dcc37c6df8c79edf7a2a0e86828cc6ce1e07e5f5a963@127.0.0.1:30303"]

# P2P related

p2p-host="127.0.0.1"
p2p-port=30304

# RPC related

rpc-http-enabled=false
rpc-http-host="127.0.0.1"
rpc-http-port=8546
rpc-http-api=["ADMIN", "CLIQUE", "ETH", "NET", "TXPOOL", "WEB3"]
rpc-http-cors-origins=["*"]

The following will be the contents of the configuration file dealer-config.toml (located in the directory $BESU_HOME/conf):


dealer-config.toml
logging="INFO"
identity="dealer"
data-path="/opt/besu/data"
genesis-file="/config/genesis.json"
node-private-key-file="/opt/besu/keys/key"
nat-method="DOCKER"
host-whitelist=["*"]
bootnodes=["enode://8be283f1550a85b8ed46fb80527acce06b55985377b761bb7701b1b0241343937fc46bc08dd96a26ef20dcc37c6df8c79edf7a2a0e86828cc6ce1e07e5f5a963@127.0.0.1:30303"]

# P2P related

p2p-host="127.0.0.1"
p2p-port=30305

# RPC related

rpc-http-enabled=false
rpc-http-host="127.0.0.1"
rpc-http-port=8547
rpc-http-api=["ADMIN", "CLIQUE", "ETH", "NET", "TXPOOL", "WEB3"]
rpc-http-cors-origins=["*"]

The following will be the contents of the configuration file dmv-config.toml (located in the directory $BESU_HOME/conf):


dmv-config.toml
logging="INFO"
identity="dmv"
data-path="/opt/besu/data"
genesis-file="/config/genesis.json"
node-private-key-file="/opt/besu/keys/key"
nat-method="DOCKER"
host-whitelist=["*"]
bootnodes=["enode://8be283f1550a85b8ed46fb80527acce06b55985377b761bb7701b1b0241343937fc46bc08dd96a26ef20dcc37c6df8c79edf7a2a0e86828cc6ce1e07e5f5a963@127.0.0.1:30303"]

# P2P related

p2p-host="127.0.0.1"
p2p-port=30306

# RPC related

rpc-http-enabled=false
rpc-http-host="127.0.0.1"
rpc-http-port=8548
rpc-http-api=["ADMIN", "CLIQUE", "ETH", "NET", "TXPOOL", "WEB3"]
rpc-http-cors-origins=["*"]

CAUTION:

The values for the configuration options p2p-port and the rpc-http-port have to be unique for every Ethereum node in the private network.

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

$ docker run --rm --name buyer --network host -e BESU_OPTS="--add-opens java.base/jdk.internal.misc=ALL-UNNAMED -Dio.netty.tryReflectionSetAccessible=true" -u $(id -u $USER):$(id -g $USER) -v $BESU_HOME/data/buyer:/opt/besu/data -v $BESU_HOME/data/buyer:/opt/besu/keys -v $BESU_HOME/conf/buyer-config.toml:/config/config.toml -v $BESU_HOME/conf/genesis.json:/config/genesis.json hyperledger/besu:22.7.7 --config-file=/config/config.toml

The following would be the typical output:

Output.10

Setting logging level to INFO
2022-10-22 19:29:37.228+00:00 | main | INFO  | Besu | Using LibEthPairings native alt bn128
2022-10-22 19:29:37.230+00:00 | main | INFO  | Besu | Using the native implementation of the signature algorithm
2022-10-22 19:29:37.234+00:00 | main | INFO  | Besu | Using the native implementation of the blake2bf algorithm
2022-10-22 19:29:37.238+00:00 | main | INFO  | Besu | Starting Besu version: besu/buyer/v22.7.7/linux-x86_64/openjdk-java-11
2022-10-22 19:29:37.656+00:00 | main | WARN  | Besu | --rpc-http-host, --rpc-http-port, --rpc-http-cors-origins and --rpc-http-api has been ignored because --rpc-http-enabled was not defined on the command line.
2022-10-22 19:29:37.658+00:00 | main | WARN  | Besu | --graphql-http-host has been ignored because --graphql-http-enabled was not defined on the command line.
2022-10-22 19:29:37.661+00:00 | main | WARN  | Besu | --rpc-ws-host has been ignored because --rpc-ws-enabled was not defined on the command line.
2022-10-22 19:29:37.663+00:00 | main | INFO  | Besu | Static Nodes file = /opt/besu/data/static-nodes.json
2022-10-22 19:29:37.664+00:00 | main | INFO  | StaticNodesParser | StaticNodes file /opt/besu/data/static-nodes.json does not exist, no static connections will be created.
2022-10-22 19:29:37.664+00:00 | main | INFO  | Besu | Connecting to 0 static nodes.
2022-10-22 19:29:37.667+00:00 | main | INFO  | Besu | Security Module: localfile
2022-10-22 19:29:37.683+00:00 | main | INFO  | RocksDBKeyValueStorageFactory | No existing database detected at /opt/besu/data. Using version 1
2022-10-22 19:29:38.152+00:00 | main | WARN  | Besu | --tx-pool-future-max-by-account has been deprecated, use --tx-pool-limit-by-account-percentage instead.
2022-10-22 19:29:38.170+00:00 | main | INFO  | KeyPairUtil | Loaded public key 0x0854eb4540a40bce1853040037df18eea7f5234f9c958664bbafb412159be681c44b0da24fc36805fc9d3da9741eae8a6226ab89e8af458f67d1d5454a3e049d from /opt/besu/keys/key
2022-10-22 19:29:38.296+00:00 | main | INFO  | ProtocolScheduleBuilder | Protocol schedule created with milestones: [Berlin: 0]
2022-10-22 19:29:38.370+00:00 | main | INFO  | TransactionPoolFactory | Enabling transaction pool
2022-10-22 19:29:38.381+00:00 | main | INFO  | BesuControllerBuilder | TTD difficulty is not present, creating initial sync phase for PoW
2022-10-22 19:29:38.406+00:00 | main | INFO  | RunnerBuilder | Detecting NAT service.
2022-10-22 19:29:38.470+00:00 | main | INFO  | Runner | Starting external services ... 
2022-10-22 19:29:38.475+00:00 | main | INFO  | AutoTransactionLogBloomCachingService | Starting auto transaction log bloom caching service.
2022-10-22 19:29:38.476+00:00 | main | INFO  | LogBloomCacheMetadata | Lookup cache metadata file in data directory: /opt/besu/data/caches
2022-10-22 19:29:38.485+00:00 | main | INFO  | Runner | Starting Ethereum main loop ... 
2022-10-22 19:29:38.485+00:00 | main | INFO  | DockerNatManager | Starting docker NAT manager.
2022-10-22 19:29:38.490+00:00 | main | INFO  | NetworkRunner | Starting Network.
2022-10-22 19:29:38.569+00:00 | nioEventLoopGroup-2-1 | INFO  | RlpxAgent | P2P RLPx agent started and listening on /0:0:0:0:0:0:0:0:30304.
2022-10-22 19:29:38.570+00:00 | main | INFO  | PeerDiscoveryAgent | Starting peer discovery agent on host=0.0.0.0, port=30304
2022-10-22 19:29:38.611+00:00 | vert.x-eventloop-thread-1 | INFO  | VertxPeerDiscoveryAgent | Started peer discovery agent successfully, on effective host=0:0:0:0:0:0:0:0%0 and port=30304
2022-10-22 19:29:38.614+00:00 | vert.x-eventloop-thread-1 | INFO  | PeerDiscoveryAgent | P2P peer discovery agent started and listening on /0:0:0:0:0:0:0:0%0:30304
2022-10-22 19:29:38.701+00:00 | vert.x-eventloop-thread-1 | INFO  | PeerDiscoveryAgent | Writing node record to disk. NodeRecord{seq=1, publicKey=0x030854eb4540a40bce1853040037df18eea7f5234f9c958664bbafb412159be681, udpAddress=Optional[/127.0.0.1:30304], tcpAddress=Optional[/127.0.0.1:30304], asBase64=-Je4QJAo0BPrj_yLqNigrRsR3raxl4FGi8320LbNBUj8GFDvNL_cT3sKc5PBlzEP0Z2Qr4cHIL_lET-wcwZl2kcHN1EBg2V0aMfGhOOh5SSAgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQMIVOtFQKQLzhhTBAA33xjup_UjT5yVhmS7r7QSFZvmgYN0Y3CCdmCDdWRwgnZg, nodeId=0x6a509e9a68bb306420dec9b5760ee653f93dca288fe6f9a39b2ed54ed4be0226, customFields={tcp=30304, udp=30304, ip=0x7f000001, eth=[[0xe3a1e524, 0x]], id=V4, secp256k1=0x030854eb4540a40bce1853040037df18eea7f5234f9c958664bbafb412159be681}}
2022-10-22 19:29:38.744+00:00 | main | INFO  | DefaultP2PNetwork | Enode URL enode://0854eb4540a40bce1853040037df18eea7f5234f9c958664bbafb412159be681c44b0da24fc36805fc9d3da9741eae8a6226ab89e8af458f67d1d5454a3e049d@127.0.0.1:30304
2022-10-22 19:29:38.744+00:00 | main | INFO  | DefaultP2PNetwork | Node address 0x760ee653f93dca288fe6f9a39b2ed54ed4be0226
2022-10-22 19:29:38.747+00:00 | main | INFO  | DefaultSynchronizer | Starting synchronizer.
2022-10-22 19:29:38.751+00:00 | main | INFO  | FullSyncDownloader | Starting full sync.
2022-10-22 19:29:38.754+00:00 | main | INFO  | FullSyncTargetManager | No sync target, waiting for peers. Current peers: 0
2022-10-22 19:29:38.775+00:00 | main | INFO  | Runner | Ethereum main loop is up.
2022-10-22 19:29:39.132+00:00 | nioEventLoopGroup-3-1 | INFO  | FullSyncTargetManager | No sync target, waiting for peers. Current peers: 1
2022-10-22 19:29:42.016+00:00 | nioEventLoopGroup-3-1 | INFO  | BlockPropagationManager | Saved announced block for future import 13 (0xb03db618ad8ce1dea634b7e466d68704d3e85767a005f4cca08a23704ffb4076) - 1 saved block(s)
2022-10-22 19:29:42.017+00:00 | nioEventLoopGroup-3-1 | INFO  | BlockPropagationManager | Retrieving parent 0xff19e9950977249f7e7a170222802da512e274cee9cd19823358c5e7ba77f9c4 of block 13 (0xb03db618ad8ce1dea634b7e466d68704d3e85767a005f4cca08a23704ffb4076)
2022-10-22 19:29:42.051+00:00 | nioEventLoopGroup-3-1 | INFO  | BlockPropagationManager | Saved announced block for future import 12 (0xff19e9950977249f7e7a170222802da512e274cee9cd19823358c5e7ba77f9c4) - 2 saved block(s)
..... SNIP .....
2022-10-22 19:29:42.223+00:00 | nioEventLoopGroup-3-1 | INFO  | BlockPropagationManager | Saved announced block for future import 2 (0xfa032005a838a9da0518dd637d8e12d1767cf556ebec127c9a87ecb6a82c03bb) - 12 saved block(s)
2022-10-22 19:29:42.224+00:00 | nioEventLoopGroup-3-1 | INFO  | BlockPropagationManager | Retrieving parent 0x8b55187027adb584f4304481a5c4891cfa3342aaa0af3c85edbc81ff91773bf7 of block 2 (0xfa032005a838a9da0518dd637d8e12d1767cf556ebec127c9a87ecb6a82c03bb)
2022-10-22 19:29:42.281+00:00 | EthScheduler-Workers-0 | INFO  | PersistBlockTask | Imported #1 / 0 tx / 0 om / 0 (0.0%) gas / (0x8b55187027adb584f4304481a5c4891cfa3342aaa0af3c85edbc81ff91773bf7) in 0.022s. Peers: 1
..... SNIP .....
2022-10-22 19:31:02.011+00:00 | EthScheduler-Workers-0 | INFO  | PersistBlockTask | Imported #21 / 0 tx / 0 om / 0 (0.0%) gas / (0x76ec46e8f9266633fda6dc32176f2c6b22096603473d65f345667be2339f7288) in 0.002s. Peers: 3
^C2022-10-22 19:31:11.115+00:00 | BesuCommand-Shutdown-Hook | INFO  | DefaultSynchronizer | Stopping synchronizer
2022-10-22 19:31:11.118+00:00 | BesuCommand-Shutdown-Hook | INFO  | NetworkRunner | Stopping Network.
2022-10-22 19:31:11.130+00:00 | BesuCommand-Shutdown-Hook | INFO  | EthProtocolManager | Stopping eth Subprotocol.
2022-10-22 19:31:11.227+00:00 | BesuCommand-Shutdown-Hook | INFO  | EthProtocolManager | eth Subprotocol stopped.
2022-10-22 19:31:11.227+00:00 | BesuCommand-Shutdown-Hook | INFO  | NetworkRunner | Network stopped.
2022-10-22 19:31:11.228+00:00 | BesuCommand-Shutdown-Hook | INFO  | AutoTransactionLogBloomCachingService | Shutting down Auto transaction logs caching service.
2022-10-22 19:31:11.229+00:00 | BesuCommand-Shutdown-Hook | INFO  | DockerNatManager | Stopping docker NAT manager.

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

$ docker run --rm --name dealer --network host -e BESU_OPTS="--add-opens java.base/jdk.internal.misc=ALL-UNNAMED -Dio.netty.tryReflectionSetAccessible=true" -u $(id -u $USER):$(id -g $USER) -v $BESU_HOME/data/dealer:/opt/besu/data -v $BESU_HOME/data/dealer:/opt/besu/keys -v $BESU_HOME/conf/dealer-config.toml:/config/config.toml -v $BESU_HOME/conf/genesis.json:/config/genesis.json hyperledger/besu:22.7.7 --config-file=/config/config.toml

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

$ docker run --rm --name dmv --network host -e BESU_OPTS="--add-opens java.base/jdk.internal.misc=ALL-UNNAMED -Dio.netty.tryReflectionSetAccessible=true" -u $(id -u $USER):$(id -g $USER) -v $BESU_HOME/data/dmv:/opt/besu/data -v $BESU_HOME/data/dmv:/opt/besu/keys -v $BESU_HOME/conf/dmv-config.toml:/config/config.toml -v $BESU_HOME/conf/genesis.json:/config/genesis.json hyperledger/besu:22.7.7 --config-file=/config/config.toml

To make API requests on our private blockchain network, we will issue the following commands at the Python interpreter prompt:

>>> import web3

>>> from web3 import Web3

>>> from web3.middleware import geth_poa_middleware

>>> provider = Web3.HTTPProvider('http://127.0.0.1:8545')

>>> w3 = Web3(provider)

>>> w3.middleware_onion.inject(geth_poa_middleware, layer=0)

To check if we have successfully connected to the bank node, execute the following command at the Python interpreter prompt:

>>> w3.isConnected()

The following would be the typical output:

Output.11

True

To get information about the bank node, execute the following command at the Python interpreter prompt:

>>> w3.geth.admin.nodeInfo()

The following would be the typical output:

Output.12

AttributeDict({'enode': 'enode://8be283f1550a85b8ed46fb80527acce06b55985377b761bb7701b1b0241343937fc46bc08dd96a26ef20dcc37c6df8c79edf7a2a0e86828cc6ce1e07e5f5a963@127.0.0.1:30303', 'listenAddr': '127.0.0.1:30303', 'ip': '127.0.0.1', 'name': 'besu/bank/v22.7.7/linux-x86_64/openjdk-java-11', 'id': '8be283f1550a85b8ed46fb80527acce06b55985377b761bb7701b1b0241343937fc46bc08dd96a26ef20dcc37c6df8c79edf7a2a0e86828cc6ce1e07e5f5a963', 'ports': AttributeDict({'discovery': 30303, 'listener': 30303}), 'protocols': AttributeDict({'eth': AttributeDict({'config': AttributeDict({'chainId': 21, 'berlinBlock': 0, 'clique': AttributeDict({'epochLength': 30000, 'blockPeriodSeconds': 10})}), 'difficulty': 11, 'genesis': '0x8a6b044d304cf5fba6bd7855ca8c38226a55e9bf39ab8182244cc613d225846e', 'head': '0xf1b4be8b6116aa6623292961ae15a3ac6abbfbbb3790a85cf47cf4e1680c333a', 'network': 21})})})

The trimmed string value enode://8be283f...@127.0.0.1:30001 is the endpoint address for the bank node.

To verify the peers connected to the bank node, execute the following command at the Python interpreter prompt:

>>> w3.geth.admin.peers()

The following would be the typical output:

Output.13

[AttributeDict({'version': '0x5', 'name': 'besu/dealer/v22.7.7/linux-x86_64/openjdk-java-11', 'caps': ['eth/62', 'eth/63', 'eth/64', 'eth/65', 'eth/66', 'snap/1'], 'network': AttributeDict({'localAddress': '127.0.0.1:30303', 'remoteAddress': '127.0.0.1:33896'}), 'port': '0x7661', 'id': '0x2a9e0e41e30a838075511189d211e6c992f774bde95277c786075693de5a5fda8b1d6cc2e8aabf4686a59f79208de647d50b15ff9fea9feedd689b9a23846759', 'protocols': AttributeDict({'eth': AttributeDict({'difficulty': '0x9', 'head': '0x9dfa85e80c4717be05c2703f98c7a88cb3c1090dd43a9cdf7324801513d62f3d', 'version': 66})}), 'enode': 'enode://2a9e0e41e30a838075511189d211e6c992f774bde95277c786075693de5a5fda8b1d6cc2e8aabf4686a59f79208de647d50b15ff9fea9feedd689b9a23846759@127.0.0.1:30305?discport=0'}), AttributeDict({'version': '0x5', 'name': 'besu/buyer/v22.7.7/linux-x86_64/openjdk-java-11', 'caps': ['eth/62', 'eth/63', 'eth/64', 'eth/65', 'eth/66', 'snap/1'], 'network': AttributeDict({'localAddress': '127.0.0.1:30303', 'remoteAddress': '127.0.0.1:33888'}), 'port': '0x7660', 'id': '0x0854eb4540a40bce1853040037df18eea7f5234f9c958664bbafb412159be681c44b0da24fc36805fc9d3da9741eae8a6226ab89e8af458f67d1d5454a3e049d', 'protocols': AttributeDict({'eth': AttributeDict({'difficulty': '0x9', 'head': '0x9dfa85e80c4717be05c2703f98c7a88cb3c1090dd43a9cdf7324801513d62f3d', 'version': 66})}), 'enode': 'enode://0854eb4540a40bce1853040037df18eea7f5234f9c958664bbafb412159be681c44b0da24fc36805fc9d3da9741eae8a6226ab89e8af458f67d1d5454a3e049d@127.0.0.1:30304?discport=0'}), AttributeDict({'version': '0x5', 'name': 'besu/dmv/v22.7.7/linux-x86_64/openjdk-java-11', 'caps': ['eth/62', 'eth/63', 'eth/64', 'eth/65', 'eth/66', 'snap/1'], 'network': AttributeDict({'localAddress': '127.0.0.1:30303', 'remoteAddress': '127.0.0.1:59406'}), 'port': '0x7662', 'id': '0x8642e38feb3bb6080bbd868f40f2c2751c0df2704de178f22cad1af31bbd53b5841ccd800c139ea0344784e7c757f3d31f09a657d65ad3e34f83c12f31cc66ce', 'protocols': AttributeDict({'eth': AttributeDict({'difficulty': '0x9', 'head': '0x9dfa85e80c4717be05c2703f98c7a88cb3c1090dd43a9cdf7324801513d62f3d', 'version': 66})}), 'enode': 'enode://8642e38feb3bb6080bbd868f40f2c2751c0df2704de178f22cad1af31bbd53b5841ccd800c139ea0344784e7c757f3d31f09a657d65ad3e34f83c12f31cc66ce@127.0.0.1:30306?discport=0'})]

To retrieve the details of a block by the block number 0 (zero), execute the following command at the Python interpreter prompt:

>>> w3.eth.getBlock(0)

The following would be the typical output:

Output.14

AttributeDict({"number": 0, "hash": "0x8a6b044d304cf5fba6bd7855ca8c38226a55e9bf39ab8182244cc613d225846e", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "nonce": "0x0000000000000000", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "stateRoot": "0xb3962f7878e5846c8cf7630d651b2b904d23b8ecbb5e2a49a5021d32b860a404", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "miner": "0x0000000000000000000000000000000000000000", "difficulty": 1, "totalDifficulty": 1, "proofOfAuthorityData": "0x0000000000000000000000000000000000000000000000000000000000000000f1da86e0f5288f0537865e7b51edd302089ae06f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "size": 626, "gasLimit": 10485760, "gasUsed": 0, "timestamp": 1548854791, "uncles": [], "transactions": []})

To display account balance for the buyer account, execute the following command at the Python interpreter prompt:

>>> w3.eth.getBalance(Web3.toChecksumAddress('0x760ee653f93dca288fe6f9a39b2ed54ed4be0226'))

The following would be the typical output:

Output.15

10000000000000000000

*** WARNING ***

If we do not use Web3.toChecksumAddress on the address to get the account balance, we will encounter an error

Similarly, to display account balance for the dealer account, execute the following command at the Python interpreter prompt:

>>> w3.eth.getBalance(Web3.toChecksumAddress('0x658ba9305cb0de275e55ccfff396446bf7add077'))

The following would be the typical output:

Output.16

5000000000000000000

We will now send a transaction to transfer 1 ether from the buyer to the dealer. For this we will need to create a send transaction message. Enter the following data at the Python interpreter prompt to create the message structure called buyer_dealer_txn:

>>> buyer_dealer_txn = {

... 'from': Web3.toChecksumAddress('0x760ee653f93dca288fe6f9a39b2ed54ed4be0226'),

... 'to': Web3.toChecksumAddress('0x658ba9305cb0de275e55ccfff396446bf7add077'),

... 'value': w3.toWei(1, 'ether'),

... 'gas': 90000,

... 'gasPrice': 18000000000,

... 'nonce': 0,

... 'chainId': 21

...}

Before sending the transaction message buyer_dealer_txn, it needs to be digitally signed using the private key of the buyer. To do that, execute the following commands at the Python interpreter prompt:

>>> buyer_priv_key = '0xabe351ed564b301bfbfc6e0e69a23720fc3bc40405f164188c88dbed548f60b1'

>>> signed_txn = w3.eth.account.sign_transaction(buyer_dealer_txn, buyer_priv_key)

To send the signed transaction to transfer 1 ether from the buyer to the dealer, execute the following command at the Python interpreter prompt:

>>> w3.eth.sendRawTransaction(signed_txn.rawTransaction)

The following would be the typical output:

Output.17

HexBytes('0x0be38c14e1e23665970c115517d1c318ed3ab5b0395992ada7d656d1e5485544')

The w3.eth.sendRawTransaction method will return a transaction hash as a hex string (similar to a transaction id).

To display all the details of a transaction on this private network, execute the following command at the Python interpreter prompt:

>>> w3.eth.getTransaction('0x0be38c14e1e23665970c115517d1c318ed3ab5b0395992ada7d656d1e5485544')

The following would be the typical output:

Output.18

AttributeDict({"blockHash": "0x70475aeb066ea503c90eb78958ee68db164dc6ca21900359157fec8ccb376116", "blockNumber": 12, "chainId": "0x15", "from": "0x760eE653f93DcA288Fe6f9a39b2ed54Ed4bE0226", "gas": 90000, "gasPrice": 18000000000, "hash": "0x0be38c14e1e23665970c115517d1c318ed3ab5b0395992ada7d656d1e5485544", "input": "0x", "nonce": 0, "publicKey": "0x0854eb4540a40bce1853040037df18eea7f5234f9c958664bbafb412159be681c44b0da24fc36805fc9d3da9741eae8a6226ab89e8af458f67d1d5454a3e049d", "raw": "0xf86d80850430e2340083015f9094658ba9305cb0de275e55ccfff396446bf7add077880de0b6b3a7640000804da0a75c0c26ca2bca4a41cd1b953adbd9e01db831c8a1a2a22266ae7704b47f25bba0295a04aeba95b5c0abb3fea62f5958b6c41588e6c86965ac0055f054b2505fc8", "to": "0x658ba9305Cb0dE275E55CcfFf396446bF7Add077", "transactionIndex": 0, "type": "0x0", "value": 1000000000000000000, "v": 77, "r": "0xa75c0c26ca2bca4a41cd1b953adbd9e01db831c8a1a2a22266ae7704b47f25bb", "s": "0x295a04aeba95b5c0abb3fea62f5958b6c41588e6c86965ac0055f054b2505fc8"})

From the Output.18 above, we see that transaction is processed in block number 12.

To display account balance for the buyer account, execute the following command at the Python interpreter prompt:

>>> w3.fromWei(w3.eth.getBalance(Web3.toChecksumAddress('0x760ee653f93dca288fe6f9a39b2ed54ed4be0226')), 'ether')

The following would be the typical output:

Output.19

8.999622

Similarly, to display account balance for the dealer account, execute the following command at the Python interpreter prompt:

>>> w3.fromWei(w3.eth.getBalance(Web3.toChecksumAddress('0x658ba9305cb0de275e55ccfff396446bf7add077')), 'ether')

The following would be the typical output:

Output.20

6

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

YAAAHOOO !!! We have successfully demonstrated a 4-node Hyperledger Besu based private Ethereum network using Docker.

The following is the link to the Github Repo that provides the config and wallet account files (with the directory structure) used in this article for a quickstart:


References

Hyperledger Besu Enterprise Ethereum Client

Hyperledger Besu Command Line Options

Web3.py Documentation

Introduction to Ethereum - Part 1

Introduction to Docker



© PolarSPARC