EOSIO Bios Boot 启动序列实战指南:使用 bios-boot-tutorial 脚本模拟主网冷启动全流程
EOSIO Bios Boot 启动序列实战指南:使用 bios-boot-tutorial 脚本模拟主网冷启动全流程
bios-boot-tutorial 是 EOSIO 开源仓库中随附的启动模拟工具集(位于 tutorials/bios-boot-tutorial/),其核心脚本 bios-boot-tutorial.py 会在单机上自动编排 nodeos、keosd、cleos 完成从创世块到 DPOS 出块网络形成的完整 BIOS Boot 启动序列。本文将以该 README 为主线,结合脚本源码、创世配置与系统合约实现,完整讲解环境准备、启动命令、每个阶段背后的操作与原理,帮助你复现一条全新的 EOSIO 链从零到投票上线的全过程。
一、什么是 Bios Boot 启动序列
在 EOSIO 中,一条全新链的启动并不像"启动一个节点"那么简单。要让链真正"活"起来,需要依次完成以下工作:以初始生产者身份出块、创建系统账户、安装系统合约、发行代币、初始化系统合约、创建带质押的普通账户、注册并启动生产者节点、投票形成生产者调度(producer schedule),最终把链的治理权从启动者 eosio 账户移交给链上的 21 个生产者。这一整套流程被统称为 Bios Boot 启动序列(Bios Boot Sequence),bios-boot-tutorial.py 的作用就是"模拟"这个过程——因此 README 开篇即说明:
The
bios-boot-tutorial.pyscript simulates the EOSIO bios boot sequence.
启动序列的第一个阶段依赖一个极简系统合约 eosio.bios。从源码注释(contracts/contracts/eosio.bios/include/eosio.bios/eosio.bios.hpp)可以看到它的定位:
eosio.biosis the first sample of system contract ... a minimalist system contract because it only supplies the actions that are absolutely critical to bootstrap a chain and nothing more.
它只暴露启动一条链所必需的极少量动作,其中包括 setprods(设置生产者调度)、setpriv(设置特权账户)、setalimits(设置资源限额)、setparams(设置链参数)、activate(激活协议特性)等,同时声明了 newaccount、updateauth、setcode、setabi 等由 EOSIO 核心层实现、而非合约层实现的 native actions(原生动作)。启动完成后,eosio.bios 会被功能完整的 eosio.system 合约替换,进入常规运行期。
二、前置条件与依赖准备
README 明确列出了三项基础前置条件:
- Python 3.x:脚本本身是 Python 3 程序;
- CMake:用于编译链二进制与系统合约;
- git:用于拉取源码仓库。
在此基础上,还需要按顺序准备三套二进制/合约产物(对应 README 的 1~4 步):
1. 安装 EOSIO 核心二进制
编译安装 nodeos、cleos、keosd。本仓库提供了两套路径:
- 预编译二进制安装:参见 docs/00_install/00_install-prebuilt-binaries.md;
- 从源码构建:参见 docs/00_install/01_build-from-source/index.md 及 scripts/eosio_install.sh 等构建脚本,
eosio_build.sh、eosio_build_ubuntu.sh、eosio_build_darwin.sh、eosio_build_centos.sh分别覆盖不同平台。
2. 安装 eosio.cdt(合约开发工具链)
eosio.cdt 用于将 C++ 合约编译为 WASM 与 ABI。README 要求安装其二进制发布版。
3. 编译 eosio.contracts 系统合约仓库
系统合约(eosio.system、eosio.token、eosio.msig、eosio.boot 等)位于独立的 eosio.contracts 仓库中,需要按其编译指南完成编译,但不要执行其部署(deploy)步骤——部署由本教程脚本统一完成。
编译完成后,合约产物默认位于 build/contracts/ 目录下,README 将该目录记为 EOSIO_CONTRACTS_DIRECTORY,后续启动命令需要替换成它的真实路径。
说明:当前 eos 仓库根目录的 contracts/ 目录仅包含
eosio.bios与eosio.boot两个合约源码(其余系统合约以编译产物形式存在于 unittests/contracts/ 中供测试使用),而脚本启动阶段会按eosio.token、eosio.msig、eosio.boot、eosio.system的顺序从--contracts-dir加载,因此该目录必须指向完整编译过的eosio.contracts产物。
三、仓库文件结构
tutorials/bios-boot-tutorial/ 目录共四个文件,各司其职:
| 文件 | 作用 |
|---|---|
| README.md | 使用说明(本文主题文档) |
| bios-boot-tutorial.py | 启动序列编排主脚本 |
| accounts.json | 预生成的用户与生产者账户密钥对 |
| genesis.json | 创世块配置(初始时间、初始密钥、链参数) |
四、最小启动命令
按 README 第 5 步,克隆仓库后进入教程目录,执行(务必把 EOSIO_CONTRACTS_DIRECTORY 替换为实际合约目录):
$ cd ~
$ git clone https://github.com/EOSIO/eos.git
$ cd ./eos/tutorials/bios-boot-tutorial/
$ python3 bios-boot-tutorial.py --cleos=cleos --nodeos=nodeos --keosd=keosd --contracts-dir="EOSIO_CONTRACTS_DIRECTORY" -w -a
其中:
--cleos、--nodeos、--keosd分别指定三个二进制的调用方式(可以给完整路径);--contracts-dir指定系统合约编译产物目录;-w启动钱包(对应--wallet步骤);-a/--all自动执行所有标记为(*)的步骤。
脚本提示语也明确:-a does almost everything. -h shows options. 不带任何参数直接运行时,会打印该提示并退出。
五、脚本编排的完整步骤清单
bios-boot-tutorial.py 将启动序列拆分为可独立执行、也可串联执行的子步骤(见 bios-boot-tutorial.py 中的 commands 列表)。每个步骤可用短参数或长参数单独触发:
| 短参 | 长参 | 说明 | 是否含于 -a |
|---|---|---|---|
-k |
--kill |
杀掉所有 nodeos 与 keosd 进程 | (*) |
-w |
--wallet |
启动 keosd、创建钱包并导入密钥 | (*) |
-b |
--boot |
启动引导(boot)节点 | (*) |
-s |
--sys |
创建系统账户(eosio.*) | (*) |
-c |
--contracts |
安装系统合约(token、msig) | (*) |
-t |
--tokens |
创建代币 | (*) |
-S |
--sys-contract |
设置系统合约 | (*) |
-I |
--init-sys-contract |
初始化系统合约 | (*) |
-T |
--stake |
创建带质押的账户 | (*) |
-p |
--reg-prod |
注册生产者 | (*) |
-P |
--start-prod |
启动生产者节点 | (*) |
-v |
--vote |
为生产者投票 | (*) |
-R |
--claim |
领取奖励(claimrewards) | (*) |
-x |
--proxy |
代理投票 | (*) |
-q |
--resign |
移交 eosio 权限 | (*) |
-m |
--msg-replace |
用多签替换系统合约 | 否 |
-X |
--xfer |
随机转账(无限循环) | 否 |
-l |
--log |
显示节点日志尾部 60 行 | (*) |
步骤内部按 commands 定义的顺序依次调用对应的 step* 函数,实现"先杀旧进程、再起钱包、再启动引导节点……"的严格时序。
六、分阶段深度解析:每个步骤做了什么
下面结合 bios-boot-tutorial.py 源码逐阶段说明,便于理解启动序列的真实调用链。
阶段 0:清理环境(-k)
stepKillAll() 执行 killall keosd nodeos || true 并等待 1.5 秒,确保上一次运行残留的进程被清理,避免端口与数据目录冲突。
阶段 1:启动钱包并导入密钥(-w)
startWallet() 会:
- 删除并重建
--wallet-dir(默认./wallet/); - 以后台进程启动
keosd --unlock-timeout 999999999 --http-server-address 127.0.0.1:6666 --wallet-dir <dir>,即钱包监听在127.0.0.1:6666; - 用
cleos wallet create --to-console创建默认钱包。
随后 importKeys() 依次导入:脚本内置默认私钥、前 --max-user-keys(默认 10)个用户账户的私钥、以及全部生产者账户的私钥(去重后)。这意味着钱包里预先持有链上所有相关账户的签名能力,供后续 cleos 命令直接使用。
阶段 2:启动引导节点(-b)
stepStartBoot() 以 eosio 账户身份启动 0 号节点,即引导节点。关键启动参数(见 startNode(),bios-boot-tutorial.py 中实现):
--genesis-json:指定创世配置(默认./genesis.json);--enable-stale-production --producer-name eosio:允许在无其他生产者的情况下由eosio独自持续出块;--private-key '["<pub>","<pvt>"]':注入eosio的签名密钥;--max-transaction-time=200:单交易最大执行时间 200ms,注释特别说明该值必须小于出块间隔(block_interval_ms,定义于 libraries/chain/include/eosio/chain/config.hpp,为 500ms);--contracts-console:打印合约输出;- HTTP 监听
127.0.0.1:8000、P2P 监听127.0.0.1:9000; - 仅 0 号节点额外启用
eosio::history_plugin与eosio::history_api_plugin; - 节点运行日志写入
<nodes-dir>00-eosio/stderr,命令行本身也会先写入该文件便于排查。
启动后脚本 sleep(10.0) 等待链稳定出块。
阶段 3:创建系统账户(-s)
createSystemAccounts() 遍历 systemAccounts 列表(见 bios-boot-tutorial.py 顶部),用 cleos create account eosio <name> <public_key> 逐一创建以下 10 个 eosio.* 系统账户:
eosio.bpay eosio.msig eosio.names eosio.ram eosio.ramfee
eosio.saving eosio.stake eosio.token eosio.vpay eosio.rex
这些账户是 EOSIO 系统合约运行所依赖的特殊账户(分别负责生产者奖励、多签、域名拍卖、内存买卖、质押、代币、REX 等职能)。
阶段 4:安装系统合约(-c)
stepInstallSystemContracts() 部署最先需要的两个合约:
cleos set contract eosio.token <contracts-dir>/eosio.token/
cleos set contract eosio.msig <contracts-dir>/eosio.msig/
此时 eosio 账户上尚未安装系统合约,链上仅有原生动作可用;eosio.token 与 eosio.msig 先行就位,为后续发币与多签做准备。
阶段 5:创建代币(-t)
stepCreateTokens() 分三步完成代币供给:
- 调用
eosio.token::create创建最大供给为10000000000.0000 <symbol>(默认 symbol 为SYS,可用--symbol修改)的代币; allocateFunds()使用 Pareto 分布(80/20 法则,参数 1.161)在全部用户与生产者之间分配总额 10 亿(1000000000)代币,资金分配写回accounts[i]['funds'];- 调用
eosio.token::issue向eosio账户发行等额代币。
阶段 6:设置系统合约并激活协议特性(-S)
这是整个脚本中最关键、最复杂的阶段(stepSetSystemContract()),完整呈现了 v1.8+ 链启动必须遵循的协议特性激活顺序:
- 激活 PREACTIVATE_FEATURE:先通过 producer API 调用
POST /v1/producer/schedule_protocol_feature_activations,激活 digest 为0ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd的 PREACTIVATE_FEATURE(该特性允许在部署完整系统合约之前预激活其他协议特性),然后sleep(3); - 部署 eosio.boot:
cleos set contract eosio <contracts-dir>/eosio.boot/。eosio.boot提供activate、reqactivated等动作(源码见 contracts/contracts/eosio.boot/),使链能够在eosio.system部署之前逐个激活其余协议特性; - 逐个激活其余协议特性:脚本通过
cleos push action eosio activate '["<digest>"]' -p eosio@active依次激活以下 16 个特性(digest 均硬编码于脚本中):
| 特性 | digest 前缀 |
|---|---|
| KV_DATABASE | 825ee6288fb1373eab... |
| ACTION_RETURN_VALUE | c3a6138c5061cf2913... |
| CONFIGURABLE_WASM_LIMITS | bf61537fd21c61a60e... |
| BLOCKCHAIN_PARAMETERS | 5443fcf88330c586bc... |
| GET_SENDER | f0af56d2c5a48d60a4... |
| FORWARD_SETCODE | 2652f5f96006294109... |
| ONLY_BILL_FIRST_AUTHORIZER | 8ba52fe7a3956c5cd3... |
| RESTRICT_ACTION_TO_SELF | ad9e3d8f650687709f... |
| DISALLOW_EMPTY_PRODUCER_SCHEDULE | 68dcaa34c0517d1966... |
| FIX_LINKAUTH_RESTRICTION | e0fb64b1085cc55389... |
| REPLACE_DEFERRED | ef43112c6543b88db2... |
| NO_DUPLICATE_DEFERRED_ID | 4a90c00d55454dc5b0... |
| ONLY_LINK_TO_EXISTING_PERMISSION | 1a99a59d87e06e09ec... |
| RAM_RESTRICTIONS | 4e7bf348da00a94548... |
| WEBAUTHN_KEY | 4fca8bd82bbd181e71... |
| WTMSIG_BLOCK_SIGNATURES | 299dcb6af692324b89... |
- 部署 eosio.system:
cleos set contract eosio <contracts-dir>/eosio.system/; - 设置 eosio.msig 特权:
cleos push action eosio setpriv '["eosio.msig", 1]' -p eosio@active——只有安装eosio.system后setpriv才可用,将eosio.msig标记为特权账户(特权账户可以跳过普通账户的资源限制),为后续多签替换合约做准备。
阶段 7:初始化系统合约(-I)
stepInitSystemContract() 调用:
cleos push action eosio init '["0", "4,SYS"]' -p eosio@active
即初始化 eosio.system:第一个参数 0 为初始总票数,第二个参数 4,SYS 为系统代币精度与符号(精度 4 位小数,符号 SYS),与阶段 5 发行的代币保持一致。
阶段 8:创建带质押的账户(-T)
createStakedAccounts() 为每个用户计算资金分配并创建账户,核心逻辑:
- 每账户资金
funds按ram + stakeNet + stakeCpu + unstaked拆解,并assert校验守恒; - 若
funds不足以覆盖 RAM 费用(--ram-funds,默认 0.1 个代币)则跳过该用户; - 先用
cleos system newaccount --transfer eosio <name> <pub> --stake-net "X" --stake-cpu "Y" --buy-ram "Z"创建账户并质押(--transfer表示质押资金从eosio划转); - 若有剩余未质押资金,再用
cleos transfer eosio <name> "..."转账。 - 命令封装了
retry()重试逻辑:失败时持续重试直到成功,规避临时性网络/链上错误。
阶段 9:注册并启动生产者(-p、-P)
stepRegProducers():对每个预生成的生产者账户执行cleos system regproducer <name> <pub> https://<name>.com/<pub>注册为区块生产者,随后cleos system listproducers确认注册结果;stepStartProducers():为每个生产者账户启动一个独立的nodeos实例(1~N 号节点)。startNode()生成的命令与引导节点类似,但额外包含:
--p2p-peer-address localhost:9000 ... --p2p-peer-address localhost:9000+i
即第 i 个生产者节点会与之前的所有节点(含引导节点)建立 P2P 连接,形成星型拓扑;各节点 HTTP 端口依次为 8000+i、P2P 端口为 9000+i,并设置 --max-clients 与 --p2p-max-nodes-per-host 为 numProducers + 10。之后 sleep(--producer-sync-delay)(默认 80 秒)等待所有节点同步到同一链头。
阶段 10:投票与代理投票(-v、-x)
vote():前--num-voters(默认 10)个用户各自随机挑选--num-producers-vote(默认 20)个生产者,执行cleos system voteproducer prods <voter> <prod1> <prod2> ...;proxyVotes():先让首个生产者账户注册为投票代理(regproxy),再让所有投票用户执行cleos system voteproducer proxy <voter> <proxy>把投票权代理出去,演示代理投票机制。
投票完成后链上形成有效生产者调度,DPOS 出块进入常规状态。
阶段 11:领取奖励(-R)
claimRewards() 读取 cleos get table eosio eosio producers -l 100,对存在 unpaid_blocks 且未领取过奖励的生产者逐一执行 cleos system claimrewards -j <owner>,并统计每次领取动作的 elapsed 耗时,验证生产者奖励发放链路。
阶段 12:移交权限(-q)
stepResign() 完成启动序列的"交权"动作,将链的超级权限从启动者手中移交出去:
resign('eosio', 'eosio.prods'):把eosio的owner、active权限改为由eosio.prods@active控制;- 对全部 10 个
eosio.*系统账户执行resign(a, 'eosio'):把这些账户权限移交给eosio@active。
updateAuth() 通过 cleos push action eosio updateauth ... -p <account>@<permission> 将对应权限的授权改为 {accounts: [{permission: {actor: controller, permission: 'active'}, weight: 1}]},即由 controller 的 active 权限代管。这模拟了真实主网启动后 eosio 权限由 21 个超级节点(eosio.prods)共同治理的最终形态。
阶段 13:多签替换合约(-m,不在 -a 中)
msigReplaceSystem() 演示了用多签升级系统合约的完整闭环(以替换为快速退款版 eosio.system 为例,wasm 路径为 ./fast.refund/eosio.system/eosio.system.wasm):
buyrambytes为eosio购买额外 RAM(200000 字节)以备合约升级占用;msigProposeReplaceSystem:由首个用户账户发起多签提案fast.unstake,请求所有生产者账户批准,交易内容为eosio setcode(以十六进制方式内嵌新合约字节码);msigApproveReplaceSystem:所有生产者账户逐一multisig approve;msigExecReplaceSystem:发起者执行multisig exec落地升级。
该步骤不在 -a 默认流程中,需要 -m 单独触发,用于展示启动完成后如何以去中心化方式治理链。
阶段 14:随机转账压测(-X,不在 -a 中)
randomTransfer() 进入无限循环,随机选择 --num-senders 范围内的两个账户互转 0.0001 <symbol>,命令带 || true 容忍失败,可用于观察链在高频小额转账下的运行表现,配合 -l 查看节点日志。
七、完整命令行参数参考
脚本通过 argparse 定义参数(见 bios-boot-tutorial.py 命令行参数区),除步骤开关外,常用配置参数如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
--public-key |
EOS8Znrtgwt8TfpmbVpTKvA2oB8Nqey625CLN8bCN3TEbgx86Dsvr |
引导密钥公钥(与 genesis.json 的 initial_key 一致) |
--private-key |
5K463ynhZoCDDa4RDcr63cUwWLTnKqmdcoTKTHBjqoKfv4u5V7p |
引导密钥私钥 |
--cleos |
../../build/programs/cleos/cleos --wallet-url http://127.0.0.1:6666 |
cleos 命令(脚本会自动追加 --url http://127.0.0.1:<http-port>) |
--nodeos |
../../build/programs/nodeos/nodeos |
nodeos 二进制路径 |
--keosd |
../../build/programs/keosd/keosd |
keosd 二进制路径 |
--contracts-dir |
../../build/contracts/ |
系统合约编译产物目录(EOSIO_CONTRACTS_DIRECTORY) |
--nodes-dir |
./nodes/ |
各节点数据/日志目录 |
--genesis |
./genesis.json |
创世配置路径 |
--wallet-dir |
./wallet/ |
钱包目录 |
--log-path |
./output.log |
脚本日志文件 |
--symbol |
SYS |
系统代币符号 |
--user-limit |
3000 |
最多使用的用户数(0 = 不限) |
--max-user-keys |
10 |
导入钱包的最大用户密钥数 |
--ram-funds |
0.1 |
每个用户用于购买 RAM 的资金 |
--min-stake |
0.9 |
分配未质押资金前的最小质押额 |
--max-unstaked |
10 |
最大未质押资金 |
--producer-limit |
0 |
最多使用的生产者数(0 = 不限) |
--min-producer-funds |
1000.0000 |
生产者最低资金(不足则补齐) |
--num-producers-vote |
20 |
每个用户投票的生产者数量 |
--num-voters |
10 |
参与投票的用户数 |
--num-senders |
10 |
随机转账的用户范围 |
--producer-sync-delay |
80 |
等待生产者节点同步的秒数 |
-H / --http-port |
8000 |
cleos 使用的 HTTP 端口 |
八、创世配置详解
genesis.json 定义引导节点的创世参数:
| 字段 | 值 | 说明 |
|---|---|---|
initial_timestamp |
2018-03-02T12:00:00.000 |
创世时间戳 |
initial_key |
EOS8Znrtgwt8TfpmbVpTKvA2oB8Nqey625CLN8bCN3TEbgx86Dsvr |
初始出块者公钥(与脚本 --public-key 一致) |
initial_chain_id |
全零哈希 | 初始链 ID |
initial_configuration |
见下 | 链级资源参数 |
initial_configuration 包含的链参数(即 eosio.system 中 setparams 管理的 blockchain_parameters 初始值):max_block_net_usage=1048576、target_block_net_usage_pct=1000、max_transaction_net_usage=524288、base_per_transaction_net_usage=12、net_usage_leeway=500、context_free_discount_net_usage_num/den=20/100、max_block_cpu_usage=100000、target_block_cpu_usage_pct=500、max_transaction_cpu_usage=90000、min_transaction_cpu_usage=100、max_transaction_lifetime=3600、deferred_trx_expiration_window=600、max_transaction_delay=3888000、max_inline_action_size=4096、max_inline_action_depth=4、max_authority_depth=6。这些参数共同约束链上每区块/每交易的 CPU 与 NET 使用上限,可在 libraries/chain/chain_config.cpp 中看到其在核心层的解析与校验逻辑。
九、账户数据说明
accounts.json 是一个大型 JSON 文件,包含:
producers:30 个预生成的生产者账户(producer111a~producer111z、producer1111~producer1114),各含name、pvt、pub字段;users:按useraaaaaaaa风格批量预生成的普通用户账户(默认取前 3000 个,可通过--user-limit调整)。
脚本启动时读取该文件,按 --user-limit / --producer-limit 截断后合并为 accounts 列表,firstProducer 即用户列表长度,生产者从该下标开始。脚本中还保留了一个辅助函数 produceNewAccounts(),可用 cleos create key --to-console 生成更多 user<hex> 账户并追加到 newusers 文件,方便扩展账户规模。
十、运行输出与排错
- 脚本每个外部命令都会先打印再执行:
bios-boot-tutorial.py: <command>,并追加写入--log-path指定的日志文件(默认./output.log); - 关键操作(如创建质押账户、注册生产者、投票)使用
retry()包装,失败会持续重试并打印*** Retry,可容忍链上瞬时失败; - 引导节点与其他节点的输出(stderr)分别写入
<nodes-dir>00-eosio/stderr与<nodes-dir>NN-<producer>/stderr,需要排查时可用-l/--log直接显示引导节点日志尾部 60 行,或自行tail对应文件; - 再次运行前建议先用
-k清理残留进程;-a已在流程开头包含kill步骤。
十一、适用前提与限制
- 本教程面向 EOSIO 2.x 时代的链启动流程,脚本中的协议特性 digest 与系统合约部署顺序以当前仓库实现为准(对应 v2.1.x 发布分支,见 docs/30_release-notes/);
- 脚本默认使用内置的固定密钥对,仅适用于本地测试网络,切勿用于生产环境密钥管理;
- 系统合约必须来自编译产物目录
EOSIO_CONTRACTS_DIRECTORY(README 强调只编译不部署),脚本本身不负责编译合约; - 对真实主网而言,Bios Boot 涉及更多治理与安全环节,本脚本主要用于开发、测试与理解启动机制的模拟环境,相关概念可进一步参考 docs/01_nodeos/07_concepts/ 等文档。
十二、小结
bios-boot-tutorial.py 以不到 500 行的 Python 编排代码,完整复现了 EOSIO 链从创世块、系统合约、代币发行、协议特性激活、账户质押、生产者注册投票到权限移交的整个冷启动生命周期。无论你是想搭建一条本地开发链、验证系统合约升级流程,还是深入理解 DPOS 链的启动机制,都可以以本教程为起点,逐阶段(-s、-c、-t、-S、-I、-T、-p、-P、-v、-q)观察每一步在链上产生的实际效果。