Starcoin-framewok绕过二阶段提交强制升级方案

 

一、背景

由于某些原因,v12 framework 需要引入黑名单机制强制销毁部分余额,不通过链上投票方式,直接二进制升级v12

升级方案采用直接修改存储方式

二、方案

1.生成v12 framework package


修改framework 在v11基础上加入patch https://github.com/starcoinorg/starcoin-framework/pull/250 ,

v11提交是cf1deda180af40a8b3e26c0c7b548c4c290cd7e7 这次, 再这次提交上面修改patch, 如果使用move version v6 scrpit function需要修改成entry, 并生成v12 对应的package v12, 注意对应starcoin项目测试用例修改(在步骤4说明)

这里有个问题目前v11的framework是move version 4的,v12是使用move version 6还是move version 4

2. 生成framework v11 升级到 v12 的差异 writeset delta,

也就是v11 对应的区块状态 apply(writeset delta)就能升级为v12
可能有多种方案

方案1

取主网某个状态,构建一个区块package v12 打包成一个Block, 使他可以强制执行package v12,交易 这里主要就是做的事情模拟两阶段提交执行 package交易阶段,将此部分的writeset delta dump出来, dump出来使用starcoin_db_exporter下面命令BlockOutput

方案2

开发一个工具,工具的作用就是将任意(StateKey, resource)和(StateKey, code) 生成对应的writeset,

这样可以直接基于工具把v11 → package v12 生成writeset delta dump出来

pub enum StateKey { AccessPath(AccessPath), TableItem(TableItem), }

这里需要有对应的测试用例,保证生存的writeset无误

方案3

构建某个测试网,测试网对应的framework为v11, 然后让其升级为v12, 找到升级时候执行的package v12的交易,将其对应的write_set dump出来,dump出来使用starcoin_db_exporter下面命令BlockOutput

这个方案存在的唯一问题就是需要对应FrozonConfigStrategy::frozen_list_v1 对应的账号地址,以及对应的余额的余额,

对应账户的地址和余额就能保证销毁的金额是对应,如果无法创建同样账号,是不是需要修改对应 resource路径,来执行删除

 

3.强制执行 矿工挖矿和execute

类似 chain.rs execute_save_directly 函数,执行某个区块时候,强制让其多写入 writeset delta

这里先说下execute_save_directly的场景和实现

主网区块16450410 是攻击区块,在有漏洞的starcoin1上执行了一个结果

在新的升级starcoin2上执行区块16450410 会报交易执行失败,于是导致txn_accmulate, state_root不一致

于是将老的starcoin1执行区块16450410产生的txn_infos, txn_events, write_sets,相关都直接写入到数据库

txn_infos里面有新加入的txn_accmulate的leafs, block_id加入block_accmulator, write_set写入到State_root对应的smt, 保证了starcoin1和starcoin2执行结果一致

 

chain/open-block/src/lib.rs push_txns相关修改

假定目前main net高度是17022671 (高度定为X), 我们约定在 X + 20000时候执行writeset delta逻辑

需要在矿工 执行打包区块交易时候

