arrow_back

Block.one: Creating a Multi Node EOSIO Blockchain

Sign in Join
Test and share your knowledge with our community!
done
Get access to over 700 hands-on labs, skill badges, and courses

Block.one: Creating a Multi Node EOSIO Blockchain

Lab 1 hour universal_currency_alt 1 Credit show_chart Introductory
Test and share your knowledge with our community!
done
Get access to over 700 hands-on labs, skill badges, and courses

This lab was developed with our partner, Block.one. Your personal information may be shared with Block.one, the lab sponsor, if you have opted-in to receive product updates, announcements, and offers in your Account Profile.

GSP991

Google Cloud self-paced labs logo

Overview

In this lab, you will extend the single node EOSIO blockchain to use multiple nodes. You will install various EOSIO software components to set up your multi node EOSIO blockchain. Finally, you will perform some node operator tasks on your new EOSIO blockchain.

Objectives

In this lab, you will learn how to perform the following tasks:

  • Load an existing Compute Engine virtual machine using the Google Cloud Platform (GCP) Console.
  • Install and build the EOSIO system contracts.
  • Deploy a multi node blockchain with four (4) EOSIO nodes.
  • Create user accounts on the EOSIO multi node blockchain.
  • Call an action to transfer tokens among user accounts.

Prerequisites

Before you start this lab, it is suggested that you complete the two previous Block.one labs: Block.one: Getting Started with the EOSIO Blockchain and Block.one: Getting Started with Smart Contracts. It is also recommended to be familiar with the following concepts:

Setup and Requirements

Before you click the Start Lab button

Read these instructions. Labs are timed and you cannot pause them. The timer, which starts when you click Start Lab, shows how long Google Cloud resources will be made available to you.

This hands-on lab lets you do the lab activities yourself in a real cloud environment, not in a simulation or demo environment. It does so by giving you new, temporary credentials that you use to sign in and access Google Cloud for the duration of the lab.

To complete this lab, you need:

  • Access to a standard internet browser (Chrome browser recommended).
Note: Use an Incognito or private browser window to run this lab. This prevents any conflicts between your personal account and the Student account, which may cause extra charges incurred to your personal account.
  • Time to complete the lab---remember, once you start, you cannot pause a lab.
Note: If you already have your own personal Google Cloud account or project, do not use it for this lab to avoid extra charges to your account.

How to start your lab and sign in to the Google Cloud console

  1. Click the Start Lab button. If you need to pay for the lab, a pop-up opens for you to select your payment method. On the left is the Lab Details panel with the following:

    • The Open Google Cloud console button
    • Time remaining
    • The temporary credentials that you must use for this lab
    • Other information, if needed, to step through this lab
  2. Click Open Google Cloud console (or right-click and select Open Link in Incognito Window if you are running the Chrome browser).

    The lab spins up resources, and then opens another tab that shows the Sign in page.

    Tip: Arrange the tabs in separate windows, side-by-side.

    Note: If you see the Choose an account dialog, click Use Another Account.
  3. If necessary, copy the Username below and paste it into the Sign in dialog.

    {{{user_0.username | "Username"}}}

    You can also find the Username in the Lab Details panel.

  4. Click Next.

  5. Copy the Password below and paste it into the Welcome dialog.

    {{{user_0.password | "Password"}}}

    You can also find the Password in the Lab Details panel.

  6. Click Next.

    Important: You must use the credentials the lab provides you. Do not use your Google Cloud account credentials. Note: Using your own Google Cloud account for this lab may incur extra charges.
  7. Click through the subsequent pages:

    • Accept the terms and conditions.
    • Do not add recovery options or two-factor authentication (because this is a temporary account).
    • Do not sign up for free trials.

After a few moments, the Google Cloud console opens in this tab.

Note: To view a menu with a list of Google Cloud products and services, click the Navigation menu at the top-left. Navigation menu icon

Background

This section provides an overview of the concepts covered in this lab.

EOSIO blockchain

An EOSIO blockchain is a highly efficient, deterministic, distributed state machine that can operate in a decentralized fashion. The blockchain keeps track of transactions within a sequence of interchanged blocks. Each block cryptographically commits to the previous blocks along the same chain. It is therefore intractable to modify a transaction recorded on a given block without breaking the cryptographic checks of successive blocks. This simple fact makes blockchain transactions immutable and secure. Block production and block validation are performed by special nodes called Block Producers.

EOSIO consensus

Block validation presents a challenge among any group of distributed nodes. A consensus model must be in place to validate such blocks in a fault tolerant way within the decentralized system. Consensus is the way for such distributed nodes and users to agree upon the current state of the blockchain. Two of the most common consensus models used in blockchains are Proof of Work (PoW) and Proof of Stake (PoS). In Proof of Stake, nodes that own the largest stake or percentage of some asset have equivalent decision power. One interesting variant is Delegated Proof-of-Stake (DPoS) in which a large number of participants or stakeholders elect a smaller number of delegates, which in turn make decisions for them. EOSIO uses Delegated Proof of Stake (DPoS) to elect the active producers who will then be authorized to produce blocks, validate them, and sign them to eventually be added to the blockchain.

EOSIO accounts, keys, and permissions

An account identifies a participant in an EOSIO blockchain. A participant can be an individual or a group depending on the assigned permissions within the account. Accounts also represent the smart contract actors that push and receive actions to and from other accounts in the blockchain. Keys in EOSIO are binary strings represented in Base58 used for signing and verification of transactions, blocks, and other messages. Keys are created within a digital wallet associated with an account. Since account ownership is defined solely by the account name, the keys associated with an account can be updated without compromising security. A novel permission scheme involving accounts, permissions, and authority tables determine what accounts can do and how the actions that make a transaction are authorized. To that end, each account is assigned a hierarchical permission structure and each permission is assigned a pair of public and private keys used for signing and verification.

EOSIO smart contracts

A smart contract is a low-level software library that contains the implementation of the actions that make a transaction. It also defines how the data that is accessed and processed by the actions are stored. In EOSIO, a smart contract is implemented in a high-level language such as C++ and compiled into a WebAssembly (WASM) binary. Thereafter, smart contracts can be deployed and run within an account's sandbox on the blockchain.

Task 1. Create a virtual machine

  1. In the Navigation menu (Navigation menu icon), click Compute Engine > VM instances.

  2. Click the Create Instance button.

  3. On the Create an Instance page, for Name, use the default instance-1.

  4. For Region and Zone, select the default region and zone assigned by Qwiklabs.

  5. For Machine Configuration, select:

    • Machine Family - General Purpose (default)
    • Series - E2
    • Machine Type - e2-standard-2 (2 vCPU, 8 GB memory)
  6. For Boot disk, if the Image shown is not Ubuntu 20.04 LTS, click Change and select Ubuntu as Operating System and Ubuntu 20.04 LTS as version.

  7. Leave the defaults for Identity and API access unmodified.

  8. Leave all other defaults unmodified.

  9. To create and launch the VM, click Create.

Note: The VM can take about two minutes to launch and be fully available for use.

Click Check my progress to verify the objective. Create a virtual machine

Task 2. Install the EOSIO platform

  1. In the Navigation menu (Navigation menu icon), click Compute Engine > VM instances.

You will see the VM instance you created.

  1. To open a command prompt on the VM instance-1 instance, click SSH in its row in the VM instances list. This will open a shell terminal window.

  2. First, make sure to update the list of software packages in your VM box:

sudo apt update
  1. Get the EOSIO software binaries. This includes nodeos the node p2p software, cleos the CLI client, keosd the wallet manager, and other tools:
curl -LO https://github.com/eosio/eos/releases/download/v2.1.0/eosio_2.1.0-1-ubuntu-20.04_amd64.deb
  1. Install the EOSIO software binaries. Press Enter if needed to install any required software dependencies:
sudo apt install ./eosio_2.1.0-1-ubuntu-20.04_amd64.deb
  1. Confirm nodeos is installed:
nodeos --version

The response will be the nodeos version:

v2.1.0
  1. Confirm cleos is installed:
cleos version client

The response will be the cleos version:

v2.1.0
  1. Confirm keosd is installed:
keosd -v

The response will be the keosd version:

v2.1.0

Click Check my progress to verify the objective. Install the EOSIO platform

Task 3. Create a default wallet

Wallets store the cryptographic keys used by accounts and producers to sign transactions and blocks, respectively. They also provide a secure enclave to perform the signing, so the private keys do not ever leave the wallet. To change state on the blockchain you need a wallet to store cryptographic keys and perform the digital signing.

  1. First, set a large unlock timeout for any wallet managed by keosd, the EOSIO wallet manager:
