原文地址:https://docs.corda.net/upgrading-cordapps.html#contract-and-state-versioning
这里有两种类型的 contract/state 升级:
- 隐式的升级:使用约束(constraints)允许提前对于 contract 开发多种实现
- 显式的升级:创建一个特殊的更新合约的 transaction然后使用升级合约 flows 来获得 state 的所有参与者的签名
这里我们会关注显式的升级。
在显式的升级中,contracts 和 state 可以按照任何的方式来变化,这些变化仅仅在 state 的所有参与者对这个升级都同意的条件下才会生效。下边是可能的更新组合:
- Contract 更新了,但是 state 定义没有更新
- State 更新了,但是 contract 保持不变
- Contract 和 State 都更新了
使用一个特殊日子(flag-day) 的方式来更新 state 或者 contract 非常简单:
- 更新并且测试 state 或者 contract
- 生成一个新的 CorDapp JAR 文件并且分发给所有相关的节点
- 每个节点的操作者停止他们的节点,将已经存在的 JAR 文件用新版本的替换,然后重启节点。他们可能会首先执行节点排空(node drain)以避免当 flow 还在进行的过程中的时候关于 state 或者 contract 的定义却变了。
- 在每个节点上对每个需要升级的 state 运行合约升级授权(contract upgrade authorisation)flow。
- 针对于每个 state,某节点应该运行合约升级初始化 flow,它会同其他的节点进行沟通。
更新流程
编写新的 state 和 contract 定义
由更新 contract 和/或 state 定义开始。对于如何更新 states,并没有任何的限制。但是更新 contracts 必须要实现 UpgradedContract
接口。接口定义如下:
interface UpgradedContract<in OldState : ContractState, out NewState : ContractState> : Contract {
val legacyContract: ContractClassName
fun upgrade(state: OldState): NewState
}
upgrade
方法描述了旧的 state 类型是如何更新成新的 state 类型的。如果 state 没有更新的话,那可以给旧的和新的 state 类型参数使用相同的 state 类型。
新的 contract 默认只能够更新在白名单中的已有的 states。如果使用了 hash 或者其他的约束类型话,新的 contract 必须要实现 UpgradedContractWithLegacyConstraint
,并且需要显式地指明是哪种约束:
interface UpgradedContractWithLegacyConstraint<in OldState : ContractState, out NewState : ContractState> : UpgradedContract<OldState, NewState> {
val legacyContractConstraint: AttachmentConstraint
}
比如,如果是 hash 约束的话,那么原始的 JAR 文件的 hash 需要被提供:
override val legacyContractConstraint: AttachmentConstraint
get() = HashAttachmentConstraint(SecureHash.parse("E02BD2B9B010BBCE49C0D7C35BECEF2C79BEB2EE80D902B54CC9231418A4FA0C"))
升级授权 Authorising
如果新的 states 和 contracts 已经被放到了所有节点的 classpath 下之后,下一步就是每个节点去运行 ContractUpgradeFlow.Authorise
flow。这个 flow 会带有一个需要更新的 StateAndRef
的 state,还有一个对新的 contract 的引用,这个 contract 必须要实现 UpgradedContract
接口。
在任何时间,节点的管理员都可以通过运行 ContractUpgradeFlow.Deauthorise
flow 来不通过一个 contract 的升级。
执行升级
当所有的节点都执行完了授权流程后,必须要选择一个参与节点通过 ContractUpgradeFlow.Initiate
flow 来初始对每个 state 对象的更新。这个 flow 有这样的特点:
class Initiate<OldState : ContractState, out NewState : ContractState>(
originalState: StateAndRef<OldState>,
newContractClass: Class<out UpgradedContract<OldState, NewState>>
) : AbstractStateReplacementFlow.Instigator<OldState, NewState, Class<out UpgradedContract<OldState, NewState>>>(originalState, newContractClass)
这个 flow 是 AbstractStateReplacementFlow
的子类(sub-class),它也可以用来对不需要更新 contract 的 state 对象进行更新。
当 flow 成功结束后,所有参与节点的旧的 state 对象应该被更新为升级过的 state 对象了,他们也会指向新的 contract code。
需要注意的点
Contract 更新 flows 的能力
- 不需要管它的名字,
ContractUpgradeFlow
同样可以处理 state 对象的更新 - 在一次升级中,State 可以彻底的发生改变。比如可以从一个
猫
state 变成狗
state,只需要确保所有猫
state 的参与者都同意这个变化 - 同样,state 对象还可以完全不变
- 如果一个节点没有运行 contract 升级授权 flow 的话,他们将不会更新 contract 和/或 state 对象的更新
- 被授权的升级可以在后来被取消授权
- 升级不需要马上执行。在一段时期内,双方还是可以继续使用旧的 state 和 contracts
- State schema 改动需要单独处理
编写新的 states 和 contracts
- 如果一个属性从一个 state 中被移除的话,那么在 contract code 中队它的任何引用也需要被移除。负责的话你会不能成功地编译你的 contract code。通常我们不建议从 states 中删除属性,而是将他们标记成已经弃用的(deprecated)
- 当向一个 state 中添加属性的时候,需要考虑一下新加的属性会对引用这个 state 的 transaction 验证产生怎样的影响。如果新的属性对于 contract code 没有影响的话,那么你可以将这个属性赋任何值
- 更新后的 state 对象还可以继续使用旧的 contract code,只要在没有必要去更新 contract code 的情况下
对于旧的 contract code JAR 文件
当前,所有的参与节点必须要在节点的 classpath 中保留旧的 state 和 contract 定义,因为在验证 transactions 的时候,他们始终会被要求验证以前使用的旧版本的 state 和 contract。
你需要注意可能的 classpath 冲突。如果你还保留着旧版本的 JAR 文件的话,请确保新版本的不会包含同样名字的类(比如在 Kotlin 中文件级别的声明会被放在一个名字在文件名后边的静态类中)
权限 Permissioning
- 只有节点管理员才能够运行 contract 升级授权和不授权的 flows
流程 Logistics
- 所有的节点都需要执行升级授权 flow
- 只有一个节点应该运行初始 contract 升级 flow。如果多个节点对相同的
StateAndRef
运行了初始化 flow,一个“双花”问题会在双方产生,最先完成的会生效 - 这里提供的升级 flows 每次只会升级一个 state 对象
网友评论