毕设10 - Juno(wasmd) 初见,节点部署和简单功能实践
本文写作时 Juno 版本:v25.0.0-2-gd47e0713
还有官方 GPTs:Juno Simpler,数据库大概是 v12 左右的,但是还不错基本问题都能解决。
Juno 是在 wasmd 的基础上开发的,我误打误撞从 Juno 开始学习了。
本文的内容几乎可以直接迁移到 wasmd 使用和学习中,部分区别我在章节末作出了补充。
(只要全文替换 juno 为 wasm ,修改仓库和docker地址,就是 wasmd 的初见教程了)
1 运行 Juno 本地节点
git clone https://github.com/CosmosContracts/juno.git
cd juno
make install
1.1 Docker
docker run -it --rm -p 26657:26657 -p 26656:26656 -p 1317:1317 -p 9090:9090 \
ghcr.io/cosmoscontracts/juno:latest ./setup_and_run.sh
当 Docker 容器启动后,你可以通过以下命令确认节点是否运行正常:
curl http://localhost:26657/status
如果节点运行正常,会收到 JSON 格式的状态信息。
但是发现这个 latest 版本才到 v12。手动换到最新的 v25。
docker pull ghcr.io/cosmoscontracts/juno:25
该版本docker镜像仍存在问题,/opt/run_junod.sh 中设置 --minimum-gas-prices 0.0001ujunox 代币类型是 ujunox ,而 setup_junod.sh 中设置的代币类型是 ustake 。
vi /opt/run_junod.sh
修改代币类型为 ustake 后,退出容器,将其保存为新的镜像:
(in docker) # exit
docker commit <my_container_name> <new_image_name>
然后启动新的镜像:
docker run --rm -it -p 26657:26657 --name my-juno-local <new_image_name> ./setup_and_run.sh
docker run --rm -it -p 26657:26657 --name my-juno-local my_juno25 ./setup_and_run.sh
补充
wasmd 的 docker 为:
cosmwasm/wasmd:latest
直接使用 wasmd 的镜像不需要以上复杂的修改。
1.2 本地脚本运行
./setup_junod.sh [其他账户]
可以直接创建其他账户,但是必须提前使用 junod keys add <账户名称> $KEYRING 创建好。
#!/bin/sh
#set -o errexit -o nounset -o pipefail
PASSWORD=${PASSWORD:-1234567890}
STAKE=${STAKE_TOKEN:-ustake}
FEE=${FEE_TOKEN:-ucosm}
CHAIN_ID=${CHAIN_ID:-testing}
MONIKER=${MONIKER:-node001}
KEYRING="--keyring-backend test"
TIMEOUT_COMMIT=${TIMEOUT_COMMIT:-5s}
BLOCK_GAS_LIMIT=${GAS_LIMIT:-10000000} # should mirror mainnet
echo "Configured Block Gas Limit: $BLOCK_GAS_LIMIT"
# check the genesis file
GENESIS_FILE="$HOME"/.juno/config/genesis.json
if [ -f "$GENESIS_FILE" ]; then
echo "$GENESIS_FILE exists..."
else
echo "$GENESIS_FILE does not exist. Generating..."
junod init --chain-id "$CHAIN_ID" "$MONIKER"
junod add-ica-config
# staking/governance token is hardcoded in config, change this
sed -i "s/\"stake\"/\"$STAKE\"/" "$GENESIS_FILE"
# this is essential for sub-1s block times (or header times go crazy)
sed -i 's/"time_iota_ms": "1000"/"time_iota_ms": "10"/' "$GENESIS_FILE"
# change gas limit to mainnet value
sed -i 's/"max_gas": "-1"/"max_gas": "'"$BLOCK_GAS_LIMIT"'"/' "$GENESIS_FILE"
# change default keyring-backend to test
sed -i 's/keyring-backend = "os"/keyring-backend = "test"/' "$HOME"/.juno/config/client.toml
fi
APP_TOML_CONFIG="$HOME"/.juno/config/app.toml
APP_TOML_CONFIG_NEW="$HOME"/.juno/config/app_new.toml
CONFIG_TOML_CONFIG="$HOME"/.juno/config/config.toml
if [ -n "$UNSAFE_CORS" ]; then
echo "Unsafe CORS set... updating app.toml and config.toml"
# sorry about this bit, but toml is rubbish for structural editing
sed -n '1h;1!H;${g;s/# Enable defines if the API server should be enabled.\nenable = false/enable = true/;p;}' "$APP_TOML_CONFIG" > "$APP_TOML_CONFIG_NEW"
mv "$APP_TOML_CONFIG_NEW" "$APP_TOML_CONFIG"
# ...and breathe
sed -i "s/enabled-unsafe-cors = false/enabled-unsafe-cors = true/" "$APP_TOML_CONFIG"
sed -i "s/cors_allowed_origins = \[\]/cors_allowed_origins = \[\"\*\"\]/" "$CONFIG_TOML_CONFIG"
fi
# speed up block times for testing environments
sed -i "s/timeout_commit = \"5s\"/timeout_commit = \"$TIMEOUT_COMMIT\"/" "$CONFIG_TOML_CONFIG"
# are we running for the first time?
if ! junod keys show validator $KEYRING; then
(echo "$PASSWORD"; echo "$PASSWORD") | junod keys add validator $KEYRING
# hardcode the validator account for this instance
echo "$PASSWORD" | junod genesis add-genesis-account validator "1000000000$STAKE,1000000000$FEE" $KEYRING
# (optionally) add a few more genesis accounts
for addr in "$@"; do
echo $addr
junod genesis add-genesis-account "$addr" "1000000000$STAKE,1000000000$FEE,5000000000uusd"
done
# submit a genesis validator tx
## Workraround for https://github.com/cosmos/cosmos-sdk/issues/8251
(echo "$PASSWORD"; echo "$PASSWORD"; echo "$PASSWORD") | junod genesis gentx validator "250000000$STAKE" --chain-id="$CHAIN_ID" --amount="250000000$STAKE" $KEYRING
## should be:
# (echo "$PASSWORD"; echo "$PASSWORD"; echo "$PASSWORD") | junod genesis gentx validator "250000000$STAKE" --chain-id="$CHAIN_ID"
junod genesis collect-gentxs
fi
run_junod.sh
#!/bin/sh
if test -n "$1"; then
# need -R not -r to copy hidden files
cp -R "$1/.juno" /root
fi
mkdir -p /root/log
junod start --rpc.laddr tcp://0.0.0.0:26657 --minimum-gas-prices 0.0001ustake --trace
setup_and_run.sh
#!/bin/sh
if test -n "$1"; then
# need -R not -r to copy hidden files
cp -R "$1/.wasmd" /root
fi
mkdir -p /root/log
wasmd start --rpc.laddr tcp://0.0.0.0:26657 --trace
补充
Juno 脚本在 wasmd 上进行了一些参数调整,wasmd 的更简洁,适合学习:
#!/bin/sh
#set -o errexit -o nounset -o pipefail
PASSWORD=${PASSWORD:-1234567890}
STAKE=${STAKE_TOKEN:-ustake}
FEE=${FEE_TOKEN:-ucosm}
CHAIN_ID=${CHAIN_ID:-testing}
MONIKER=${MONIKER:-node001}
wasmd init --chain-id "$CHAIN_ID" "$MONIKER"
# staking/governance token is hardcoded in config, change this
sed -i "s/\"stake\"/\"$STAKE\"/" "$HOME"/.wasmd/config/genesis.json
# this is essential for sub-1s block times (or header times go crazy)
sed -i 's/"time_iota_ms": "1000"/"time_iota_ms": "10"/' "$HOME"/.wasmd/config/genesis.json
if ! wasmd keys show validator; then
(echo "$PASSWORD"; echo "$PASSWORD") | wasmd keys add validator
fi
# hardcode the validator account for this instance
echo "$PASSWORD" | wasmd genesis add-genesis-account validator "1000000000$STAKE,1000000000$FEE"
# (optionally) add a few more genesis accounts
for addr in "$@"; do
echo $addr
wasmd genesis add-genesis-account "$addr" "1000000000$STAKE,1000000000$FEE"
done
# submit a genesis validator tx
## Workraround for https://github.com/cosmos/cosmos-sdk/issues/8251
(echo "$PASSWORD"; echo "$PASSWORD"; echo "$PASSWORD") | wasmd genesis gentx validator "250000000$STAKE" --chain-id="$CHAIN_ID" --amount="250000000$STAKE"
## should be:
# (echo "$PASSWORD"; echo "$PASSWORD"; echo "$PASSWORD") | wasmd gentx validator "250000000$STAKE" --chain-id="$CHAIN_ID"
wasmd genesis collect-gentxs
1.3 分析输出信息
从你提供的节点输出日志来看,运行的节点正在正常参与区块链网络的共识和状态更新流程。以下是日志中各部分的解析:
1.3.1 关键信息解析
-
区块执行和状态提交
INF executed block height=171 module=state num_invalid_txs=0 num_valid_txs=0- 含义:第 171 个区块已经被成功执行。没有无效交易(
num_invalid_txs=0),也没有有效交易(num_valid_txs=0)。 - 模块:
state模块负责应用区块内容并更新状态。
INF committed state app_hash=30D13E32FA9357F0C27B6A5DC48E65E99EA2C302B3CEEED4F44A673BA1D85AEC height=171 module=state num_txs=0- 含义:第 171 个区块的状态变更已被提交,
app_hash是状态哈希,用于验证状态一致性。 - 模块:
state模块。
INF indexed block events height=171 module=txindex- 含义:与第 171 个区块相关的事件(如交易)已被索引,便于后续查询。
- 模块:
txindex模块(交易索引)。
- 含义:第 171 个区块已经被成功执行。没有无效交易(
-
新高度的启动
INF Timed out dur=1994.796713 height=172 module=consensus round=0 step=RoundStepNewHeight- 含义:网络进入了新的区块高度(172)的第 0 轮共识阶段。这表明之前的区块已完成处理,节点正在准备生成新的区块。
-
提案接收与共识
INF received proposal module=consensus proposal="Proposal{172/0 (...)}"- 含义:节点收到了高度为 172 的区块提案(
proposal),提议者是3DAD15B485682FBD43119CDDE685C5142B9B0915。 - 模块:
consensus模块。
INF received complete proposal block hash=01B19714D56F0DB95008F250E626B8E3531AE5A74AA3FBB22768991CD3450D4B height=172 module=consensus- 含义:节点接收到了完整的提案,包含区块的哈希(
block hash)。 - 模块:
consensus模块。
INF finalizing commit of block hash=01B19714D56F0DB95008F250E626B8E3531AE5A74AA3FBB22768991CD3450D4B height=172 module=consensus num_txs=0- 含义:节点达成共识并提交高度为 172 的区块,区块内没有交易。
- 含义:节点收到了高度为 172 的区块提案(
-
铸造代币
INF minted coins from module account amount=63ustake from=mint module=x/bank- 含义:区块链网络铸造了 63 个单位的
ustake代币(可能是通胀奖励或区块奖励)。
- 含义:区块链网络铸造了 63 个单位的
-
确保网络连接
INF Ensure peers module=pex numDialing=0 numInPeers=0 numOutPeers=0 numToDial=10- 含义:节点当前没有入站或出站的网络连接,但它尝试拨号以连接到其他节点(最大 10 个节点)。
- 模块:
pex模块(负责点对点连接管理)。
INF No addresses to dial. Falling back to seeds module=pex- 含义:没有可用的节点地址用于连接,节点将回退到预设的种子节点。
1.3.2 总结
- 节点正在正常运行,且成功参与了网络共识流程。
- 当前的区块中没有有效交易(所有
num_valid_txs=0),可能是因为没有用户向链发送交易。 - 状态哈希(
app_hash)验证了区块链状态的一致性。 - 节点正在尝试建立与其他节点的连接,但暂时没有连接上,可能需要检查网络配置或种子节点的可用性。
2 简单测试
为了模拟远程网络环境(即本地通过 rpc/http 请求访问区块链),按照 1.1 节的方式,使用修改后的docker镜像启动节点:
docker run -it -p 26657:26657 --name my-juno-local my_juno25 ./setup_and_run.sh
为了方便关机后恢复区块链的状态,不添加 --rm 选项。通过 log 实时获取区块链的输出:
docker log -f my-juno-local
以下提到的 本地 即宿主机,模拟普通用户通过 junod 来访问区块链。
~/.juno/config/client.toml 中设置了 junod 作为客户端的配置,将其修改为:
chain-id = "testing"
keyring-backend = "test"
output = "text"
node = "tcp://localhost:26657"
broadcast-mode = "sync"
并且记住,stake 和 交易货币的名字:
STAKE=ustake
FEE=ucosm
2.1 创建本地钱包,并转账
1 创建本地钱包
junod keys add mywallet
这一步并不需要和区块链交互,只是在本地的 ~/.juno/keyring-test 中生成了 mywallet.info 。
检查是否已创建钱包:
junod keys show mywallet
只获取钱包的地址(-a, address):
junod keys show mywallet -a
钱包地址例如:
juno1ddtgryk2xgt3qlsrx8z2kat9gpluw8886wpwhu
此时在本地创建了一个名叫 mywallet 的钱包,里面什么资产都没有。
查询钱包余额:
junod query bank balances <address>
或合并成一行:
junod query bank balances $(junod keys show mywallet -a)
2 validator 转账
如果你是按照第一步运行区块链的,那么区块链在创建之初,就已经分配了一个名外 validator 的账户,有很多初始资产 1000000000ustake,1000000000ujuno 。
现在让 validator 给 mywallet 进行转账。
(这一步不能在本地执行,因为 validator 的账户信息存放在 docker 容器里,没有账户的私钥是无法让它转账的)
进入 docker ,执行:
junod tx bank send validator <recipient_address> \
100000ucosm --fees 200ustake --chain-id testing --keyring-backend test
将 <recipient_address> 替换为你的钱包地址。
junod tx bank send validator juno1ddtgryk2xgt3qlsrx8z2kat9gpluw8886wpwhu 100000ucosm --fees 200ustake --chain-id testing --keyring-backend test
junod tx bank send validator juno1ddtgryk2xgt3qlsrx8z2kat9gpluw8886wpwhu 100000ustake --fees 200ustake --chain-id testing --keyring-backend test
如果执行成功,可以在本地查询到你的资产:
junod query bank balances $(junod keys show mywallet -a)
balances:
- amount: "100000"
denom: ucosm
- amount: "100000"
denom: ustake
pagination:
next_key: null
total: "0"
2.2 部署和调用智能合约
0 前置步骤
安装 rust 环境
sudo apt update && sudo apt upgrade -y
sudo apt install curl build-essential -y
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.bashrc
检查是否安装成功:
rustc --version
cargo --version
安装 cargo 和 wasm 编译工具链,用于编译 Rust 智能合约:
rustup default stable
rustup target add wasm32-unknown-unknown
确保你有一个账户(例如 mywallet)并且有足够的资产来支付部署和调用费用。
1 创建合约项目
如果未安装 cargo-generate,请运行:
cargo install cargo-generate --locked
使用模板创建项目:
cargo generate --git https://github.com/CosmWasm/cw-template.git --name simple_counter
cd simple_counter
这将获取一个简单计数器智能合约。
2 编译智能合约
编译(未优化)
cargo wasm
得到编译好的wasm文件 ./target/wasm32-unknown-unknown/release/simple_counter.wasm
编译并优化
只要不超过大小限制, Juno 支持上传未优化的 wasm。但是为了减少合约的大小,以节省区块链上的存储成本和提高加载效率,通常需要对 wasm 文件进行优化。
cosmwasm 提供了一个优化器,封装了编译和优化的全过程。
docker pull cosmwasm/optimizer:0.16.0
# 确保位于智能合约项目的根目录
docker run --rm -v "$(pwd)":/code cosmwasm/optimizer:0.16.0
-v "$(pwd)":/code将当前目录挂载到容器中的 /code 目录。- 镜像会自动构建和优化你的智能合约,并输出优化后的 .wasm 文件到项目的 artifacts 目录中。
但是有两个问题:
- 下载速度很慢,甚至有 crate 下载失败,可能docker镜像不能走我宿主机的代理?
- 每次启动都得下载,效率太低。
为了解决这些问题:
-
使用共享 Cargo 缓存
将宿主机的 Cargo 缓存目录挂载到容器中,这样即使重新运行也能复用已下载的依赖:
-v "$HOME/.cargo/registry":/usr/local/cargo/registry:挂载依赖缓存。-v "$HOME/.cargo/git":/usr/local/cargo/git:挂载 Git 缓存。 -
运行 docker run 时传递代理环境变量:
-e HTTP_PROXY=http://your-proxy:port -e HTTPS_PROXY=http://your-proxy:port
完整的命令如下:
docker run --rm -v "$(pwd)":/code \
-v "$HOME/.cargo/registry":/usr/local/cargo/registry \
-v "$HOME/.cargo/git":/usr/local/cargo/git \
-e HTTP_PROXY=http://192.168.3.56:21882 \
-e HTTPS_PROXY=http://192.168.3.56:21882 \
cosmwasm/optimizer:0.16.0
此时在 ./artifacts 中得到了优化后的 wasm 文件,文件缩小了约 25%:
240K simple_counter.wasm(优化前)
184K simple_counter.wasm(优化后)
3 部署智能合约
3.1 上传合约:
(注意需要 mywallet 钱包里有足够的钱)
junod tx wasm store simple_counter.wasm --from mywallet --gas 2000000 --fees 50000ustake
gas表示交易执行所允许的最大 Gas 单位数量。fees是为交易支付的手续费。
最后会返回 txhash ,根据 txhash 查询结果:
junod query tx <txhash> --output json | jq '.raw_log | fromjson'
记录其中的 code_id 。
3.2 实例化合约
junod tx wasm instantiate <code_id> '{"count":0}' --from mywallet --label "Simple Counter" --gas 200000 --fees 5000ustake --admin $(junod keys show mywallet -a)
junod tx wasm instantiate 1 '{"count":0}' --from mywallet --label "Simple Counter" --gas 200000 --fees 5000ustake --admin $(junod keys show mywallet -a)
--admin指定一个管理员账户- 或者设置
--no-admin表示合约不可更改,一旦部署,任何人都不能升级或迁移该合约。
根据 txhash 查询结果:
junod query tx <txhash> --output json | jq '.raw_log | fromjson'
记录其中的 _contract_address ,例如:
juno14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9skjuwg8
3.3 调用合约方法
-
增加计数器的值:
junod tx wasm execute <contract_address> '{"increment":{}}' --from mywallet --gas 200000 --fees 5000ustakejunod tx wasm execute juno14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9skjuwg8 '{"increment":{}}' --from mywallet --gas 200000 --fees 5000ustake -
查询计数器的值:
junod query wasm contract-state smart <contract_address> '{"get_count":{}}'junod query wasm contract-state smart juno14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9skjuwg8 '{"get_count":{}}'
尝试多次增加计数器的值,查看返回结果:
data:
count: 2
| 操作类型 | 是否消耗 Gas | 是否支付手续费 | 是否改变链上状态 | 是否需要签名 |
|---|---|---|---|---|
查询(query) | 否 | 否 | 否 | 否 |
执行(execute) | 是 | 是 | 是 | 是 |
2.3 增加新节点
新增一个节点,加入这个区块链。使用本地的多个 docker 容器模拟多个独立的节点。
1 初始化新节点
用之前配置好的镜像 my_juno25 启动一个新的 docker 容器。(记得换一个端口映射)
docker run -it -p 26658:26657 --name my-juno-local-2 my_juno25 /bin/sh
初始化新节点:
junod init <new_node_name> --chain-id <existing_chain_id>
junod init node002 --chain-id testing
2 复制创世区块
从主节点(现有节点)获取 genesis.json 文件,并复制到新节点的配置目录。在宿主机中使用 docker cp:
mkdir -p ~/tmp/
docker cp my-juno-local:/root/.juno/config/genesis.json /root/tmp/
docker cp /root/tmp/genesis.json my-juno-local-2:/root/.juno/config/
3 设置种子节点
种子节点是区块链网络中特定设置的几个稳定的节点,能保持在线状态并提供稳定的连接。新节点通过和种子节点建立P2P连接,来加入区块链网络。
在我们这个例子里,种子节点就是第一个创建的主节点。
修改 ~/.juno/config/config.toml 中的:
vi ~/.juno/config/config.toml
seeds = "<node_id>@<ip_address>:26656"
其中 node_id 是种子节点的 id,在 my-juno-local 镜像中执行如下命令获取节点 id:
junod tendermint show-node-id
ip地址,使用 docker 的默认虚拟网络,在主节点中执行:
ip addr
如果主节点是第一个 docker 容器,应该是 172.17.0.2 。(也可以自定义新的docker network,不在此赘述)
最终修改为:
seeds = "4a47e5f773f7012fd82ea195be94ace24bca13ca@172.17.0.2:26656"
或者使用一行指令:
sed -i 's/^seeds = ""/seeds = "<node_id>@<ip_address>:26656"/' ~/.juno/config/config.toml
sed -i 's/^seeds = ""/seeds = "4a47e5f773f7012fd82ea195be94ace24bca13ca@172.17.0.2:26656"/' ~/.juno/config/config.toml
4 启动新节点
使用 run_junod.sh 脚本:
cd /opt
./run_junod.sh
会开始同步区块链数据,直到同步到最近的节点。
5 验证新节点
在宿主机中:
curl http://localhost:26658/status
向新的节点发起查询请求:
junod query wasm contract-state smart <contract_address> '{"get_count":{}}' \
--node "tcp://localhost:26658"
总结
至此,终于运行了一个 cosmos 区块链,并且在上面部署运行了 Wasm 智能合约。
最后,我发现 Juno 是在 wasmd 的基础上开发的,我误打误撞从 Juno 开始学习了。本文的内容几乎可以直接迁移到 wasmd 使用和学习中。
之后的任务是:
- 如何把 Wasm 模块单独拿出来,例如把 CosmWasm 的虚拟机放到另一个服务器上,让节点通过远程调用的方式执行智能合约。
- 把 CosmWasm 虚拟机移植到 TEE 中。
明天先继续写专利。