Technical Introduction
This document includes some technical information which LikeCoin Chain validators should know.
Overview
LikeCoin Chain is developed base on Cosmos SDK. When started, the software will launch 2 components: Tendermint, which communicates with other nodes and process blockchain / consensus related stuffs; Cosmos SDK application, which receives transactions from Tendermint and process them according to the application logic.
Tendermint
Tendermint is a consensus engine, which helps developers to develop their own blockchain by separating the blockchain specific parts (consensus, block processing, P2P networking, etc) and the application logic.
Developers only need to implement their application logic according to an interface called Application BlockChain Interface (ABCI)
, then Tendermint will take care the blockchain parts.
With Tendermint alone, the chain will be a consortium chain, which means that only predefined set of key holders can propose and validate blocks.
However, Tendermint provides mechanisms for the application logic to determine who can be the validators, making it possible to select validators from a large pool of participants and thus turning into a public chain.
Cosmos SDK Application
Cosmos SDK is an SDK which helps developers to developer their ABCI app by providing standard modules of common blockchain features, e.g. coin transfer.
It also provides Proof of Stake related module, which makes the chain a public blockchain by selecting validators according to the amount of coins they staked.
Deployment
Basic Launching Flow
Initial the chain via
liked init MONIKER
. This will generate basic configuration files and a templategenesis.json
.Modify configuration files, especially
genesis.json
for chain parameters.Add accounts by
liked add-genesis-account
(or just modifygenesis.json
manually) to initialize genesis account list.All validators use the same
genesis.json
to generate and sign genesis transaction byliked gentx
. The genesis transaction with signature will be stored in.liked/config/gentx/
folder.Someone aggregate these genesis transaction files and call
liked collect-gentxs
to collect the genesis transactions intogenesis.json
.Discuss and set the genesis block time in
genesis.json
.Distribute the final
genesis.json
to all validators.Somebody set up seed nodes for nodes to bootstrap the P2P network.
All validators use the same
genesis.json
to start the node.At genesis blocktime, the nodes will connect to seed nodes to exchange IP addresses, connect to each other and start to produce blocks.
Availability
The staking related modules have availability requirements on validator nodes. If validator nodes cannot meet the availability requirement, some portion of delegated coins will be slashed (burnt) and the validator node will be temporary jailed, unable to participate in consensus and receive rewards.
Currently, LikeCoin Chain follows Cosmos Hub, setting availability parameters as follows: if a validator node's participation is less than 5% in the previous 10000 blocks, 0.01% of its delegated coins will be slashed.
To maintain a high availability validator node, the Cosmos cummunity has proposed the Sentry Node Architecture.
Sentry Node Architecture
The validator node which keeps the consensus key is hidden behind the sentry nodes.
The sentry nodes connect with the validator node using persistent and private connection, so the validator node's address will not be broadcasted in the network. The validator node only communicate with the network through the sentry nodes.
If sentry nodes are down, or loading increases, one can deploy more sentry nodes to balance the loadings.
See: https://forum.cosmos.network/t/sentry-node-architecture-overview/454
Key Management
A validator node will hold a few keys.
Consensus Key
The consensus key is the key a validator node used to sign the blocks. One can find signatures from these keys from prevote and precommit messages from validators.
By default, the consensus key is generated and stored in .liked/config/priv_validator_key.json
in plaintext, while the signing state is stored in .liked/data/priv_validator_state.json
. One can get its consensus public key by running liked tendermint show-validator
.
If the consensus key is leaked, the attacker can use the key to sign conflicting blocks, making the corresponding validator gets slashed due to double signing. If more than 2/3 of voting power's consensus keys are leaked, attacker will be able to forge another chain which looks complete valid, making the whole blockchain meaningless.
Note that improper failover architecture (e.g. 2 nodes with the same consensus key are online simutanously due to invalid failure detection) also make double signing possible, so either the architecture should ensure that only one node will have the consensus key, or there should be measurements to explicitly prevent double signing (e.g. ZooKeeper between signing services to ensure only one block hash will be signed for the same height and same round).
Tendermint allows user to use an external service as signer service. User can set priv_validator_laddr
in config.toml
to allow the external service to connect to the node and provide signatures when needed.
Tendermint community has worked out a KMS service which uses hardware wallets (Ledger / YubiHSM 2) to keep the consensus key, to prevent the consensus key from leaking. See: https://github.com/tendermint/kms
Node Key
The node key is for authenticating the connection. For instance, seed peers and persistent peers are authenticated using the node ID, which is generated from the node key.
By default, the node key is generated and stored in .liked/config/node_key.json
in plaintext. One can get its node ID by running liked tendermint show-node-id
.
The security of the node key is not as important as the other two keys, because attacker would need to take over the IP addresses or domain names used for the node in order to forge the node's connection.
Operator Key
Operator key is for managing the application logic, e.g. delegation, editing validator commission rate, governance, etc.
This key is similar to a normal user's private key, except it has more permission on the validator.
When the operator key is initialized by likecli keys add
, it is stored and encrypted inside the database in .likecli
. The initialization process will show mnemonic words for backup the key.
One can keep this key using hardware wallets like Ledger:
Install the Ledger Cosmos application on your Ledger hardware following the instructions from here.
Compile the
liked
andlikecli
applications withledger
tag. The applications require Go 1.13+ to compile. One can compile the applications bygo build -o likecli --tags 'ledger' cmd/likecli/main.go
(change the paths forliked
).When initializing the operator key, connect your Ledger and open the Cosmos application, and add the
--ledger
option in command line arguments:likecli keys add my_key_name --ledger
.When signing transactions using the key, connect your Ledger and open the Cosmos application.
Transactions
A transaction is like a command to the blockchain, making the blockchain to change its state.
Transaction Structure
In Tendermint, a transaction can be any binary string, as long as the application logic can process it.
In Cosmos SDK, a standard transaction is defined as a serialization of the following structure:
Messages
sdk.Msg
is an interface implemented by Cosmos modules. It wraps the application logic related data, and has interface on things like who should be signing the message, how should the message be signed, how to validate the message, etc.
For example, a MsgSend defined by the bank module is as follows:
which indicates who is sending the coins, who is the receiver, and the amount of coins to be sent.
A transaction can include more than one message.
Gas, Gas Price and Transaction Fee
Each transaction will consume gas, which roughly indicates how much work is needed for processing the transaction. For instance, a normal transfer takes about 40000 gas.
A transaction must provide its maximum gas consumption, so that nodes can evaluate whether the transaction should be accepted or not by the transaction fee. During execution, if the actual gas consumption is greater than the claimed one, the execution will fail.
User can get the rough gas consumption by asking the node to run simulations. However, sometimes the simulated gas consumption will be much lower than the actual one, so one should multiply the simulated result by some factor (e.g. multiply by 2) to prevent transaction failure due to out of gas.
User can assign a transaction fee in the transaction. The gas price of the transaction equals to the transaction fee assigned divided by the gas consumption.
The fee will not be returned even if the actual gas consumption is lower than the assigned one.
Each node can set a minimum gas price. These nodes will not accept transactions which has gas price lower than the minimum they have set.
For validator nodes, these nodes will not get the transactions into their memory pool, hence will never propose a block including these transactions when they become a block proposer.
However, other nodes may have lower minimum gas price setting and hence including these transactions in their proposed blocks. In this case, all nodes must accept these transactions even if they have gas price lower than their minimum.
On the other hand, the lower the gas price is set, the lower the chance that a block proposer will get it into the block, hence delaying the execution of the transaction.
Sequence
Transactions are usually signed by user for authentication. Since blockchain transactions are public, it must have mechanism for preventing signature replay.
In Cosmos SDK, each account comes with a sequence number, which can be queried from account info. The sequence indicates the number of executed transactions this account has signed.
A new transaction must include the sequence number matching the signing account's current sequence. Therefore if a transaction is replayed, it will fail due to invalid sequence number.
A consequence is that if multiple transactions are being sent and processed in the same block, they must have contiguous sequence number and be sent in the order according to their sequence number.
Transaction Execution Flow
This section explains the lifecycle of a transaction.
Constructing and Signing
First, the user constructs and signs a transaction through a client (CLI / web). The transaction is then either sent to a node directly (CLI), or first sent to a lite client (web), then encoded and forwarded by the lite client to a node.
CheckTx and Mempool
When a node receives a transaction, it performs CheckTx
to do basic verifications for the trasnaction, e.g. checking sequence and signature.
It also checks the gas price of the transaction to see if it meets the minimum gas price requirement set by the node, and rejects the transaction if not.
After passing through the checkings, the transactions enters the memory pool of the node, and is forwarded to other nodes connected to this node.
Other nodes do the same procedure, check the transaction, add it into local memory pool and forward it to other nodes, spreading the transaction in the network.
Including into a block
When a validator becomes a proposer, it get transactions from its own memory pool and use them to construct a candidate block.
After the consensus process, the candidate block will be finalized as the new block.
See Tendermint docs for more details on the consensus process.
DeliverTx
After validators have finalized a block, nodes in the network the will execute transactions in the block.
Since the transactions are finalized, nodes must deterministically execute the transactions with the same result. Therefore in this stage, nodes must not reject the transaction only according to their local setting on minimum gas price.
The Cosmos SDK application updates the blockchain state (e.g. account sequence and balance) according to the content of the transactions.
Known Issues and Workarounds
IP Address Exchange
Tendermint claims that if p2p.external_address
is not set in config.toml
, Tendermint will detect the external address from the listener or UPnP.
However, when using default settings, UPnP option will be turned off, and the listener address will be tcp://0.0.0.0:26656
, the node will broadcast its own IP as 0.0.0.0:26657
, which is invalid for others to connect to.
This makes seed node unable to exchange addresses for these nodes, and the nodes can only connect to persistent peers. In previous testnet, the persistent peer provided by LikeCoin Foundation was the only node which other nodes are able to connect to, making it a centralized, failure-prone network architecture.
Even worse, the p2p.external_address
option cannot be overridden by command line arguments or environment variables, so it is not possible to fix by external programs.
Current workaround: added a --get-ip
option in liked
, which detects external IP by some common IP detecting services (e.g. httpbin.org) and set the p2p.external_address
field in the configuration structure before real execution.
Validators may disable this flag and set their p2p.external_address
instead in config.toml
, but should remember to update this field when the external address changes.
See:
https://github.com/tendermint/tendermint/issues/3714
https://github.com/tendermint/tendermint/pull/3646
Empty Blocks
Currently, the block time is set to about 5 seconds, no matter there are transactions or not.
By statistics, the storage consumption of empty blocks is about 500 bytes per validator.
So for instance, a chain with 10 validators will consumes about 5 KB per 5 seconds, or about 29 GB per year, when there is totally no transaction. Such storage consumption for empty block data is not desirable.
Tendermint actually supports not creating empty blocks when there is no transaction. As an alternative, we may also adjust blocktime to a longer period.
However, there are some considerations in practice:
Application state changes
When application state changes, Tendermint will produce additional empty blocks for confirming that all validators has consensus in the application state, until there are consecutive blocks with the same application hash (representing the application state).
However, with the Proof of Stake modules, the application state is changing for every block. For example, the mint
module will create coins for inflation, and the distribution
module will record block validators precommit info to count missing validators. Since the application state is always changing, Tendermint will keep creating new empty blocks.
Inflation rate
Another issue is that the inflation rate is computed by number of blocks instead of the actual block's time.
In the mint
module, there is a parameter on the expected number of blocks per year. Then, in the chain's application logic, the inflation rate and annual provision will be computed by that block period instead of actually one year.
For instance, 4% inflation rate per year actually means "4% of inflation per N blocks", where N is the number of blocks set per year.
However, in Tendermint, there is no way to fix the block time. When to create a block is determined by the proposer. Others may consider the proposer as timeout if the block time is too long, but there is no limit on how short the block time can be.
Therefore if the block time is "set" to say 60 seconds, and the expected blocks per year is set to 365.25 * 24 * 3600 / 60 = 525960
blocks per year, but then there are transactions every 10 seconds, then the actual number of blocks per year will be 6 times as the expected one.
This makes the number of coins minted for inflation 6 times as the expected one, breaking the economy.
This issue limits the expected block time to a relative short period.
It is possible to modify the mint
module to use block timestamp instead of block number as the measure on time, but more investigation is needed.
Last updated