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 ),参考以下步骤
修改配置:
修改
./genesis/src/genesis_config.rs
中的G_[network]_CONFIG
初始化代码中timestamp
字段,将其修改为当前时间戳,并且以毫秒为准(时间戳可以从这里取https://www.unixtimestamp.com/ ),提交打标签
halley[0-9]_force_upgrade_v11
,并执行https://github.com/starcoinorg/starcoin/actions/workflows/docker_build.yml 构建镜像方便k8s进行部署
部署准备,导入以下账号并且保证其账号中拥有一些STC
其他配置
去掉GAS_SCHEDULE_UPGRADE_VERSION_MARK 相关,需要加上这个PR https://github.com/starcoinorg/starcoin/commit/0cd1e39e81f0e0f75bb94fec377bf21352af4fbd
测试halley网络,来模拟 stdlib使用v11版本,version使用v6版本,
需要修改下v11所使用的framework,测试halley网络通过后再恢复过来 相关PRrust 更新 version 为12 https://github.com/starcoinorg/starcoin/commit/03e4617209b90bc7ed3b99c756fd9c0db9c5af69
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
是否需要修改
寻找一个合适的区块进行版本号检查,需要确认该区块的Stdlib 版本号是v11且 MoveLanguage的版本号为6,可以使用使用以下命令进行连接和查看,版本升级应该是在16113030和16113040之间,这里取16113030
需要在分支build的tag中包含以下文字以方便跟peer进行区分:
barnard_rollback_block_fix
,该部分在 https://github.com/starcoinorg/starcoin/blob/master/network/src/service.rs#L42 中被定义,这里的逻辑是当发现peer的版本名称中不包含该名字,则其同步块会被排除在外配置延长区块回滚的容忍时间,由于区块回滚可能会导致长时间无响应,该定义在
kube/manifest/starcoin-barnard.yaml
中initialDelaySeconds相关字段(改成600),其中livenessProbe
用于检测容器是否还在运行。如果探测失败,Kubernetes 会重启该容器readinessProbe
用于检测容器是否准备好接受流量。如果探测失败,Kubernetes 会停止将流量转发到该容器,直到探测成功。
如果当前节点中的Peer列表中的低版本的节点的区块比当前节点的区块要长,此时应该将该低版本节点禁止掉以防止同步旧的区块,操作参考以下命令
由于mater链上的交易被关闭,关闭的逻辑在 open-block 工程下的
AddressFilter
中, 由于区块回滚的块号过低导致当前块号低于了需要被屏蔽的块号从而导致可以发出交易,故需要将该部分的块号同步为回滚目标块号。需要预估一个强制升级块号,比较了barnard两个节点的日志,其中starcoin-0 在770秒内出了232个块,starcoin-1在202秒出了65个块,计算平均3.4秒出一个块每小时可出 3600/3.4=1,058.8235294118,预计1060个块,按1100个计算,则强制升级块在回滚区块之后的+2000~3000个块比较合理