pub fn push_txns(&mut self, user_txns: Vec<SignedUserTransaction>) -> Result<ExcludedTxns> { for (txn, output) in txns.into_iter().zip(txn_outputs.into_iter()) { let txn_hash = txn.id(); match output.status() { TransactionStatus::Discard(status) => { debug!("discard txn {}, vm status: {:?}", txn_hash, status); discard_txns.push(txn.try_into().expect("user txn")); } TransactionStatus::Keep(status) => { if status != &KeptVMStatus::Executed { debug!("txn {:?} execute error: {:?}", txn_hash, status); } let gas_used = output.gas_used(); self.push_txn_and_state(txn_hash, output)?; self.gas_used += gas_used; self.included_user_txns .push(txn.try_into().expect("user txn")); } TransactionStatus::Retry => { debug!("impossible retry txn {}", txn_hash); discard_txns.push(txn.try_into().expect("user txn")); } }; } Ok(ExcludedTxns { discarded_txns: discard_txns, untouched_txns: untouched_user_txns, })

需要基于高度判断,如果是X + 20000行高度,就执行apply_write_set(writeset delta), 这里这么执行的原因是为了openblock.finaize时候生成的BlockHeader的state_root是正确的以及执行BlockChain::fork(HashValue)这个接口的正确

 

chain.rs execute实现做类似execute_save_directly修改

 

这里存在的问题

starcoin的存储是三棵树,block_accumulator, txn_accumulator, state_root, 目前看来block_accumulator和state_root这两棵树都是对的上的,唯一就是txn_accumulator,少了些销毁黑账户的交易

由于txn_accumulator是TransactionInfo构建,但是

pub enum Transaction { /// Transaction submitted by the user. e.g: P2P payment transaction, publishing module /// transaction, etc. UserTransaction(SignedUserTransaction), /// Transaction to update the block metadata resource at the beginning of a block. BlockMetadata(BlockMetadata), }

我们拿不到这个SignedUserTransaction的签名,构造不了txn_accumulator

三、部署测试

测试准备

测试环境主要以dev、barnard、halley为主要测试网络,需要先准备黑名单账户,强制部署发起账户、基金会账户(基金会账户的导入参考这里https://starcoin.atlassian.net/wiki/spaces/WESTAR/pages/1933320 ),参考以下步骤

  1. 修改配置:

    1. 修改./genesis/src/genesis_config.rs 中的G_[network]_CONFIG 初始化代码中timestamp 字段,将其修改为当前时间戳,并且以毫秒为准(时间戳可以从这里取https://www.unixtimestamp.com/ ),提交

    2. 打标签 halley[0-9]_force_upgrade_v11 ,并执行https://github.com/starcoinorg/starcoin/actions/workflows/docker_build.yml 构建镜像方便k8s进行部署

  2. 部署准备,导入以下账号并且保证其账号中拥有一些STC

  3. 其他配置

    1. 去掉GAS_SCHEDULE_UPGRADE_VERSION_MARK 相关,需要加上这个PR https://github.com/starcoinorg/starcoin/commit/0cd1e39e81f0e0f75bb94fec377bf21352af4fbd

    2. 测试halley网络,来模拟 stdlib使用v11版本,version使用v6版本,
      需要修改下v11所使用的framework,测试halley网络通过后再恢复过来 相关PR

      1. https://github.com/starcoinorg/starcoin/commit/8e279c54cac772558941f31eebaa49322ae52327

      2. https://github.com/starcoinorg/starcoin/commit/81f5af1e2c66935028bd387061c4ab281ff33984

    3. rust 更新 version 为12 https://github.com/starcoinorg/starcoin/commit/03e4617209b90bc7ed3b99c756fd9c0db9c5af69

    4. barnard网络需要回滚到高度 https://stcscan.io/barnard/blocks/height/16113038 以下

Halley网络测试

 

barnard网络测试

回滚barnard到v11版本,给barnard做个特殊v12, 修改FrozonConfigStrategy::frozen_list_v1对应的账号地址,强制执行升级看是否达到对应效果

starcion_vm.rs 对应的GAS_SCHEDULE_UPGRADE_VERSION_MARK逻辑需要去掉

确认 test_package_init_function 是否需要修改

 

  1. 寻找一个合适的区块进行版本号检查,需要确认该区块的Stdlib 版本号是v11且 MoveLanguage的版本号为6,可以使用使用以下命令进行连接和查看,版本升级应该是在16113030和16113040之间,这里取16113030

  2. 需要在分支build的tag中包含以下文字以方便跟peer进行区分:barnard_rollback_block_fix ,该部分在 https://github.com/starcoinorg/starcoin/blob/master/network/src/service.rs#L42 中被定义,这里的逻辑是当发现peer的版本名称中不包含该名字,则其同步块会被排除在外

  3. 配置延长区块回滚的容忍时间,由于区块回滚可能会导致长时间无响应,该定义在kube/manifest/starcoin-barnard.yamlinitialDelaySeconds相关字段(改成600),其中

    1. livenessProbe 用于检测容器是否还在运行。如果探测失败,Kubernetes 会重启该容器

    2. readinessProbe 用于检测容器是否准备好接受流量。如果探测失败,Kubernetes 会停止将流量转发到该容器,直到探测成功。

  4. 如果当前节点中的Peer列表中的低版本的节点的区块比当前节点的区块要长,此时应该将该低版本节点禁止掉以防止同步旧的区块,操作参考以下命令

  5. 由于mater链上的交易被关闭,关闭的逻辑在 open-block 工程下的AddressFilter 中, 由于区块回滚的块号过低导致当前块号低于了需要被屏蔽的块号从而导致可以发出交易,故需要将该部分的块号同步为回滚目标块号。

  6. 需要预估一个强制升级块号,比较了barnard两个节点的日志,其中starcoin-0 在770秒内出了232个块,starcoin-1在202秒出了65个块,计算平均3.4秒出一个块每小时可出 3600/3.4=1,058.8235294118,预计1060个块,按1100个计算,则强制升级块在回滚区块之后的+2000~3000个块比较合理

YSG
March 21, 2024

barnard和main都升级了language version为v6, https://stcscan.io/main/address/0x01/resources 搜version字段可以看到结果