keosd --unlock-timeout 999999999 &

This will prevent unlocking the wallet every 15 minutes, the default timeout. It will also launch the keosd daemon service. The response should be similar to:

info thread-0 wallet_plugin.cpp:38 plugin_initialize ] initializing wallet plugin info thread-0 wallet_api_plugin.cpp:84 plugin_startup ] starting wallet_api_plugin info thread-0 http_plugin.cpp:983 add_handler ] add api url: /v1/wallet/create ... info thread-0 http_plugin.cpp:983 add_handler ] add api url: /v1/wallet/unlock info thread-0 http_plugin.cpp:983 add_handler ] add api url: /v1/node/get_supported_apis

Press Enter if needed to get the shell prompt back.

  1. Create a default wallet and output the wallet password to a file named my_wallet_password:
cleos wallet create --file my_wallet_password

This will create a wallet named default. The response should be:

Creating wallet: default Save password to use in the future to unlock this wallet. Without password imported keys will not be retrievable. saving password to my_wallet_password

After the wallet is created, keosd opens it and unlocks it by default.

  1. To view all the managed wallets, execute this command:
cleos wallet list

The response should be:

Wallets: [ "default *" ]

The asterisk next to the wallet name indicates that the wallet is unlocked.

Click Check my progress to verify the objective. Create a default wallet

Task 4. Add the private key of the EOSIO system account to the wallet

Every new EOSIO blockchain has a default system account called eosio. This account is used initially to set up the blockchain. It is associated to the following private key by default: 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3.

Import this private key into your wallet to sign transactions on behalf of the eosio account.

  1. Use the cleos wallet open command to open the default wallet. Note that the wallet is already opened and unlocked. However, you would need to execute the following procedure if the wallet gets locked again:
cleos wallet open

The response should be:

Opened: default
  1. Unlock the default wallet by using the cleos wallet unlock command and passing the wallet password stored in the my_wallet_password file:
cleos wallet unlock <my_wallet_password

The response should be:

password: Unlocked: default
  1. Use the cleos wallet import command to import the eosio private key, 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3 to the default wallet:
cleos wallet import --private-key 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3

The response should be:

imported private key for: EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV

This is the corresponding public key for the imported private key. Ignore any warnings regarding the use of a default wallet. In production deployments, you might want to name your wallet other than using the default one.

Click Check my progress to verify the objective. Add the private key of the eosio system account to the wallet

Task 5. Install the EOSIO Contract Development Toolkit (CDT)

The EOSIO CDT toolkit includes the CDT C++ compiler, which you can use to build the system contracts and, later on, your own C++ smart contracts. The system contracts are very important since they enable the second layer software functionality that allows additional features to be added and supported.

  1. First, install the build-essential package. This package contains various tools necessary for building C/C++ software on your Linux box.
sudo apt install build-essential
  1. Get the EOSIO CDT binaries:
curl -LO https://github.com/eosio/eosio.cdt/releases/download/v1.8.1/eosio.cdt_1.8.1-1-ubuntu-18.04_amd64.deb
  1. Install the EOSIO CDT binaries:
sudo apt install ./eosio.cdt_1.8.1-1-ubuntu-18.04_amd64.deb
  1. Confirm the EOSIO CDT compiler is installed:
eosio-cpp --version

The response should be the EOSIO CDT compiler version:

eosio-cpp version 1.8.1

Click Check my progress to verify the objective. Install the EOSIO Contract Development Toolkit

Task 6. Build the system contracts and eosio.boot

To have a functional multi node EOSIO blockchain, you need to deploy the EOSIO system contracts. Among other things, the system contracts provide the layer-2 functionality required for the EOSIO consensus, the ability to stake tokens, vote, create your own tokens, etc. They also allow new features to be supported.

  1. Install CMake, a software dependency used by the eosio.contracts build script:
sudo apt install cmake
  1. Download the latest system eosio.contracts:
git clone https://github.com/EOSIO/eosio.contracts.git \ --branch release/1.9.x --single-branch
  1. Build the system eosio.contracts. In this step, you also need to pass the installation directories of eosio and eosio.cdt.
cd eosio.contracts ./build.sh -e /usr/opt/eosio/2.1.0 -c /usr/opt/eosio.cdt/1.8.1 cd ~

Ignore the warnings about missing ricardian contracts, if any. The response should be something similar to (without the warnings):

