ansheng’s blog!

Cosmos-SDK+Cosmosvisor开发区块链升级实战

Cosmos-SDK基于Tendermint开发了很多实用的工具包,且都是模块化的,通过Cosmos-SDK可以快速构建一条L0链,迫于前期调研需要对升级方案做一些研究,所以就有了这篇文章。

理论部分请参考如下文章:

安装所需软件包

通过docker创建一个simapp的容器,测试都在容器中进行

docker run -it -d --name simapp --hostname simapp debian:11
docker exec -it simapp bash
cd
apt update && apt upgrade -y
apt install vim tmux git curl wget tree jq -y
wget https://go.dev/dl/go1.19.1.linux-amd64.tar.gz
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.19.1.linux-amd64.tar.gz
rm -f go1.19.1.linux-amd64.tar.gz
echo 'export PATH=$PATH:/usr/local/go/bin' >> .bashrc
echo 'export GOPATH=$(go env GOPATH)' >> .bashrc
echo 'export PATH=$PATH:$(go env GOPATH)/bin' >> .bashrc
source .bashrc

$ go version
go version go1.19.1 linux/amd64
curl https://get.ignite.com/cli@v0.25.1! | bash

$ ignite version
Ignite CLI version:     v0.25.1
Ignite CLI build date:  2022-10-20T15:52:00Z
Ignite CLI source hash: cc393a9b59a8792b256432fafb472e5ac0738f7c
Cosmos SDK version:     v0.46.3
Your OS:                linux
Your arch:              amd64
Your go version:        go version go1.19.1 linux/amd64
Your uname -a:          Linux cosmos 5.10.0-18-amd64 #1 SMP Debian 5.10.140-1 (2022-09-02) x86_64 GNU/Linux
Your cwd:               /root
Is on Gitpod:           false
go install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@v1.4.0

$ cosmovisor version
cosmovisor version: v1.4.0

构建SimApp链

创建项目

ignite scaffold chain simapp
cd simapp

将默认分支改为v1.0.0

git branch -m v1.0.0

创建一个查询ping,返回PONG+版本号

$ ignite scaffold query ping --response text
$ vim x/simapp/keeper/grpc_query_ping.go +22
return &types.QueryPingResponse{Text: "PONG, v1.0.0"}, nil

运行链

ignite chain serve -r

查询测试

$ simappd q simapp ping
text: PONG, v1.0.0

提交代码

echo "build" >> .gitignore
git add . && git commit -a -m "v1.0.0"

build二进制文件并更名为simappd_v1.0.0

ignite chain build -o build
mv build/simappd build/simappd_v1.0.0

基于v1.0.0创建v2.0.0版本的branch

git checkout -b v2.0.0

v2.0.0的版本只是做了一个小改动,即把PING返回的结果PONG, v1.0.0改为PONG, v2.0.0

$ vim x/simapp/keeper/grpc_query_ping.go +22
return &types.QueryPingResponse{Text: "PONG, v2.0.0"}, nil

添加升级plan

$ vim app/upgrades.go
package app

import (
        sdk "github.com/cosmos/cosmos-sdk/types"
        "github.com/cosmos/cosmos-sdk/types/module"
        upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"
)

const UpgradeName = "v2.0.0"

func (app App) RegisterUpgradeHandlers() {
        app.UpgradeKeeper.SetUpgradeHandler(UpgradeName,
                func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) {
                        logger := ctx.Logger().With("upgrade", UpgradeName)
                        logger.Info("v1.0.0 upgrade to v2.0.0...")
                        return app.mm.RunMigrations(ctx, app.configurator, fromVM)
                })
}

注册plan

$ vim app/app.go +655
......
app.RegisterUpgradeHandlers()
......

运行链

ignite chain serve -r

功能测试

$ simappd q simapp ping
text: PONG, v2.0.0

提交代码

git add . && git commit -a -m "v2.0.0"

build二进制文件并重命名

ignite chain build -o build
mv build/simappd build/simappd_v2.0.0

最后的可执行文件如下

