Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
An interoperable L1 network driven by its ecosystem and focused in delivering experience-based services. ZChains is a modular and extensible framework for building Ethereum-compatible blockchain networks, sidechains, and general scaling solutions.
Its primary use is to bootstrap a new blockchain network while providing full compatibility with Ethereum smart contracts and transactions. It uses IBFT (Istanbul Byzantine Fault Tolerant) consensus mechanism, supported in one flavour as PoS (proof of stake).
ZChains also supports communication with multiple blockchain networks, enabling transfers of both ERC-20 and ERC-721 tokens, by utilising the centralised bridge solution.
Industry standard wallets can be used to interact with ZChains through the JSON-RPC endpoints and node operators can perform various actions on the nodes through the gRPC protocol.
To find out more about ZChains, visit the: Official Website
GitHub Telegram X (Twitter) Instagram Discord ---
CAUTION
This is a work in progress so architectural changes may happen in the future, so please contact the ZChains team if you would like to use it in production.
To get started by running a polygon-edge
network locally, please read: Installation and Local Setup.
Blockchain: A system of recording information in a way that makes it difficult or impossible to change, hack, or cheat the system. A blockchain is essentially a digital ledger of transactions that is duplicated and distributed across the entire network of computer systems on the blockchain.
Ethereum Virtual Machine (EVM): A computation engine that acts as a decentralized computer, containing millions of executable projects. It serves as the runtime environment for every smart contract on the Ethereum network.
Proof of Stake (PoS): A consensus mechanism in a blockchain network where transactions and blocks are validated by approved accounts, known as validators based on the amount of cryptocurrency locked up, or staked. PoS is known for its speed and efficiency compared to proof-of-work systems.
Smart Contract: Self-executing contracts with the terms of the agreement between buyer and seller being directly written into lines of code. The code and the agreements contained therein exist across a distributed, decentralized blockchain network.
Tokenomics: A term that combines 'token' and 'economics' and refers to the economics concerning the creation, distribution, and usage of a cryptocurrency or blockchain token.
Validator: A participant in the blockchain network responsible for verifying, voting on, and maintaining the ledger's accuracy and helping the network reach consensus.
Decentralized Applications (dApps): Digital applications or programs that exist and run on a blockchain or peer-to-peer network of computers instead of a single computer, and are outside the purview and control of a single authority.
Liquidity Pool: A collection of funds locked in a smart contract. Liquidity pools are used to facilitate decentralized trading, lending, and many more functions.
:::info This guide is for mainnet or testnet setups
The below guide will instruct you on how to set up a ZChains network on a cloud provider for a production setup of your testnet or mainnet.
If you would like to setup a ZChains network locally to quickly test the polygon-edge
before doing a production-like setup, please refer to Local Setup :::
Refer to Installation to install ZChains.
Depending on your choice of cloud provider, you may set up connectivity and rules between the VMs using a firewall, security groups, or access control lists.
As the only part of the polygon-edge
that needs to be exposed to other VMs is the libp2p server, simply allowing all communication between VMs on the default libp2p port 1478
is enough.
In this guide, our goal is to establish a working polygon-edge
blockchain network working with IBFT consensus protocol. The blockchain network will consist of 4 nodes of whom all 4 are validator nodes, and as such are eligible for both proposing block, and validating blocks that came from other proposers. Each of the 4 nodes will run on their own VM, as the idea of this guide is to give you a fully functional ZChains network while keeping the validator keys private to ensure a trustless network setup.
To achieve that, we will guide you through 4 easy steps:
Take a look at the list of Requirements above
Generate the private keys for each of the validators, and initialize the data directory
Prepare the connection string for the bootnode to be put into the shared genesis.json
Create the genesis.json
on your local machine, and send/transfer it to each of the nodes
Start all the nodes
:::info Number of validators
There is no minimum to the number of nodes in a cluster, which means clusters with only 1 validator node are possible. Keep in mind that with a single node cluster, there is no crash tolerance and no BFT guarantee.
The minimum recommended number of nodes for achieving a BFT guarantee is 4 - since in a 4 node cluster, the failure of 1 node can be tolerated, with the remaining 3 functioning normally.
:::
To get up and running with ZChains, you need to initialize the data folders, on each node:
Each of these commands will print the node ID. You will need that information for the next step.
:::warning Keep your data directory to yourself!
The data directories generated above, besides initializing the directories for holding the blockchain state, will also generate your validator's private keys. This key should be kept as a secret, as stealing it would render somebody capable of impersonating you as the validator in the network! :::
For a node to successfully establish connectivity, it must know which bootnode
server to connect to gain information about all the remaining nodes on the network. The bootnode
is sometimes also known as the rendezvous
server in p2p jargon.
bootnode
is not a special instance of a Polygon Edge node. Every Polygon Edge node can serve as a bootnode
and every Polygon Edge node needs to have a set of bootnodes specified which will be contacted to provide information on how to connect with all remaining nodes in the network.
To create the connection string for specifying the bootnode, we will need to conform to the multiaddr format:
In this guide, we will treat the first and second nodes as the bootnodes for all other nodes. What will happen in this scenario is that nodes that connect to the node 1
or node 2
will get information on how to connect to one another through the mutually contacted bootnode.
:::info You need to specify at least one bootnode to start a node
At least one bootnode is required, so other nodes in the network can discover each other. More bootnodes are recommended, as they provide resilience to the network in case of outages. In this guide we will list two nodes, but this can be changed on the fly, with no impact on the validity of the genesis.json
file. :::
As the first part of the multiaddr connection string is the <ip_address>
, here you will need to enter the IP address as reachable by other nodes, depending on your setup this might be a private or a public IP address, not 127.0.0.1
.
For the <port>
we will use 1478
, since it is the default libp2p port.
And lastly, we need the <node_id>
which we can get from the output of the previously ran command polygon-edge secrets init --data-dir data-dir
command (which was used to generate keys and data directories for the node 1
)
After the assembly, the multiaddr connection string to the node 1
which we will use as the bootnode will look something like this (only the <node_id>
which is at the end should be different):
Similarly, we construct multiaddr for the second bootnode as shown below
This step can be run on your local machine, but you will need the public validator keys for each of the 4 validators.
Validators can safely share the Public key (address)
as displayed below in the output to their secrets init
commands, so that you may securely generate the genesis.json with those validators in the initial validator set, identified by their public keys:
Given that you have received all 4 of the validators' public keys, you can run the following command to generate the genesis.json
What this command does:
The --ibft-validator
sets the public key of the validator that should be included in the initial validator set in the genesis block. There can be many initial validators.
The --bootnode
sets the address of the bootnode that will enable the nodes to find each other. We will use the multiaddr string of the node 1
, as mentioned in step 2, although you can add as many bootnodes as you want, as displayed above.
:::info Premining account balances
You will probably want to set up your blockchain network with some addresses having "premined" balances.
To achieve this, pass as many --premine
flags as you want per address that you want to be initialized with a certain balance on the blockchain.
For example, if we would like to premine 1000 ETH to address 0x3956E90e632AEbBF34DEB49b71c28A83Bc029862
in our genesis block, then we would need to supply the following argument:
Note that the premined amount is in WEI, not ETH.
:::
:::info Set the block gas limit
The default gas limit for each block is 5242880
. This value is written in the genesis file, but you may want to increase / decrease it.
To do so, you can use the flag --block-gas-limit
followed by the desired value as shown below :
:::
:::info Set system file descriptor limit
The default file descriptor limit ( maximum number of open files ) on some operating systems is pretty small. If the nodes are expected to have high throughput, you might consider increasing this limit on the OS level.
For Ubuntu distro the procedure is as follows ( if you're not using Ubuntu/Debian distro, check the official docs for your OS ) :
Check current os limits ( open files )
Increase open files limit
Localy - affects only current session:
Globaly or per user ( add limits at the end of /etc/security/limits.conf file ) :
Optionaly, modify additional parameters, save the file and restart the system. After restart check file descriptor limit again. It should be set to the value you defined in limits.conf file. :::
After specifying the:
Public keys of the validators to be included in the genesis block as the validator set
Bootnode multiaddr connection strings
Premined accounts and balances to be included in the genesis block
and generating the genesis.json
, you should copy it over to all of the VMs in the network. Depending on your setup you may copy/paste it, send it to the node operator, or simply SCP/FTP it over.
The structure of the genesis file is covered in the CLI Commands section.
:::note Networking on Cloud providers
Most cloud providers don't expose the IP addresses (especially public ones) as a direct network interface on your VM but rather setup an invisible NAT proxy.
To allow the nodes to connect to each other in this case you would need to listen on the 0.0.0.0
IP address to bind on all interfaces, but you would still need to specify the IP address or DNS address which other nodes can use to connect to your instance. This is achieved either by using the --nat
or --dns
argument where you can specify your external IP or DNS address respectively.
Example
The associated IP address that you wish to listen on is 192.0.2.1
, but it is not directly bound to any of your network interfaces.
To allow the nodes to connect you would pass the following parameters:
polygon-edge ... --libp2p 0.0.0.0:10001 --nat 192.0.2.1
Or, if you wish to specify a DNS address dns/example.io
, pass the following parameters:
polygon-edge ... --libp2p 0.0.0.0:10001 --dns dns/example.io
This would make your node listen on all interfaces, but also make it aware that the clients are connecting to it through the specified --nat
or --dns
address.
:::
To run the first client:
To run the second client:
To run the third client:
To run the fourth client:
After running the previous commands, you have set up a 4 node Polygon Edge network, capable of sealing blocks and recovering from node failure.
:::info Start the client using config file
Instead of specifying all configuration parameters as CLI arguments, the Client can also be started using a config file by executing the following command:
Example :
Currently, we only support json
based configuration file, sample config file can be found here
:::
:::info Steps to run a non-validator node
A Non-validator will always sync the latest blocks received from the validator node, you can start a non-validator node by running the following command.
For example, you can add fifth Non-validator client by executing the following command :
:::
:::info Specify the price limit A Zchains e node can be started with a set price limit for incoming transactions.
The unit for the price limit is wei
.
Setting a price limit means that any transaction processed by the current node will need to have a gas price higher than the set price limit, otherwise it will not be included into a block.
Having the majority of nodes respect a certain price limit enforces the rule that transactions in the network cannot be below a certain price threshold.
The default value for the price limit is 0
, meaning it is not enforced at all by default.
Example of using the --price-limit
flag:
It is worth noting that price limits are enforced only on non-local transactions, meaning that the price limit does not apply to transactions added locally on the node. :::
:::caution This guide is for testing purposes only
The below guide will instruct you on how to set up a ZChains network on your local machine for testing and development purposes.
The procedure differs greatly from the way you would want to set up ZChains network for a real use scenario on a cloud provider: Cloud Setup
:::
Refer to Installation to install ZChains.
In this guide, our goal is to establish a working polygon-edge
blockchain network working with IBFT consensus protocol. The blockchain network will consist of 4 nodes of whom all 4 are validator nodes, and as such are eligible for both proposing block, and validating blocks that came from other proposers. All 4 nodes will run on the same machine, as the idea of this guide is to give you a fully functional IBFT cluster in the least amount of time.
To achieve that, we will guide you through 4 easy steps:
Initializing data directories will generate both the validator keys for each of the 4 nodes, and initialize empty blockchain data directories. The validator keys are important as we need to bootstrap the genesis block with the initial set of validators using these keys.
Preparing the connection string for the bootnode will be the vital information for every node we will run as to which node to connect to when starting for the first time.
Generating the genesis.json
file will require as input both the validator keys generated in step 1 used for setting the initial validators of the network in the genesis block and the bootnode connection string from step 2.
Running all the nodes is the end goal of this guide and will be the last step we do, we will instruct the nodes which data directory to use and where to find the genesis.json
which bootstraps the initial network state.
As all four nodes will be running on localhost, during the setup process it is expected that all the data directories for each of the nodes are in the same parent directory.
:::info Number of validators
There is no minimum to the number of nodes in a cluster, which means clusters with only 1 validator node are possible. Keep in mind that with a single node cluster, there is no crash tolerance and no BFT guarantee.
The minimum recommended number of nodes for achieving a BFT guarantee is 4 - since in a 4 node cluster, the failure of 1 node can be tolerated, with the remaining 3 functioning normally.
:::
In order to get up and running with IBFT, you need to initialize the data folders, one for each node:
Each of these commands will print the validator key and the node ID. You will need the Node ID of the first node for the next step.
For a node to successfully establish connectivity, it must know which bootnode
server to connect to in order to gain information about all the remaining nodes on the network. The bootnode
is sometimes also known as the rendezvous
server in p2p jargon.
bootnode
is not a special instance of the polygon-edge node. Every polygon-edge node can serve as a bootnode
, but every polygon-edge node needs to have a set of bootnodes specified which will be contacted to provide information on how to connect with all remaining nodes in the network.
To create the connection string for specifying the bootnode, we will need to conform to the multiaddr format:
In this guide, we will treat the first and second nodes as the bootnodes for all other nodes. What will happen in this scenario is that nodes that connect to the node 1
or node 2
will get information on how to connect to one another through the mutually contacted bootnode.
:::info You need to specify at least one bootnode to start a node
At least one bootnode is required, so other nodes in the network can discover each other. More bootnodes are recommended, as they provide resilience to the network in case of outages. In this guide we will list two nodes, but this can be changed on the fly, with no impact on the validity of the genesis.json
file. :::
Since we are running on localhost, it is safe to assume that the <ip_address>
is 127.0.0.1
.
For the <port>
we will use 10001
since we will configure the libp2p server for node 1
to listen on this port later.
And lastly, we need the <node_id>
which we can get from the output of the previously ran command polygon-edge secrets init --data-dir test-chain-1
command (which was used to generate keys and data directories for the node1
)
After the assembly, the multiaddr connection string to the node 1
which we will use as the bootnode will look something like this (only the <node_id>
which is at the end should be different):
Similarly, we construct the multiaddr for second bootnode as shown below
What this command does:
The --ibft-validators-prefix-path
sets the prefix folder path to the one specified which IBFT in Zchains can use. This directory is used to house the consensus/
folder, where the validator's private key is kept. The validator's public key is needed in order to build the genesis file - the initial list of bootstrap nodes. This flag only makes sense when setting up the network on localhost, as in a real-world scenario we cannot expect all the nodes' data directories to be on the same filesystem from where we can easily read their public keys.
The --bootnode
sets the address of the bootnode that will enable the nodes to find each other. We will use the multiaddr string of the node 1
, as mentioned in step 2.
The result of this command is the genesis.json
file which contains the genesis block of our new blockchain, with the predefined validator set and the configuration for which node to contact first in order to establish connectivity.
:::info Premining account balances
You will probably want to set up your blockchain network with some addresses having "premined" balances.
To achieve this, pass as many --premine
flags as you want per address that you want to be initialized with a certain balance on the blockchain.
For example, if we would like to premine 1000 ETH to address 0x3956E90e632AEbBF34DEB49b71c28A83Bc029862
in our genesis block, then we would need to supply the following argument:
Note that the premined amount is in WEI, not ETH.
:::
:::info Set the block gas limit
The default gas limit for each block is 5242880
. This value is written in the genesis file, but you may want to increase / decrease it.
To do so, you can use the flag --block-gas-limit
followed by the desired value as shown below :
:::
:::info Set system file descriptor limit
The default file descriptor limit ( maximum number of open files ) on some operating systems is pretty small. If the nodes are expected to have high throughput, you might consider increasing this limit on the OS level.
For Ubuntu distro the procedure is as follows ( if you're not using Ubuntu/Debian distro, check the official docs for your OS ) :
Check current os limits ( open files )
Increase open files limit
Localy - affects only current session:
Globaly or per user ( add limits at the end of /etc/security/limits.conf file ) :
Optionaly, modify additional parameters, save the file and restart the system. After restart check file descriptor limit again. It should be set to the value you defined in limits.conf file. :::
Because we are attempting to run a Polygon Edge network consisting of 4 nodes all on the same machine, we need to take care to avoid port conflicts. This is why we will use the following reasoning for determining the listening ports of each server of a node:
10000
for the gRPC server of node 1
, 20000
for the GRPC server of node 2
, etc.
10001
for the libp2p server of node 1
, 20001
for the libp2p server of node 2
, etc.
10002
for the JSON-RPC server of node 1
, 20002
for the JSON-RPC server of node 2
, etc.
To run the first client (note the port 10001
since it was used as a part of the libp2p multiaddr in step 2 alongside node 1's Node ID):
To run the second client:
To run the third client:
To run the fourth client:
To briefly go over what has been done so far:
The directory for the client data has been specified to be ./test-chain-*
The GRPC servers have been started on ports 10000, 20000, 30000 and 40000, for each node respectively
The libp2p servers have been started on ports 10001, 20001, 30001 and 40001, for each node respectively
The JSON-RPC servers have been started on ports 10002, 20002, 30002 and 40002, for each node respectively
The seal flag means that the node which is being started is going to participate in block sealing
The chain flag specifies which genesis file should be used for chain configuration
The structure of the genesis file is covered in the CLI Commands section.
After running the previous commands, you have set up a 4 node Polygon Edge network, capable of sealing blocks and recovering from node failure.
:::info Start the client using config file
Instead of specifying all configuration parameters as CLI arguments, the Client can also be started using a config file by executing the following command:
Example:
Currently, we only support json
based configuration file, sample config file can be found here
:::
:::info Steps to run a non-validator node
A Non-validator will always sync the latest blocks received from the validator node, you can start a non-validator node by running the following command.
For example, you can add fifth Non-validator client by executing the following command :
:::
:::info Specify the price limit A Polygon Edge node can be started with a set price limit for incoming transactions.
The unit for the price limit is wei
.
Setting a price limit means that any transaction processed by the current node will need to have a gas price higher then the set price limit, otherwise it will not be included in a block.
Having the majority of nodes respect a certain price limit enforces the rule that transactions in the network cannot be below a certain price threshold.
The default value for the price limit is 0
, meaning it is not enforced at all by default.
Example of using the --price-limit
flag:
It is worth noting that price limits are enforced only on non-local transactions, meaning that the price limit does not apply to transactions added locally on the node. :::
Now that you've set up at least 1 running client, you can go ahead and interact with the blockchain using the account you premined above and by specifying the JSON-RPC URL to any of the 4 nodes:
Node 1: http://localhost:10002
Node 2: http://localhost:20002
Node 3: http://localhost:30002
Node 4: http://localhost:40002
Follow this guide to issue operator commands to the newly built cluster: How to query operator information (the GRPC ports for the cluster we have built are 10000
/20000
/30000
/40000
for each node respectively)
We started with the idea of making software that is modular.
This is something that is present in almost all parts of the ZChains. Below, you will find a brief overview of the built architecture and its layering.
It all starts at the base networking layer, which utilizes libp2p. We decided to go with this technology because it fits into the designing philosophies of ZChains. Libp2p is:
Modular
Extensible
Fast
Most importantly, it provides a great foundation for more advanced features, which we'll cover later on.
The separation of the synchronization and consensus protocols allows for modularity and implementation of custom sync and consensus mechanisms - depending on how the client is being run.
ZChains is designed to offer off-the-shelf pluggable consensus algorithms.
The current list of supported consensus algorithms:
IBFT PoS
The Blockchain layer is the central layer that coordinates everything in the ZChains system. It is covered in depth in the corresponding Modules section.
The State inner layer contains state transition logic. It deals with how the state changes when a new block is included. It is covered in depth in the corresponding Modules section.
The JSON RPC layer is an API layer that dApp developers use to interact with the blockchain. It is covered in depth in the corresponding Modules section.
The TxPool layer represents the transaction pool, and it is closely linked with other modules in the system, as transactions can be added from multiple entry points.
The GRPC layer is vital for operator interactions. Through it, node operators can easily interact with the client, providing an enjoyable UX.
This section details the present commands, command flags in the EVMBuilder Edge, and how they're used.
JSON OUTPUT SUPPORT
The --json
flag is supported on some commands. This flag instructs the command to print the output in JSON format
server flags
seal
SyntaxExample
server [--seal SHOULD_SEAL]
Sets the flag indicating that the client should seal blocks. Default: true
.
data-dir
SyntaxExample
server [--data-dir DATA_DIRECTORY]
Used to specify the data directory used for storing EVMBuilder Edge client data. Default: ./test-chain
.
jsonrpc
SyntaxExample
server [--jsonrpc JSONRPC_ADDRESS]
Sets the address and port for the JSON-RPC service address:port
. If only port is defined :10001
it will bind to all interfaces 0.0.0.0:10001
. If omitted the service will bind to the default address:port
. Default address: 0.0.0.0:8545
.
grpc
SyntaxExample
server [--grpc-address GRPC_ADDRESS]
Sets the address and port for the gRPC service address:port
. Default address: 127.0.0.1:9632
.
libp2p
SyntaxExample
server [--libp2p LIBP2P_ADDRESS]
Sets the address and port for the libp2p service address:port
. Default address: 127.0.0.1:1478
.
prometheus
SyntaxExample
server [--prometheus PROMETHEUS_ADDRESS]
Sets the address and port for the prometheus server address:port
. If only port is defined :5001
the service will bind to all interfaces 0.0.0.0:5001
. If omitted the service will not be started.
block-gas-target
SyntaxExample
server [--block-gas-target BLOCK_GAS_TARGET]
Sets the target block gas limit for the chain. Default (not enforced): 0
.
A more detailed explanation on the block gas target can be found in the TxPool section.
max-peers
SyntaxExample
server [--max-peers PEER_COUNT]
Sets the client's maximum peer count. Default: 40
.
Peer limit should be specified either by using max-peers
or max-inbound/outbound-peers
flag.
max-inbound-peers
SyntaxExample
server [--max-inbound-peers PEER_COUNT]
Sets the client's maximum inbound peer count. If max-peers
is set, max-inbound-peer limit is calculated using the following formulae.
max-inbound-peer = InboundRatio * max-peers
, where InboundRatio
is 0.8
.
max-outbound-peers
SyntaxExample
server [--max-outbound-peers PEER_COUNT]
Sets the client's maximum outbound peer count. If max-peers
is set, max-outbound-peer count is calculated using the following formulae.
max-outbound-peer = OutboundRatio * max-peers
, where OutboundRatio
is 0.2
.
log-level
SyntaxExample
server [--log-level LOG_LEVEL]
Sets the log level for console output. Default: INFO
.
log-to
SyntaxExample
server [--log-to LOG_FILE]
Defines log file name that will hold all log output from the server command. By default, all server logs will be outputted to console (stdout), but if the flag is set, there will be no output to the console when running server command.
chain
SyntaxExample
server [--chain GENESIS_FILE]
Specifies the genesis file used for starting the chain. Default: ./genesis.json
.
join
SyntaxExample
server [--join JOIN_ADDRESS]
Specifies the address of the peer that should be joined.
nat
SyntaxExample
server [--nat NAT_ADDRESS]
Sets the external IP address without the port, as it can be seen by peers.
dns
SyntaxExample
server [--dns DNS_ADDRESS]
Sets the host DNS address. This can be used to advertise an external DNS. Supports dns
,dns4
,dns6
.
price-limit
SyntaxExample
server [--price-limit PRICE_LIMIT]
Sets minimum gas price limit to enforce for acceptance into the pool. Default: 1
.
max-slots
SyntaxExample
server [--max-slots MAX_SLOTS]
Sets maximum slots in the pool. Default: 4096
.
config
SyntaxExample
server [--config CLI_CONFIG_PATH]
Specifies the path to the CLI config. Supports .json
.
secrets-config
SyntaxSecond Tab
server [--secrets-config SECRETS_CONFIG]
Sets the path to the SecretsManager config file. Used for Hashicorp Vault, AWS SSM and GCP Secrets Manager. If omitted, the local FS secrets manager is used.
dev
SyntaxExample
server [--dev DEV_MODE]
Sets the client to dev mode. Default: false
.
dev-interval
SyntaxExample
server [--dev-interval DEV_INTERVAL]
Sets the client's dev notification interval in seconds. Default: 0
.
no-discover
SyntaxExample
server [--no-discover NO_DISCOVER]
Prevents the client from discovering other peers. Default: false
.
restore
SyntaxExample
server [--restore RESTORE]
Restore blocks from the specified archive file
block-time
SyntaxExample
server [--block-time BLOCK_TIME]
Sets block production time in seconds. Default: 2
ibft-base-timeout
SyntaxExample
server [--ibft-base-timeout IBFT_BASE_TIMEOUT]
Sets the base value of timeout on IBFT consensus. IBFT consensus timeout is calculated by BaseTimeout + 2^(round)
, or BaseTimeout * 30
where round exceeds 8. It needs to be larger than block time and BlockTime * 5
is set if it's not specified.
access-control-allow-origins
SyntaxExample
server [--access-control-allow-origins ACCESS_CONTROL_ALLOW_ORIGINS]
Sets the authorized domains to be able to share responses from JSON-RPC requests. Add multiple flags --access-control-allow-origins "https://example1.com" --access-control-allow-origins "https://example2.com"
to authorize multiple domains. If omitted Access-Control-Allow-Origins header will be set to *
and all domains will be authorized.
genesis flags
dir
SyntaxExample
genesis [--dir DIRECTORY]
Sets the directory for the EVMBuilder Edge genesis data. Default: ./genesis.json
.
name
SyntaxExample
genesis [--name NAME]
Sets the name for the chain. Default: polyton-edge
.
pos
SyntaxExample
genesis [--pos IS_POS]
Sets the flag indicating that the client should use Proof of Stake IBFT. Defaults to Proof of Authority if flag is not provided or false
.
epoch-size
SyntaxExample
genesis [--epoch-size EPOCH_SIZE]
Sets the epoch size for the chain. Default 100000
.
premine
SyntaxExample
genesis [--premine ADDRESS:VALUE]
Sets the premined accounts and balances in the format address:amount
. The amount can be in either decimal or hex. Default premined balance: 0x3635C9ADC5DEA00000
.
chainid
SyntaxExample
genesis [--chain-id CHAIN_ID]
Sets the ID of the chain. Default: 100
.
ibft-validators-prefix-path
SyntaxExample
genesis [--ibft-validators-prefix-path IBFT_VALIDATORS_PREFIX_PATH]
Prefix path for validator folder directory. Needs to be present if the flag ibft-validator
is omitted.
ibft-validator
SyntaxExample
genesis [--ibft-validator IBFT_VALIDATOR_LIST]
Sets passed in addresses as IBFT validators. Needs to be present if the flag ibft-validators-prefix-path
is omitted.
block-gas-limit
SyntaxExample
genesis [--block-gas-limit BLOCK_GAS_LIMIT]
Refers to the maximum amount of gas used by all operations in a block. Default: 5242880
.
consensus
SyntaxExample
genesis [--consensus CONSENSUS_PROTOCOL]
Sets consensus protocol. Default: pow
.
bootnode
SyntaxExample
genesis [--bootnode BOOTNODE_URL]
Multiaddr URL for p2p discovery bootstrap. This flag can be used multiple times. Instead of an IP address, the DNS address of the bootnode can be provided.
max-validator-count
SyntaxExample
genesis [--max-validator-count MAX_VALIDATOR_COUNT]
The maximum number of stakers able to join the validator set in a PoS consensus. This number cannot exceed the value of MAX_SAFE_INTEGER (2^53 - 2).
min-validator-count
SyntaxExample
genesis [--min-validator-count MIN_VALIDATOR_COUNT]
The minimum number of stakers needed to join the validator set in a PoS consensus. This number cannot exceed the value of max-validator-count. Defaults to 1.
Peer Commands
peers add flags
addr
SyntaxExample
peers add --addr PEER_ADDRESS
Peer's libp2p address in the multiaddr format.
grpc-address
SyntaxExample
peers add [--grpc-address GRPC_ADDRESS]
Address of the gRPC API. Default: 127.0.0.1:9632
.
peers list flags
grpc-address
SyntaxExample
peers list [--grpc-address GRPC_ADDRESS]
Address of the gRPC API. Default: 127.0.0.1:9632
.
peers status flags
peer-id
SyntaxExample
peers status --peer-id PEER_ID
Libp2p node ID of a specific peer within p2p network.
grpc-address
SyntaxExample
peers status [--grpc-address GRPC_ADDRESS]
Address of the gRPC API. Default: 127.0.0.1:9632
.
IBFT Commands
ibft snapshot flags
number
SyntaxExample
ibft snapshot [--number BLOCK_NUMBER]
The block height (number) for the snapshot.
grpc-address
SyntaxExample
ibft snapshot [--grpc-address GRPC_ADDRESS]
Address of the gRPC API. Default: 127.0.0.1:9632
.
ibft candidates flags
grpc-address
SyntaxExample
ibft candidates [--grpc-address GRPC_ADDRESS]
Address of the gRPC API. Default: 127.0.0.1:9632
.
ibft propose flags
vote
SyntaxExample
ibft propose --vote VOTE
Proposes a change to the validator set. Possible values: [auth, drop]
.
addr
SyntaxExample
ibft propose --addr ETH_ADDRESS
Address of the account to be voted for.
grpc-address
SyntaxExample
peers add [--grpc-address GRPC_ADDRESS]
Address of the gRPC API. Default: 127.0.0.1:9632
.
ibft status flags
grpc-address
SyntaxExample
peers list [--grpc-address GRPC_ADDRESS]
Address of the gRPC API. Default: 127.0.0.1:9632
.
ibft switch flags
chain
SyntaxExample
ibft switch [--chain GENESIS_FILE]
Specifies the genesis file to update. Default: test
.
type
SyntaxExample
ibft switch [--type TYPE]
Specifies the IBFT type to switch. Possible values: [PoS]
.
deployment
SyntaxExample
ibft switch [--deployment DEPLOYMENT]
Specifies the height of contract deployment. Only available with PoS.
from
SyntaxExample
ibft switch [--from FROM]
max-validator-count
SyntaxExample
ibft switch [--max-validator-count MAX_VALIDATOR_COUNT]
The maximum number of stakers able to join the validator set in a PoS consensus. This number cannot exceed the value of MAX_SAFE_INTEGER (2^53 - 2).
min-validator-count
SyntaxExample
ibft switch [--min-validator-count MIN_VALIDATOR_COUNT]
The minimum number of stakers needed to join the validator set in a PoS consensus. This number cannot exceed the value of max-validator-count. Defaults to 1.
Specifies the beginning height of the fork.
Transaction Pool Commands
txpool status flags
grpc-address
SyntaxExample
txpool status [--grpc-address GRPC_ADDRESS]
Address of the gRPC API. Default: 127.0.0.1:9632
.
txpool subscribe flags
grpc-address
SyntaxExample
txpool subscribe [--grpc-address GRPC_ADDRESS]
Address of the gRPC API. Default: 127.0.0.1:9632
.
promoted
SyntaxExample
txpool subscribe [--promoted LISTEN_PROMOTED]
Subscribes for promoted tx events in the TxPool.
dropped
SyntaxExample
txpool subscribe [--dropped LISTEN_DROPPED]
Subscribes for dropped tx events in the TxPool.
demoted
SyntaxExample
txpool subscribe [--demoted LISTEN_DEMOTED]
Subscribes for demoted tx events in the TxPool.
added
SyntaxExample
txpool subscribe [--added LISTEN_ADDED]
Subscribes for added tx events to the TxPool.
enqueued
SyntaxExample
txpool subscribe [--enqueued LISTEN_ENQUEUED]
Subscribes for enqueued tx events in the account queues.
Blockchain commands
status flags
grpc-address
SyntaxExample
status [--grpc-address GRPC_ADDRESS]
Address of the gRPC API. Default: 127.0.0.1:9632
.
monitor flags
grpc-address
SyntaxExample
monitor [--grpc-address GRPC_ADDRESS]
Address of the gRPC API. Default: 127.0.0.1:9632
.
secrets init flags
config
SyntaxExample
secrets init [--config SECRETS_CONFIG]
Sets the path to the SecretsManager config file. Used for Hashicorp Vault. If omitted, the local FS secrets manager is used.
data-dir
SyntaxExample
secrets init [--data-dir DATA_DIRECTORY]
Sets the directory for the EVMBuilder Edge data if the local FS is used.
secrets generate flags
dir
SyntaxExample
secrets generate [--dir DATA_DIRECTORY]
Sets the directory for the secrets manager configuration file Default: ./secretsManagerConfig.json
type
SyntaxExample
secrets generate [--type TYPE]
Specifies the type of the secrets manager [hashicorp-vault
]. Default: hashicorp-vault
token
SyntaxExample
secrets generate [--token TOKEN]
Specifies the access token for the service
server-url
SyntaxExample
secrets generate [--server-url SERVER_URL]
Specifies the server URL for the service
name
SyntaxExample
secrets generate [--name NODE_NAME]
Specifies the name of the node for on-service record keeping. Default: z-edge-node
namespace
SyntaxExample
secrets generate [--namespace NAMESPACE]
Specifies the namespace used for the Hashicorp Vault secrets manager. Default: admin
Status Response
The response object is defined using Protocol Buffers.
minimal/proto/system.proto
Copy
Monitor Response
minimal/proto/system.proto
Copy
loadbot flags
tps
SyntaxExample
loadbot [--tps NUMBER_OF_TXNS_PER_SECOND]
The number of transactions per second to send. Default: 100
.
mode
SyntaxExample
loadbot [--mode MODE]
Sets the loadbot run mode [transfer
, deploy
]. Default: transfer
.
chain-id
SyntaxExample
loadbot [--chain-id CHAIN_ID]
Sets the network chain ID for transactions. Default: 100
.
gas-price
SyntaxExample
loadbot [--gas-price GAS_PRICE]
The gas price that should be used for the transactions. If omitted, the average gas price is fetched from the network.
gas-limit
SyntaxExample
loadbot [--gas-limit GAS_LIMIT]
The gas limit that should be used for the transactions. If omitted, the gas limit is estimated before starting the loadbot.
grpc-address
SyntaxExample
loadbot --grpc-address GRPC_ADDRESS
The GRPC endpoint used to send transactions
detailed
SyntaxExample
loadbot [--detailed DETAILED]
Flag indicating if the error logs should be shown. Default: false
.
contract
SyntaxExample
loadbot [--contract CONTRACT_PATH]
The path to the contract JSON artifact containing the bytecode. If omitted, a default contract is used.
sender
SyntaxExample
loadbot [--sender ADDRESS]
Address of the sender account.
receiver
SyntaxExample
loadbot [--receiver ADDRESS]
Address of the receiver account.
jsonrpc
SyntaxExample
loadbot [--jsonrpc ENDPOINT]
A JSON RPC endpoint used to send transactions.
count
SyntaxExample
loadbot [--count COUNT]
The total number of transactions to send. Default: 1000
.
value
SyntaxExample
loadbot [--value VALUE]
The value to send in each transaction.
max-conns
SyntaxExample
loadbot [--max-conns MAX_CONNECTIONS_COUNT]
Sets the maximum no.of connections allowed per host. Default: 2*tps
.
backup flags
grpc-address
SyntaxExample
backup [--grpc-address GRPC_ADDRESS]
Address of the gRPC API. Default: 127.0.0.1:9632
.
out
SyntaxExample
backup [--out OUT]
Path of archive file to save.
from
SyntaxExample
from [--from FROM]
The beginning height of blocks in archive. Default: 0.
to
SyntaxExample
to [--to TO]
The end height of blocks in archive.
The genesis file should be used to set the initial state of the blockchain (ex. if some accounts should have a starting balance).
The following ./genesis.json file is generated:
Copy
Data Directory
When executing the data-dir flag, a test-chain folder is generated. The folder structure consists of the following sub-folders:
blockchain - Stores the LevelDB for blockchain objects
trie - Stores the LevelDB for the Merkle tries
keystore - Stores private keys for the client. This includes the libp2p private key and the sealing/validator private key
consensus - Stores any consensus information that the client might need while working
Please refer to the installation method more applicable to you.
Our recommendation is to use the pre-built releases and verify the provided checksums.
Prior to using go install
make sure that you have Go >=1.17
installed and properly configured.
The stable branch is develop
.
Copy
go install
Prior to using go install
make sure that you have Go >=1.17
installed and properly configured.
go install github.com/
ZChain-168168/ZChain-Blockchain/polygon-edge@develop
The binary will be available in your GOBIN
environment variable, and will include the latest changes from the mainline develop
branch.
This guide assumes that:
You have a working ZChains network up and running
Both your JSON-RPC and GRPC endpoints are reachable
The ZChains Loadbot is a helper utility that is meant to stress test a ZChains network.
Currently, it supports 2 modes:
transfer
- mode that does stress testing using fund-transfer transactions. [Default].
deploy
- mode that deploys specified smart contracts with each transaction.
The transfer mode assumes that there is a sender account that has initial funds to conduct the loadbot run.
The sender's account address and private key need to be set in the environment variables:
The deploy mode conducts contract deployment with each new transaction in the loadbot run. The contract being deployed can be specified using , or if the contract path is omitted, a default Greeter.sol
is used instead.
This section covers some basic terminology regarding the loadbot configuration.
count - The number of transactions to be submitted in the specified mode
tps - The number of transactions that should be submitted to the node per second
As an example, here is a valid command you can use to run the loadbot using two premined accounts:
You should get a result similar to this on your terminal :
Test-net and Main-net file in:
Command
Description
server
The default command that starts the blockchain client, by bootstrapping all modules together
genesis
Generates a genesis.json file, which is used to set a predefined chain state before starting the client. The structure of the genesis file is described below
Command
Description
peers add
Adds a new peer using their libp2p address
peers list
Lists all the peers the client is connected to through libp2p
peers status
Returns the status of a specific peer from the peers list, using the libp2p address
Command
Description
ibft snapshot
Returns the IBFT snapshot
ibft candidates
Queries the current set of proposed candidates, as well as candidates that have not been included yet
ibft propose
Proposes a new candidate to be added/removed from the validator set
ibft status
Returns the overall status of the IBFT client
ibft switch
Add fork configurations into genesis.json file to switch IBFT type
Command
Description
txpool status
Returns the number of transactions in the pool
txpool subscribe
Subscribes for events in the transaction pool
Command
Description
status
Returns the status of the client. The detailed response can be found below
monitor
Subscribes to a blockchain event stream. The detailed response can be found below
version
Returns the current version of the client
Command
Description
secrets init
Initializes the private keys to the corresponding secrets manager
secrets generate
Generates a secrets manager configuration file which can be parsed by the EVMBuilder Edge
The Crypto module contains crypto utility functions.
The Chain module contains chain parameters (active forks, consensus engine, etc.)
chains - Predefined chain configurations (mainnet, goerli, ibft)
The Helper module contains helper packages.
dao - Dao utils
enode - Enode encoding/decoding function
hex - Hex encoding/decoding functions
ipc - IPC connection functions
keccak - Keccak functions
rlputil - Rlp encoding/decoding helper function
The Command module contains interfaces for CLI commands.
The Consensus module provides an interface for consensus mechanisms.
Currently, the following consensus engines are available:
IBFT PoS
The Zchains wants to maintain a state of modularity and pluggability. This is why the core consensus logic has been abstracted away, so new consensus mechanisms can be built on top, without compromising on usability and ease of use.
The Consensus interface is the core of the mentioned abstraction.
The VerifyHeader method represents a helper function which the consensus layer exposes to the blockchain layer It is there to handle header verification
The Start method simply starts the consensus process, and everything associated with it. This includes synchronization, sealing, everything that needs to be done
The Close method closes the consensus connection
There may be times when you might want to pass in a custom location for the consensus protocol to store data, or perhaps a custom key-value map that you want the consensus mechanism to use. This can be achieved through the Config struct, which gets read when a new consensus instance is created.
The blockchain header object, among other fields, has a field called ExtraData. To review the fields present in the block header, please check out the State in Ethereum section.
IBFT uses this extra field to store operational information regarding the block, answering questions like:
"Who signed this block?"
"Who are the validators for this block?"
These extra fields for IBFT are defined as follows:
In order for the node to sign information in IBFT, it leverages the signHash method:
Another notable method is the VerifyCommittedFields method, which verifies that the committed seals are from valid validators:
Snapshots, as the name implies, are there to provide a snapshot, or the state of a system at any block height (number).
Snapshots contain a set of nodes who are validators, as well as voting information (validators can vote for other validators). Validators include voting information in the Miner header filed, and change the value of the nonce:
Nonce is all 1s if the node wants to remove a validator
Nonce is all 0s if the node wants to add a validator
Snapshots are calculated using the processHeaders method:
This method is usually called with 1 header, but the flow is the same even with multiple headers. For each passed-in header, IBFT needs to verify that the proposer of the header is the validator. This can be done easily by grabbing the latest snapshot, and checking if the node is in the validator set.
Next, the nonce is checked. The vote is included, and tallied - and if there are enough votes a node is added/removed from the validator set, following which the new snapshot is saved.
Snapshot Store
The snapshot service manages and updates an entity called the snapshotStore, which stores the list of all available snapshots. Using it, the service is able to quickly figure out which snapshot is associated with which block height.
To start up IBFT, the Polygon Edge firstly needs to set up the IBFT transport:
It essentially creates a new topic with IBFT proto, with a new proto buff message. The messages are meant to be used by validators. The Polygon Edge then subscribes to the topic and handles messages accordingly.
MessageReq
The message exchanged by validators:
The View field in the MessageReq represents the current node position inside the chain. It has a round, and a sequence attribute.
round represents the proposer round for the height
sequence represents the height of the blockchain
The msgQueue filed in the IBFT implementation has the purpose of storing message requests. It orders messages by the View (firstly by sequence, then by round). The IBFT implementation also possesses different queues for different states in the system.
After the consensus mechanism is started using the Start method, it runs into an infinite loop which simulates a state machine:
SyncState
All nodes initially start in the Sync state.
This is because fresh data needs to be fetched from the blockchain. The client needs to find out if it's the validator, find the current snapshot. This state resolves any pending blocks.
After the sync finishes, and the client determines it is indeed a validator, it needs to transfer to AcceptState. If the client is not a validator, it will continue syncing, and stay in SyncState
AcceptState
The Accept state always check the snapshot and the validator set. If the current node is not in the validators set, it moves back to the Sync state.
On the other hand, if the node is a validator, it calculates the proposer. If it turns out that the current node is the proposer, it builds a block, and sends preprepare and then prepare messages.
Preprepare messages - messages sent by proposers to validators, to let them know about the proposal
Prepare messages - messages where validators agree on a proposal. All nodes receive all prepare messages
Commit messages - messages containing commit information for the proposal
If the current node is not a validator, it uses the getNextMessage method to read a message from the previously shown queue. It waits for the preprepare messages. Once it is confirmed everything is correct, the node moves to the Validate state.
ValidateState
The Validate state is rather simple - all nodes do in this state is read messages and add them to their local snapshot state.
One of the main modules of the ZChains are Blockchain and State.
Blockchain is the powerhouse that deals with block reorganizations. This means that it deals with all the logic that happens when a new block is included in the blockchain.
State represents the state transition object. It deals with how the state changes when a new block is included. Among other things, State handles:
Executing transactions
Executing the EVM
Changing the Merkle tries
Much more, which is covered in the corresponding State section 🙂
The key takeaway is that these 2 parts are very connected, and they work closely together in order for the client to function. For example, when the Blockchain layer receives a new block (and no reorganization occurred), it calls the State to perform a state transition.
Blockchain also has to deal with some parts relating to consensus (ex. is this ethHash correct?, is this PoW correct?). In one sentence, it is the main core of logic through which all blocks are included.
One of the most important parts relating to the Blockchain layer is the WriteBlocks method:
The WriteBlocks method is the entry point to write blocks into the blockchain. As a parameter, it takes in a range of blocks. Firstly, the blocks are validated. After that, they are written to the chain.
The actual state transition is performed by calling the processBlock method within WriteBlocks.
It is worth mentioning that, because it is the entry point for writing blocks to the blockchain, other modules (such as the Sealer) utilize this method.
There needs to be a way to monitor blockchain-related changes. This is where Subscriptions come in.
Subscriptions are a way to tap into blockchain event streams and instantly receive meaningful data.
The Blockchain Events contain information regarding any changes made to the actual chain. This includes reorganizations, as well as new blocks:
:::tip Refresher Do you remember when we mentioned the monitor command in the CLI Commands?
The Blockchain Events are the original events that happen in Zchains, and they're later mapped to a Protocol Buffers message format for easy transfer. :::
As mentioned before, ZChains is a set of different modules, all connected to each other. The Blockchain is connected to the State, or for example, Synchronization, which pipes new blocks into the Blockchain.
Minimal is the cornerstone for these inter-connected modules. It acts as a central hub for all the services that run on the ZChains.
Among other things, Minimal is responsible for:
Setting up data directories
Creating a keystore for libp2p communication
Creating storage
Setting up consensus
Setting up the blockchain object with GRPC, JSON RPC, and Synchronization
A node has to communicate with other nodes on the network, in order to exchange useful information. To accomplish this task, the ZChains leverages the battle-tested libp2p framework.
The choice to go with libp2p is primarily focused on:
Speed - libp2p has a significant performance improvement over devp2p (used in GETH and other clients)
Extensibility - it serves as a great foundation for other features of the system
Modularity - libp2p is modular by nature, just like the Polygon Edge. This gives greater flexibility, especially when parts of the Polygon Edge need to be swappable
On top of libp2p, the Polygon Edge uses the GRPC protocol. Technically, the Polygon Edge uses several GRPC protocols, which will be covered later on.
The GRPC layer helps abstract all the request/reply protocols and simplifies the streaming protocols needed for the Polygon Edge to function.
GRPC relies on Protocol Buffers to define services and message structures. The services and structures are defined in .proto files, which are compiled and are language-agnostic.
Earlier, we mentioned that the Polygon Edge leverages several GRPC protocols. This was done to boost the overall UX for the node operator, something which often lags with clients like GETH and Parity.
The node operator has a better overview of what is going on with the system by calling the GRPC service, instead of sifting through logs to find the information they're looking for.
The following section might seem familiar because it was briefly covered in the CLI Commands section.
The GRPC service that is intended to be used by node operators is defined like so:
:::tip The CLI commands actually call the implementations of these service methods.
These methods are implemented in minimal/system_service.go. :::
The ZChains also implements several service methods that are used by other nodes on the network. The mentioned service is described in the Protocol section.
The JSON RPC module implements the JSON RPC API layer, something that dApp developers use to interact with the blockchain.
It includes support for standard json-rpc endpoints, as well as websocket endpoints.
TheZchains uses the blockchain interface to define all the methods that the JSON RPC module needs to use, in order to deliver its endpoints.
The blockchain interface is implemented by the Minimal server. It is the base implementation that's passed into the JSON RPC layer.
All the standard JSON RPC endpoints are implemented in:
The Filter Manager is a service that runs alongside the JSON RPC server.
It provides support for filtering blocks on the blockchain. Specifically, it includes both a log and a block level filter.
The Filter Manager relies heavily on Subscription Events, mentioned in the Blockchain section
Filter Manager events get dispatched in the Run method:
The ZChains introduces a comprehensive tokenomics framework designed to support network operations, governance, and growth. With a total supply of 15 billion ZCD, the distribution is meticulously planned to ensure long-term viability and community engagement.
Amount: 15 Billion ZCD (100.00%)
Both total and maximum supplies are capped at 15 billion ZCD, ensuring a predictable inflation rate and safeguarding value.
Amount: Available Soon
Fully Diluted Market Cap: Available Soon
The circulating supply represents the number of tokens currently available to the public and actively traded.
Liquidity and Exchange Listings The majority of tokens are allocated to liquidity pools on top centralized exchanges to ensure ample market liquidity and facilitate easy trading.
Allocation: 10 Billion ZCD (66.67%)
Purpose: Supports liquidity on centralized exchanges, facilitating broader market access and reducing price volatility.
Marketing
Allocation: 2 Billion ZCD (13.33%)
Purpose: Funds the ecosystem's growth through investments in new projects and community initiatives.
Team Holding
Allocation: 2 Billion ZCD (13.33%)
Purpose: The allocation for the team is structured with a 12-month lock, followed by linear vesting over a 60-month period. This extended vesting period aligns with our vision for sustained collaboration and dedication.
Foundation
Allocation: 1 Billion ZCD (6.67%)
Purpose: Reserved for governance activities and long term initiatives.
The Types module implements core object types, such as:
Address
Hash
Header
lots of helper functions
Unlike clients such as GETH, the ZChains doesn't use reflection for the encoding. The preference was to not use reflection because it introduces new problems, such as performance degradation, and harder scaling.
The Types module provides an easy-to-use interface for RLP marshaling and unmarshalling, using the FastRLP package.
Marshaling is done through the MarshalRLPWith and MarshalRLPTo methods. The analogous methods exist for unmarshalling.
By manually defining these methods, the ZChains doesn't need to use reflection. In rlp_marshal.go, you can find methods for marshaling:
Bodies
Blocks
Headers
Receipts
Logs
Transactions
The ZChains currently utilizes LevelDB for data storage, as well as an in-memory data store.
Throughout the ZChains, when modules need to interact with the underlying data store, they don't need to know which DB engine or service they're speaking to.
The DB layer is abstracted away between a module called Storage, which exports interfaces that modules query.
Each DB layer, for now only LevelDB, implements these methods separately, making sure they fit in with their implementation.
In order to make querying the LevelDB storage deterministic, and to avoid key storage clashing, the ZChains leverages prefixes and sub-prefixes when storing data
The plans for the near future include adding some of the most popular DB solutions, such as:
PostgreSQL
MySQL
The Sealer is an entity that gathers the transactions, and creates a new block. Then, that block is sent to the Consensus module to seal it.
The final sealing logic is located within the Consensus module.
:::caution Work in progress The Sealer and the Consensus modules will be combined into a single entity in the near future.
The new module will incorporate modular logic for different kinds of consensus mechanisms, which require different sealing implementations:
PoS (Proof of Stake)
PoA (Proof of Authority)
Currently, the Sealer and the Consensus modules work with PoW (Proof of Work). :::
To truly understand how State works, you must understand some basic Ethereum concepts.
We highly recommend reading the State in Ethereum guide.
Now that we've familiarized ourselves with basic Ethereum concepts, the next overview should be easy.
We mentioned that the World state trie has all the Ethereum accounts that exist. These accounts are the leaves of the Merkle trie. Each leaf has encoded Account State information.
This enables the Zchains to get a specific Merkle trie, for a specific point in time. For example, we can get the hash of the state at block 10.
The Merkle trie, at any point in time, is called a Snapshot.
We can have Snapshots for the state trie, or for the storage trie - they are basically the same. The only difference is in what the leaves represent:
In the case of the storage trie, the leaves contain an arbitrary state, which we cannot process or know what's in there
In the case of the state trie, the leaves represent accounts
The Snapshot interface is defined as such:
The information that can be committed is defined by the Object struct:
The implementation for the Merkle trie is in the state/immutable-trie folder. state/immutable-trie/state.go implements the State interface.
state/immutable-trie/trie.go is the main Merkle trie object. It represents an optimized version of the Merkle trie, which reuses as much memory as possible.
state/executor.go includes all the information needed for the Zchains to decide how a block changes the current state. The implementation of ProcessBlock is located here.
The apply method does the actual state transition. The executor calls the EVM.
When a state transition is executed, the main module that executes the state transition is the EVM (located in state/runtime/evm).
The dispatch table does a match between the opcode and the instruction.
The core logic that powers the EVM is the Run loop.
This is the main entry point for the EVM. It does a loop and checks the current opcode, fetches the instruction, checks if it can be executed, consumes gas and executes the instruction until it either fails or stops.
If you want to include a fix, or just contribute to the code, it is highly encouraged for you to reach out to the team first. The ZChains uses a relatively basic feature proposition template, that is concise and to the point.
Please provide a detailed description of what was done in this PR
Please complete this section if any breaking changes have been made, otherwise delete it
Please complete this section if you ran manual tests for this functionality, otherwise delete it
Please link the documentation update PR in this section if it's present, otherwise delete it
Please post additional comments in this section if you have them, otherwise delete it
Sometimes things break. It's always better to let the team know about any issues you might be encountering. On the ZChains GitHub page, you can file a new issue, and start discussing it with the team.
Describe your issue in as much detail as possible here
OS and version
version of the ZChains
branch that causes this issue
Tell us how to reproduce this issue
Where the issue is, if you know
Which commands triggered the issue, if any
Tell us what should happen
Tell us what happens instead
Please paste any logs here that demonstrate the issue, if they exist
If you have an idea of how to fix this issue, please write it down here, so we can begin discussing it
The ZChains has two types of private keys that it directly manages:
Private key used for the consensus mechanism
Private key used for networking by libp2p
Currently, the ZChains doesn't offer support for direct account management.
Based on the directory structure outlined in the , the ZChains stores these mentioned key files in two distinct directories - consensus and keystore.
The private keys are stored in simple Base64 format, so they can be human-readable and portable.
:::info Key Type All private key files generated and used inside the ZChains are relying on ECDSA with the curve .
As the curve is non-standard, it cannot be encoded and stored in any standardized PEM format. Importing keys that don't conform to this key type is not supported. :::
The private key file mentioned as the consensus private key is also referred to as the validator private key. This private key is used when the node is acting as a validator in the network and needs to sign new data.
The private key file is located in consensus/validator.key
, and adheres to the mentioned.
The private key file mentioned for networking is used by libp2p to generate the corresponding PeerID, and allow the node to participate in the network.
As the key files are stored in simple Base64 on disk, they can be easily backed up or imported.
:::caution Changing the key files Any kind of change made to the key files on an already set up / running network can lead to serious network/consensus disruption, since the consensus and peer discovery mechanisms store the data derived from these keys in node-specific storage, and rely on this data to initiate connections and perform consensus logic :::
Starting the server with various configuration options can be done using a configuration file instead of using just flags. The command used to start the server with a config file: polygon-edge server --config <config_file_name>
The configuration with default settings for the ZChains server can be exported into a config file in either yaml
or json
file format. This file can be used as a template for running the server using a configuration file.
To generate the config file in yaml
format:
or just
the config file named default-config.yaml
will be created in the same directory that this command has been run from.
File example:
To generate the config file in json
format:
the config file named default-config.json
will be created in the same directory that this command has been run from.
File example:
The following is the sample format for the configuration file. It's written in TypeScript to express the properties types (string
, number
, boolean
), from it you could derive your configuration. It's worth mentioning that the PartialDeep
type from type-fest
is used to express that all properties are optional.
Before discussing the main data objects in Ethereum, we need to go over what Merkle trees are, and what are the properties that make them useful.
A Merkle tree is a tree data structure, where the leaf nodes contain the hash of a block of data and the non-leaf nodes contain the hash of its children nodes.
In a Merkle tree, any change to the underlying data causes the hash of the node referring to the data to change. Since each parent node hash depends on the data of its children, any change to the data of a child node causes the parent hash to change. This happens to each parent node up to the root node. Therefore, any change to the data at the leaf nodes causes the root node hash to change. From this, we can derive two important properties:
We don't need to compare all the data across the leaf nodes to know if they have the same data. We can just compare the root node hash.
If we want to prove that specific data is part of the tree, we can use a technique called Merkle proofs. We won't dive into details here but it is an easy and effective way to prove that a piece of data is in the Merkle tree.
The first property is important because it makes it possible to store only a hash of the root node to represent the data at that point in time. This means we only need to store the root hash of the tree representing the block on the blockchain (as opposed to storing all the data in the blockchain) and still keep the data immutable.
The world state is a mapping between addresses (accounts) and account states.
The world state is not stored on the blockchain, but the Yellow Paper states it is expected that implementations store this data in a trie (also referred to as the state database or state trie). The world state can be seen as the global state that is constantly updated by transaction executions.
All the information about Ethereum accounts lives in the world state and is stored in the world state trie. If you want to know the balance of an account, or the current state of a smart contract, you query the world state trie to retrieve the account state of that account. We’ll describe how this data is stored shortly.
In Ethereum, there are two types of accounts: External Owned Accounts (EOA) and Contract Accounts.
An EOA account is an account that regular users have, that they can use to send Ether to one another and deploy smart contracts with.
A contract account is an account that is created when a smart contract is deployed. Every smart contract has its own Ethereum account.
The account state contains information about an Ethereum account. For example, it stores how much Ether an account has, and the number of transactions sent by the account. Each account has an account state.
Let's take a look into each one of the fields in the account state:
nonce - Number of transactions sent from this address (if this is an External Owned Account - EOA) or the number of contract creations made by this account
balance - Total Ether (in Wei) owned by this account
storageRoot - Hash of the root node of the account storage trie
codeHash - For contract accounts, the hash of the EVM code of this account. For EOAs, this will be empty.
One important detail about the account state is that all fields (except the codeHash) are mutable. For example, when one account sends some Ether to another, the nonce will be incremented, and the balance will be updated to reflect the new balance.
One of the consequences of the codeHash being immutable is that if you deploy a contract with a bug, you can't update the same contract. You need to deploy a new contract (the buggy version will be available forever). This is why it is important to use tools like Truffle to develop and test your smart contracts and follow the best practices when working with Solidity.
The Account Storage trie is where the data associated with an account is stored. This is only relevant for Contract Accounts, as for EOAs the storageRoot is empty, and the codeHash is the hash of an empty string.
Transactions are what make the state change from the current state to the next state. In Ethereum, we have three types of transactions:
Transactions that transfer value between two EOAs (e.g, change the sender and receiver account balances)
Transactions that send a message call to a contract (e.g, set a value in the smart contract by sending a message call that executes a setter method)
Transactions that deploy a contract (therefore, create an account, the contract account)
:::tip Clarification Technically, 1 and 2 are the same - transactions that send message calls that affect an account state, either EOA or contract accounts.
It is easier to think about them as three different types. :::
These are the fields of a transaction:
nonce - Number of transactions sent by the account that created the transaction
gasPrice - Value (in Wei) that will be paid per unit of gas for the computation costs of executing this transaction
gasLimit - Maximum amount of gas to be used while executing this transaction
to
If this transaction is transferring Ether, the address of the EOA account that will receive a value transfer
If this transaction is sending a message to a contract (e.g, calling a method in the smart contract), this is address of the contract
If this transaction is creating a contract, this value is always empty
value
If this transaction is transferring Ether, the amount in Wei that will be transferred to the recipient account
If this transaction is sending a message to a contract, the amount of Wei payable by the smart contract receiving the message
If this transaction is creating a contract, this is the amount of Wei that will be added to the balance of the created contract
v, r, s - Values used in the cryptographic signature of the transaction used to determine the sender of the transaction
data (only for value transfer and sending a message call to a smart contract) -Input data of the message call ( e.g, imagine you are trying to execute a setter method in your smart contract, the data field would contain the identifier of the setter method, and the value that should be passed as a parameter)
init (only for contract creation) - The EVM-code utilized for initialization of the contract
Not surprisingly, all transactions in a block are stored in a trie. The root hash of this trie is stored in the... block header! Let's take a look into the anatomy of an Ethereum block.
The block header is divided into two parts, the block header and the block body.
The block header is the blockchain part of Ethereum. This is the structure that contains the hash of its predecessor block (also known as parent block), building a cryptographically guaranteed chain.
The block body contains a list of transactions that have been included in this block, and a list of uncle (ommer) block headers.
The block header contains the following fields:
parentHash - Hash of the block header from the previous block. Each block contains a hash of the previous block, all the way to the first block in the chain. This is how all the data is protected against modifications (any modification in a previous block would change the hash of all blocks after the modified block)
ommersHash - Hash of the uncle blocks headers part of the block body
beneficiary - Ethereum account that will get fees for mining this block
stateRoot - Hash of the root node of the world state trie (after all transactions are executed)
transactionsRoot - Hash of the root node of the transactions trie. This trie contains all transactions in the block body
receiptsRoot - Every time a transaction is executed, Ethereum generates a transaction receipt that contains information about the transaction execution. This field is the hash of the root node of the transactions receipt trie
logsBloom - Bloom filter that can be used to find out if logs were generated on transactions in this block (if you want more details check this Stack Overflow answer). This avoids storing logs in the block (saving a lot of space)
difficulty - Difficulty level of this block. This is a measure of how hard it was to mine this block (I'm not diving into the details of how this is calculated in this post)
number - Number of ancestor blocks. This represents the height of the chain (how many blocks are in the chain). The genesis block has number zero
gasLimit - Each transaction consumes gas. The gas limit specifies the maximum gas that can be used by the transactions included in the block. It is a way to limit the number of transactions in a block
gasUsed - Sum of the gas cost of each transaction in the block
timestamp - Unix timestamp when the block was created. Note that due to the decentralized nature of Ethereum, we can't trust in this value (especially when implementing smart contracts that have time-related business logic)
extraData - Arbitrary byte array that can contain anything. When a miner is creating the block, it can choose to add anything in this field
mixHash - Hash used to verify that a block has been mined properly (if you want to really understand this, read about the Ethash proof-of-work function)
nonce - Same as the mixHash, this value is used to verify that a block has been mined properly
Ethereum has 4 types of tries:
The world state trie contains the mapping between addresses and account states. The hash of the root node of the world state trie is included in a block (in the stateRoot field) to represent the current state when that block was created. We only have one world state trie
The account storage trie contains the data associated with a smart contract. The hash of the root node of the Account storage trie is included in the account state (in the storageRoot field). We have one Account storage trie for each account
The transaction trie contains all the transactions included in a block. The hash of the root node of the Transaction trie is included in the block header (in the transactionsRoot field). We have one transaction trie per block
The transaction receipt trie contains all the transaction receipts for the transactions included in a block. The hash of the root node of the transaction receipts trie is included in also included in the block header (in the receiptsRoot field); We have one transaction receipts trie per block
Objects covered:
World state: the hard drive of the distributed computer that is Ethereum. It is a mapping between addresses and account states
Account state: stores the state of each one of Ethereum's accounts. It also contains the storageRoot of the account state trie, which contains the storage data for the account
Transaction: represents a state transition in the system. It can be a funds transfer, a message call, or a contract deployment
Block: contains the link to the previous block (parentHash) and contains a group of transactions that, when executed, will yield the new state of the system. It also contains the stateRoot, the transactionRoot and the receiptsRoot, the hash of the root nodes of the world state trie, the transaction trie, and the transaction receipts trie, respectively
The TxPool module represents the transaction pool implementation, where transactions are added from different parts of the system. The module also exposes several useful features for node operators, which are covered below.
Node operators can query these GRPC endpoints, as described in the section.
The addImpl method is the bread and butter of the TxPool module. It is the central place where transactions are added in the system, being called from the GRPC service, JSON RPC endpoints, and whenever the client receives a transaction through the gossip protocol.
It takes in as an argument ctx, which just denotes the context from which the transactions are being added (GRPC, JSON RPC...). The other parameter is the list of transactions to be added to the pool.
The key thing to note here is the check for the From field within the transaction:
If the From field is empty, it is regarded as an unencrypted/unsigned transaction. These kinds of transactions are only accepted in development mode
If the From field is not empty, that means that it's a signed transaction, so signature verification takes place
After all these validations, the transactions are considered to be valid.
The fields in the TxPool object that can cause confusion are the queue and sorted lists.
queue - Heap implementation of a sorted list of account transactions (by nonce)
sorted - Sorted list for all the current promoted transactions (all executable transactions). Sorted by gas price
Whenever you submit a transaction, there are three ways it can be processed by the TxPool.
All pending transactions can fit in a block
One or more pending transactions can not fit in a block
One or more pending transactions will never fit in a block
Here, the word fit means that the transaction has a gas limit that is lower than the remaining gas in the TxPool.
The first scenario does not produce any error.
The TxPool remaining gas is set to the gas limit of the last block, lets say 5000
A first transaction is processed and consumes 3000 gas of the TxPool
The remaining gas of the TxPool is now 2000
A second transaction, which is the same as the first one - they both consume 3000 units of gas, is submitted
Since the remaining gas of the TxPool is lower than the transaction gas, it cannot be processed in the current block
It is put back into a pending transaction queue so that it can be processed in the next block
The first block is written, lets call it block #1
The TxPool remaining gas is set to the parent block - block #1's gas limit
The transaction which was put back into the TxPool pending transaction queue is now processed and written in the block
The TxPool remaining gas is now 2000
The second block is written
...
The TxPool remaining gas is set to the gas limit of the last block, lets say 5000
A first transaction is processed and consumes 3000 gas of the TxPool
The remaining gas of the TxPool is now 2000
A second transaction, with a gas field set to 6000 is submitted
Since the block gas limit is lower than the transaction gas, this transaction is discarded
It will never be able to fit in a block
The first block is written
...
This happens whenever you get the following error:
There are situations when nodes want to keep the block gas limit below or at a certain target on a running chain.
The node operator can set the target gas limit on a specific node, which will try to apply this limit to newly created blocks. If the majority of the other nodes also have a similar (or same) target gas limit set, then the block gas limit will always hover around that block gas target, slowly progressing towards it (at max 1/1024 * parent block gas limit
) as new blocks are created.
The node operator sets the block gas limit for a single node to be 5000
Other nodes are configured to be 5000
as well, apart from a single node which is configured to be 7000
When the nodes who have their gas target set to 5000
become proposers, they will check to see if the gas limit is already at the target
If the gas limit is not at the target (it is greater / lower), the proposer node will set the block gas target to at most (1/1024 * parent gas limit) in the direction of the target
Ex: parentGasLimit = 4500
and blockGasTarget = 5000
, the proposer will calculate the gas limit for the new block as 4504.39453125
(4500/1024 + 4500
)
Ex: parentGasLimit = 5500
and blockGasTarget = 5000
, the proposer will calculate the gas limit for the new block as 5494.62890625
(5500 - 5500/1024
)
This ensures that the block gas limit in the chain will be kept at the target, because the single proposer who has the target configured to 7000
cannot advance the limit much, and the majority of the nodes who have it set at 5000
will always attempt to keep it there
It is located in keystore/libp2p.key
, and adheres to the mentioned.
Checkout section to get information on how to use these parameters.
This clear and concise walkthrough of Ethereum's yellow paper was originally posted by Lucas Saldanha, on .
This guide goes into detail on how to set up a Proof of Stake network with the ZChains, how to stake funds for nodes to become validators and how to unstake funds.
It is highly encouraged to read and go through the Local Setup / Cloud Setup sections, before going along with this PoS guide. These sections outline the steps needed to start a Proof of Stake (PoS) cluster with the ZChains.
Currently, there is no limit to the number of validators that can stake funds on the Staking Smart Contract.
The repo for the Staking Smart Contract is located here.
It holds the necessary testing scripts, ABI files and most importantly the Staking Smart Contract itself.
Setting up a network with the ZChains is covered in the Local Setup / Cloud Setup sections.
The only difference between setting up a PoS and PoA cluster is in the genesis generation part.
When generating the genesis file for a PoS cluster, an additional flag is needed --pos
:
Epochs are covered in detail in the Epoch Blocks section.
To set the size of an epoch for a cluster (in blocks), when generating the genesis file, an additional flag is specified --epoch-size
:
This value specified in the genesis file that the epoch size should be 50
blocks.
The default value for the size of an epoch (in blocks) is 100000
.
:::info Lowering the epoch length As outlined in the Epoch Blocks section, epoch blocks are used to update the validator sets for nodes.
The default epoch length in blocks (100000
) may be a long time to way for validator set updates. Considering that new blocks are added ~2s, it would take ~55.5h for the validator set to possibly change.
Setting a lower value for the epoch length ensures that the validator set is updated more frequently. :::
The Staking Smart Contract repo is a Hardhat project, which requires NPM.
To initialize it correctly, in the main directory run:
Scripts for interacting with the deployed Staking Smart Contract are located on the Staking Smart Contract repo.
Create an .env
file with the following parameters in the Smart Contracts repo location:
Where the parameters are:
JSONRPC_URL - the JSON-RPC endpoint for the running node
PRIVATE_KEYS - private keys of the staker address
STAKING_CONTRACT_ADDRESS - the address of the staking smart contract ( default 0x0000000000000000000000000000000000001001
)
:::info Staking address The Staking Smart Contract is pre-deployed at address 0x0000000000000000000000000000000000001001
.
Any kind of interaction with the staking mechanism is done through the Staking Smart Contract at the specified address.
To learn more about the Staking Smart Contract, please visit the Staking Smart Contract section. :::
In order to become part of the validator set, an address needs to stake a certain amount of funds above a threshold.
Currently, the default threshold for becoming part of the validator set is 1 ETH
.
Staking can be initiated by calling the stake
method of the Staking Smart Contract, and specifying a value >= 1 ETH
.
After the .env
file mentioned in the previous section has been set up, and a chain has been started in PoS mode, staking can be done with the following command in the Staking Smart Contract repo:
The stake
Hardhat script stakes a default amount of 1 ETH
, which can be changed by modifying the scripts/stake.ts
file.
If the funds being staked are >= 1 ETH
, the validator set on the Staking Smart Contract is updated, and the address will be part of the validator set starting from the next epoch.
Addresses that have a stake can only unstake all of their funds at once.
After the .env
file mentioned in the previous section has been set up, and a chain has been started in PoS mode, unstaking can be done with the following command in the Staking Smart Contract repo:
All addresses that stake funds are saved to the Staking Smart Contract.
After the .env
file mentioned in the previous section has been set up, and a chain has been started in PoS mode, fetching the list of validators can be done with the following command in the Staking Smart Contract repo:
Currently, the ZChains is concerned with keeping 2 major runtime secrets:
The validator private key used by the node, if the node is a validator
The networking private key used by libp2p, for participating and communicating with other peers
For additional information, please read through the Managing Private Keys Guide
The modules of the ZChains should not need to know how to keep secrets. Ultimately, a module should not care if a secret is stored on a far-away server or locally on the node's disk.
Everything a module needs to know about secret-keeping is knowing to use the secret, knowing which secrets to get or save. The finer implementation details of these operations are delegated away to the SecretsManager
, which of course is an abstraction.
The node operator that's starting the ZChains can now specify which secrets manager they want to use, and as soon as the correct secrets manager is instantiated, the modules deal with the secrets through the mentioned interface - without caring if the secrets are stored on a disk or on a server.
This article details the necessary steps to get the ZChains up and running with a Hashicorp Vault server.
:::info previous guides It is highly recommended that before going through this article, articles on Local Setup and Cloud Setup are read. :::
This article assumes that a functioning instance of the Hashicorp Vault server is already set up.
Additionally, it is required that the Hashicorp Vault server being used for the ZChains should have enabled KV storage.
Required information before continuing:
The server URL (the API URL of the Hashicorp Vault server)
Token (access token used for access to the KV storage engine)
In order for the ZChains to be able to seamlessly communicate with the Vault server, it needs to parse an already generated config file, which contains all the necessary information for secret storage on Vault.
To generate the configuration, run the following command:
Parameters present:
PATH
is the path to which the configuration file should be exported to. Default ./secretsManagerConfig.json
TOKEN
is the access token previously mentioned in the prerequisites section
SERVER_URL
is the URL of the API for the Vault server, also mentioned in the prerequisites section
NODE_NAME
is the name of the current node for which the Vault configuration is being set up as. It can be an arbitrary value. Default polygon-edge-node
:::caution Node names Be careful when specifying node names.
The ZChains uses the specified node name to keep track of the secrets it generates and uses on the Vault instance. Specifying an existing node name can have consequences of data being overwritten on the Vault server.
Secrets are stored on the following base path: secrets/node_name
:::
Now that the configuration file is present, we can initialize the required secret keys with the configuration file set up in step 1, using the --config
:
The PATH
param is the location of the previously generated secrets manager param from step 1.
The genesis file should be generated in a similar manner to the Local Setup and Cloud Setup guides, with minor changes.
Since Hashicorp Vault is being used instead of the local file system, validator addresses should be added through the --ibft-validator
flag:
Now that the keys are set up, and the genesis file is generated, the final step to this process would be starting the Polygon Edge with the server
command.
The server
command is used in the same manner as in the previously mentioned guides, with a minor addition - the --secrets-config
flag:
The PATH
param is the location of the previously generated secrets manager param from step 1.
This guide goes into detail on how to back up and restore a ZChains node instance. It covers the base folders and what they contain, as well as which files are critical for performing a successful backup and restore.
ZChains leverages LevelDB as its storage engine. When starting a ZChains node, the following sub-folders are created in the specified working directory:
blockchain - Stores the blockchain data
trie - Stores the Merkle tries (world state data)
keystore - Stores private keys for the client. This includes the libp2p private key and the sealing/validator private key
consensus - Stores any consensus information that the client might need while working. For now, it stores the node's private validator key
It is critical for these folders to be preserved in order for the ZChains instance to run smoothly.
This section guides you through creating archive data of the blockchain in a running node and restoring it in another instance.
backup
command fetches blocks from a running node by gRPC and generates an archive file. If --from
and --to
are not given in the command, this command will fetch blocks from genesis to latest.
A server imports blocks from an archive at the start when starting with --restore
flag. Please make sure that there is a key for new node. To find out more about importing or generating keys, visit the Secret Managers section.
This section guides you through backup the data including state data and key and restoring into the new instance.
Since the ZChains uses LevelDB for data storage, the node needs to be stopped for the duration of the backup, as LevelDB doesn't allow for concurrent access to its database files.
Additionally, the ZChains also does data flushing on close.
The first step involves stopping the running client (either through a service manager or some other mechanism that sends a SIGINT signal to the process), so it can trigger 2 events while gracefully shutting down:
Running data flush to disk
Release of the DB files lock by LevelDB
Now that the client is not running, the data directory can be backed up to another medium. Keep in mind that the files with a .key
extension contain the private key data that can be used to impersonate the current node, and they should never be shared with a third/unknown party.
:::info Please back up and restore the generated genesis
file manually, so the restored node is fully operational. :::
If any instance of the ZChains is running, it needs to be stopped in order for step 2 to be successful.
Once the client is not running, the data directory which was previously backed up can be copied over to the desired folder. Additionally, restore the previously copied genesis
file.
In order for the Polygon Edge to use the restored data directory, at launch, the user needs to specify the path to the data directory. Please consult the CLI Commands section on information regarding the data-dir
flag.
Currently, the ZChains is concerned with keeping 2 major runtime secrets:
The validator private key used by the node, if the node is a validator
The networking private key used by libp2p, for participating and communicating with other peers
For additional information, please read through the Managing Private Keys Guide
The modules of the ZChains should not need to know how to keep secrets. Ultimately, a module should not care if a secret is stored on a far-away server or locally on the node's disk.
Everything a module needs to know about secret-keeping is knowing to use the secret, knowing which secrets to get or save. The finer implementation details of these operations are delegated away to the SecretsManager
, which of course is an abstraction.
The node operator that's starting the ZChains can now specify which secrets manager they want to use, and as soon as the correct secrets manager is instantiated, the modules deal with the secrets through the mentioned interface - without caring if the secrets are stored on a disk or on a server.
This article details the necessary steps to get the ZChains up and running with GCP Secret Manager.
:::info previous guides It is highly recommended that before going through this article, articles on Local Setup and Cloud Setup are read. :::
In order to utilize GCP Secrets Manager, the user has to have Billing Account enabled on the GCP portal. New Google accounts on GCP platform are provided with some funds to get started, as a king of free trial. More info GCP docs
The user will need to enable the GCP Secrets Manager API, before he can use it. This can be done via Secrets Manager API portal. More info: Configuring Secret Manger
Finally, the user needs to generate new credentials that will be used for authentication. This can be done by following the instructions posted here. The generated json file containing credentials, should be transferred to each node that needs to utilize GCP Secrets Manager.
Required information before continuing:
Project ID (the project id defined on GCP platform)
Credentials File Location (the path to the json file containing the credentials)
In order for the ZChains to be able to seamlessly communicate with the GCP SM, it needs to parse an already generated config file, which contains all the necessary information for secret storage on GCP SM.
To generate the configuration, run the following command:
Parameters present:
PATH
is the path to which the configuration file should be exported to. Default ./secretsManagerConfig.json
NODE_NAME
is the name of the current node for which the GCP SM configuration is being set up as. It can be an arbitrary value. Default polygon-edge-node
PROJECT_ID
is the ID of the project the user has defined in GCP console during account setup and Secrets Manager API activation.
GCP_CREDS_FILE
is the path to the json file containing credentials which will allow read/write access to the Secrets Manager.
:::caution Node names Be careful when specifying node names.
The ZChains uses the specified node name to keep track of the secrets it generates and uses on the GCP SM. Specifying an existing node name can have consequences of failing to write secret to GCP SM.
Secrets are stored on the following base path: projects/PROJECT_ID/NODE_NAME
:::
Now that the configuration file is present, we can initialize the required secret keys with the configuration file set up in step 1, using the --config
:
The PATH
param is the location of the previously generated secrets manager param from step 1.
The genesis file should be generated in a similar manner to the Local Setup and Cloud Setup guides, with minor changes.
Since GCP SM is being used instead of the local file system, validator addresses should be added through the --ibft-validator
flag:
Now that the keys are set up, and the genesis file is generated, the final step to this process would be starting the Polygon Edge with the server
command.
The server
command is used in the same manner as in the previously mentioned guides, with a minor addition - the --secrets-config
flag:
The PATH
param is the location of the previously generated secrets manager param from step 1.
This section aims to give a better overview of some concepts currently present in the Proof of Stake (PoS) implementation of the ZChains.
The ZChains Proof of Stake (PoS) implementation is meant to be an alternative to the existing PoA IBFT implementation, giving node operators the ability to easily choose between the two when starting a chain.
The core logic behind the Proof of Stake implementation is situated within the Staking Smart Contract.
This contract is pre-deployed whenever a PoS mechanism Zchains chain is initialized, and is available on the address 0x0000000000000000000000000000000000001001
from block 0
.
Epochs are a concept introduced with the addition of PoS to the Zchains.
Epochs are considered to be a special time frame (in blocks) in which a certain set of validators can produce blocks. Their lengths are modifiable, meaning node operators can configure the length of an epoch during genesis generation.
At the end of each epoch, an epoch block is created, and after that event a new epoch starts. To learn more about epoch blocks, see the Epoch Blocks section.
Validator sets are updated at the end of each epoch. Nodes query the validator set from the Staking Smart Contract during the creation of the epoch block, and save the obtained data to local storage. This query and save cycle is repeated at the end of each epoch.
Essentially, it ensures that the Staking Smart Contract has full control over the addresses in the validator set, and leaves nodes with only 1 responsibility - to query the contract once during an epoch for fetching the latest validator set information. This alleviates the responsibility from individual nodes from taking care of validator sets.
Addresses can stake funds on the Staking Smart Contract by invoking the stake
method, and by specifying a value for the staked amount in the transaction:
By staking funds on the Staking Smart Contract, addresses can enter the validator set and thus be able to participate in the block production process.
:::info Threshold for staking Currently, the minimum threshold for entering the validator set is staking 1 ETH
:::
Addresses that have staked funds can only unstake all of their staked funds at once.
Unstaking can be invoked by calling the unstake
method on the Staking Smart Contract:
After unstaking their funds, addresses are removed from the validator set on the Staking Smart Contract, and will not be considered validators during the next epoch.
Epoch Blocks are a concept introduced in the PoS implementation of IBFT in Zchains.
Essentially, epoch blocks are special blocks that contain no transactions and occur only at the end of an epoch. For example, if the epoch size is set to 50
blocks, epoch blocks would be considered to be blocks 50
, 100
, 150
and so on.
They are used to performing additional logic that shouldn't occur during regular block production.
Most importantly, they are an indication to the node that it needs to fetch the latest validator set information from the Staking Smart Contract.
After updating the validator set at the epoch block, the validator set (either changed or unchanged) is used for the subsequent epochSize - 1
blocks, until it gets updated again by pulling the latest information from the Staking Smart Contract.
Epoch lengths (in blocks) are modifiable when generating the genesis file, by using a special flag --epoch-size
:
The default size of an epoch is 100000
blocks in the Zchains.
The Zchains pre-deploys the Staking Smart Contract during genesis generation to the address 0x0000000000000000000000000000000000001001
.
It does so without a running EVM, by modifying the blockchain state of the Smart Contract directly, using the passed in configuration values to the genesis command.
This guide assumes you have followed the Local Setup or guide on how to set up an IBFT cluster on the cloud.
A functioning node is required in order to query any kind of operator information.
With the ZChains, node operators are in control and informed about what the node they're operating is doing. At any time, they can use the node information layer, built on top of gRPC, and get meaningful information - no log sifting required.
:::note
If your node isn't running on 127.0.0.1:8545
you should add a flag --grpc-address <address:port>
to the commands listed in this document.
:::
To get a complete list of connected peers (including the running node itself), run the following command:
This will return a list of libp2p addresses that are currently peers of the running client.
For the status of a specific peer, run:
With the address parameter being the libp2p address of the peer.
Lots of times, an operator might want to know about the state of the operating node in IBFT consensus.
Luckily, the Zchains provides an easy way to find this information.
Running the following command returns the most recent snapshot.
To query the snapshot at a specific height (block number), the operator can run:
To get the latest info on candidates, the operator can run:
This command queries the current set of proposed candidates, as well as candidates that have not been included yet
The following command returns the current validator key of the running IBFT client:
To find the current number of transactions in the transaction pool, the operator can run:
The JSON-RPC layer of the ZChains provides developers with the functionality of easily interacting with the blockchain, through HTTP requests.
This example covers using tools like curl to query information, as well as starting the chain with a premined account, and sending a transaction.
To generate a genesis file, run the following command:
The premine flag sets the address that should be included with a starting balance in the genesis file.
In this case, the address 0x1010101010101010101010101010101010101010
will have a starting default balance of 0x3635C9ADC5DEA00000 wei
.
If we wanted to specify a balance, we can separate out the balance and address with a :
, like so:
The balance can be either a hex
or uint256
value.
:::warning Only premine accounts for which you have a private key! If you premine accounts and do not have a private key to access them, you premined balance will not be usable :::
To start the ZChains in development mode, which is explained in the CLI Commands section, run the following:
Now that the client is up and running in dev mode, using the genesis file generated in step 1, we can use a tool like curl to query the account balance:
The command should return the following output:
Now that we've confirmed the account we set up as premined has the correct balance, we can transfer some ether:
Currently, the ZChains is concerned with keeping 2 major runtime secrets:
The validator private key used by the node, if the node is a validator
The networking private key used by libp2p, for participating and communicating with other peers
For additional information, please read through the Managing Private Keys Guide
The modules of the ZChains should not need to know how to keep secrets. Ultimately, a module should not care if a secret is stored on a far-away server or locally on the node's disk.
Everything a module needs to know about secret-keeping is knowing to use the secret, knowing which secrets to get or save. The finer implementation details of these operations are delegated away to the SecretsManager
, which of course is an abstraction.
The node operator that's starting the ZChains can now specify which secrets manager they want to use, and as soon as the correct secrets manager is instantiated, the modules deal with the secrets through the mentioned interface - without caring if the secrets are stored on a disk or on a server.
This article details the necessary steps to get the ZChains up and running with AWS Systems Manager Parameter Store.
:::info previous guides It is highly recommended that before going through this article, articles on Local Setup and Cloud Setup are read. :::
User needs to create an IAM Policy that allows read/write operations for AWS Systems Manager Parameter Store. After successfully creating IAM Policy, the user needs to attach it to the EC2 instance that is running the ZChains server. The IAM Policy should look something like this:
More information on AWS SSM IAM Roles you can find in the AWS docs.
Required information before continuing:
Region (the region in which Systems Manager and nodes reside)
Parameter Path (arbitrary path that the secret will be placed in, for example /polygon-edge/nodes
)
In order for the ZChains to be able to seamlessly communicate with the AWS SSM, it needs to parse an already generated config file, which contains all the necessary information for secret storage on AWS SSM.
To generate the configuration, run the following command:
Parameters present:
PATH
is the path to which the configuration file should be exported to. Default ./secretsManagerConfig.json
NODE_NAME
is the name of the current node for which the AWS SSM configuration is being set up as. It can be an arbitrary value. Default polygon-edge-node
REGION
is the region in which the AWS SSM resides. This has to be the same region as the node utilizing AWS SSM.
SSM_PARAM_PATH
is the name of the path that the secret will be stored in. For example if --name node1
and ssm-parameter-path=/polygon-edge/nodes
are specified, the secret will be stored as /polygon-edge/nodes/node1/<secret_name>
:::caution Node names Be careful when specifying node names.
The Polygon Edge uses the specified node name to keep track of the secrets it generates and uses on the AWS SSM. Specifying an existing node name can have consequences of failing to write secret to AWS SSM.
Secrets are stored on the following base path: SSM_PARAM_PATH/NODE_NAME
:::
Now that the configuration file is present, we can initialize the required secret keys with the configuration file set up in step 1, using the --config
:
The PATH
param is the location of the previously generated secrets manager param from step 1.
:::info IAM Policy This step will fail if IAM Policy that allows read/write operations is not configured correctly and/or not attached to the EC2 instance running this command. :::
The genesis file should be generated in a similar manner to the Local Setup and Cloud Setup guides, with minor changes.
Since AWS SSM is being used instead of the local file system, validator addresses should be added through the --ibft-validator
flag:
Now that the keys are set up, and the genesis file is generated, the final step to this process would be starting the Polygon Edge with the server
command.
The server
command is used in the same manner as in the previously mentioned guides, with a minor addition - the --secrets-config
flag:
The PATH
param is the location of the previously generated secrets manager param from step 1.
Q2 2024: Development of ZChains Network, Swap and Bridge with EVM
ZChains L1 Network Development:
Implementation based on EVM and Proof of Stake (PoS) mechanism.
Development of swapping and bridging mechanism for interoperability with EVM-compatible networks.
Token Transfer Mechanism:
Seamless transfer of tokens like USDT from EVM networks to ZChains and vice versa.
Process includes locking/minting and burning/unlocking of tokens for cross-network transfers.
Q2-Q3: Development of Launchpad dApp
Launchpad Features:
Offering Audit, KYC and SAFU badges for grading purpose.
Intuitive interfaces for users to easily mint, buy, and trade tokens, making it accessible to a broader audience.
Portal to help you easily update content for your launchpad.
Customizable smart contract templates that enable project creators to define the rules and conditions for their token sale and distribution.
Access to a community of early adopters and investors.
Q3-Q4: Development of Multichain Wallet
Network Support and Functionality:
Compatibility with ZChains and all EVM networks.
Features for coin transfer and standard wallet functionalities.
User Interface and Technical Specifications:
Portfolio management dashboard with P/L tracking and overview.
Mobile and web extension compatibility with a consistent user experience.
Integration with ZChains and EVM networks for extended functionalities.
Security and Performance:
Advanced encryption and security protocols.
Performance optimization for responsiveness and minimal latency.
Future Development Phases (Beyond Q3):
Continuous Integration of dApps:
Continue building various dApps with high TVL and user base, aimed to increase the adoption of ZChains and ZCD, creating limitless possibilities with ZChains.
Continuous Network and Feature Expansion:
Currently focusing on ongoing integration with networks compatible with Ethereum Virtual Machine (EVM). However, plans are in place to extend this integration to include a variety of other network types in the future, broadening the scope beyond just EVM-compatible networks. This approach aims to enhance connectivity and interoperability across a diverse range of blockchain networks.
Further development of wallet and DEX features based on user feedback and market trends.
Enhanced Interoperability and User Experience:
Focus on improving cross-network interoperability and user interface enhancements.
Community Engagement and New Campaigns:
Increasing community involvement in ZChains development.
Regular campaigns based on community proposals and voting.
This roadmap outlines a progressive development strategy for the ZChains, with a focus on expanding its ecosystem and focused in delivering experience-based services.