Using EOSIO.CDT installation at: /usr/opt/eosio.cdt/1.8.1 =========== Building eosio.contracts =========== ... -- Building eosio.contracts v1.9.2 ... -- Unit tests will not be built. To build tests, set BUILD_TESTS to true. -- Configuring done -- Generating done -- Build files have been written to: ./eosio.contracts/build [ 11%] Performing build step for 'contracts_project' [ 18%] Built target eosio.bios [ 18%] Built target eosio.msig [ 36%] Built target powup.results [ 36%] Built target rex.results [ 54%] Built target eosio.token [ 90%] Built target eosio.system [100%] Built target eosio.wrap [ 22%] No install step for 'contracts_project' [ 33%] No test step for 'contracts_project' [ 44%] Completed 'contracts_project' [100%] Built target contracts_project`

Make note of the location where the EOSIO system contracts are built. You will need it later when deploying specific system contracts: ~/eosio.contracts/build/contracts.

  1. Build the eosio.boot system contract:
git clone https://github.com/EOSIO/eos.git \ --branch release/2.1.x --single-branch cd eos/contracts/contracts/eosio.boot cmake . make cd ~

Ignore warnings. The response should be something similar to:

-- Configuring done -- Generating done -- Build files have been written to: ~/eos/contracts/contracts/eosio.boot
  1. Make a note of the location where the eosio.boot contract is built. You will need to deploy it later: ~/eos/contracts/contracts/eosio.boot.

Click Check my progress to verify the objective. Build the system contracts and eosio.boot

Task 7. Start the boot node and create system accounts

In this task, you will launch the boot genesis node and create the system accounts. Unlike wallet creation, you need to send a transaction to the blockchain to create an account.

Therefore, at least the boot node must be running. To that end, first you need to create the data directory and the genesis file to be used by all nodes. In a production environment, every node will be hosted on its own server and use its own resources. Here, you will deploy all nodes in the same box.

  1. Create new directory nodes. This is where the blockchain database, the log file, and the configuration file for each node will be stored.
mkdir nodes
  1. Create the default genesis.json file in the nodes directory:
cat >nodes/genesis.json
  1. Copy/paste the following JSON contents to the terminal, then press Ctrl-D to save the genesis.json file:
{ "initial_timestamp": "2018-06-01T12:00:00.000", "initial_key": "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV", "initial_configuration": { "max_block_net_usage": 1048576, "target_block_net_usage_pct": 1000, "max_transaction_net_usage": 524288, "base_per_transaction_net_usage": 12, "net_usage_leeway": 500, "context_free_discount_net_usage_num": 20, "context_free_discount_net_usage_den": 100, "max_block_cpu_usage": 200000, "target_block_cpu_usage_pct": 1000, "max_transaction_cpu_usage": 150000, "min_transaction_cpu_usage": 100, "max_transaction_lifetime": 3600, "deferred_trx_expiration_window": 600, "max_transaction_delay": 3888000, "max_inline_action_size": 524288, "max_inline_action_depth": 4, "max_authority_depth": 6 }, "initial_chain_id": "0000000000000000000000000000000000000000000000000000000000000000" }

The "initial_key" indicates the initial development public key used by the eosio account. Here you can override it with your own development key, if needed, which is recommended for production deployments. Such a key must exist in the wallet used by the eosio account. You imported that key in a previous section.

  1. Check that the genesis.json file was indeed created and populated:
cat nodes/genesis.json

You should see the same contents as in the previous step.

  1. Create the boot node directory nodes/00-boot to store all blockchain data related to the boot node:
mkdir nodes/00-boot
  1. Launch the boot node, starting nodeos service daemon as a background task, and redirecting stdout/stderr to the 00-boot/00.log file:
nodeos \ --plugin eosio::http_plugin --plugin eosio::chain_api_plugin --plugin eosio::chain_plugin --plugin eosio::producer_api_plugin --plugin eosio::producer_plugin --plugin eosio::history_plugin --plugin eosio::history_api_plugin --plugin eosio::net_api_plugin \ --contracts-console \ --max-transaction-time=200 \ --chain-state-db-size-mb 1024 \ --enable-stale-production \ --producer-name eosio \ --genesis-json ~/nodes/genesis.json \ --blocks-dir ~/nodes/00-boot/blocks \ --config-dir ~/nodes/00-boot \ --data-dir ~/nodes/00-boot \ --http-server-address 127.0.0.1:8888 \ --p2p-listen-endpoint 127.0.0.1:9876 \ --signature-provider EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV=KEY:5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3 \ >>~/nodes/00-boot/00.log 2>&1 &

This will launch the boot node with some plugins, producer name eosio, listening on http port 8888, p2p port 9876, and using the default development keys EOS6MRy..., 5KQwrP... for block signing. If not overridden with --signature-provider, the default eosio development keys are used.

  1. Verify that the boot node is running and producing blocks:
tail -f nodes/00-boot/00.log

The output will look as follows.

Output:

... info 2021-03-08T02:44:11.900 nodeos producer_plugin.cpp:2333 produce_block ] Produced block e0dc6324ce3c8f35... #588 @ 2021-03-08T02:44:12.000 signed by eosio [trxs: 0, lib: 587, confirmed: 0]

The last line reveals the last block number produced and the current last irreversible block (lib) which marks the final block and the end of the blockchain.

  1. Press Ctrl+C and then press Enter to exit the tail program.

  2. Now that the boot node is running, you can get information about the eosio account stored in the chain database:

cleos get account eosio

The response should be:

created: 2018-06-01T12:00:00.000 privileged: true permissions: owner 1: 1 EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV active 1: 1 EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV memory: quota: unlimited used: 2.66 KiB net bandwidth: used: unlimited available: unlimited limit: unlimited cpu bandwidth: used: unlimited available: unlimited limit: unlimited

Note that the eosio account is indeed a privileged account, and the keys associated with both owner and active permissions are the same default development keys that were imported to the wallet. This will allow the eosio account to authorize transactions as the multi node blockchain is being set up.

  1. Create additional system accounts. These accounts will also be stored in the blockchain database.
cleos create account eosio eosio.bpay EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV cleos create account eosio eosio.msig EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV cleos create account eosio eosio.names EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV cleos create account eosio eosio.ram EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV cleos create account eosio eosio.ramfee EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV cleos create account eosio eosio.saving EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV cleos create account eosio eosio.stake EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV cleos create account eosio eosio.token EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV cleos create account eosio eosio.vpay EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV cleos create account eosio eosio.rex EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV

You assign the same development key pair to all system accounts and to both the owner and active permissions. In production deployments, you would want to assign a different key pair to each permission for added security. You might also use different keys for each system account.

The response for the above commands should be something similar to:

executed transaction: 98f22027c6703e554042b1d4a93beb70b1dc30be0fe65267932155c9d70e6493 200 bytes 153 us # eosio &lt;= eosio::newaccount {"creator":"eosio","name":"eosio.bpay","owner":{"threshold":1,"keys":[{"key":"EOS6MRyAjQq8ud7hVNYcfn... warning: transaction executed locally, but may not be confirmed by the network yet ] ... executed transaction: 2f0cb00110374976f3cd9195bccac81fe0ee330c515b4a656d6d41f6181f636d 200 bytes 189 us # eosio &lt;= eosio::newaccount {"creator":"eosio","name":"eosio.rex","owner":{"threshold":1,"keys":[{"key":"EOS6MRyAjQq8ud7hVNYcfnV... warning: transaction executed locally, but may not be confirmed by the network yet ]

Ignore the warnings. You will use these accounts in the next tasks.

Click Check my progress to verify the objective. Build the system contracts and eosio.boot

Task 8. Deploy the eosio.token, eosio.msig contracts, and create/issue the SYS token

In this task, you will create and issue a fictional SYS token. To that end, you will also need to deploy the eosio.token and eosio.msig system contracts that were built previously.

  1. Deploy the eosio.token contract. This contract allows you to create, issue, and transfer new or existing tokens.
cleos set contract eosio.token ~/eosio.contracts/build/contracts/eosio.token/

The response should be something similar to:

Reading WASM from ~/eosio.contracts/build/contracts/eosio.token/eosio.token.wasm... Publishing contract... executed transaction: 11aca7fb68e0821cf8f74be0188741964787f830d0918abdfccae4a54c25e291 19360 bytes 4445 us # eosio &lt;= eosio::setcode {"account":"eosio.token","vmtype":0,"vmversion":0,"code":"0061736d010000000180022860000060037f7f7f01... # eosio &lt;= eosio::setabi {"account":"eosio.token","abi":"0e656f73696f3a3a6162692f312e320008076163636f756e7400010762616c616e63...
  1. Deploy the eosio.msig contract. This contract enables the multi signature process among accounts and simplifies the management of permissions.
cleos set contract eosio.msig ~/eosio.contracts/build/contracts/eosio.msig/

The response will be something similar to:

Reading WASM from ~/eosio.contracts/build/contracts/eosio.msig/eosio.msig.wasm... Publishing contract... executed transaction: c2260b7d05bb38d2df097799baedd890ce7560bedbd1b79e7d07e4f6a5b25238 23256 bytes 3684 us # eosio &lt;= eosio::setcode {"account":"eosio.msig","vmtype":0,"vmversion":0,"code":"0061736d0100000001b8022f60000060037f7f7f017... # eosio &lt;= eosio::setabi {"account":"eosio.msig","abi":"0e656f73696f3a3a6162692f312e32001006616374696f6e0004076163636f756e740...
  1. Create the SYS token with a total distribution of 10 billion tokens:
cleos push action eosio.token create '["eosio", "10000000000.0000 SYS"]' -p eosio.token

The above command pushes a transaction with the create action from the eosio.token contract, authorized by the eosio.token account's active permission. The response should be something similar to:

executed transaction: 6a4766a00c8fe26343d5eb6c1ad5e53b35064dc6f3a5f55908eff2edba30efca 120 bytes 603 us # eosio.token &lt;= eosio.token::create {"issuer":"eosio","maximum_supply":"10000000000.0000 SYS"}
  1. Issue 1 billion SYS tokens from reserve into circulation:
cleos push action eosio.token issue '["eosio", "1000000000.0000 SYS", "memo"]' -p eosio

The above command pushes a transaction with the issue action from the eosio.token contract, authorized by the eosio account's active permission.

The response will be something similar to:

executed transaction: 4613fcff26bc6f96010273b475e839018f0afe2ef2af840d481070c9c54e36f1 128 bytes 405 us # eosio.token &lt;= eosio.token::issue {"to":"eosio","quantity":"1000000000.0000 SYS","memo":"memo"}

Click Check my progress to verify the objective. Create and deploy the eosio.token, eosio.msig contracts, and SYS token

Task 9. Activate protocol features and deploy eosio.boot and eosio.system contracts

Protocol features bring additional optional capabilities to EOSIO blockchains. In this task, you will activate some protocol features that are highly recommended for EOSIO blockchains. To that end, you will first deploy the eosio.boot contract, then activate these features. Finally, you will deploy the eosio.system contract to complete the second layer of the EOSIO blockchain.

This enables system resources (CPU, RAM, NET), enables tokens to be staked and unstaked, resources to be purchased, potential producers to be registered and subsequently voted on, producer rewards to be claimed, privileges and limits to be set, etc. Protocol features must be activated through EOSIO consensus by a supermajority of producers. In this case, the running boot node is all you need.

  1. First, install the jq tool to parse JSON strings in a more readable form:
sudo apt install jq
  1. Now let's check which protocol features are supported:
curl -X POST http://127.0.0.1:8888/v1/producer/get_supported_protocol_features -d '{"exclude_disabled": false, "exclude_unactivatable": false}' | jq | more

The above command sends an HTTP POST request to the get_supported_protocol_features endpoint provided by the Producer API plugin.

The response will display all protocol features supported by the current EOSIO network (which consists of the boot node so far).

Check the output (press the spacebar multiple times) until you get the shell prompt back.

Output:

[{"feature_digest": "0ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd", "subjective_restrictions": {"enabled": true, "preactivation_required": false, "earliest_allowed_activation_time": "1970-01-01T00:00:00.000"}, "description_digest": "64fe7df32e9b86be2b296b3f81dfd527f84e82b98e363bc97e40bc7a83733310", "dependencies": [], "protocol_feature_type": "builtin", "specification": [{"name": "builtin_feature_codename", "value": "PREACTIVATE_FEATURE"}]}, ... {"feature_digest": "ded2e25adcd78cbb94fa7f63a8f80a9af2b1a905e551a6e124e7d7829da1ea02", "subjective_restrictions": {"enabled": true, "preactivation_required": true, "earliest_allowed_activation_time": "1970-01-01T00:00:00.000"}, "description_digest": "72ec6337e369cbb33ef7716d3267db9d5678fe54555c25ca4c9f5b9dfb7739f3", "dependencies": [], "protocol_feature_type": "builtin", "specification": [{"name": "builtin_feature_codename", "value": "SECURITY_GROUP"}]}, ... {"feature_digest": "f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1d", "subjective_restrictions": {"enabled": true, "preactivation_required": true, "earliest_allowed_activation_time": "1970-01-01T00:00:00.000"}, "description_digest": "1eab748b95a2e6f4d7cb42065bdee5566af8efddf01a55a0a8d831b823f8828a", "dependencies": [], "protocol_feature_type": "builtin", "specification": [{"name": "builtin_feature_codename", "value": "GET_SENDER"}]}]
  1. Activate the PREACTIVATE_FEATURE feature. This allows other features to be enabled, including potential upgrades to the eosio.system contract that might implement new features. Also note that a protocol feature must be activated by passing a feature digest or hashcode for the specific feature.
curl -X POST http://127.0.0.1:8888/v1/producer/schedule_protocol_feature_activations -d '{"protocol_features_to_activate": ["0ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd"]}'

Note that the feature digest hashcode 0ec7e... can be obtained from the feature_digest field returned by the get_supported_protocol_features endpoint in the previous step. If the activation is successful, the response should be as indicated.

Output:

{"result": "ok"}

If the feature has already been activated, the following response is returned instead, which can be safely ignored.

Output:

{"code": 500, "message": "Internal Service Error", "error": {"code": 3250000, "name": "protocol_feature_exception", "what": "Protocol feature exception", "details": [{"message": "protocol feature with digest '<hex-value>' has already been activated", "file": "controller.cpp", "line_number": 1650, "method": "check_protocol_features"}]}}
  1. Deploy the eosio.boot contract:
cleos set contract eosio ~/eos/contracts/contracts/eosio.boot/

The response should be something similar to:

Reading WASM from ~/eos/contracts/contracts/eosio.boot/eosio.boot.wasm... Publishing contract... executed transaction: a029c644104da357a201f75058fc6772cf63f5fdfa9135be2c3b7c498d81d46d 2752 bytes 852 us # eosio &lt;= eosio::setcode {"account":"eosio","vmtype":0,"vmversion":0,"code":"0061736d01000000013e0c60000060027f7f0060017e0060... # eosio &lt;= eosio::setabi {"account":"eosio","abi":"0e656f73696f3a3a6162692f312e32001008616374697661746500010e666561747572655f...
  1. Activate additional protocol features, such as KV_DATABASE, ACTION_RETURN_VALUE, etc. This can be performed after deploying the eosio.boot contract.
# KV_DATABASE cleos push action eosio activate '["825ee6288fb1373eab1b5187ec2f04f6eacb39cb3a97f356a07c91622dd61d16"]' -p eosio # ACTION_RETURN_VALUE cleos push action eosio activate '["c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead45071"]' -p eosio # CONFIGURABLE_WASM_LIMITS cleos push action eosio activate '["bf61537fd21c61a60e542a5d66c3f6a78da0589336868307f94a82bccea84e88"]' -p eosio # BLOCKCHAIN_PARAMETERS cleos push action eosio activate '["5443fcf88330c586bc0e5f3dee10e7f63c76c00249c87fe4fbf7f38c082006b4"]' -p eosio # GET_SENDER cleos push action eosio activate '["f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1d"]' -p eosio # FORWARD_SETCODE cleos push action eosio activate '["2652f5f96006294109b3dd0bbde63693f55324af452b799ee137a81a905eed25"]' -p eosio # ONLY_BILL_FIRST_AUTHORIZER cleos push action eosio activate '["8ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a405"]' -p eosio # RESTRICT_ACTION_TO_SELF cleos push action eosio activate '["ad9e3d8f650687709fd68f4b90b41f7d825a365b02c23a636cef88ac2ac00c43"]' -p eosio # DISALLOW_EMPTY_PRODUCER_SCHEDULE cleos push action eosio activate '["68dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a297428"]' -p eosio # FIX_LINKAUTH_RESTRICTION cleos push action eosio activate '["e0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff526"]' -p eosio # REPLACE_DEFERRED cleos push action eosio activate '["ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea99"]' -p eosio # NO_DUPLICATE_DEFERRED_ID cleos push action eosio activate '["4a90c00d55454dc5b059055ca213579c6ea856967712a56017487886a4d4cc0f"]' -p eosio # ONLY_LINK_TO_EXISTING_PERMISSION cleos push action eosio activate '["1a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b7241"]' -p eosio # RAM_RESTRICTIONS cleos push action eosio activate '["4e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d67"]' -p eosio # WEBAUTHN_KEY cleos push action eosio activate '["4fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c2"]' -p eosio # WTMSIG_BLOCK_SIGNATURES cleos push action eosio activate '["299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b4476707"]' -p eosio
  1. Deploy the eosio.system contract. This will enable second layer features and will allow the transition from a single node to multi node blockchain.
cleos set contract eosio ~/eosio.contracts/build/contracts/eosio.system/

Click Check my progress to verify the objective. Deploy eosio.boot and eosio.system contracts

Task 10. Create the block producer accounts

In this task, you will create the producer accounts that will eventually take over the EOSIO blockchain and produce blocks. The first step is to designate eosio.msig as a privileged account, so it can authorize on behalf of the eosio account, which later on will resign its authority and let the eosio.prods, account, which represents the group of elected producers, take over the responsibilities of block production and validation.

  1. Set eosio.msig as a privileged account:
cleos push action eosio setpriv '["eosio.msig", 1]' -p eosio

The response should be something similar to:

executed transaction: 385f0eb3c169898cc7faae46bb4371dbbfc46c5d482249c9933c4f8627fb271c 104 bytes 208 us # eosio &lt;= eosio::setpriv {"account":"eosio.msig","is_priv":1}
  1. Initialize SYS token to version 0 and assign 4 decimals for precision:
cleos push action eosio init '["0", "4,SYS"]' -p eosio

The response should be something similar to:

executed transaction: c537fbd77983104d8dcb1c58b09b433f68a2dd12c876a27b7556d4a722d42939 104 bytes 3467 us # eosio &lt;= eosio::init {"version":0,"core":"4,SYS"} # eosio.token &lt;= eosio.token::open {"owner":"eosio.rex","symbol":"4,SYS","ram_payer":"eosio"}
  1. Import keys to be used by producers for block signing. In production deployments, these keys should be created first, then imported.
cleos wallet import --private-key 5KLGj1HGRWbk5xNmoKfrcrQHXvcVJBPdAckoiJgFftXSJjLPp7b cleos wallet import --private-key 5K6qk1KaCYYWX86UhAfUsbMwhGPUqrqHrZEQDjs9ekP5j6LgHUu cleos wallet import --private-key 5JCStvbRgUZ6hjyfUiUaxt5iU3HP6zC1kwx3W7SweaEGvs4EPfQ cleos wallet import --private-key 5JJjaKnAb9KM2vkkJDgrYXoeUEdGgWtB5WK1a38wrmKnS3KtkS6

Importing these preexisting keys will simplify resource management for this lab. For production deployments, however, new keys should be created.

The response for each command should be:

imported private key for: EOS8imf2TDq6FKtLZ8mvXPWcd6EF2rQwo8zKdLNzsbU9EiMSt9Lwz imported private key for: EOS7Ef4kuyTbXbtSPP5Bgethvo6pbitpuEz2RMWhXb8LXxEgcR7MC imported private key for: EOS5n442Qz4yVc4LbdPCDnxNSseAiUCrNjRxAfPhUvM8tWS5svid6 imported private key for: EOS5y3Tm1czTCia3o3JidVKmC78J9gRQU8qHjmRjFxTyhh2vxvF5d

If the nodes are distributed, each key pair will be stored independently on each server's wallet. In this lab, you store it in the same wallet for simplicity.

  1. Create the block producer accounts with initial resources and public key:
cleos system newaccount --transfer eosio producer.one EOS8imf2TDq6FKtLZ8mvXPWcd6EF2rQwo8zKdLNzsbU9EiMSt9Lwz --stake-net "50000000.0000 SYS" --stake-cpu "50000000.0000 SYS" --buy-ram-kbytes 8192 cleos system newaccount --transfer eosio producer.two EOS7Ef4kuyTbXbtSPP5Bgethvo6pbitpuEz2RMWhXb8LXxEgcR7MC --stake-net "50000000.0000 SYS" --stake-cpu "50000000.0000 SYS" --buy-ram-kbytes 8192 cleos system newaccount --transfer eosio produc.three EOS5n442Qz4yVc4LbdPCDnxNSseAiUCrNjRxAfPhUvM8tWS5svid6 --stake-net "50000000.0000 SYS" --stake-cpu "50000000.0000 SYS" --buy-ram-kbytes 8192 cleos system newaccount --transfer eosio produce.four EOS5y3Tm1czTCia3o3JidVKmC78J9gRQU8qHjmRjFxTyhh2vxvF5d --stake-net "50000000.0000 SYS" --stake-cpu "50000000.0000 SYS" --buy-ram-kbytes 8192

The above command will create new accounts producer.one, producer.two, produc.three, produce.four and associate each key imported in the previous step to each account. It will also assign and stake 50 million SYS for NET and CPU and 8k for RAM.

The response should be something similar to (for each new account):

executed transaction: 6164d2741433c68edf426d430cb0986b198a6f4c63e7f7a46bcf8343562eacc0 336 bytes 1029 us # eosio &lt;= eosio::newaccount {"creator":"eosio","name":"producer.one","owner":{"threshold":1,"keys":[{"key":"EOS8imf2TDq6FKtLZ8mv... # eosio &lt;= eosio::buyrambytes {"payer":"eosio","receiver":"producer.one","bytes":8388608} # eosio &lt;= eosio::delegatebw {"from":"eosio","receiver":"producer.one","stake_net_quantity":"50000000.0000 SYS","stake_cpu_quantity... # eosio.token &lt;= eosio.token::transfer {"from":"eosio","to":"eosio.ram","quantity":"122.0851 SYS","memo":"buy ram"} # eosio.token &lt;= eosio.token::transfer {"from":"eosio","to":"eosio.ramfee","quantity":"0.6135 SYS","memo":"ram fee"} # eosio &lt;= eosio.token::transfer {"from":"eosio","to":"eosio.ram","quantity":"122.0851 SYS","memo":"buy ram"} # eosio.ram &lt;= eosio.token::transfer {"from":"eosio","to":"eosio.ram","quantity":"122.0851 SYS","memo":"buy ram"} # eosio &lt;= eosio.token::transfer {"from":"eosio","to":"eosio.ramfee","quantity":"0.6135 SYS","memo":"ram fee"} # eosio.ramfee &lt;= eosio.token::transfer {"from":"eosio","to":"eosio.ramfee","quantity":"0.6135 SYS","memo":"ram fee"} # eosio.token &lt;= eosio.token::transfer {"from":"eosio","to":"eosio.stake","quantity":"100000000.0000 SYS","memo":"stake bandwidth"} # eosio &lt;= eosio.token::transfer {"from":"eosio","to":"eosio.stake","quantity":"100000000.0000 SYS","memo":"stake bandwidth"} # eosio.stake &lt;= eosio.token::transfer {"from":"eosio","to":"eosio.stake","quantity":"100000000.0000 SYS","memo":"stake bandwidth"}
  1. Register the new accounts as producers. Note that each command also expects the web URL of the producer. Bogus URLs are used for illustration.
cleos system regproducer producer.one EOS8imf2TDq6FKtLZ8mvXPWcd6EF2rQwo8zKdLNzsbU9EiMSt9Lwz https://producer.one.com/EOS8imf2TDq6FKtLZ8mvXPWcd6EF2rQwo8zKdLNzsbU9EiMSt9Lwz cleos system regproducer producer.two EOS7Ef4kuyTbXbtSPP5Bgethvo6pbitpuEz2RMWhXb8LXxEgcR7MC https://producer.two.com/EOS7Ef4kuyTbXbtSPP5Bgethvo6pbitpuEz2RMWhXb8LXxEgcR7MC cleos system regproducer produc.three EOS5n442Qz4yVc4LbdPCDnxNSseAiUCrNjRxAfPhUvM8tWS5svid6 https://produc.three.com/EOS5n442Qz4yVc4LbdPCDnxNSseAiUCrNjRxAfPhUvM8tWS5svid6 cleos system regproducer produce.four EOS5y3Tm1czTCia3o3JidVKmC78J9gRQU8qHjmRjFxTyhh2vxvF5d https://produce.four.com/EOS5y3Tm1czTCia3o3JidVKmC78J9gRQU8qHjmRjFxTyhh2vxvF5d

The response for the above commands should be something similar to:

executed transaction: 28ced4a34abd64fb2fc11fbb51d3e4e18d2933a9ab2560fc1c6fb80b0cfc336d 216 bytes 753 us # eosio &lt;= eosio::regproducer {"producer":"producer.one","producer_key":"EOS8imf2TDq6FKtLZ8mvXPWcd6EF2rQwo8zKdLNzsbU9EiMSt9Lwz","u... executed transaction: 06b8be4187c5fbabc3197064ae4c6d55ba553fdbcc8a560f060078e59d5dfc5e 216 bytes 306 us # eosio &lt;= eosio::regproducer {"producer":"producer.two","producer_key":"EOS7Ef4kuyTbXbtSPP5Bgethvo6pbitpuEz2RMWhXb8LXxEgcR7MC","u... executed transaction: c23a5164bf0cc1e35dfa3e55fed56e935ceb7d764dbe2e7a3bf4aee446e7d88d 216 bytes 270 us # eosio &lt;= eosio::regproducer {"producer":"produc.three","producer_key":"EOS5n442Qz4yVc4LbdPCDnxNSseAiUCrNjRxAfPhUvM8tWS5svid6","u... executed transaction: 081aa8aca08b6d4e8e6bf576b2882f578faa7148e7c550196ff256ab0a9dcca3 216 bytes 224 us # eosio &lt;= eosio::regproducer {"producer":"produce.four","producer_key":"EOS5y3Tm1czTCia3o3JidVKmC78J9gRQU8qHjmRjFxTyhh2vxvF5d","u...
  1. List the current producer accounts. There should be four in total.
cleos system listproducers

The response should be:

Producer Producer key Url Scaled votes` **`produc.three`** `EOS5n442Qz4yVc4LbdPCDnxNSseAiUCrNjRxAfPhUvM8tWS5svid6 https://produc.three.com/EOS5n442Qz4yVc4LbdPCDnxNSseAiUCrNj` **`0.0000`** **`produce.four`** `EOS5y3Tm1czTCia3o3JidVKmC78J9gRQU8qHjmRjFxTyhh2vxvF5d https://produce.four.com/EOS5y3Tm1czTCia3o3JidVKmC78J9gRQU8` **`0.0000`** **`producer.one`** `EOS8imf2TDq6FKtLZ8mvXPWcd6EF2rQwo8zKdLNzsbU9EiMSt9Lwz https://producer.one.com/EOS8imf2TDq6FKtLZ8mvXPWcd6EF2rQwo8` **`0.0000`** **`producer.two`** `EOS7Ef4kuyTbXbtSPP5Bgethvo6pbitpuEz2RMWhXb8LXxEgcR7MC https://producer.two.com/EOS7Ef4kuyTbXbtSPP5Bgethvo6pbitpuE` **`0.0000`**