$ tree ~/simapp/build
/root/simapp/build
|-- simappd_v1.0.0
`-- simappd_v2.0.0

0 directories, 2 files

运行创世节点(v1.0.0)

# 删除测试时生成的目录
rm -fr ~/.simapp
# 配置链ID
~/simapp/build/simappd_v1.0.0 config chain-id test
# 初始化链
~/simapp/build/simappd_v1.0.0 init test --chain-id test
# 更新voting_period为20s
cat <<< $(jq '.app_state.gov.voting_params.voting_period = "20s"' $HOME/.simapp/config/genesis.json) > $HOME/.simapp/config/genesis.json
# 创建一个账户
~/simapp/build/simappd_v1.0.0 keys add validator
# 为帐户添加默认资金,如果报错可以将validator改为validator的address,类似:cosmos1slp3vtq4yrcfjcwcdvxj23trt8utq726pghueq
~/simapp/build/simappd_v1.0.0 add-genesis-account validator 100000000000000000000000000stake
# 把账户设置为验证节点
~/simapp/build/simappd_v1.0.0 gentx validator 1000000000000000000000stake --chain-id test
~/simapp/build/simappd_v1.0.0 collect-gentxs

voting_period参数表示在升级提案提交之后,在voting_period时间内需要有结果,否则这个提案就会作废,假设出快时间为1s/个,在高度10的时候提案创建,如果在高度10-30之间这个提案没有结果,最后这个提案就会被作废,也不会被升级。

配置cosmovisor环境变量

$ vim ~/.bashrc
# 二进制文件的名称
export DAEMON_NAME=simappd
# cosmovisor工作目录
export DAEMON_HOME=$HOME/.simapp
# 是否从网上下载二进制文件
export DAEMON_ALLOW_DOWNLOAD_BINARIES=false
# 更新完毕后自动重启
export DAEMON_RESTART_AFTER_UPGRADE=true
$ source ~/.bashrc

创建cosmovisor目录结构

mkdir -p $DAEMON_HOME/cosmovisor/genesis/bin
cp ~/simapp/build/simappd_v1.0.0 $DAEMON_HOME/cosmovisor/genesis/bin/simappd

通过cosmovisor运行节点

cosmovisor run start

cosmovisor run start的时候,默认会创建一个current的软链,例如

ln -s $DAEMON_HOME/cosmovisor/genesis $DAEMON_HOME/cosmovisor/current

然后运行的脚本是从$DAEMON_HOME/cosmovisor/current/bin/simappd,这样在下次升级的时候只需要把新版本的位置重新link到current即可。

查询版本信息,目前返回的信息是v1.0.0版本

$ $DAEMON_HOME/cosmovisor/current/bin/simappd q simapp ping
text: PONG, v1.0.0

创建升级提案

创建提案

$ $DAEMON_HOME/cosmovisor/genesis/bin/simappd tx gov submit-legacy-proposal software-upgrade v2.0.0 \
 --upgrade-height 30 \
 --title="Test Upgrade Proposal" \
 --description="testing, testing, 1, 2, 3" \
 --no-validate=true \
 --deposit 10000000stake \
 --from validator \
 --chain-id test
Enter keyring passphrase:
auth_info:
  fee:
    amount: []
    gas_limit: "200000"
    granter: ""
    payer: ""
  signer_infos: []
  tip: null
body:
  extension_options: []
  memo: ""
  messages:
  - '@type': /cosmos.gov.v1beta1.MsgSubmitProposal
    content:
      '@type': /cosmos.upgrade.v1beta1.SoftwareUpgradeProposal
      description: testing, testing, 1, 2, 3
      plan:
        height: "30"
        info: ""
        name: v2.0.0
        time: "0001-01-01T00:00:00Z"
        upgraded_client_state: null
      title: Test Upgrade Proposal
    initial_deposit:
    - amount: "10000000"
      denom: stake
    proposer: cosmos1g0da62czgmynvrzhd5dj6zev6tefw8dleqpp8d
  non_critical_extension_options: []
  timeout_height: "0"
signatures: []

confirm transaction before signing and broadcasting [y/N]: y
code: 0
codespace: ""
data: ""
events: []
gas_used: "0"
gas_wanted: "0"
height: "0"
info: ""
logs: []
raw_log: '[]'
timestamp: ""
tx: null
txhash: CA306DA0DAE16176D559212A475F23B98CF8BAAD256F081B176EE733A0E345F3

参数说明如下

software-upgrade v2.0.0    # 软件升级的提案名称,可以写为版本号,且这个名称要和app/upgrades.go文件中的UpgradeName保持一致
--upgrade-height 20        # 预计在什么高度进行升级
--no-validate=true         # 不进行验证,表示不对--update-info参数进行验证,即可以不填写--update-info
--deposit 10000000stake    # 创建提案时默认存入多少资金,这是为了防止恶意刷题案的,提案在通过或者失败后会退回,但是如果no with veto比例超过33.3%就会被罚掉

投票

$ $DAEMON_HOME/cosmovisor/genesis/bin/simappd tx gov vote 1 yes --from validator --yes --chain-id test
Enter keyring passphrase:
code: 0
codespace: ""
data: ""
events: []
gas_used: "0"
gas_wanted: "0"
height: "0"
info: ""
logs: []
raw_log: '[]'
timestamp: ""
tx: null
txhash: 6230C775911CBB091CC16BDE09E53501EC9B6EEE65B5CCCD21A5C94D122A8759

升级v2.0.0

将新版本的二进制文件复制到对应的目录下,升级提案的名称是v2.0.0,所以创建的目录也是v2.0.0

mkdir -p $DAEMON_HOME/cosmovisor/upgrades/v2.0.0/bin
cp ~/simapp/build/simappd_v2.0.0 $DAEMON_HOME/cosmovisor/upgrades/v2.0.0/bin/simappd

等高度到30时候会停掉v1.0.0版本运行v2.0.0版本

9:06AM INF Timed out dur=4990.040215 height=30 module=consensus round=0 step=1
9:06AM INF received proposal module=consensus proposal={"Type":32,"block_id":{"hash":"6D3284C9F45FCA211DFC9B7BC368DDE638BDF320FC42C48D1E5D0DABE12C770F","parts":{"hash":"16C3A4AD0CD8B9F05800F199ED2EC7F24B5CB29EAEB89C3E85D4B5FABE4FF193","total":1}},"height":30,"pol_round" :-1,"round":0,"signature":"CL7/cRo5egyJ40bZrIbRH0Pp8D+dc/fYsINc2tSSGgdLnjSHVYocysiNc2CJ8H++0Y+gXMdPPaozCu336Uh4CQ==","timestamp":"2022-10-28T09:06:08.418559499Z"}
9:06AM INF received complete proposal block hash=6D3284C9F45FCA211DFC9B7BC368DDE638BDF320FC42C48D1E5D0DABE12C770F height=30 module=consensus
9:06AM INF finalizing commit of block hash={} height=30 module=consensus num_txs=0 root=AC81F9D50049F65D69052881AB4E433FE07005DF773E6AEA680C4AA982BEF68E
9:06AM ERR UPGRADE "v2.0.0" NEEDED at height: 30:
9:06AM ERR CONSENSUS FAILURE!!! err="UPGRADE \"v2.0.0\" NEEDED at height: 30: " module=consensus stack="......"
9:06AM INF service stop impl={"Logger":{}} module=consensus msg={} wal=/root/.simapp/data/cs.wal/wal
9:06AM INF service stop impl={"Dir":"/root/.simapp/data/cs.wal","Head":{"ID":"nwzjrIk7tTEe:/root/.simapp/data/cs.wal/wal","Path":"/root/.simapp/data/cs.wal/wal"},"ID":"group:nwzjrIk7tTEe:/root/.simapp/data/cs.wal/wal","Logger":{}} module=consensus msg={} wal=/root/.simapp/data/cs.wal/wal
9:06AM INF daemon shutting down in an attempt to restart module=cosmovisor
9:06AM INF starting to take backup of data directory backup start time=2022-10-28T09:06:08Z module=cosmovisor
9:06AM INF backup completed backup completion time=2022-10-28T09:06:08Z backup saved at=/root/.simapp/data-backup-2022-10-28 module=cosmovisor time taken to complete backup=4.102271
9:06AM INF pre-upgrade command does not exist. continuing the upgrade. module=cosmovisor
9:06AM INF upgrade detected, relaunching app=simappd module=cosmovisor
9:06AM INF running app args=["start"] module=cosmovisor path=/root/.simapp/cosmovisor/upgrades/v2.0.0/bin/simappd
9:06AM INF starting node with ABCI Tendermint in-process
9:06AM INF service start impl=multiAppConn module=proxy msg={}
9:06AM INF service start connection=query impl=localClient module=abci-client msg={}
9:06AM INF service start connection=snapshot impl=localClient module=abci-client msg={}
9:06AM INF service start connection=mempool impl=localClient module=abci-client msg={}
9:06AM INF service start connection=consensus impl=localClient module=abci-client msg={}
9:06AM INF service start impl=EventBus module=events msg={}
9:06AM INF service start impl=PubSub module=pubsub msg={}
9:06AM INF service start impl=IndexerService module=txindex msg={}
9:06AM INF ABCI Handshake App Info hash="____\x00I_]i\x05(__NC?_p\x05_w>j_h\fJ_____" height=29 module=consensus protocol-version=0 software-version=
9:06AM INF ABCI Replay Blocks appHeight=29 module=consensus stateHeight=29 storeHeight=30
9:06AM INF Replay last block using real app module=consensus
9:06AM INF applying upgrade "v2.0.0" at height: 30
9:06AM INF v1.0.0 upgrade to v2.0.0... upgrade=v2.0.0
9:06AM INF minted coins from module account amount=2059736728406547617stake from=mint module=x/bank
9:06AM INF executed block height=30 module=consensus num_invalid_txs=0 num_valid_txs=0
9:06AM INF commit synced commit=436F6D6D697449447B5B31323620323331203533203232203235203136203230302036312032313820313920373620353120363320323920333020313631203334203136392032313620323436203637203933203136332031353820313331203433203233372031363820323330203930203231352037375D3A31457D
9:06AM INF committed state app_hash=7EE735161910C83DDA134C333F1D1EA122A9D8F6435DA39E832BEDA8E65AD74D height=30 module=consensus num_txs=0
9:06AM INF Completed ABCI Handshake - Tendermint and App are synced appHash="____\x00I_]i\x05(__NC?_p\x05_w>j_h\fJ_____" appHeight=29 module=consensus
9:06AM INF Version info block=11 p2p=8 tendermint_version=0.34.22
9:06AM INF This node is a validator addr=5341CFE8F809473DBE1F5D3F9535BF211C3153D7 module=consensus pubKey=4LFpdBg5/1KTqlphNhM+gHgvplicGc9im4K1VA1QQiQ=
9:06AM INF indexed block exents height=30 module=txindex
9:06AM INF P2P Node ID ID=ac8d5e4a28915725d1a6769ffdf47492bd066fc0 file=/root/.simapp/config/node_key.json module=p2p
9:06AM INF Adding persistent peers addrs=[] module=p2p
9:06AM INF Adding unconditional peer ids ids=[] module=p2p
9:06AM INF Add our address to book addr={"id":"ac8d5e4a28915725d1a6769ffdf47492bd066fc0","ip":"0.0.0.0","port":26656} book=/root/.simapp/config/addrbook.json module=p2p
9:06AM INF service start impl=Node msg={}
9:06AM INF Starting pprof server laddr=localhost:6060
9:06AM INF service start impl="P2P Switch" module=p2p msg={}
9:06AM INF service start impl=BlockchainReactor module=blockchain msg={}
9:06AM INF service start impl=ConsensusReactor module=consensus msg={}
9:06AM INF Reactor  module=consensus waitSync=false
9:06AM INF serve module=rpc-server msg={}
9:06AM INF service start impl=ConsensusState module=consensus msg={}
9:06AM INF service start impl=baseWAL module=consensus msg={} wal=/root/.simapp/data/cs.wal/wal
9:06AM INF service start impl=Group module=consensus msg={} wal=/root/.simapp/data/cs.wal/wal
9:06AM INF service start impl=TimeoutTicker module=consensus msg={}
9:06AM INF Searching for height height=31 max=0 min=0 module=consensus wal=/root/.simapp/data/cs.wal/wal
9:06AM INF Searching for height height=30 max=0 min=0 module=consensus wal=/root/.simapp/data/cs.wal/wal
9:06AM INF Found height=30 index=0 module=consensus wal=/root/.simapp/data/cs.wal/wal
9:06AM INF Catchup by replaying consensus messages height=31 module=consensus
9:06AM INF Replay: Done module=consensus
9:06AM INF service start impl=Evidence module=evidence msg={}
9:06AM INF service start impl=StateSync module=statesync msg={}
9:06AM INF service start impl=PEX module=pex msg={}
9:06AM INF service start book=/root/.simapp/config/addrbook.json impl=AddrBook module=p2p msg={}
9:06AM INF Saving AddrBook to file book=/root/.simapp/config/addrbook.json module=p2p size=0
9:06AM INF Ensure peers module=pex numDialing=0 numInPeers=0 numOutPeers=0 numToDial=10
9:06AM INF No addresses to dial. Falling back to seeds module=pex
9:06AM INF Timed out dur=4997.559085 height=31 module=consensus round=0 step=1
9:06AM INF received proposal module=consensus proposal={"Type":32,"block_id":{"hash":"380F92D76D209DF516F2A5EF2FAE2D27CA421F888C91A3F250C3AC7A86A97FA1","parts":{"hash":"B8ED33096C255242269779A8EFF6B185F9C64486ECBBB936A431352C332765EC","total":1}},"height":31,"pol_round":-1,"round":0,"signature":"4elm5tQex9Ml62qIB5Nj0zIWckMDTjr1ebYSMvCzI0ZGAm4YGaPsIExjaW48Qp7cYct3S4cyEScwAN/3mfzoAQ==","timestamp":"2022-10-28T09:06:13.798906293Z"}
9:06AM INF received complete proposal block hash=380F92D76D209DF516F2A5EF2FAE2D27CA421F888C91A3F250C3AC7A86A97FA1 height=31 module=consensus
9:06AM INF finalizing commit of block hash={} height=31 module=consensus num_txs=0 root=7EE735161910C83DDA134C333F1D1EA122A9D8F6435DA39E832BEDA8E65AD74D
9:06AM INF minted coins from module account amount=2059737097170852562stake from=mint module=x/bank
9:06AM INF executed block height=31 module=state num_invalid_txs=0 num_valid_txs=0
9:06AM INF commit synced commit=436F6D6D697449447B5B3130322037203638203737203230392031393620313420313332203138362031323420363620352038332034322031353220313620323434203231203234372033203231312031373620343220313520323320313932203531203233302032343020393220323432203230375D3A31467D
9:06AM INF committed state app_hash=6607444DD1C40E84BA7C4205532A9810F415F703D3B02A0F17C033E6F05CF2CF height=31 module=state num_txs=0
9:06AM INF indexed block exents height=31 module=txindex
9:06AM INF Timed out dur=4992.31555 height=32 module=consensus round=0 step=1
9:06AM INF received proposal module=consensus proposal={"Type":32,"block_id":{"hash":"E5F9B6D971F837DF5680A59CF1AB5200D5E9880F90F5A3B4CC97054B555DC162","parts":{"hash":"2F1DB2D3E5015FCFED72252006C112F31564165304AF78EF25930889566AC9E8","total":1}},"height":32,"pol_round":-1,"round":0,"signature":"P1EjWPQBijQNl78A9JFRA7ou+SYnEN0kCH0SZlk7cqSdQOyggTf8VLt36TuOJh9g5GJcDjcMusvVdOyUbWPbAw==","timestamp":"2022-10-28T09:06:18.816719347Z"}
9:06AM INF received complete proposal block hash=E5F9B6D971F837DF5680A59CF1AB5200D5E9880F90F5A3B4CC97054B555DC162 height=32 module=consensus
9:06AM INF finalizing commit of block hash={} height=32 module=consensus num_txs=0 root=6607444DD1C40E84BA7C4205532A9810F415F703D3B02A0F17C033E6F05CF2CF
9:06AM INF minted coins from module account amount=2059737465935178546stake from=mint module=x/bank
9:06AM INF executed block height=32 module=state num_invalid_txs=0 num_valid_txs=0
9:06AM INF commit synced commit=436F6D6D697449447B5B35352032303720383220393920373220323037203135342034332036302032323420323430203234362032343020363820393920343320362032313020333720392033372034203134342031353120313732203236203831203139392031323220323038203238203134345D3A32307D
9:06AM INF committed state app_hash=37CF526348CF9A2B3CE0F0F6F044632B06D2250925049097AC1A51C77AD01C90 height=32 module=state num_txs=0

从上述日志中可以看出,高度到30的时候程序出现恐慌,然后报错,最后程序被终止

9:06AM ERR UPGRADE "v2.0.0" NEEDED at height: 30:

cosmovisor会生成一个upgrade-info.json文件

$ ls $DAEMON_HOME/cosmovisor/upgrades/v2.0.0/upgrade-info.json
/root/.simapp/cosmovisor/upgrades/v2.0.0/upgrade-info.json

$ cat $DAEMON_HOME/cosmovisor/upgrades/v2.0.0/upgrade-info.json
{"name":"v2.0.0","time":"0001-01-01T00:00:00Z","height":30}

当cosmovisor读取到upgrade-info.json文件,会终止v1.0.0的程序,然后运行v2.0.0版本,从日志中也可以看到我们升级的日志,大部分情况下,每一次升级apphash都会变更

9:06AM INF applying upgrade "v2.0.0" at height: 30
9:06AM INF v1.0.0 upgrade to v2.0.0... upgrade=v2.0.0

再次查询版本

$ $DAEMON_HOME/cosmovisor/genesis/bin/simappd q simapp ping
text: PONG, v2.0.0