Note that the number of votes for each producer is still zero since no producers have been voted for yet.

Click Check my progress to verify the objective. Create the block producer accounts

Task 11. Transition from single-node producer to multi-node producers

In this task, you will finally transition from a single node blockchain to a multi node blockchain. Currently, only the eosio account is privileged and can sign blocks. The goal is to create an EOSIO blockchain managed by a collection of elected producers where consensus is reached after a supermajority of 2/3 + 1 producers confirm a block. Then, all transactions within that block will be final.

  1. Create the remaining node directories nodes/01-node, nodes/02-node, nodes/03-node, nodes/04-node to store all blockchain data related to the new producing nodes 1, 2, 3, 4:
mkdir nodes/01-node nodes/02-node nodes/03-node nodes/04-node
  1. Launch node 1, starting nodeos service daemon as a background task, and redirecting stdout/stderr to the 01-node/01.log file:
nodeos \ --plugin eosio::http_plugin --plugin eosio::chain_api_plugin --plugin eosio::chain_plugin --plugin eosio::producer_api_plugin --plugin eosio::producer_plugin --plugin eosio::history_plugin --plugin eosio::history_api_plugin --plugin eosio::net_api_plugin \ --contracts-console \ --max-transaction-time=200 \ --chain-state-db-size-mb 1024 \ --enable-stale-production \ --producer-name producer.one \ --genesis-json ~/nodes/genesis.json \ --blocks-dir ~/nodes/01-node/blocks \ --config-dir ~/nodes/01-node \ --data-dir ~/nodes/01-node \ --http-server-address 127.0.0.1:8889 \ --p2p-listen-endpoint 127.0.0.1:9877 \ --signature-provider EOS8imf2TDq6FKtLZ8mvXPWcd6EF2rQwo8zKdLNzsbU9EiMSt9Lwz=KEY:5KLGj1HGRWbk5xNmoKfrcrQHXvcVJBPdAckoiJgFftXSJjLPp7b \ --p2p-peer-address 127.0.0.1:9876 \ >>~/nodes/01-node/01.log 2>&1 &

This will launch node 1 with some plugins, producer name producer.one, listening on http port 8889, p2p port 9877, overriding default development keys with its own keys EOS8imf..., 5KLGj1..., and connecting to p2p node 9876 (boot node).

  1. You can inspect the node 1 log for any messages exchanged with the boot node (p2p node 9876) by running the following command:
cat nodes/01-node/01.log | grep :9876 -C 3

The output should look something similar to the following.

Output:

info 2021-10-22T21:56:43.161 nodeos producer_plugin.cpp:421 on_incoming_block ] Received block e79c2cc7df2bd7bc... #3040 @ 2021-10-22T21:56:41.500 signed by eosio [trxs: 0, lib: 3039, conf: 0, latency: 1661 ms] ... info 2021-10-22T21:56:43.162 net-1 net_plugin.cpp:1604 set_state ] old state lib catchup becoming in sync info 2021-10-22T21:56:43.162 net-1 net_plugin.cpp:1132 operator() ] Sending handshake generation 2 to 127.0.0.1:9876- 446609d, lib 3040, head 3041, id 5d69f1fa874933cf info 2021-10-22T21:56:43.163 net-0 net_plugin.cpp:1947 sync_recv_notice ] notice_message, pending 3043, blk_num 3043, id b8f3389c52ff416c... info 2021-10-22T21:56:43.163 net-0 net_plugin.cpp:1908 verify_catchup ] catch_up while in in sync, fork head num = 3043 target LIB = 3041 next_expected = 3042, id b8f3389c52ff416c..., peer 127.0.0.1:9876 - 446609d info 2021-10-22T21:56:43.163 net-0 net_plugin.cpp:1604 set_state ] old state in sync becoming head catchup info 2021-10-22T21:56:43.164 nodeos producer_plugin.cpp:421 on_incoming_block ] Received block 917275dee8c12e57... #3042 @ 2021-10-22T21:56:42.500 signed by eosio [trxs: 0, lib: 3041, conf: 0, latency: 664 ms] ...

Note that node 1 is not producing blocks yet, but receiving/syncing them from other nodes, such as the boot node, for now.

  1. Launch node 2, starting nodeos service daemon as a background task and redirecting stdout/stderr to the 02-node/02.log file:
nodeos \ --plugin eosio::http_plugin --plugin eosio::chain_api_plugin --plugin eosio::chain_plugin --plugin eosio::producer_api_plugin --plugin eosio::producer_plugin --plugin eosio::history_plugin --plugin eosio::history_api_plugin --plugin eosio::net_api_plugin \ --contracts-console \ --max-transaction-time=200 \ --chain-state-db-size-mb 1024 \ --enable-stale-production \ --producer-name producer.two \ --genesis-json ~/nodes/genesis.json \ --blocks-dir ~/nodes/02-node/blocks \ --config-dir ~/nodes/02-node \ --data-dir ~/nodes/02-node \ --http-server-address 127.0.0.1:8890 \ --p2p-listen-endpoint 127.0.0.1:9878 \ --signature-provider EOS7Ef4kuyTbXbtSPP5Bgethvo6pbitpuEz2RMWhXb8LXxEgcR7MC=KEY:5K6qk1KaCYYWX86UhAfUsbMwhGPUqrqHrZEQDjs9ekP5j6LgHUu \ --p2p-peer-address 127.0.0.1:9876 \ --p2p-peer-address 127.0.0.1:9877 \ >>~/nodes/02-node/02.log 2>&1 &

This will launch node 2 with some plugins, producer name producer.two, listening on http port 8890, p2p port 9878, overriding default development keys with its own keys EOS8imf..., 5KLGj1..., and connecting to p2p node 9876 (boot node) and p2p node 9877 (node 1).

  1. You can inspect the node 2 log for any messages exchanged with say node 1 (p2p node 9877) by running the following command:
cat nodes/02-node/02.log | grep :9877 -C 3

The output should look something similar to the following.

Output:

info 2021-10-22T22:28:31.496 net-1 net_plugin.cpp:1771 start_sync ] Catching up with chain, our last req is 6852, theirs is 6859 peer 127.0.0.1:9877 - 1a245d2 info 2021-10-22T22:28:31.496 net-1 net_plugin.cpp:1717 operator() ] requesting range 6853 to 6859, from 127.0.0.1:9877- 1a245d2 info 2021-10-22T22:28:31.497 nodeos producer_plugin.cpp:421 on_incoming_block ] Received block 3073fa4230d8d232... #6853 @ 2021-10-22T22:28:28.000 signed by eosio [trxs: 0, lib: 6852, conf: 0, latency: 3497 ms] ... info 2021-10-22T22:28:31.501 net-0 net_plugin.cpp:1604 set_state ] old state lib catchup becoming in sync info 2021-10-22T22:28:31.501 net-0 net_plugin.cpp:1132 operator() ] Sending handshake generation 3 to 127.0.0.1:9877- 1a245d2, lib 6858, head 6859, id 9a791a880b9b875e info 2021-10-22T22:28:31.501 net-1 net_plugin.cpp:1947 sync_recv_notice ] notice_message, pending 6860, blk_num 6860, id 4dd2c7c8ae5301ed... info 2021-10-22T22:28:31.501 net-1 net_plugin.cpp:1908 verify_catchup ] catch_up while in in sync, fork head num = 6860 target LIB = 6859 next_expected = 6860, id 4dd2c7c8ae5301ed..., peer 127.0.0.1:9877 - 1a245d2 info 2021-10-22T22:28:31.501 net-1 net_plugin.cpp:1604 set_state ] old state in sync becoming head catchup ...

Note that node 2 is not producing blocks yet, but receiving/syncing them from other nodes, such as the boot node and node 1, for now.

  1. Launch node 3, starting nodeos service daemon as a background task and redirecting stdout/stderr to the 03-node/03.log file:
nodeos \ --plugin eosio::http_plugin --plugin eosio::chain_api_plugin --plugin eosio::chain_plugin --plugin eosio::producer_api_plugin --plugin eosio::producer_plugin --plugin eosio::history_plugin --plugin eosio::history_api_plugin --plugin eosio::net_api_plugin \ --contracts-console \ --max-transaction-time=200 \ --chain-state-db-size-mb 1024 \ --enable-stale-production \ --producer-name produc.three \ --genesis-json ~/nodes/genesis.json \ --blocks-dir ~/nodes/03-node/blocks \ --config-dir ~/nodes/03-node \ --data-dir ~/nodes/03-node \ --http-server-address 127.0.0.1:8891 \ --p2p-listen-endpoint 127.0.0.1:9879 \ --signature-provider EOS5n442Qz4yVc4LbdPCDnxNSseAiUCrNjRxAfPhUvM8tWS5svid6=KEY:5JCStvbRgUZ6hjyfUiUaxt5iU3HP6zC1kwx3W7SweaEGvs4EPfQ \ --p2p-peer-address 127.0.0.1:9876 \ --p2p-peer-address 127.0.0.1:9877 \ --p2p-peer-address 127.0.0.1:9878 \ >>~/nodes/03-node/03.log 2>&1 &

This will launch node 3 with some plugins, producer name produc.three, listening on http port 8891, p2p port 9879, overriding default development keys with its own keys EOS5n44..., 5JCStv..., and connecting to p2p node 9876 (boot node), p2p node 9877 (node 1), and p2p node 9878 (node 2).

  1. You can inspect the node 3 log for any messages exchanged with say node 2 (p2p node 9878) by running the following command:
cat nodes/03-node/03.log | grep :9878 -C 3

The output should look something similar to the following.

Output:

info 2021-10-22T22:28:31.496 net-1 net_plugin.cpp:1771 start_sync ] Catching up with chain, our last req is 6852, theirs is 6859 peer 127.0.0.1:`**`9878`** `- 1a245d2` info 2021-10-22T22:28:31.496 net-1 net_plugin.cpp:1717 operator() ] requesting range 6853 to 6859, from 127.0.0.1:`**`9878`** `- 1a245d2` info 2021-10-22T22:28:31.497 nodeos producer_plugin.cpp:421 on_incoming_block ] Received block 3073fa4230d8d232... #6853 @ 2021-10-22T22:28:28.000 signed by eosio [trxs: 0, lib: 6852, conf: 0, latency: 3497 ms] ... info 2021-10-22T22:28:31.501 net-0 net_plugin.cpp:1604 set_state ] old state lib catchup becoming in sync info 2021-10-22T22:28:31.501 net-0 net_plugin.cpp:1132 operator() ] Sending handshake generation 3 to 127.0.0.1:`**`9878`** `- 1a245d2, lib 6858, head 6859, id 9a791a880b9b875e info 2021-10-22T22:28:31.501 net-1 net_plugin.cpp:1947 sync_recv_notice ] notice_message, pending 6860, blk_num 6860, id 4dd2c7c8ae5301ed... info 2021-10-22T22:28:31.501 net-1 net_plugin.cpp:1908 verify_catchup ] catch_up while in in sync, fork head num = 6860 target LIB = 6859 next_expected = 6860, id 4dd2c7c8ae5301ed..., peer 127.0.0.1:`**`9878`** `- 1a245d2 info 2021-10-22T22:28:31.501 net-1 net_plugin.cpp:1604 set_state ] old state in sync becoming head catchup ...

Note that node 3 is not producing blocks yet, but receiving/syncing them from other nodes, such as the boot node, node 1 and 2, for now.

  1. Launch node 4, starting nodeos service daemon as a background task and redirecting stdout/stderr to the 04-node/04.log file:
nodeos \ --plugin eosio::http_plugin --plugin eosio::chain_api_plugin --plugin eosio::chain_plugin --plugin eosio::producer_api_plugin --plugin eosio::producer_plugin --plugin eosio::history_plugin --plugin eosio::history_api_plugin --plugin eosio::net_api_plugin \ --contracts-console \ --max-transaction-time=200 \ --chain-state-db-size-mb 1024 \ --enable-stale-production \ --producer-name produce.four \ --genesis-json ~/nodes/genesis.json \ --blocks-dir ~/nodes/04-node/blocks \ --config-dir ~/nodes/04-node \ --data-dir ~/nodes/04-node \ --http-server-address 127.0.0.1:8892 \ --p2p-listen-endpoint 127.0.0.1:9880 \ --signature-provider EOS5y3Tm1czTCia3o3JidVKmC78J9gRQU8qHjmRjFxTyhh2vxvF5d=KEY:5JJjaKnAb9KM2vkkJDgrYXoeUEdGgWtB5WK1a38wrmKnS3KtkS6 \ --p2p-peer-address 127.0.0.1:9876 \ --p2p-peer-address 127.0.0.1:9877 \ --p2p-peer-address 127.0.0.1:9878 \ --p2p-peer-address 127.0.0.1:9879 \ >>~/nodes/04-node/04.log 2>&1 &

This will launch node 4 with some plugins, producer name produce.four, listening on http port 8892, p2p port 9880, overriding default development keys with its own keys EOS5y3T..., 5JJjaK..., and connecting to p2p node 9876 (boot node), p2p node 9877 (node 1), p2p node 9878 (node 2), and p2p node 9879 (node 3).

  1. You can inspect the node 4 log for any messages exchanged with say node 3 (p2p node 9879) by running the following command:
cat nodes/04-node/04.log | grep :9879 -C 3

The response should look something similar to:

info 2021-10-22T22:28:31.496 net-1 net_plugin.cpp:1771 start_sync ] Catching up with chain, our last req is 6852, theirs is 6859 peer 127.0.0.1:9879 - 1a245d2 info 2021-10-22T22:28:31.496 net-1 net_plugin.cpp:1717 operator() ] requesting range 6853 to 6859, from 127.0.0.1:9879 - 1a245d2 info 2021-10-22T22:28:31.497 nodeos producer_plugin.cpp:421 on_incoming_block ] Received block 3073fa4230d8d232... #6853 @ 2021-10-22T22:28:28.000 signed by eosio [trxs: 0, lib: 6852, conf: 0, latency: 3497 ms] ... info 2021-10-22T22:28:31.501 net-0 net_plugin.cpp:1604 set_state ] old state lib catchup becoming in sync info 2021-10-22T22:28:31.501 net-0 net_plugin.cpp:1132 operator() ] Sending handshake generation 3 to 127.0.0.1:9879 - 1a245d2, lib 6858, head 6859, id 9a791a880b9b875e info 2021-10-22T22:28:31.501 net-1 net_plugin.cpp:1947 sync_recv_notice ] notice_message, pending 6860, blk_num 6860, id 4dd2c7c8ae5301ed... info 2021-10-22T22:28:31.501 net-1 net_plugin.cpp:1908 verify_catchup ] catch_up while in in sync, fork head num = 6860 target LIB = 6859 next_expected = 6860, id 4dd2c7c8ae5301ed..., peer 127.0.0.1:9879 - 1a245d2 info 2021-10-22T22:28:31.501 net-1 net_plugin.cpp:1604 set_state ] old state in sync becoming head catchup ...

Note that node 4 is not producing blocks yet, but receiving/syncing them from other nodes, such as the boot node, node 1, 2 and 3, for now.

At this point all new nodes 1, 2, 3, 4, are launched and connected. They receive and synchronize blocks produced by the boot node, but they do not produce yet. To that end, the new producers need to be elected. In a production deployment, users would typically vote for up to 30 producers from a pool of prospective producers in voting command. The top 21 producers get elected as block producers.

Here, you will use the existing producer accounts to elect the only four producers available. After 15% of the SYS token supply has been staked, the top elected producers are then proposed for production. After that block that contains the list of producers is confirmed and becomes final, the new producers begin producing blocks in the next scheduled round.

  1. Use the cleos system voteproducer prods command to vote for specific producers. Account producer.one votes for itself, producers 2, 3, and 4. producer.two votes for itself, producer 3 and 4. produc.three, and produce.four; then produc.three to vote for itself and produce.four; then produce.four to vote for itself.
cleos system voteproducer prods producer.one producer.one producer.two produc.three produce.four cleos system voteproducer prods producer.two producer.two produc.three produce.four cleos system voteproducer prods produc.three produc.three produce.four cleos system voteproducer prods produce.four produce.four

The response should look something similar to:

executed transaction: 833178bdc9d5b8c86778fa2b2c0f20e976f4c253cb6ebcaa6ade8eb8bf3d0a4f 144 bytes 861 us # eosio &lt;= eosio::voteproducer {"voter":"producer.one","proxy":"","producers":["produc.three","produce.four","producer.one","produc... executed transaction: 9c4961678017bfc2a7fd1398a2e4b33e472a6cadc0340400ebe9207e188d7692 136 bytes 379 us # eosio &lt;= eosio::voteproducer {"voter":"producer.two","proxy":"","producers":["produc.three","produce.four","producer.two"]} executed transaction: d3d23bae8730f18e82da53defaff7184c49b83181bb9b6fa32096684e61ed74d 128 bytes 371 us` # eosio &lt;= eosio::voteproducer {"voter":"produc.three","proxy":"","producers":["produc.three","produce.four"]} executed transaction: 95f63d46554dd436388e36b017b8e350514ef5b407b1c33c5779dc29c862cdb6 120 bytes 304 us` # eosio &lt;= eosio::voteproducer {"voter":"produce.four","proxy":"","producers":["produce.four"]}
  1. Check the percentage of votes casted for each producer:
cleos system listproducers

The response for the above commands should be:

Producer Producer key Url Scaled votes produce.four EOS5y3Tm1czTCia3o3JidVKmC78J9gRQU8qHjmRjFxTyhh2vxvF5d https://produce.four.com/EOS5y3Tm1czTCia3o3JidVKmC78J9gRQU8 0.4000 produc.three EOS5n442Qz4yVc4LbdPCDnxNSseAiUCrNjRxAfPhUvM8tWS5svid6 https://produc.three.com/EOS5n442Qz4yVc4LbdPCDnxNSseAiUCrNj 0.3000 producer.two EOS7Ef4kuyTbXbtSPP5Bgethvo6pbitpuEz2RMWhXb8LXxEgcR7MC https://producer.two.com/EOS7Ef4kuyTbXbtSPP5Bgethvo6pbitpuE 0.2000 producer.one EOS8imf2TDq6FKtLZ8mvXPWcd6EF2rQwo8zKdLNzsbU9EiMSt9Lwz https://producer.one.com/EOS8imf2TDq6FKtLZ8mvXPWcd6EF2rQwo8 0.1000

Note that producer 1 got 10% of the votes, then producer 2 followed with 20%, producer 3 got 30%, and finally producer 4 obtained 40%. Since the total number of SYS tokens staked by all producers is greater than 15% of the token supply, the top 21 producers (by default) will get proposed for block production.

Production itself will begin after the block that contains the list of the block producers gets confirmed as final. Since there are only four producers, all of them will produce, and a supermajority will be reached with any three of them.

  1. Check that the new producers are indeed producing blocks:
tail -f nodes/01-node/01.log | grep Produced

The above command should display the last blocks produced by Producer 1. If you don't see any output yet, please wait a few seconds. There are four producers each producing 12 blocks every 6 seconds. So there should be 18 seconds between the last block produced and the first one for the next round.

The response should look something similar to:

info 2021-10-28T15:31:47.904 nodeos producer_plugin.cpp:2333 produce_block ] Produced block adeb43a9249e0ef8...` **`#7035`** `@ 2021-10-28T15:31:48.000 signed by` **`producer.one`** `[trxs: 0, lib: 6998, confirmed: 36] info 2021-10-28T15:31:48.400 nodeos producer_plugin.cpp:2333 produce_block ] Produced block 991c7f7addb6006e...` **`#7036`** `@ 2021-10-28T15:31:48.500 signed by` **`producer.one`** `[trxs: 0, lib: 6998, confirmed: 0] ... info 2021-10-28T15:31:52.901 nodeos producer_plugin.cpp:2333 produce_block ] Produced block c947dfab144600f7...` **`#7045`** `@ 2021-10-28T15:31:53.000 signed by` **`producer.one`** `[trxs: 0, lib: 6998, confirmed: 0] info 2021-10-28T15:31:53.300 nodeos producer_plugin.cpp:2333 produce_block ] Produced block c871f71b3ebe9151...` **`#7046`** `@ 2021-10-28T15:31:53.500 signed by` **`producer.one`** `[trxs: 0, lib: 6998, confirmed: 0]`

If you wait 18 more seconds you should see the next round of 12 blocks produced, and so on. You may also check that the other three nodes are indeed producing blocks by replacing the 01s in the above command with 02s, 03s, 04s.

  1. Press Ctrl+C to exit the tail command.

  2. Check that the boot node is not producing blocks anymore:

tail -f nodes/00-boot/00.log | grep Produced

Note that the above command should not produce any output, which demonstrates that the boot node relinquished block production to the new producers that were elected.

Now that you have a fully functioning blockchain with four producers, you should strip the default eosio and related eosio.* accounts of any powers to sign transactions and modify state on the blockchain. To that end, you need to resign the keys used by the eosio and eosio.* accounts by resetting them to null.

  1. Get the eosio account information. Note the keys associated with the owner and active permissions:
cleos get account eosio

The response should look something similar to:

created: 2018-06-01T12:00:00.000 privileged: true permissions: owner 1: 1 EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV active 1: 1 EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV memory: quota: unlimited used: 2.989 MiB net bandwidth: used: unlimited available: unlimited limit: unlimited cpu bandwidth: used: unlimited available: unlimited limit: unlimited SYS balances: liquid: 599999509.0258 SYS staked: 0.0000 SYS unstaking: 0.0000 SYS total: 599999509.0258 SYS
  1. Resign the keys used by the eosio account and link its permissions to the eosio.prods account:
cleos push action eosio updateauth '{"account": "eosio", "permission": "owner", "parent": "", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio.prods", "permission": "active"}}]}}' -p eosio@owner cleos push action eosio updateauth '{"account": "eosio", "permission": "active", "parent": "owner", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio.prods", "permission": "active"}}]}}' -p eosio@active cleos get account eosio

The response should look something similar to:

created: 2018-06-01T12:00:00.000 privileged: true permissions: owner 1: 1 eosio.prods@active active 1: 1 eosio.prods@active `memory: quota: unlimited used: 2.989 MiB net bandwidth: used: unlimited available: unlimited limit: unlimited cpu bandwidth: used: unlimited available: unlimited limit: unlimited SYS balances: liquid: 599999509.0258 SYS staked: 0.0000 SYS unstaking: 0.0000 SYS total: 599999509.0258 SYS

Note that the eosio account no longer has the default development keys associated with the owner and active permissions. Instead, the permissions are now child permissions of the parent eosio.prods@active permission. This is the same account group that represents all the active block producers.

Therefore, now the eosio account is essentially controlled by the eosio.prods account.

  1. Resign the keys used by the eosio.* accounts and link their permissions to the eosio account:
cleos push action eosio updateauth '{"account": "eosio.bpay", "permission": "owner", "parent": "", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.bpay@owner cleos push action eosio updateauth '{"account": "eosio.bpay", "permission": "active", "parent": "owner", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.bpay@active cleos push action eosio updateauth '{"account": "eosio.msig", "permission": "owner", "parent": "", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.msig@owner cleos push action eosio updateauth '{"account": "eosio.msig", "permission": "active", "parent": "owner", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.msig@active cleos push action eosio updateauth '{"account": "eosio.names", "permission": "owner", "parent": "", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.names@owner cleos push action eosio updateauth '{"account": "eosio.names", "permission": "active", "parent": "owner", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.names@active cleos push action eosio updateauth '{"account": "eosio.ram", "permission": "owner", "parent": "", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.ram@owner cleos push action eosio updateauth '{"account": "eosio.ram", "permission": "active", "parent": "owner", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.ram@active cleos push action eosio updateauth '{"account": "eosio.ramfee", "permission": "owner", "parent": "", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.ramfee@owner cleos push action eosio updateauth '{"account": "eosio.ramfee", "permission": "active", "parent": "owner", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.ramfee@active cleos push action eosio updateauth '{"account": "eosio.saving", "permission": "owner", "parent": "", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.saving@owner cleos push action eosio updateauth '{"account": "eosio.saving", "permission": "active", "parent": "owner", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.saving@active cleos push action eosio updateauth '{"account": "eosio.stake", "permission": "owner", "parent": "", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.stake@owner cleos push action eosio updateauth '{"account": "eosio.stake", "permission": "active", "parent": "owner", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.stake@active cleos push action eosio updateauth '{"account": "eosio.token", "permission": "owner", "parent": "", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.token@owner cleos push action eosio updateauth '{"account": "eosio.token", "permission": "active", "parent": "owner", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.token@active cleos push action eosio updateauth '{"account": "eosio.vpay", "permission": "owner", "parent": "", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.vpay@owner cleos push action eosio updateauth '{"account": "eosio.vpay", "permission": "active", "parent": "owner", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.vpay@active cleos push action eosio updateauth '{"account": "eosio.rex", "permission": "owner", "parent": "", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.rex@owner cleos push action eosio updateauth '{"account": "eosio.rex", "permission": "active", "parent": "owner", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.rex@active

Now the eosio.* accounts no longer have the default development keys associated with the owner and active permissions. Instead, the permissions are now child permissions of the parent eosio@active permission, which in turn is a child permission of the eosio.prods@active account.

Therefore, now all the system accounts, including eosio, are essentially controlled by the eosio.prods account, which represents the account group of all active block producers. Finally, now you have built a fully operational multi node EOSIO blockchain!

Click Check my progress to verify the objective. Transition from single node producer to multi node producers

Congratulations!

In this lab, you extended the single node EOSIO blockchain deployed previously to a multiple blockchain network. You also installed various EOSIO software components to set up your multi node EOSIO blockchain. Finally, you performed some node operator tasks on your new EOSIO blockchain.

Next steps / learn more

Google Cloud training and certification

...helps you make the most of Google Cloud technologies. Our classes include technical skills and best practices to help you get up to speed quickly and continue your learning journey. We offer fundamental to advanced level training, with on-demand, live, and virtual options to suit your busy schedule. Certifications help you validate and prove your skill and expertise in Google Cloud technologies.

Manual Last Updated October 3, 2023

Lab Last Tested October 5, 2023

Copyright 2024 Google LLC All rights reserved. Google and the Google logo are trademarks of Google LLC. All other company and product names may be trademarks of the respective companies with which they are associated.