大纲

通用方法

↑ 返回大纲 为表达错误条件,下面对子协议的规范使用宿主状态机的异常系统,该系统通过两个函数暴露出来(定义见 ICS 24):abortTransactionUnless 和 abortSystemUnless。

BeginBlock 与 EndBlock

↑ 返回大纲 函数 BeginBlock() 和 EndBlock()(见已实现的接口)被拆分到 CCV 子协议中。

[CCV-PCF-BBLOCK.1]

// PCF: Provider Chain Function
// implements the AppModule interface
function BeginBlock() {
    BeginBlockInit()
    BeginBlockCCR()
}
  • 调用者
    • ABCI 应用。
  • 触发事件
    • 从共识引擎接收到一条 BeginBlock 消息;BeginBlock 消息每个区块发送一次。
  • 前置条件
    • True.
  • 后置条件
    • 调用 BeginBlockInit()(见 [CCV-PCF-BBLOCK-INIT.1],即它包含初始化子协议所需的 BeginBlock() 逻辑)。
    • 调用 BeginBlockCCR()(见 [CCV-PCF-BBLOCK-CCR.1],即它包含消费者链移除子协议所需的 BeginBlock() 逻辑)。
  • 错误条件
    • 无。

[CCV-PCF-EBLOCK.1]

// PCF: Provider Chain Function
// implements the AppModule interface
function EndBlock(): [ValidatorUpdate] {
  EndBlockCIS()
  EndBlockCCR()
  EndBlockVSU()

  // do not return anything to the consensus engine
  return []   
}
  • 调用者
    • ABCI 应用。
  • 触发事件
    • 从共识引擎接收到一条 EndBlock 消息;EndBlock 消息每个区块发送一次。
  • 前置条件
    • True.
  • 后置条件
    • 调用 EndBlockCIS()(见 [CCV-PCF-EBLOCK-CIS.1],即它包含消费者发起的惩罚子协议所需的 EndBlock() 逻辑)。
    • 调用 EndBlockCCR()(见 [CCV-PCF-EBLOCK-CCR.1],即它包含消费者链移除子协议所需的 EndBlock() 逻辑)。
    • 调用 EndBlockVSU()(见 [CCV-PCF-EBLOCK-VSU.1],即它包含验证者集合更新子协议所需的 EndBlock() 逻辑)。
  • 错误条件
    • 无。
注意:提供者 CCV 模块期望提供者 Staking 模块在调用提供者 CCV 模块的 EndBlock() 之前,先更新其对验证者集合的视图。 一种解决方案是让提供者 Staking 模块在 EndBlock() 期间更新其视图,然后使提供者 Staking 模块的 EndBlock() 先于提供者 CCV 模块的 EndBlock() 执行。

[CCV-CCF-BBLOCK.1]

// CCF: Consumer Chain Function
// implements the AppModule interface
function BeginBlock() {
    BeginBlockInit()
    BeginBlockCCR()
    BeginBlockCIS()
}
  • 调用者
    • ABCI 应用。
  • 触发事件
    • 从共识引擎接收到一条 BeginBlock 消息;BeginBlock 消息每个区块发送一次。
  • 前置条件
    • True.
  • 后置条件
    • 调用 BeginBlockInit()(见 [CCV-CCF-BBLOCK-INIT.1],即它包含通道初始化子协议所需的 BeginBlock() 逻辑)。
    • 调用 BeginBlockCCR()(见 [CCV-CCF-BBLOCK-CCR.1],即它包含消费者链移除子协议所需的 BeginBlock() 逻辑)。
    • 调用 BeginBlockCIS()(见 [CCV-CCF-BBLOCK-CIS.1],即它包含消费者发起的惩罚子协议所需的 BeginBlock() 逻辑)。
  • 错误条件
    • 无。

[CCV-CCF-EBLOCK.1]

// CCF: Consumer Chain Function
// implements the AppModule interface
function EndBlock(): [ValidatorUpdate] {
  EndBlockRD()

  // return the validator set updates to the consensus engine
  return EndBlockVSU()
}
  • 调用者
    • ABCI 应用。
  • 触发事件
    • 从共识引擎接收到一条 EndBlock 消息;EndBlock 消息每个区块发送一次。
  • 前置条件
    • True. x
  • 后置条件
    • 调用 EndBlockRD()(见 [CCV-PCF-EBLOCK-RD.1],即它包含奖励分配子协议所需的 EndBlock() 逻辑)。
    • 调用 EndBlockVSU(),并将返回值返回给共识引擎(见 [CCV-CCF-EBLOCK-VSU.1],即它包含验证者集合更新子协议所需的 EndBlock() 逻辑)。
  • 错误条件
    • 无。

数据包中继

↑ 返回大纲

[CCV-PCF-RCVP.1]

// PCF: Provider Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onRecvPacket(packet: Packet): bytes {
  switch typeof(packet.data) {
    case VSCMaturedPacketData:
      return onRecvVSCMaturedPacket(packet)
    case SlashPacketData:
      return onRecvSlashPacket(packet)
    default:
      // unexpected packet type
      return PacketError
  }    
}
  • 调用者
    • 提供者 IBC 路由模块。
  • 触发事件
    • 提供者 IBC 路由模块在由提供者 CCV 模块拥有的通道上接收到一个数据包。
  • 前置条件
    • True.
  • 后置条件
    • 如果该数据包是 VSCMaturedPacket,则返回调用 onRecvVSCMaturedPacket 方法得到的确认。
    • 如果该数据包是 SlashPacket,则返回调用 onRecvSlashPacket 方法得到的确认。
    • 否则,返回错误确认。
  • 错误条件
    • 无。

[CCV-PCF-ACKP.1]

// PCF: Provider Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onAcknowledgePacket(packet: Packet, ack: bytes) {
  switch typeof(packet.data) {
    case VSCPacketData:
      onAcknowledgeVSCPacket(packet, ack)
    default:
      // unexpected packet type
      abortTransactionUnless(FALSE)
  }
}
  • 调用者
    • 提供者 IBC 路由模块。
  • 触发事件
    • 提供者 IBC 路由模块在由提供者 CCV 模块拥有的通道上接收到一个确认。
  • 前置条件
    • True.
  • 后置条件
    • 如果该确认对应于一个 VSCPacket,则调用 onAcknowledgeVSCPacket 方法。
    • 否则,中止该交易。
  • 错误条件
    • 无。

[CCV-PCF-TOP.1]

// PCF: Provider Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onTimeoutPacket(packet Packet) {
  switch typeof(packet.data) {
    case VSCPacketData:
      onTimeoutVSCPacket(packet)
    default:
      // unexpected packet type
      abortTransactionUnless(FALSE) 
  }
}
  • 调用者
    • 提供者 IBC 路由模块。
  • 触发事件
    • 在由提供者 CCV 模块拥有的通道上发送的一个数据包因以下任一原因而超时:
      • 在消费者链上,超时高度或超时时间戳已过,但该数据包尚未被接收(见 ICS4 中定义的 timeoutPacket);
      • 或者,通道在该数据包尚未被接收时已关闭(见 ICS4 中定义的 timeoutOnClose)。
  • 前置条件
    • Correct Relayer 假设被违反(见假设一节)。
  • 后置条件
    • 如果该超时对应于一个 VSCPacket,则调用 onTimeoutVSCPacket 方法。
    • 否则,中止该交易。
  • 错误条件
    • 无。

[CCV-CCF-RCVP.1]

// CCF: Consumer Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onRecvPacket(packet: Packet): bytes {
  switch typeof(packet.data) {
    case VSCPacketData:
      return onRecvVSCPacket(packet)
    default:
      // unexpected packet type
      return PacketError
  }
}
  • 调用方
    • 消费者链 IBC 路由模块。
  • 触发事件
    • 消费者链 IBC 路由模块在一个由消费者 CCV 模块拥有的通道上接收到数据包。
  • 前置条件
    • True。
  • 后置条件
    • 如果该数据包是 VSCPacket,则返回调用 onRecvVSCPacket 方法所获得的确认。
    • 否则,返回一个错误确认。
  • 错误条件
    • 无。

[CCV-CCF-ACKP.1]

// CCF: Consumer Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onAcknowledgePacket(packet: Packet, ack: bytes) {
  switch typeof(packet.data) {
    case VSCMaturedPacketData:
      onAcknowledgeVSCMaturedPacket(packet, ack)
    case SlashPacketData:
      onAcknowledgeSlashPacket(packet, ack)
    default:
      // unexpected packet type
      abortTransactionUnless(FALSE)
  }
}
  • 调用方
    • 消费者链 IBC 路由模块。
  • 触发事件
    • 消费者链 IBC 路由模块在一个由消费者 CCV 模块拥有的通道上接收到确认。
  • 前置条件
    • True。
  • 后置条件
    • 如果该确认对应的是 VSCMaturedPacket,则调用 onAcknowledgeVSCMaturedPacket 方法。
    • 如果该确认对应的是 SlashPacket,则调用 onAcknowledgeSlashPacket 方法。
    • 否则,中止交易。
  • 错误条件
    • 无。

[CCV-CCF-TOP.1]

// CCF: Consumer Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onTimeoutPacket(packet Packet) {
  switch typeof(packet.data) {
    case VSCMaturedPacketData:
      onTimeoutVSCMaturedPacket(packet)
    case SlashPacketData:
      onTimeoutSlashPacket(packet)
    default:
      // unexpected packet type
      abortTransactionUnless(FALSE) 
  }
}
  • 调用方
    • 消费者链 IBC 路由模块。
  • 触发事件
    • 在由消费者 CCV 模块拥有的通道上发送的数据包发生超时,原因是以下两者之一:
      • 提供者链上的超时高度或超时时间戳已过,但数据包尚未被接收(见 ICS4 中定义的 timeoutPacket);
      • 或者通道已关闭,但数据包尚未被接收(见 ICS4 中定义的 timeoutOnClose)。
  • 前置条件
    • 正确中继者 假设被违反(见假设一节)。
  • 后置条件
    • 如果超时对应的是 VSCMaturedPacket,则调用 onTimeoutVSCMaturedPacket 方法。
    • 如果超时对应的是 SlashPacket,则调用 onTimeoutSlashPacket 方法。
    • 否则,中止交易。
  • 错误条件
    • 无。

子协议

初始化

↑ 返回大纲 初始化 子协议使提供者链和消费者链能够创建一个 CCV 通道,即一个用于交换数据包的唯一有序 IBC 通道。作为前提,初始化子协议必须创建两个 IBC 客户端,一个位于提供者链上并连接到消费者链,另一个位于消费者链上并连接到提供者链。这对于验证两条链的身份是必要的(只要这些客户端是可信的)。

[CCV-PCF-INITG.1]

// PCF: Provider Chain Function
// implements the AppModule interface
function InitGenesis(state: ProviderGenesisState): [ValidatorUpdate] {
  // bind to ProviderPortId port 
  err = portKeeper.bindPort(ProviderPortId)
  // check whether the capability for the port can be claimed
  abortSystemUnless(err == nil)

  foreach cs in state.consumerStates {
    abortSystemUnless(validateChannelIdentifier(cs.channelId))
    chainToChannel[cs.chainId] = cs.channelId
    channelToChain[cs.channelId] = cc.chainId
  }

  // do not return anything to the consensus engine 
  return []
}
  • 调用方
    • ABCI 应用程序。
  • 触发事件
    • 从共识引擎接收到一条 InitChain 消息;当提供者链首次启动时,会发送 InitChain 消息。
  • 前置条件
    • 提供者 CCV 模块处于初始状态。
  • 后置条件
    • 端口 ProviderPortId 的 capability 被认领。
    • 对于 ProviderGenesisState 中的每个消费者状态,都会设置初始状态,即设置如下映射:chainToChannel、channelToChain。
  • 错误条件
    • 端口 ProviderPortId 的 capability 无法被认领。
    • 对于 ProviderGenesisState 中的任一消费者状态,其通道 ID 无效(参见 ICS 4 中定义的验证函数)。

[CCV-PCF-HCAPROP.1]

// PCF: Provider Chain Function
// implements governance proposal Handler 
function HandleConsumerAdditionProposal(p: ConsumerAdditionProposal) {
    // store the proposal as a pending addition proposal
    pendingConsumerAdditionProposals.Append(p)
}
  • 调用方
    • 治理模块的 EndBlock() 方法。
  • 触发事件
    • 一项治理提案 ConsumerAdditionProposal 已通过(即获得了所需票数)。
  • 前置条件
    • True。
  • 后置条件
    • 该提案被追加到待处理新增提案列表中,即 pendingConsumerAdditionProposals。
  • 错误条件
    • 无。

[CCV-PCF-BBLOCK-INIT.1]

// PCF: Provider Chain Function
function BeginBlockInit() {
  // iterate over the pending addition proposals and create 
  // the consumer client if the spawn time has passed
  foreach p IN pendingConsumerAdditionProposals {
    if currentTimestamp() > p.spawnTime {
      CreateConsumerClient(p)
      pendingConsumerAdditionProposals.Remove(p)
    }
  }
}
  • 调用方
    • BeginBlock() 方法。
  • 触发事件
    • 从共识引擎接收到一条 BeginBlock 消息;BeginBlock 消息每个区块发送一次。
  • 前置条件
    • True。
  • 后置条件
    • 对于待处理新增提案列表 pendingConsumerAdditionProposals 中的每个 ConsumerAdditionProposal p,如果 currentTimestamp() > p.spawnTime,则:
      • 调用 CreateConsumerClient(p);
      • 从 pendingConsumerAdditionProposals 中移除 p。
  • 错误条件
    • 无。

[CCV-PCF-CRCLIENT.1]

// PCF: 提供者链函数
// 工具方法
function CreateConsumerClient(p: ConsumerAdditionProposal) {
  // 检查不存在其他具有相同 chain ID 的消费者链
  if p.chainId IN chainToClient.Keys() {
    // 忽略治理提案
    return
  }

  // 设置消费者链初始验证者集合,即,
  // 该验证者集合与当前高度下自身共识状态中的
  // 验证者集合相同
  // 
  // TODO: ownConsensusState.validatorSet VS consensusState.nextValidatorsHash
  //       指定初始验证者集合使用哪个验证者集合
  ownConsensusState = getConsensusState(getCurrentHeight())
  initialValSet = ownConsensusState.validatorSet

  if p.connId != "" { // 已提供连接 ID
    // 检查有效性
    connectionEnd = provableStore.get("connections/{p.connId}")
    if connectionEnd == nil {
      // 无效提案:找不到连接
      return
    }
    clientState = provableStore.get("clients/{connectionEnd.clientIdentifier}/clientState")
    if clientState.chainID != p.chainId {
      // 无效提案:连接未指向预期的链 ID
      return
    }

    // 存储客户端 ID
    chainToClient[p.chainId] = connectionEnd.clientIdentifier
    // 存储连接 ID
    chainToConnection[p.chainId] = connId

    // 创建并存储 ConsumerGenesisState
    consumerGenesisState[p.chainId] = ConsumerGenesisState {
      // 消费者链必须以 pre-CCV 状态启动,即,
      // 消费者 CCV 模块不得将验证者更新
      // 传递给底层共识引擎
      preCCV: true,
      unbondingPeriod: p.unbondingPeriod,
      connId: connectionEnd.counterpartyConnectionIdentifier,
      providerClientState: nil,
      providerConsensusState: nil,
      counterpartyClientId: "",
      initialValSet: initialValSet,
      transferChannelId: p.transferChannelId,
    }
  } 
  else {
    // 创建客户端状态
    clientState = ClientState{
      chainId: p.chainId,
      unbondingPeriod: p.unbondingPeriod,
      // 客户端上次更新时的高度被设置为第一个可能的高度;
      // 例如,对于 Tendermint Client,这里是 Height{0, 1}(见 ICS-7)
      latestHeight: 0, 
    }
    // 创建共识状态
    consensusState = ConsensusState{
      validatorSet: initialValSet,
    }
    // 创建消费者链客户端并存储它
    clientId = clientKeeper.CreateClient(clientState, consensusState)
    chainToClient[p.chainId] = clientId
    
    // 创建并存储 ConsumerGenesisState
    consumerGenesisState[p.chainId] = ConsumerGenesisState {
      // 消费者链不得以 pre-CCV 状态启动,即,
      // 消费者 CCV 模块必须将验证者更新
      // 传递给底层共识引擎
      preCCV: false,
      unbondingPeriod: p.unbondingPeriod,
      connId: "",
      providerClientState: getHostClientState(getCurrentHeight()),
      providerConsensusState: ownConsensusState,
      counterpartyClientId: clientId,
      initialValSet: initialValSet,
      transferChannelId: p.transferChannelId,
    }
  }

  // 存储 lockUnbondingOnTimeout 标志
  lockUnbondingOnTimeout[p.chainId] = p.lockUnbondingOnTimeout

  // 为该消费者链添加初始化超时时间戳
  initTimeoutTimestamps[p.chainId] = currentTimestamp().Add(initTimeout)
}
  • 调用方
  • 触发事件
    • 治理提案 ConsumerAdditionProposal p 已通过(即获得了所需票数)。
  • 前置条件
    • currentTimestamp() > p.spawnTime。
  • 后置条件
    • 如果 p.chainId 的客户端已存在,则状态不变。
    • 否则,
      • 将提供者链在当前高度下自身共识状态的验证者集合设置为消费者链的初始验证者集合;
      • 如果设置了 p.connId,则
        • 如果找不到 ID 为 p.connId 的连接端,则状态不变;
        • 否则,
          • 如果 ID 为 p.connId 的连接未连接到 ID 为 p.chainId 的链,则状态不变;
          • 否则,
            • 存储客户端 ID 和连接 ID;
            • 创建并存储一个 ConsumerGenesisState;
      • 否则,
        • 否则,
          • 创建一个客户端状态,其中 chainId = p.chainId 且 unbondingPeriod = p.unbondingPeriod;
          • 创建一个共识状态,其中 validatorSet 被设置为消费者链的初始验证者集合;
          • 创建消费者链客户端并存储客户端 ID;
          • 创建并存储一个 ConsumerGenesisState;
      • 将 lockUnbondingOnTimeout[p.chainId] 设置为 p.lockUnbondingOnTimeout。
      • 计算初始化超时时间戳并将其存储在 initTimeoutTimestamps[p.chainId] 中。
  • 错误条件
    • 无。
注意: 对于 ConsumerAdditionProposal 的 clientId 字段未设置的情况,创建远程链客户端需要一个 ClientState 和一个 ConsensusState(示例可参见 ICS 7)。 ConsensusState 需要设置远程链的验证者集合。 提供者链利用这样一个事实:消费者链的验证者集合与其自身的验证者集合相同。 注意: 引导消费者 CCV 模块需要一个 ConsumerGenesisState(见CCV 数据结构一节)。提供者 CCV 模块在处理治理提案 ConsumerAdditionProposal 时会创建这样的 ConsumerGenesisState。 注意: 如果消费者链的通道初始化超过 initTimeout 周期,则提供者链会移除该消费者链。 因此,消费者侧此后所有建立 CCV 通道的尝试都会失败。 这意味着消费者链需要某种社会共识,来决定是重新启动成为消费者链的流程,还是重新转回主权链。

[CCV-PCF-COINIT.1]

// PCF: 提供者链函数
// 实现 ICS26 中定义的 ModuleCallbacks 接口
function onChanOpenInit(
  order: ChannelOrder,
  connectionHops: [Identifier],
  portIdentifier: Identifier,
  channelIdentifier: Identifier,
  counterpartyPortIdentifier: Identifier,
  counterpartyChannelIdentifier: Identifier,
  version: string): string {
    // 通道握手必须由消费者链发起
    abortTransactionUnless(FALSE)
}
  • 调用方
    • 提供者 IBC 路由模块。
  • 触发事件
    • 提供者 IBC 路由模块在提供者 CCV 模块所绑定的端口上接收到一条 ChanOpenInit 消息。
  • 前置条件
    • True。
  • 后置条件
    • 事务总是会被中止;因此,状态不变。
  • 错误条件
    • 无。

[CCV-PCF-COTRY.1]

// PCF: Provider Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onChanOpenTry(
  order: ChannelOrder,
  connectionHops: [Identifier],
  portIdentifier: Identifier,
  channelIdentifier: Identifier,
  counterpartyPortIdentifier: Identifier,
  counterpartyChannelIdentifier: Identifier,
  counterpartyVersion: string):  string {
    // validate parameters:
    // - only ordered channels allowed
    abortTransactionUnless(order == ORDERED)
    // - require the portIdentifier to be the port ID the CCV module is bound to
    abortTransactionUnless(portIdentifier == ProviderPortId)

    // assert that the counterpartyPortIdentifier matches 
    // the expected consumer port ID
    abortTransactionUnless(counterpartyPortIdentifier == ConsumerPortId)

    // assert that the counterpartyVersion matches the expected version
    abortTransactionUnless(counterpartyVersion == ccvVersion)
    
    // get the client state associated with the underlying client
    channelEnd = provableStore.get("channelEnds/ports/{portIdentifier}/channels/{channelIdentifier}")
    abortTransactionUnless(channelEnd != nil AND len(channelEnd.connectionHops) == 1)
    connId = channelEnd.connectionHops[0]
    connectionEnd = provableStore.get("connections/{connId}")
    clientState = provableStore.get("clients/{connectionEnd.clientIdentifier}/clientState")

    if clientState.chainId IN chainToConnection.Keys() {
      // if a connection is stored for this consumer chain, 
      // verify that the underlying connection is the expected one
      abortTransactionUnless(chainToConnection[clientState.chainId] == connId)
    }
    
    // verify that the underlying client is the expected client of the consumer chain
    abortTransactionUnless(chainToClient[clientState.chainId] == connectionEnd.clientIdentifier)

    // require that no other CCV channel exists for this consumer chain
    abortTransactionUnless(clientState.chainId NOTIN chainToChannel.Keys())

    return CCVHandshakeMetadata{
      providerDistributionAccount: GetDistributionAccountAddress(),
      version: ccvVersion
    }
}
  • 调用者
    • 提供者 IBC 路由模块。
  • 触发事件
    • 提供者 IBC 路由模块在提供者 CCV 模块绑定的端口上收到 ChanOpenTry 消息。
  • 前置条件
    • 真。
  • 后置条件
    • 如果以下任一条件为真,则中止该交易:
      • 通道不是有序通道;
      • portIdentifier != ProviderPortId;
      • counterpartyPortIdentifier != ConsumerPortId;
      • counterpartyVersion != ccvVersion;
      • 不存在带有 portIdentifier 和 channelIdentifier 的通道;
      • 该通道具有多于一个连接跳;
      • 已为该消费者链存储了一条连接,且该连接与此通道的底层连接不匹配;
      • 该通道并非构建在为该消费者链创建的客户端之上;
      • 该消费者链已存在另一条 CCV 通道。
    • 返回一个 CCVHandshakeMetadata,其中 providerDistributionAccount 设置为提供者链上 distribution 模块账户的地址,version 设置为 ccvVersion。
    • 状态不发生改变。
  • 错误条件
    • 无。

[CCV-PCF-COACK.1]

// PCF: Provider Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onChanOpenAck(
  portIdentifier: Identifier,
  channelIdentifier: Identifier,
  counterpartyVersion: string) {
    // the channel handshake MUST be initiated by consumer chain
    abortTransactionUnless(FALSE)
}
  • 调用者
    • 提供者 IBC 路由模块。
  • 触发事件
    • 提供者 IBC 路由模块在提供者 CCV 模块绑定的端口上收到 ChanOpenAck 消息。
  • 前置条件
    • 真。
  • 后置条件
    • 该交易总是会被中止;因此,状态不发生改变。
  • 错误条件
    • 无。

[CCV-PCF-COCONFIRM.1]

// PCF: Provider Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onChanOpenConfirm(
  portIdentifier: Identifier,
  channelIdentifier: Identifier) {
    // get the client state associated with the underlying client
    channelEnd = provableStore.get("channelEnds/ports/{portIdentifier}/channels/{channelIdentifier}")
    abortTransactionUnless(channelEnd != nil AND len(channelEnd.connectionHops) == 1)
    connId = channelEnd.connectionHops[0]
    connectionEnd = provableStore.get("connections/{connId}")
    clientState = provableStore.get("clients/{connectionEnd.clientIdentifier}/clientState")

    // require that no other CCV channel exists for this consumer chain;
    // note: this is a sanity check; this check should always pass by construction
    abortTransactionUnless(clientState.chainId NOTIN chainToChannel.Keys())

    // set channel mappings
    chainToConnection[clientState.chainId] = connId
    chainToChannel[clientState.chainId] = channelIdentifier
    channelToChain[channelIdentifier] = clientState.chainId
    // set initialHeights for this consumer chain
    initialHeights[chainId] = getCurrentHeight()
   
   // remove init timeout timestamp
   initTimeoutTimestamps.Remove(clientState.chainId)
}
  • 调用者
    • 提供者 IBC 路由模块。
  • 触发事件
    • 提供者 IBC 路由模块在提供者 CCV 模块绑定的端口上收到 ChanOpenConfirm 消息。
  • 前置条件
    • 真。
  • 后置条件
    • 如果以下任一条件为真,则中止该交易:
      • 不存在带有 portIdentifier 和 channelIdentifier 的通道;
      • 该通道具有多于一个连接跳;
      • 该消费者链已存在另一条 CCV 通道。
    • 设置连接映射,即 chainToConnection。
    • 设置通道映射,即 chainToChannel 和 channelToChain。
    • initialHeights[chainId] 被设置为当前高度。
    • 移除 ID 为 clientState.chainId 的消费者链的初始化超时时间戳。
  • 错误条件
    • 无。

[CCV-CCF-INITG.1]

// CCF:消费者链函数
// 实现 AppModule 接口
function InitGenesis(gs: ConsumerGenesisState): [ValidatorUpdate] {
  // ValidateGenesis
  // - 包含非空的初始验证者集
  abortSystemUnless(gs.initialValSet NOT empty)
  if gs.preCCV {
    // - 包含有效的 connId
    connectionEnd = provableStore.get("connections/{gs.connId}")
    abortSystemUnless(connectionEnd != nil)
  }
  else {
    // - 包含有效的 providerClientState
    abortSystemUnless(gs.providerClientState != nil AND gs.providerClientState.Valid())
    // - 包含有效的 providerConsensusState
    abortSystemUnless(gs.providerConsensusState != nil AND gs.providerConsensusState.Valid())
    // - 包含一个与 providerConsensusState 中验证者集匹配的
    //   初始验证者集(例如,ICS 7)
    abortSystemUnless(gs.initialValSet == gs.providerConsensusState.validatorSet)
  }
  if gs.transferChannelId != "" {
      // - 如果提供了 transferChannelId,它必须是
      //   连接到 "transfer" 端口的通道 ID
      channelEnd = provableStore.get("channelEnds/ports/transfer/channels/{gs.transferChannelId}")
      abortSystemUnless(channelEnd != nil)
  }

  // 绑定到 ConsumerPortId 端口
  err = portKeeper.bindPort(ConsumerPortId)
  // 检查是否可以认领该端口的 capability
  abortSystemUnless(err == nil)

  // 设置 pre-CCV 状态
  preCCV = gs.preCCV

  if preCCV {
    // 以 pre-CCV 状态启动消费者链;
    // 存储提供者链客户端的 ID
    providerClientId = connectionEnd.clientIdentifier
  }
  else {
    // 以正常 CCV 状态启动消费者链;
    // 创建提供者链客户端并存储其 ID
    providerClientId = clientKeeper.CreateClient(gs.providerClientState, gs.providerConsensusState)
  }

  // 设置消费者链解绑期
  ConsumerUnbondingPeriod = gs.unbondingTime

  // 为 HtoVSC 设置默认值
  HtoVSC[getCurrentHeight()] = 0

  // 为消费者链设置初始验证者集
  foreach val IN gs.initialValSet {
    ccvValidatorSet[hash(val.pubKey)] = val
  }

  // 设置分发通道 ID
  distributionChannelId = gs.transferChannelId

  // 发起握手
  if preCCV {
    // 发起 CCV 通道打开握手
    // 即使用 ICS-26 中定义的 handleChanOpenInit
    datagram = ChanOpenInit{
      order: ORDERED,
      connectionHops: [gs.connId],
      portIdentifier: ConsumerPortId,
      counterpartyPortIdentifier: ProviderPortId,
      version: ccvVersion,
    }
    handleChanOpenInit(datagram)
  }
  else {
    // 发起连接打开握手
    // 即使用 ICS-26 中定义的 handleConnOpenInit
    datagram = ConnOpenInit{
      clientIdentifier: providerClientId,
      counterpartyClientIdentifier: gs.counterpartyClientId,
      version: "ccv"
    }
    connId = handleConnOpenInit(datagram)

    // 发起 CCV 通道打开握手
    // 即使用 ICS-26 中定义的 handleChanOpenInit
    datagram = ChanOpenInit{
      order: ORDERED,
      connectionHops: [connId],
      portIdentifier: ConsumerPortId,
      counterpartyPortIdentifier: ProviderPortId,
      version: ccvVersion,
    }
    handleChanOpenInit(datagram)
  }

  return gs.initialValSet
}
  • 调用方
    • ABCI 应用程序。
  • 触发事件
    • 从共识引擎接收到一条 InitChain 消息;当消费者链首次启动时,会发送该 InitChain 消息。
  • 前置条件
    • 消费者 CCV 模块处于初始状态。
  • 后置条件
    • 端口 ConsumerPortId 的 capability 已被认领。
    • preCCV 被设置为 gs.preCCV。
    • 如果 preCCV == true,则将基于 gs.connId 连接所构建的客户端 ID 存入 providerClientId。
    • 否则,会创建一个提供者链客户端,并将该客户端 ID 存入 providerClientId。
    • ConsumerUnbondingPeriod 被设置为 gs.unbondingPeriod。
    • 当前区块的 HtoVSC 被设置为 0。
    • ccvValidatorSet 映射会用初始验证者集填充。
    • 分发代币转移通道的 ID 被设置为 gs.transferChannelId。
    • 如果 preCCV == true,则初始化 CCV 通道打开握手。
    • 否则,初始化连接打开握手。
    • 初始验证者集会返回给共识引擎。
  • 错误条件
    • 创世状态包含空的初始验证者集。
    • 如果创世状态中的 preCCV 字段被设置为 true,则创世状态中不包含有效的连接 ID。
    • 否则,
      • 创世状态中不包含有效的提供者客户端状态,其有效性由相应的客户端规范定义(例如,ICS 7);
      • 创世状态中不包含有效的提供者共识状态,其有效性由相应的客户端规范定义(例如,ICS 7);
      • 创世状态包含一个与提供者共识状态中的验证者集不匹配的初始验证者集;
    • 创世状态包含无效的分发通道 ID。
    • 无法认领端口 ConsumerPortId 的 capability。
说明:CCV 假设消费者链初始验证者集中的所有正确验证者都会接收到相同的消费者链二进制文件和消费者链创世状态。 虽然传播该二进制文件和创世状态的机制不在本规范范围内,但一种可行方法是在提供者链上的治理提案中包含这些信息。

[CCV-CCF-COINIT.1]

// CCF:消费者链函数
// 实现 ICS26 中定义的 ModuleCallbacks 接口
function onChanOpenInit(
  order: ChannelOrder,
  connectionHops: [Identifier],
  portIdentifier: Identifier,
  channelIdentifier: Identifier,
  counterpartyPortIdentifier: Identifier,
  counterpartyChannelIdentifier: Identifier,
  version: string): string {
    // 确保 provider 通道尚未创建
    abortTransactionUnless(providerChannel == "")

    // 校验参数:
    // - 只允许有序通道
    abortTransactionUnless(order == ORDERED)
    // - 要求 portIdentifier 是 CCV 模块绑定的端口 ID
    abortTransactionUnless(portIdentifier == ConsumerPortId)
    // - 要求 version 为预期版本
    abortTransactionUnless(version == "" OR version == ccvVersion)

    // 断言 counterpartyPortIdentifier 与
    // 预期的消费者端口 ID 匹配
    abortTransactionUnless(counterpartyPortIdentifier == ProviderPortId)
   
    // 要求与该通道关联的客户端 ID
    // 与预期的 provider 客户端 ID 匹配
    channelEnd = provableStore.get("channelEnds/ports/{portIdentifier}/channels/{channelIdentifier}")
    abortTransactionUnless(channelEnd != nil AND len(channelEnd.connectionHops) == 1)
    connId = channelEnd.connectionHops[0]
    connectionEnd = provableStore.get("connections/{connId}")
    abortTransactionUnless(providerClientId != connectionEnd.clientIdentifier)

    return ccvVersion
}
  • 调用方
    • 消费者 IBC 路由模块。
  • 触发事件
    • 消费者 IBC 路由模块在消费者 CCV 模块所绑定的端口上接收到一条 ChanOpenInit 消息。
  • 前置条件
    • True。
  • 后置条件
    • 如果以下任一条件为真,则事务中止:
      • providerChannel 已设置;
      • portIdentifier != ConsumerPortId;
      • version 已设置但不是预期版本;
      • counterpartyPortIdentifier != ProviderPortId;
      • 与该通道关联的客户端不是预期的提供者客户端。
    • 返回 ccvVersion。
    • 状态不发生变化。
  • 错误条件
    • 无。

[CCV-CCF-COTRY.1]

// CCF:消费者链函数
// 实现 ICS26 中定义的 ModuleCallbacks 接口
function onChanOpenTry(
  order: ChannelOrder,
  connectionHops: [Identifier],
  portIdentifier: Identifier,
  channelIdentifier: Identifier,
  counterpartyPortIdentifier: Identifier,
  counterpartyChannelIdentifier: Identifier,
  counterpartyVersion: string):  string {
    // 通道握手必须由消费者链发起
    abortTransactionUnless(FALSE)
}
  • 调用方
    • 消费者 IBC 路由模块。
  • 触发事件
    • 消费者 IBC 路由模块在消费者 CCV 模块所绑定的端口上接收到一条 ChanOpenTry 消息。
  • 前置条件
    • True。
  • 后置条件
    • 事务总是会中止;因此,状态不会发生变化。
  • 错误条件
    • 无。

[CCV-CCF-COACK.1]

// CCF: Consumer Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onChanOpenAck(
  portIdentifier: Identifier,
  channelIdentifier: Identifier,
  counterpartyVersion: string) {
    // ensure provider channel hasn't already been created
    abortTransactionUnless(providerChannel == "")

    // the version must be encoded in JSON format (as defined in ICS4)
    md = UnmarshalJSON(counterpartyVersion)

    // assert that the counterpartyVersion matches the expected version
    abortTransactionUnless(md.version == ccvVersion)

    // set the address of the distribution module account on the provider chain
    providerDistributionAccount = md.providerDistributionAccount

    if distributionChannelId == "" {
      // initiate opening handshake for the distribution token transfer channel
      // over the same connection as the CCV channel
      // i.e., use handleChanOpenInit as defined in ICS-26
      datagram = ChanOpenInit{
          order: UNORDERED,
          connectionHops: channelKeeper.GetConnectionHops(channelIdentifier), // same as the CCV channel
          portIdentifier: "transfer",
          counterpartyPortIdentifier: "transfer",
          version: "ics20-1",
      }
      distributionChannelId = handleChanOpenInit(datagram)
    }

    // set the channel as the provider channel
    providerChannel = channelIdentifier

    // send pending slash requests;
    // note: this can happen only if preCCV == false, as the ABCI application 
    // can invoke SendSlashRequest only once the chain is upgraded to 
    // a consumer chain, see BeginBlockInit below
    SendPendingSlashRequests()

    if preCCV {
      // replace valset with initial valset
      stakingKeeper.ReplaceValset(ccvValidatorSet.Values()) 
    }
}
  • 调用方
    • 消费者 IBC 路由模块。
  • 触发事件
    • 消费者 IBC 路由模块在消费者 CCV 模块所绑定的端口上收到 ChanOpenAck 消息。
  • 前置条件
    • True。
  • 后置条件
    • counterpartyVersion 被反序列化为 CCVHandshakeMetadata 结构 md。
    • 如果满足以下任一条件,则事务中止:
      • providerChannel 已经被设置;
      • md.version != ccvVersion。
    • 提供者链上的分发模块账户地址被设置为 md.providerDistributionAccount。
    • 如果 distributionChannelId 尚未设置,则发起分发代币传输通道的打开握手,并将 distributionChannelId 设置为结果通道 ID。
    • CCV 通道被标记为已建立,即将 providerChannel 设置为该通道。
    • 待处理的惩罚请求会被发送到提供者链(见 [CCV-CCF-SNDPESLASH.1])。 请注意,这只会在 preCCV == false 时发生,因为 ABCI 应用只有在链升级为消费者链后才能调用 SendSlashRequest(见 [CCV-CCF-BBLOCK-INIT.1])。
    • 如果 preCCV == true,则质押模块中的验证者集合会被替换为 ccvValidatorSet,即初始验证者集合。
  • 错误条件
    • 无。

[CCV-CCF-COCONFIRM.1]

// CCF: Consumer Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onChanOpenConfirm(
  portIdentifier: Identifier,
  channelIdentifier: Identifier) {
    // the channel handshake MUST be initiated by consumer chain
    abortTransactionUnless(FALSE)
}
  • 调用方
    • 消费者 IBC 路由模块。
  • 触发事件
    • 消费者 IBC 路由模块在消费者 CCV 模块所绑定的端口上收到 ChanOpenConfirm 消息。
  • 前置条件
    • True。
  • 后置条件
    • 事务总是会中止;因此,状态不会发生变化。
  • 错误条件
    • 无。

[CCV-CCF-BBLOCK-INIT.1]

// CCF: Consumer Chain Function
function BeginBlockInit() {
  if preCCV {  
    ownConsensusState = getConsensusState(getCurrentHeight())
    if ownConsensusState.validatorSet == ccvValidatorSet.Values() {
      // pre-CCV state is over; upgrade chain to consumer chain
      //  - set preCCV to false
      //  - the existing staking module no longer provides 
      //    validator updates to the underlying consensus engine
      //  - the CCV module starts providing validator updates 
      //    to the underlying consensus engine
      //  - for safety, the existing staking module must be kept 
      //    for at least the unbonding period
    }
  }
}
  • 调用方
    • BeginBlock() 方法。
  • 触发事件
    • 从共识引擎接收到一条 BeginBlock 消息;BeginBlock 消息每个区块发送一次。
  • 前置条件
    • True。
  • 后置条件
    • 如果 preCCV == true 且当前验证者集合与 ccvValidatorSet 匹配(即初始验证者集合),则该链必须升级为完整的消费者链。 升级机制不在本规范的范围内。
  • 错误条件
    • 无。

消费者链移除

↑ 返回大纲

[CCV-PCF-HCRPROP.1]

// PCF: Provider Chain Function
// implements governance proposal Handler 
function HandleConsumerRemovalProposal(p: ConsumerRemovalProposal) {
    // store the proposal as a pending removal proposal
    pendingConsumerRemovalProposals.Append(p)
}
  • 调用方
    • 治理模块的 EndBlock() 方法。
  • 触发事件
    • 某个治理提案 ConsumerRemovalProposal 已通过(即获得了所需票数)。
  • 前置条件
    • True。
  • 后置条件
    • 该提案会被追加到待处理移除提案列表中,即 pendingConsumerRemovalProposals。
  • 错误条件
    • 无。

[CCV-PCF-BBLOCK-CCR.1]

// PCF: Provider Chain Function
function BeginBlockCCR() {
  // iterate over the pending removal proposals 
  // and stop the consumer chain
  foreach p IN pendingConsumerRemovalProposals {
    if currentTimestamp() > p.stopTime {
      // stop the consumer chain and do not lock the unbonding
      StopConsumerChain(p.chainId, false)
      pendingConsumerRemovalProposals.Remove(p)
    }
  }
}
  • 调用方
    • BeginBlock() 方法。
  • 触发事件
    • 从共识引擎接收到一条 BeginBlock 消息;BeginBlock 消息每个区块发送一次。
  • 前置条件
    • True。
  • 后置条件
    • 对于待处理移除提案列表 pendingConsumerRemovalProposals 中的每个 ConsumerRemovalProposal p,如果 currentTimestamp() > p.stopTime,则
      • 调用 StopConsumerChain(p.chainId, false);
      • 从 pendingConsumerRemovalProposals 中移除 p。
  • 错误条件
    • 无。

[CCV-PCF-STCC.1]

// PCF: Provider Chain Function
function StopConsumerChain(chainId: string, lockUnbonding: Bool) {
  // check that a client for chainId exists 
  if chainId NOT IN chainToClient.Keys() {
    return
  }

  // cleanup state
  chainToClient.Remove(chainId)
  lockUnbondingOnTimeout.Remove(chainId)
  if chainId IN chainToChannel.Keys() {
    // CCV channel is established
    channelToChain.Remove(chainToChannel[chainId])
    channelKeeper.ChanCloseInit(chainToChannel[chainId])
    chainToChannel.Remove(chainId)
  }
  pendingVSCPackets.Remove(chainId)
  initialHeights.Remove(chainId)
  downtimeSlashRequests.Remove(chainId)
  initTimeoutTimestamps.Remove(chainId)
  vscSendTimestamps.Remove((chainId, *))

  if !lockUnbonding {
    // remove chainId form all outstanding unbonding operations
    foreach id IN vscToUnbondingOps[(chainId, _)] {
      unbondingOps[id].unbondingChainIds.Remove(chainId)
      // if the unbonding operation has unbonded on all consumer chains
      if unbondingOps[id].unbondingChainIds.IsEmpty() {
        // append the id of the unbonding to maturedUnbondingOps
        maturedUnbondingOps.Append(id)
        // remove unbonding operation
        unbondingOps.Remove(id)
      }
    }
    // clean up vscToUnbondingOps mapping
    vscToUnbondingOps.Remove((chainId, _))
  }
}
  • 调用方
  • 触发事件
    • 以下事件之一:
      • 停止 chainId 对应消费者链的治理提案已通过(即获得了所需票数);
      • 通过 CCV 通道发送到 chainId 对应消费者链的 VSCPacket 已超时;
      • 通道初始化已超时。
  • 前置条件
    • 为真。
  • 后置条件
    • 如果 p.chainId 的客户端不存在,则状态不发生变化。
    • 否则,
      • 从 chainToClient 中移除映射到 chainId 的客户端 ID;
      • 从 lockUnbondingOnTimeout 中移除映射到 chainId 的值;
      • 如果已建立到 chainId 对应消费者链的 CCV 通道,则
        • 从 channelToChain 中移除映射到 chainToChannel[chainId] 的链 ID;
        • 启动该 CCV 通道的关闭握手;
        • 从 chainToChannel 中移除映射到 chainId 的通道 ID。
      • 从 pendingVSCPackets 中移除所有映射到 chainId 的 VSCPacketData;
      • 从 initialHeights 中移除映射到 chainId 的高度;
      • 清空 downtimeSlashRequests[chainId];
      • 如果 lockUnbonding == false,则
        • 从所有尚未完成的解绑定操作中移除 chainId;
        • 如果某个尚未完成的解绑定操作已在所有消费者链上成熟,
        • 则将该已成熟的解绑定操作加入 maturedUnbondingOps;
        • 并从 unbondingOps 中移除该已成熟的解绑定操作;
        • 从 vscToUnbondingOps 映射中移除所有带有 chainId 的条目。
  • 错误条件
    • 无
注意:当 lockUnbonding == FALSE 时调用 StopConsumerChain(chainId, lockUnbonding),意味着所有尚未完成的解绑定操作都可以在 chainId 对应消费者链上的 ConsumerUnbondingPeriod 到期前完成。 因此,对任意 chainId 调用 StopConsumerChain(chainId, false) 可能违反 Bond-Based Consumer Voting Power 和 Slashable Consumer Misbehavior 属性(见系统属性一节)。 StopConsumerChain(chainId, false) 会在两种场景下被调用(见上文“触发事件”)。
  • 第一种场景是停止 chainId 对应消费者链的治理提案通过,此时提供者链上的验证者必须确保停止该消费者链是安全的。 由于治理提案需要获得多数投票权才能通过,调用 StopConsumerChain(chainId, false) 的安全性由 Safe Blockchain 假设保证(见假设一节)。
  • 第二种场景是超时。只有在违反 Correct Relayer 假设时,这种情况才可能发生(见假设一节);而该假设是保证 Bond-Based Consumer Voting Power 和 Slashable Consumer Misbehavior 两项属性所必需的(见假设一节)。

[CCV-PCF-EBLOCK-CCR.1]

// PCF: Provider Chain Function
function EndBlockCCR() {
  // iterate over vscSendTimestamps
  for (chainId, vscId) IN vscSendTimestamps.Keys() {
    // check get first timestamp, i.e., the smallest
    if currentTimestamp() > vscSendTimestamps[(chainId, vscId)] + vscTimeout {
      // vscTimeout expired: 
      // stop the consumer chain and use lockUnbondingOnTimeout 
      // to decide whether to lock the unbonding
      StopConsumerChain(chainId, lockUnbondingOnTimeout[chainId])
    }
  }

  // iterate over initTimeoutTimestamps
  for chainId IN initTimeoutTimestamps.Keys() {
    if currentTimestamp() > initTimeoutTimestamps[chainId] {
      // initTimeout expired:
      // stop the consumer chain and unlock the unbonding 
      StopConsumerChain(chainId, false)
    }
  }
}
  • 调用方
    • EndBlock() 方法。
  • 触发事件
    • 从共识引擎收到一条 EndBlock 消息;EndBlock 消息每个区块发送一次。
  • 前置条件
    • 为真。
  • 后置条件
    • 对于 vscSendTimestamps.Keys() 中的每个消费者链 ID chainId,
      • 如果 vscSendTimestamps[(chainId, vscId)] + vscTimeout 小于当前时间戳,则停止 ID 为 chainId 的消费者链。
    • 对于 initTimeoutTimestamps.Keys() 中的每个消费者链 ID chainId,
      • 如果 initTimeoutTimestamps[chainId] 中的时间戳小于当前时间戳,则停止 ID 为 chainId 的消费者链。
  • 错误条件
    • 无。
注意:为避免误判而导致不必要地移除消费者链, vscTimeout 必须大于 consumerUnbondingPeriod,并且 应当将把 VSCPacket 中继到消费者链,以及将相应的 VSCMaturedPacket 中继回提供者链所需的时间考虑在内。

[CCV-PCF-CCINIT.1]

// PCF: Provider Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onChanCloseInit(
  portIdentifier: Identifier,
  channelIdentifier: Identifier) {
    // Disallow user-initiated channel closing
    abortTransactionUnless(FALSE)
}
  • 调用方
    • 提供者 IBC 路由模块。
  • 触发事件
    • 提供者 IBC 路由模块在提供者 CCV 模块所绑定的端口上收到 ChanCloseInit 消息。
  • 前置条件
    • 为真。
  • 后置条件
    • 事务总是会被中止;因此,状态不发生变化。
  • 错误条件
    • 无。

[CCV-PCF-CCCONFIRM.1]

// PCF: Provider Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onChanCloseConfirm(
  portIdentifier: Identifier,
  channelIdentifier: Identifier) {
    // do nothing
}
  • 调用方
    • 提供者 IBC 路由模块。
  • 触发事件
    • 提供者 IBC 路由模块在提供者 CCV 模块所绑定的端口上收到 ChanCloseConfirm 消息。
  • 前置条件
    • 为真。
  • 后置条件
    • 状态不发生变化。
  • 错误条件
    • 无。

[CCV-CCF-BBLOCK-CCR.1]

// CCF: Consumer Chain Function
function BeginBlockCCR() {
  if providerChannel != "" AND channelKeeper.GetChannelState(providerChannel) == CLOSED {
    // the CCV channel was established, but it was then closed; 
    // the consumer chain is no longer safe

    // cleanup state, e.g., 
    // providerChannel = ""

    // shut down consumer chain
    abortSystemUnless(FALSE)
  } 
}
  • 调用方
    • BeginBlock() 方法。
  • 触发事件
    • 从共识引擎接收到一条 BeginBlock 消息;BeginBlock 消息每个区块发送一次。
  • 前置条件
    • True。
  • 后置条件
    • 如果 CCV 已建立,但随后进入 CLOSED 状态,则清理 consumer CCV 模块的状态,例如取消设置 providerChannel。
  • 错误条件
    • 如果 CCV 已建立,但随后进入 CLOSED 状态。
注意:一旦 CCV 通道关闭,provider 链将无法再提供安全性。因此,consumer 链必须关闭。 关于在实践中如何实现,可参考 Cosmos SDK 的实现。

[CCV-CCF-CCINIT.1]

// CCF: Consumer Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onChanCloseInit(
  portIdentifier: Identifier,
  channelIdentifier: Identifier) {
    // allow relayers to close duplicate OPEN channels, 
    // if the provider channel has already been established
    if providerChannel == "" || providerChannel == channelIdentifier {
      // user cannot close channel
      abortTransactionUnless(FALSE)
    }
}
  • 调用方
    • consumer IBC 路由模块。
  • 触发事件
    • consumer IBC 路由模块在 consumer CCV 模块所绑定的端口上收到一条 ChanCloseInit 消息。
  • 前置条件
    • True。
  • 后置条件
    • 如果 providerChannel 未设置,或 providerChannel 与收到 ChanCloseInit 消息的通道 ID 匹配,则中止该交易。
    • 状态不发生变化。
  • 错误条件
    • 无。

[CCV-CCF-CCCONFIRM.1]

// CCF: Consumer Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onChanCloseConfirm(
  portIdentifier: Identifier,
  channelIdentifier: Identifier) {
    // do nothing
}
  • 调用方
    • consumer IBC 路由模块。
  • 触发事件
    • consumer IBC 路由模块在 consumer CCV 模块所绑定的端口上收到一条 ChanCloseConfirm 消息。
  • 前置条件
    • True。
  • 后置条件
    • 状态不发生变化。
  • 错误条件
    • 无。

验证者集合更新

↑ 返回大纲 验证者集合更新子协议使 provider 链能够
  • 将 provider 链上分配给验证者的投票权更新到 consumer 链
  • 并确保为在 consumer 链上出块的验证者正确完成解绑操作。

[CCV-PCF-EBLOCK-VSU.1]

// PCF: Provider Chain Function
function EndBlockVSU() {
  // notify the Staking module to complete all matured unbondings
  for id IN maturedUnbondingOps {
    stakingKeeper.UnbondingCanComplete(id)
  }
  maturedUnbondingOps.RemoveAll()

  // get list of validator updates from the provider Staking module
  valUpdates = stakingKeeper.GetValidatorUpdates()

  // iterate over all consumer chains registered with this provider chain
  foreach chainId IN chainToClient.Keys() {
    // check whether there are changes in the validator set;
    // note that this also entails unbonding operations 
    // w/o changes in the voting power of the validators in the validator set
    if len(valUpdates) != 0 OR len(vscToUnbondingOps[(chainId, vscId)]) != 0 {
      // create VSCPacket data
      data = VSCPacketData{
        id: vscId, 
        updates: valUpdates,
        downtimeSlashAcks: downtimeSlashRequests[chainId]
      }
      downtimeSlashRequests.Remove(chainId)

      // add VSCPacket data to the list of pending VSCPackets 
      pendingVSCPackets.Append(chainId, data)
    }

    // check whether there is an established CCV channel to the consumer chain
    if chainId IN chainToChannel.Keys() {
      // get the channel ID for the given consumer chain ID
      channelId = chainToChannel[chainId]

      foreach data IN pendingVSCPackets[chainId] {
        // send data using the interface exposed by ICS-4
        channelKeeper.sendPacket(
          portKeeper.getCapability(portKeeper.portPath(ProviderPortId)),
          ProviderPortId, // source port ID
          channelId, // source channel ID
          zeroTimeoutHeight,
          ccvTimeoutTimestamp,
          data
        )
        // add VSC send timestamp to vscSendTimestamps
        vscSendTimestamps[(vscId, chainId)] = currentTimestamp()
      }

      // remove pending VSCPackets
      pendingVSCPackets.Remove(chainId)
    }
  }
  // increment VSC ID
  vscId++ 
}
  • 调用方
    • EndBlock() 方法。
  • 触发事件
    • 从共识引擎接收到一条 EndBlock 消息;EndBlock 消息每个区块发送一次。
  • 前置条件
    • True。
  • 后置条件
    • 对于 maturedUnbondingOps 中每一个已成熟的解绑操作,都会通知 Staking 模块该解绑可以完成。
    • maturedUnbondingOps 中的所有解绑操作都会被移除。
    • 从 provider 的 Staking 模块获取验证者更新列表 valUpdates。
    • 对于每条 chainId 对应的 consumer 链
      • 如果 valUpdates 非空,或者在当前区块期间发起了解绑操作,则
        • 创建一个 VSCPacket 数据 data,使得 data.id = vscId、data.updates = valUpdates 且 data.downtimeSlashAcks = downtimeSlashRequests[chainId];
        • 清空 downtimeSlashRequests[chainId];
        • 将 packetData 追加到与 chainId 关联的待发送 VSCPacket 列表,即 pendingVSCPackets[chainId]。
      • 如果存在面向 chainId 对应 consumer 链的已建立 CCV 通道,则
        • 对于与 chainId 关联的待发送 VSCPacket 列表中的每个 VSCPacketData
          • 在与 chainId 对应 consumer 链关联的通道上发送一个携带该 VSCPacketData 的数据包;
          • 将 vscSendTimestamps[(vscId, chainId)] 设为当前时间戳;
        • 移除与 chainId 关联的所有待发送 VSCPacket。
    • vscId 递增。
  • 错误条件
    • 无。

[CCV-PCF-ACKVSC.1]

// PCF: Provider Chain Function
function onAcknowledgeVSCPacket(packet: Packet, ack: bytes) {
  // providing the VSC with id packet.data.id can fail, 
  // i.e., ack == VSCPacketError, 
  // only if the VSCPacket was sent on a channel 
  // other than the established CCV channel;
  // that should never happen, see EndBlock()
  abortSystemUnless(ack != VSCPacketError)
}
  • 调用方
    • onAcknowledgePacket() 方法。
  • 触发事件
    • provider IBC 路由模块在由 provider CCV 模块拥有的通道上收到一个 VSCPacket 的确认。
  • 前置条件
    • True。
  • 后置条件
    • 状态不发生变化。
  • 错误条件
    • 该确认是 VSCPacketError。

[CCV-PCF-TOVSC.1]

// PCF: Provider Chain Function
function onTimeoutVSCPacket(packet: Packet) {
  // cleanup state
  abortTransactionUnless(packet.getDestinationChannel() IN channelToChain.Keys())
  chainId = channelToChain[packet.getDestinationChannel()]
  // stop the consumer chain and use lockUnbondingOnTimeout 
  // to decide whether to lock the unbonding
  StopConsumerChain(chainId, lockUnbondingOnTimeout[chainId])
}
  • 调用者
    • onTimeoutPacket() 方法。
  • 触发事件
    • 在由 provider CCV 模块拥有的通道上发送的 VSCPacket 发生超时,原因是以下两者之一:
      • 在消费者链上,超时高度或超时时间戳已到达,但该数据包尚未被接收(见 ICS4 中定义的 timeoutPacket);
      • 或者通道在该数据包尚未被接收时被关闭(见 ICS4 中定义的 timeoutOnClose)。
  • 前置条件
    • Correct Relayer 假设被违反(见假设一节)。
  • 后置条件
    • 如果发送该数据包的通道 ID 没有映射到某个链 ID(在 channelToChain 中),则中止该交易。
    • 调用 StopConsumerChain(chainId, lockUnbondingOnTimeout[chainId]),其中 chainId = channelToChain[packet.getDestinationChannel()]。
  • 错误条件
    • 无

[CCV-PCF-RCVMAT.1]

// PCF: Provider Chain Function
function onRecvVSCMaturedPacket(packet: Packet): bytes {
  // get the ID of the consumer chain mapped to this channel ID
  abortTransactionUnless(packet.getDestinationChannel() IN channelToChain.Keys())
  chainId = channelToChain[packet.getDestinationChannel()]

  // iterate over the unbonding operations mapped to
  // this chainId and vscId (i.e., packet.data.id)
  foreach op in GetUnbondingsFromVSC(chainId, packet.data.id) {
    // remove the consumer chain from 
    // the list of consumer chain that are still unbonding
    op.unbondingChainIds.Remove(chainId)
    // if the unbonding operation has unbonded on all consumer chains
    if op.unbondingChainIds.IsEmpty() {
      // append the id of the unbonding to maturedUnbondingOps
      maturedUnbondingOps.Append(op.id)
      // remove unbonding operation
      unbondingOps.Remove(op.id)
    }
  }
  // clean up vscToUnbondingOps mapping
  vscToUnbondingOps.Remove((chainId, vscId))

  // clean up vscSendTimestamps mapping
  vscSendTimestamps.Remove((chainId, vscId))

  return VSCMaturedPacketSuccess
}
  • 调用者
    • onRecvPacket() 方法。
  • 触发事件
    • provider IBC 路由模块在由 provider CCV 模块拥有的通道上接收到一个 VSCMaturedPacket。
  • 前置条件
    • 真。
  • 后置条件
    • 如果接收该数据包的通道不是已建立的 CCV 通道(即不在 channelToChain 中),则中止该交易。
    • chainId 被设置为与接收该数据包的通道所映射的消费者链 ID。
    • 对于 GetUnbondingsFromVSC(chainId, packet.data.id) 返回的每个解绑操作 op:
      • 从 op.unbondingChainIds 中移除 chainId;
      • 如果 op.unbondingChainIds 为空,
        • 则将 op.id 加入 maturedUnbondingOps;
        • 并从 unbondingOps 中移除 op.id。
    • 从 vscToUnbondingOps 中移除 (chainId, vscId)。
    • 从 vscSendTimestamps 中移除 (chainId, vscId)。
    • 返回成功确认。
  • 错误条件
    • 无。

[CCV-PCF-GETUBS.1]

// PCF: Provider Chain Function
// Utility method
function GetUnbondingsFromVSC(
  chainId: Identifier, 
  _vscId: uint64): [UnbondingOperation] {
    // get all unbonding operations associated with (chainId, _vscId)
    ops = []
    foreach id in vscToUnbondingOps[(chainId, _vscId)] {
      // get the unbonding operation with this ID
      op = unbondingOps[id]
      // append the operation to the list of operations to be returned
      ops.Append(op)
    }
    return ops
}
  • 调用者
    • onRecvVSCMaturedPacket() 方法。
  • 触发事件
    • provider IBC 路由模块在由 provider CCV 模块拥有的通道上接收到一个 VSCMaturedPacket。
  • 前置条件
    • provider CCV 模块从 ID 为 chainId 的消费者链接收到一个 VSCMaturedPacket P,且 P.data.id == _vscId。
  • 后置条件
    • 返回映射到 (chainId, _vscId) 的解绑操作列表。
  • 错误条件
    • 无。

[CCV-PCF-HOOK-AFUBOPCR.1]

// PCF: Provider Chain Function
// implements a Staking module hook
function AfterUnbondingInitiated(opId: uint64) {
  // get the IDs of all consumer chains registered with this provider chain;
  // note: this includes also consumer chains in the pre-CCV state
  chainIds = chainToClient.Keys()
  if len(chainIds) > 0 {
    // create and store a new unbonding operation
    unbondingOps[opId] = UnbondingOperation{
      id: opId,
      unbondingChainIds: chainIds
    }
    // add the unbonding operation id to vscToUnbondingOps
    foreach chainId in chainIds {
      vscToUnbondingOps[(chainId, vscId)].Append(opId)
    }

    // ask the Staking module to wait for this operation 
    // to reach maturity on the consumer chains
    stakingKeeper.PutUnbondingOnHold(opId)
  }
}
  • 调用者
    • Staking 模块。
  • 触发事件
    • 发起一个 ID 为 opId 的解绑操作。
  • 前置条件
    • 真。
  • 后置条件
    • chainIds 被设置为所有在该 provider 链上注册的消费者链列表,即 chainToClient.Keys()。
    • 如果 chainIds 中至少有一条消费者链,则:
      • 创建一个 UnbondingOperation op 并将其加入 unbondingOps,使得 op.id = opId 且 op.unbondingChainIds = chainIds。
      • 将 opId 追加到 vscToUnbondingOps[(chainId, vscId)] 的每个列表中,其中 chainId 是在该 provider 链上注册的某条消费者链的 ID,vscId 是当前的 VSC ID。
      • 调用 Staking 模块的 PutUnbondingOnHold(opId)。
  • 错误条件
    • 无。

[CCV-CCF-RCVVSC.1]

// CCF: Consumer Chain Function
function onRecvVSCPacket(packet: Packet): bytes {
  // check whether the packet was sent on the CCV channel
  if providerChannel != "" && providerChannel != packet.getDestinationChannel() {
    // packet sent on a channel other than the established provider channel;
    // return error acknowledgement
    return VSCPacketError
  }

  // set HtoVSC mapping
  HtoVSC[getCurrentHeight() + 1] = packet.data.id

  // store the packet data
  receivedVSCs.Append(packet.data)

  return VSCPacketSuccess
}
  • 调用者
    • onRecvPacket() 方法。
  • 触发事件
    • consumer IBC 路由模块在由 consumer CCV 模块拥有的通道上接收到一个 VSCPacket。
  • 前置条件
    • 真。
  • 后置条件
    • 如果已设置 providerChannel,且其与接收该数据包的通道(ID 为 packet.getDestinationChannel())不匹配,则返回错误确认。
    • 否则:
      • 将后续区块的高度映射到 packet.data.id(即 HtoVSC 映射);
      • 将 packet.data 追加到 receivedVSCs。
      • 返回成功确认。
  • 错误条件
    • 无。

[CCV-CCF-ACKMAT.1]

// CCF: Consumer Chain Function
function onAcknowledgeVSCMaturedPacket(packet: Packet, ack: bytes) {
  // notifications of VSC maturity cannot fail by construction
  abortSystemUnless(ack != VSCMaturedPacketError)
}
  • 调用者
    • onAcknowledgePacket() 方法。
  • 触发事件
    • consumer IBC 路由模块在由 consumer CCV 模块拥有的通道上接收到一个 VSCMaturedPacket 的确认。
  • 前置条件
    • 真。
  • 后置条件
    • 状态不发生变化。
  • 错误条件
    • 该确认为 VSCMaturedPacketError。

[CCV-CCF-TOMAT.1]

// CCF: Consumer Chain Function
function onTimeoutVSCMaturedPacket(packet Packet) {
  // the CCV channel state is changed to CLOSED 
  // by the IBC handler (since the channel is ORDERED)
}
  • 调用者
    • onTimeoutPacket() 方法。
  • 触发事件
    • 由消费者链 CCV 模块拥有的通道上发送的一个 VSCMaturedPacket 发生超时,原因是以下任一情况:
      • 在提供者链上,超时高度或超时时间戳已到,但数据包尚未被接收(见 ICS4 中定义的 timeoutPacket);
      • 或者通道在数据包尚未被接收时被关闭(见 ICS4 中定义的 timeoutOnClose)。
  • 前置条件
    • 正确中继者 假设被违反(见假设一节)。
  • 后置条件
    • 状态不发生变化。
  • 错误条件
    • 无

[CCV-CCF-EBLOCK-VSU.1]

// CCF: Consumer Chain Function
function EndBlockVSU(): [ValidatorUpdate] {
  // unbond mature packets if the CCV channel is established
  if providerChannel != "" {
    UnbondMaturePackets()
  }

  if preCCV {
    // do nothing
    return []
  }
  else {
    // handle received VSCs
    changes = HandleReceivedVSCs()

    // update ccvValidatorSet
    UpdateValidatorSet(changes)

    // return the validator set updates
    return changes
  }
}
  • 调用者
    • EndBlock() 方法。
  • 触发事件
    • 从共识引擎接收到一条 EndBlock 消息;每个区块会发送一次 EndBlock 消息。
  • 前置条件
    • True.
  • 后置条件
    • 如果 providerChannel != "",则调用 UnbondMaturePackets();
    • 如果 preCCV == true,状态不发生变化。
    • 否则,
      • 处理 receivedVSCs 中的数据项(见 [CCV-CCF-HAREVSC.1]),其结果是得到一个验证者更新列表 changes;
      • 调用 UpdateValidatorSet(changes);
      • 返回 changes。
  • 错误条件
    • 无。

[CCV-CCF-HAREVSC.1]

// CCF: Consumer Chain Function
function HandleReceivedVSCs(): [ValidatorUpdate] {
  changes = []
  foreach data IN receivedVSCs {
    // store the list of updates
    changes.Append(data.updates)

    // calculate and store the maturity timestamp for the VSC
    maturityTimestamp = currentTimestamp().Add(ConsumerUnbondingPeriod)
    maturingVSCs.Add(data.id, maturityTimestamp)

    // reset outstandingDowntime for validators in data.downtimeSlashAcks
    foreach valAddr IN data.downtimeSlashAcks {
      outstandingDowntime[valAddr] = FALSE
    }
  }
  // remove all entries
  receivedVSCs = []

  // aggregate the updates, 
  // i.e., keep only the latest update per validator;
  // note: in the implementation, the aggregation is done directly 
  // when receiving a VSCPacket via the AccumulateChanges method
  return changes.Aggregate()
}
  • 调用者
    • EndBlock() 方法。
  • 触发事件
    • 从共识引擎接收到一条 EndBlock 消息。
  • 前置条件
    • preCCV == false。
  • 后置条件
    • 对于列表 receivedVSCs 中的每个 data 项,
      • 将 data.updates 追加到 changes,其中 changes 初始为空的验证者更新列表;
      • 将 (data.id, maturityTimestamp) 添加到 maturingVSCs,其中 maturityTimestamp = currentTimestamp() + ConsumerUnbondingPeriod;
      • 对于从提供者链接收到的 slash 确认中的每个 valAddr,将 outstandingDowntime[valAddr] 设为 false。
    • 清空 receivedVSCs。
    • 对 changes 中的更新进行聚合,即每个验证者只保留最新的一次更新,并将其返回。
  • 错误条件
    • 无。

[CCV-CCF-UPVALS.1]

// CCF: Consumer Chain Function
function UpdateValidatorSet(changes: [ValidatorUpdate]) {
  foreach update IN changes {
    addr := hash(update.pubKey)
    if addr NOT IN ccvValidatorSet.Keys() {
      // new validator bonded;
      // note that due changes.Aggregate(), 
      // a validator can be added to the valset and 
      // then removed in the subsequent block, 
      // resulting in update.power == 0 
      if update.power > 0 {
        // add new validator to validator set
        ccvValidatorSet[addr] = update
        // call AfterCCValidatorBonded hook
        AfterCCValidatorBonded(addr)
      }
    }
    else if update.power == 0 {
      // existing validator begins unbonding
      ccvValidatorSet.Remove(addr)
      // call AfterCCValidatorBeginUnbonding hook
      AfterCCValidatorBeginUnbonding(addr)
    }
    else {
      ccvValidatorSet[addr].power = update.power
    }
  }
}
  • 调用者
    • EndBlock() 方法。
  • 触发事件
    • 从共识引擎接收到一条 EndBlock 消息。
  • 前置条件
    • preCCV == false。
  • 后置条件
    • 对于 changes 中的每个验证者 update,
      • 如果该验证者不在验证者集合中且 update.power > 0,则
        • 将新的验证者添加到 ccvValidatorSet;
        • 调用 AfterCCValidatorBonded 钩子;
      • 否则,如果该验证者的新投票权为 0,则,
        • 将该验证者从 ccvValidatorSet 中移除;
        • 调用 AfterCCValidatorBeginUnbonding 钩子;
      • 否则,更新该验证者的投票权。
  • 错误条件
    • 无。

[CCV-CCF-UMP.1]

// CCF: Consumer Chain Function
function UnbondMaturePackets() {
  foreach (id, ts) in maturingVSCs.SortedByMaturityTime() {
    if currentTimestamp() < ts {
      break // stop loop
    }
    // create VSCMaturedPacketData
    packetData = VSCMaturedPacketData{id: id}

    // send VSCMaturedPacketData using the interface exposed by ICS-4
    channelKeeper.sendPacket(
      portKeeper.getCapability(portKeeper.portPath(ConsumerPortId)),
      ConsumerPortId, // source port ID
      providerChannel, // source channel ID
      zeroTimeoutHeight,
      ccvTimeoutTimestamp,
      packetData
    )
          
    // remove entry from the list
    maturingVSCs.Remove(id, ts)
  }
}
  • 调用者
    • EndBlock() 方法。
  • 触发事件
    • 从共识引擎接收到一条 EndBlock 消息。
  • 前置条件
    • 到提供者链的 CCV 通道已建立,即 providerChannel != ""。
  • 后置条件
    • 对于按成熟时间戳排序的待成熟 VSC 列表中的每个 (id, ts)
      • 如果 currentTimestamp() < ts,则停止循环;
      • 创建一个 VSCMaturedPacketData 数据包数据;
      • 将携带所创建 VSCMaturedPacketData 的数据包发送到提供者链;
      • 从 maturingVSCs 中移除元组 (id, ts)。
  • 错误条件
    • 无。

由消费者发起的 Slash

↑ 返回大纲

[CCV-PCF-EBLOCK-CIS.1]

// PCF: Provider Chain Function
function EndBlockCIS() {
  // set VSCtoH mapping
  VSCtoH[vscId] = getCurrentHeight() + 1
}
  • 调用者
    • EndBlock() 方法。
  • 触发事件
    • 从共识引擎接收到一条 EndBlock 消息;每个区块会发送一次 EndBlock 消息。
  • 前置条件
    • True.
  • 后置条件
    • vscId 被映射到后续区块的高度。
  • 错误条件
    • 无。

[CCV-PCF-RCVSLASH.1]

// PCF: 提供者链函数
function onRecvSlashPacket(packet: Packet): bytes {
  // 检查该数据包是否在已建立的 CCV 通道上收到
  if packet.getDestinationChannel() NOT IN channelToChain.Keys() {
    // 在未建立的通道上收到数据包;行为不正确
    return SlashPacketError
  }

  // 获取与数据包数据中的 VSC ID 对应的高度
  if packet.data.vscId == 0 {
    // 违规发生在向该链发送任何 VSC 之前
    chainId = channelToChain[packet.getDestinationChannel()]
    infractionHeight = initialHeights[chainId]
  }
  else {
    infractionHeight = VSCtoH[packet.data.vscId]
  }

  // 请求 Slashing 模块对验证者执行惩罚
  // 使用提供者链上设置的 slashFactor
  slashFactor = slashingKeeper.GetSlashFactor(packet.data.downtime)
  slashingKeeper.Slash(
    packet.data.valAddress, 
    infractionHeight, 
    packet.data.valPower, 
    slashFactor))

  // 请求 Slashing 模块监禁该验证者
  // 使用提供者链上设置的 jailTime
  jailTime = slashingKeeper.GetJailTime(packet.data.downtime)
  slashingKeeper.JailUntil(packet.data.valAddress, currentTimestamp() + jailTime)

  if packet.data.downtime {
    // 将验证者添加到 chainId 的宕机惩罚请求列表中
    downtimeSlashRequests[chainId].Append(packet.data.valAddress)
  }

  return SlashPacketSuccess
}
  • 调用者
    • onRecvPacket() 方法。
  • 触发事件
    • 提供者 IBC 路由模块在由提供者 CCV 模块拥有的通道上收到一个 SlashPacket。
  • 前置条件
    • True。
  • 后置条件
    • 如果收到该数据包的通道不是已建立的 CCV 通道,则返回错误确认。
    • 否则,
      • 若 packet.data.vscId == 0,则将 infractionHeight 设为 initialHeights[chainId],其中 chainId = channelToChain[packet.getDestinationChannel()],即到该消费者链的 CCV 通道建立时的高度;
      • 否则,将 infractionHeight 设为 VSCtoH[packet.data.vscId],即验证者更新在 ID 为 packet.data.vscId 的 VSC 中最后一次更新投票权重时的高度;
      • 向 Slashing 模块发出请求,对地址为 packet.data.valAddress 的验证者在 infractionHeight 处质押的代币按 slashFactor 进行惩罚,其中 slashFactor 是提供者链上设置的惩罚因子;
      • 向 Slashing 模块发出请求,将地址为 packet.data.valAddress 的验证者监禁一段 jailTime 时长,其中 jailTime 是提供者链上设置的监禁时长;
      • 如果该惩罚请求是针对宕机的,则将验证者地址 packet.data.valAddress 添加到来自该 chainId 的宕机惩罚请求列表中;
      • 返回成功确认。
  • 错误条件
    • 无。

[CCV-CCF-BBLOCK-CIS.1]

// CCF: 消费者链函数
function BeginBlockCIS() {
  HtoVSC[getCurrentHeight() + 1] = HtoVSC[getCurrentHeight()]
}
  • 调用者
    • BeginBlock() 方法。
  • 触发事件
    • 从共识引擎接收到一条 BeginBlock 消息;每个区块会发送一次 BeginBlock 消息。
  • 前置条件
    • True。
  • 后置条件
    • 后续区块高度对应的 HtoVSC 被设置为与当前区块高度相同的 VSC ID。
  • 错误条件
    • 无。

[CCV-CCF-ACKSLASH.1]

// CCF: 消费者链函数
function onAcknowledgeSlashPacket(packet: Packet, ack: bytes) {
  // 惩罚请求失败,即 ack == SlashPacketError,
  // 仅会在 SlashPacket 被发送到
  // 非已建立的 CCV 通道时发生;
  // 这本不应发生,
  // 参见 SendSlashRequest() 和 SendPendingSlashRequests()
  abortSystemUnless(ack != SlashPacketError)
}
  • 调用者
    • onAcknowledgePacket() 方法。
  • 触发事件
    • 消费者 IBC 路由模块在由消费者 CCV 模块拥有的通道上收到一个 SlashPacket 的确认。
  • 前置条件
    • True。
  • 后置条件
    • 状态不发生变化。
  • 错误条件
    • 确认为 SlashPacketError。

[CCV-CCF-TOSLASH.1]

// CCF: 消费者链函数
function onTimeoutSlashPacket(packet Packet) {
  // CCV 通道状态会被 IBC 处理程序改为 CLOSED
  // (因为该通道是 ORDERED)
}
  • 调用者
    • onTimeoutPacket() 方法。
  • 触发事件
    • 在由消费者 CCV 模块拥有的通道上发送的一个 SlashPacket 因以下任一原因超时:
      • 提供者链上的超时高度或超时时间戳已过,但该数据包仍未被接收(见 ICS4 中定义的 timeoutPacket);
      • 或者通道在该数据包未被接收的情况下被关闭(见 ICS4 中定义的 timeoutOnClose)。
  • 前置条件
    • 正确中继者 假设被违反(见假设一节)。
  • 后置条件
    • 状态不发生变化。
  • 错误条件
    • 无

[CCV-CCF-SNDSLASH.1]

// CCF: Consumer Chain Function
// Enables consumer initiated slashing
function SendSlashRequest(
  valAddress: string, 
  power: int64, 
  infractionHeight: Height,
  downtime: Bool) {
    if downtime AND outstandingDowntime[data.valAddress] {
      // do not send multiple requests for the same downtime
      return
    }

    // create SlashPacket data
    packetData = SlashPacketData{
      valAddress: valAddress,
      valPower: power,
      vscId: HtoVSC[infractionHeight],
      downtime: downtime
    }

    // check whether the CCV channel to the provider chain is established
    if providerChannel != "" {
      // send SlashPacket data using the interface exposed by ICS-4
      channelKeeper.sendPacket(
        portKeeper.getCapability(portKeeper.portPath(ConsumerPortId)),
        ConsumerPortId, // source port ID
        providerChannel, // source channel ID
        zeroTimeoutHeight,
        ccvTimeoutTimestamp,
        packetData
      )

      if downtime {
        // set outstandingDowntime for this validator
        outstandingDowntime[data.valAddress] = TRUE
      }
    }
    else {
      // add SlashPacket data to the list of pending SlashPackets 
      req := SlashRequest{data: packetData, downtime: downtime}
      pendingSlashRequests.Append(req)
    }
}
  • 调用方
    • ABCI 应用程序(例如 Slashing 模块)。
  • 触发事件
    • 收到地址为 valAddress 的验证者存在不当行为的证据。
  • 前置条件
    • True.
  • 后置条件
    • 如果该请求针对宕机,并且已经存在针对该验证者宕机的待处理惩罚请求,则状态不发生变化。
    • 否则,
      • 创建 SlashPacket 数据 packetData,并满足 packetData.vscId = VSCtoH[infractionHeight];
      • 如果到提供者链的 CCV 通道已建立,则
        • 将携带 packetData 的数据包发送到提供者链;
        • 如果该请求针对宕机,则将 outstandingDowntime[data.valAddress] 设为 true;
      • 否则,将 SlashRequest{data: packetData, downtime: downtime} 追加到 pendingSlashRequests。
  • 错误条件
    • 无。
注意:ABCI 应用程序在调用 SendSlashRequest 之前,必须先从违规高度中减去 ValidatorUpdateDelay, 其中 ValidatorUpdateDelay 是验证者更新从返回给共识引擎到实际生效之间的延迟(以区块数计)。 例如,如果 ValidatorUpdateDelay = x,并且在区块 10 结束时返回了一次包含新验证者的验证者集合更新, 那么这些新验证者应当从区块 11+x 开始对区块进行签名 (更多细节请参见 ABCI 规范)。 因此,消费者链 CCV 模块要求 SendSlashRequest() 的 infractionHeight 参数按此规则设置。 注意:在单链验证的上下文中,针对宕机的惩罚是一个原子操作,即一旦检测到宕机,就会立即对违规验证者执行惩罚并将其监禁。 因此,一旦某个验证者因宕机受到处罚,它就会从验证者集合中移除,并且不能再次因宕机受到处罚。 由于验证者不会被自动加入回验证者集合,这意味着验证者在重新加入并可能再次受罚之前,必然已经知晓该处罚。 在 CCV 的上下文中,针对宕机的惩罚不再是原子操作,也就是说,宕机是在消费者链上被检测到的,但监禁发生在提供者链上。 为了避免针对同一次宕机违规发送多个惩罚请求,消费者链 CCV 模块为每个验证者使用一个 outstandingDowntime 标志。 CCV 假设消费者链 ABCI 应用程序(例如 slashing 模块)不会将 outstandingDowntime == TRUE 的验证者宕机情况包含在宕机证据中。

[CCV-CCF-SNDPESLASH.1]

// CCF: Consumer Chain Function
// Utility method
function SendPendingSlashRequests() {
  // iterate over every pending SlashRequest in reverse order
  foreach req IN pendingSlashRequests.Reverse() {
    if !req.downtime OR !outstandingDowntime[req.data.valAddress] {
      // send req.data using the interface exposed by ICS-4
      channelKeeper.sendPacket(
        portKeeper.getCapability(portKeeper.portPath(ConsumerPortId)),
        ConsumerPortId, // source port ID
        providerChannel, // source channel ID
        zeroTimeoutHeight,
        ccvTimeoutTimestamp,
        req.data
      )

      if req.downtime {
        // set outstandingDowntime for this validator
        outstandingDowntime[req.data.valAddress] = TRUE
      }
    }
  }
  // remove pending SlashRequest
  pendingSlashRequests.RemoveAll()
}
  • 调用方
  • 触发事件
    • 收到来自提供者链的第一个 VSCPacket。
  • 前置条件
    • providerChannel != ""。
  • 后置条件
    • 按逆序遍历 pendingSlashRequests 中的每个惩罚请求 req,如果该惩罚请求不是针对宕机,或者当前不存在针对宕机的待处理惩罚请求,则
      • 将携带 req.data 的数据包发送到提供者链;
      • 如果该请求针对宕机,则将 outstandingDowntime[req.data.valAddress] 设为 true。
    • 移除所有待处理的 SlashRequest。
  • 错误条件
    • 无。
注意:按逆序遍历待处理的 SlashRequest,可确保在通道初始化期间连续多个区块宕机的验证者,会依据最新的宕机证据受到惩罚。

奖励分配

↑ 返回大纲

[CCV-CCF-EBLOCK-RD.1]

// CCF: Consumer Chain Function
function EndBlockRD() {
  if getCurrentHeight() - lastDistributionTransferHeight >= BlocksPerDistributionTransfer {
    DistributeRewards()
  }
}
  • 调用方
    • EndBlock() 方法。
  • 触发事件
    • 从共识引擎接收到一条 EndBlock 消息;每个区块发送一次 EndBlock 消息。
  • 前置条件
    • True.
  • 后置条件
    • 如果 getCurrentHeight() - lastDistributionTransferHeight >= BlocksPerDistributionTransfer,则调用 DistributeRewards() 方法。
  • 错误条件
    • 无。

[CCV-CCF-DISTRREW.1]

// CCF: Consumer Chain Function
function DistributeRewards() {
  // iterate over all different tokens in ccvAccount
  foreach (denomination, amount) IN ccvAccount.GetAllBalances() {
    // transfer token using ICS20
    transferKeeper.sendFungibleTokens(
      denomination,
      amount,
      ccvAccount, // sender
      providerDistributionAccount, // receiver
      "transfer", // transfer port
      distributionChannelId, // transfer channel ID
      zeroTimeoutHeight, // timeoutHeight
      transferTimeoutTimestamp // timeoutTimestamp
    )
  }
  lastDistributionTransferHeight = getCurrentHeight()
}
  • 调用方
    • EndBlockRD() 方法。
  • 触发事件
    • 从共识引擎接收到一条 EndBlock 消息。
  • 前置条件
    • getCurrentHeight() - lastDistributionTransferHeight >= BlocksPerDistributionTransfer
  • 后置条件
    • 对于 ccvAccount 中定义的每一种代币类型对 (denomination, amount),都会发起一次代币转移操作(定义见 ICS 20)。
    • lastDistributionTransferHeight 被设置为当前高度。
  • 错误条件
    • 无。

Outline

General Methods

↑ Back to Outline To express the error conditions, the following specification of the sub-protocols uses the exception system of the host state machine, which is exposed through two functions (as defined in ICS 24): abortTransactionUnless and abortSystemUnless.

BeginBlock and EndBlock

↑ Back to Outline The functions BeginBlock() and EndBlock() (see Implemented Interfaces) are split across the CCV sub-protocols.

[CCV-PCF-BBLOCK.1]

// PCF: Provider Chain Function
// implements the AppModule interface
function BeginBlock() {
    BeginBlockInit()
    BeginBlockCCR()
}
  • Caller
    • The ABCI application.
  • Trigger Event
    • A BeginBlock message is received from the consensus engine; BeginBlock messages are sent once per block.
  • Precondition
    • True.
  • Postcondition
    • BeginBlockInit() is invoked (see [CCV-PCF-BBLOCK-INIT.1], i.e., it contains the BeginBlock() logic needed for the Initialization sub-protocol).
    • BeginBlockCCR() is invoked (see [CCV-PCF-BBLOCK-CCR.1], i.e., it contains the BeginBlock() logic needed for the Consumer Chain Removal sub-protocol).
  • Error Condition
    • None.

[CCV-PCF-EBLOCK.1]

// PCF: Provider Chain Function
// implements the AppModule interface
function EndBlock(): [ValidatorUpdate] {
  EndBlockCIS()
  EndBlockCCR()
  EndBlockVSU()

  // do not return anything to the consensus engine
  return []   
}
  • Caller
    • The ABCI application.
  • Trigger Event
    • An EndBlock message is received from the consensus engine; EndBlock messages are sent once per block.
  • Precondition
    • True.
  • Postcondition
    • EndBlockCIS() is invoked (see [CCV-PCF-EBLOCK-CIS.1], i.e., it contains the EndBlock() logic needed for the Consumer Initiated Slashing sub-protocol).
    • EndBlockCCR() is invoked (see [CCV-PCF-EBLOCK-CCR.1], i.e., it contains the EndBlock() logic needed for the Consumer Chain Removal sub-protocol).
    • EndBlockVSU() is invoked (see [CCV-PCF-EBLOCK-VSU.1], i.e., it contains the EndBlock() logic needed for the Validator Set Update sub-protocol).
  • Error Condition
    • None.
Note: The provider CCV module expects the provider Staking module to update its view of the validator set before the EndBlock() of the provider CCV module is invoked. A solution is for the provider Staking module to update its view during EndBlock() and then, the EndBlock() of the provider Staking module to be executed before the EndBlock() of the provider CCV module.

[CCV-CCF-BBLOCK.1]

// CCF: Consumer Chain Function
// implements the AppModule interface
function BeginBlock() {
    BeginBlockInit()
    BeginBlockCCR()
    BeginBlockCIS()
}
  • Caller
    • The ABCI application.
  • Trigger Event
    • A BeginBlock message is received from the consensus engine; BeginBlock messages are sent once per block.
  • Precondition
    • True.
  • Postcondition
    • BeginBlockInit() is invoked (see [CCV-CCF-BBLOCK-INIT.1], i.e., it contains the BeginBlock() logic needed for the Channel Initialization sub-protocol).
    • BeginBlockCCR() is invoked (see [CCV-CCF-BBLOCK-CCR.1], i.e., it contains the BeginBlock() logic needed for the Consumer Chain Removal sub-protocol).
    • BeginBlockCIS() is invoked (see [CCV-CCF-BBLOCK-CIS.1], i.e., it contains the BeginBlock() logic needed for the Consumer Initiated Slashing sub-protocol).
  • Error Condition
    • None.

[CCV-CCF-EBLOCK.1]

// CCF: Consumer Chain Function
// implements the AppModule interface
function EndBlock(): [ValidatorUpdate] {
  EndBlockRD()

  // return the validator set updates to the consensus engine
  return EndBlockVSU()
}
  • Caller
    • The ABCI application.
  • Trigger Event
    • An EndBlock message is received from the consensus engine; EndBlock messages are sent once per block.
  • Precondition
    • True. x
  • Postcondition
    • EndBlockRD() is invoked (see [CCV-PCF-EBLOCK-RD.1], i.e., it contains the EndBlock() logic needed for the Reward Distribution sub-protocol).
    • EndBlockVSU() is invoked and the return value is returned to the consensus engine (see [CCV-CCF-EBLOCK-VSU.1], i.e., it contains the EndBlock() logic needed for the Validator Set Update sub-protocol).
  • Error Condition
    • None.

Packet Relay

↑ Back to Outline

[CCV-PCF-RCVP.1]

// PCF: Provider Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onRecvPacket(packet: Packet): bytes {
  switch typeof(packet.data) {
    case VSCMaturedPacketData:
      return onRecvVSCMaturedPacket(packet)
    case SlashPacketData:
      return onRecvSlashPacket(packet)
    default:
      // unexpected packet type
      return PacketError
  }    
}
  • Caller
    • The provider IBC routing module.
  • Trigger Event
    • The provider IBC routing module receives a packet on a channel owned by the provider CCV module.
  • Precondition
    • True.
  • Postcondition
    • If the packet is a VSCMaturedPacket, the acknowledgement obtained from invoking the onRecvVSCMaturedPacket method is returned.
    • If the packet is a SlashPacket, the acknowledgement obtained from invoking the onRecvSlashPacket method is returned.
    • Otherwise, an error acknowledgement is returned.
  • Error Condition
    • None.

[CCV-PCF-ACKP.1]

// PCF: Provider Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onAcknowledgePacket(packet: Packet, ack: bytes) {
  switch typeof(packet.data) {
    case VSCPacketData:
      onAcknowledgeVSCPacket(packet, ack)
    default:
      // unexpected packet type
      abortTransactionUnless(FALSE)
  }
}
  • Caller
    • The provider IBC routing module.
  • Trigger Event
    • The provider IBC routing module receives an acknowledgement on a channel owned by the provider CCV module.
  • Precondition
    • True.
  • Postcondition
    • If the acknowledgement is for a VSCPacket, the onAcknowledgeVSCPacket method is invoked.
    • Otherwise, the transaction is aborted.
  • Error Condition
    • None.

[CCV-PCF-TOP.1]

// PCF: Provider Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onTimeoutPacket(packet Packet) {
  switch typeof(packet.data) {
    case VSCPacketData:
      onTimeoutVSCPacket(packet)
    default:
      // unexpected packet type
      abortTransactionUnless(FALSE) 
  }
}
  • Caller
    • The provider IBC routing module.
  • Trigger Event
    • A packet sent on a channel owned by the provider CCV module timed out as a result of either
      • the timeout height or timeout timestamp passing on the consumer chain without the packet being received (see timeoutPacket defined in ICS4);
      • or the channel being closed without the packet being received (see timeoutOnClose defined in ICS4).
  • Precondition
    • The Correct Relayer assumption is violated (see the Assumptions section).
  • Postcondition
    • If the timeout is for a VSCPacket, the onTimeoutVSCPacket method is invoked.
    • Otherwise, the transaction is aborted.
  • Error Condition
    • None.

[CCV-CCF-RCVP.1]

// CCF: Consumer Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onRecvPacket(packet: Packet): bytes {
  switch typeof(packet.data) {
    case VSCPacketData:
      return onRecvVSCPacket(packet)
    default:
      // unexpected packet type
      return PacketError
  }
}
  • Caller
    • The consumer IBC routing module.
  • Trigger Event
    • The consumer IBC routing module receives a packet on a channel owned by the consumer CCV module.
  • Precondition
    • True.
  • Postcondition
    • If the packet is a VSCPacket, the acknowledgement obtained from invoking the onRecvVSCPacket method is returned.
    • Otherwise, an error acknowledgement is returned.
  • Error Condition
    • None.

[CCV-CCF-ACKP.1]

// CCF: Consumer Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onAcknowledgePacket(packet: Packet, ack: bytes) {
  switch typeof(packet.data) {
    case VSCMaturedPacketData:
      onAcknowledgeVSCMaturedPacket(packet, ack)
    case SlashPacketData:
      onAcknowledgeSlashPacket(packet, ack)
    default:
      // unexpected packet type
      abortTransactionUnless(FALSE)
  }
}
  • Caller
    • The consumer IBC routing module.
  • Trigger Event
    • The consumer IBC routing module receives an acknowledgement on a channel owned by the consumer CCV module.
  • Precondition
    • True.
  • Postcondition
    • If the acknowledgement is for a VSCMaturedPacket, the onAcknowledgeVSCMaturedPacket method is invoked.
    • If the acknowledgement is for a SlashPacket, the onAcknowledgeSlashPacket method is invoked.
    • Otherwise, the transaction is aborted.
  • Error Condition
    • None.

[CCV-CCF-TOP.1]

// CCF: Consumer Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onTimeoutPacket(packet Packet) {
  switch typeof(packet.data) {
    case VSCMaturedPacketData:
      onTimeoutVSCMaturedPacket(packet)
    case SlashPacketData:
      onTimeoutSlashPacket(packet)
    default:
      // unexpected packet type
      abortTransactionUnless(FALSE) 
  }
}
  • Caller
    • The consumer IBC routing module.
  • Trigger Event
    • A packet sent on a channel owned by the consumer CCV module timed out as a result of either
      • the timeout height or timeout timestamp passing on the provider chain without the packet being received (see timeoutPacket defined in ICS4);
      • or the channel being closed without the packet being received (see timeoutOnClose defined in ICS4).
  • Precondition
    • The Correct Relayer assumption is violated (see the Assumptions section).
  • Postcondition
    • If the timeout is for a VSCMaturedPacket, the onTimeoutVSCMaturedPacket method is invoked.
    • If the timeout is for a SlashPacket, the onTimeoutSlashPacket method is invoked.
    • Otherwise, the transaction is aborted.
  • Error Condition
    • None.

Sub-protocols

Initialization

↑ Back to Outline The initialization sub-protocol enables a provider chain and a consumer chain to create a CCV channel — a unique, ordered IBC channel for exchanging packets. As a prerequisite, the initialization sub-protocol MUST create two IBC clients, one on the provider chain to the consumer chain and one on the consumer chain to the provider chain. This is necessary to verify the identity of the two chains (as long as the clients are trusted).

[CCV-PCF-INITG.1]

// PCF: Provider Chain Function
// implements the AppModule interface
function InitGenesis(state: ProviderGenesisState): [ValidatorUpdate] {
  // bind to ProviderPortId port 
  err = portKeeper.bindPort(ProviderPortId)
  // check whether the capability for the port can be claimed
  abortSystemUnless(err == nil)

  foreach cs in state.consumerStates {
    abortSystemUnless(validateChannelIdentifier(cs.channelId))
    chainToChannel[cs.chainId] = cs.channelId
    channelToChain[cs.channelId] = cc.chainId
  }

  // do not return anything to the consensus engine 
  return []
}
  • Caller
    • The ABCI application.
  • Trigger Event
    • An InitChain message is received from the consensus engine; the InitChain message is sent when the provider chain is first started.
  • Precondition
    • The provider CCV module is in the initial state.
  • Postcondition
    • The capability for the port ProviderPortId is claimed.
    • For each consumer state in the ProviderGenesisState, the initial state is set, i.e., the following mappings chainToChannel, channelToChain are set.
  • Error Condition
    • The capability for the port ProviderPortId cannot be claimed.
    • For any consumer state in the ProviderGenesisState, the channel ID is not valid (cf. the validation function defined in ICS 4).

[CCV-PCF-HCAPROP.1]

// PCF: Provider Chain Function
// implements governance proposal Handler 
function HandleConsumerAdditionProposal(p: ConsumerAdditionProposal) {
    // store the proposal as a pending addition proposal
    pendingConsumerAdditionProposals.Append(p)
}
  • Caller
    • EndBlock() method of Governance module.
  • Trigger Event
    • A governance proposal ConsumerAdditionProposal has passed (i.e., it got the necessary votes).
  • Precondition
    • True.
  • Postcondition
    • The proposal is appended to the list of pending addition proposals, i.e., pendingConsumerAdditionProposals.
  • Error Condition
    • None.

[CCV-PCF-BBLOCK-INIT.1]

// PCF: Provider Chain Function
function BeginBlockInit() {
  // iterate over the pending addition proposals and create 
  // the consumer client if the spawn time has passed
  foreach p IN pendingConsumerAdditionProposals {
    if currentTimestamp() > p.spawnTime {
      CreateConsumerClient(p)
      pendingConsumerAdditionProposals.Remove(p)
    }
  }
}
  • Caller
    • The BeginBlock() method.
  • Trigger Event
    • A BeginBlock message is received from the consensus engine; BeginBlock messages are sent once per block.
  • Precondition
    • True.
  • Postcondition
    • For each ConsumerAdditionProposal p in the list of pending addition proposals pendingConsumerAdditionProposals, if currentTimestamp() > p.spawnTime, then
      • CreateConsumerClient(p) is invoked;
      • p is removed from pendingConsumerAdditionProposals.
  • Error Condition
    • None.

[CCV-PCF-CRCLIENT.1]

// PCF: Provider Chain Function
// Utility method
function CreateConsumerClient(p: ConsumerAdditionProposal) {
  // check that no other consumer chain with the same chain ID exists
  if p.chainId IN chainToClient.Keys() {
    // ignore governance proposal
    return
  }

  // set consumer chain initial validator set, i.e.,
  // the validator set is the same as the validator set 
  // from own consensus state at current height
  // 
  // TODO: ownConsensusState.validatorSet VS consensusState.nextValidatorsHash
  //       specify which validator set is used as the initial val set
  ownConsensusState = getConsensusState(getCurrentHeight())
  initialValSet = ownConsensusState.validatorSet

  if p.connId != "" { // connection ID provided
    // check validity
    connectionEnd = provableStore.get("connections/{p.connId}")
    if connectionEnd == nil {
      // invalid proposal: cannot find connection
      return
    }
    clientState = provableStore.get("clients/{connectionEnd.clientIdentifier}/clientState")
    if clientState.chainID != p.chainId {
      // invalid proposal: connection not to expected chain ID
      return
    }

    // store client ID
    chainToClient[p.chainId] = connectionEnd.clientIdentifier
    // store connection ID
    chainToConnection[p.chainId] = connId

    // create and store ConsumerGenesisState
    consumerGenesisState[p.chainId] = ConsumerGenesisState {
      // consumer chain MUST start in pre-CCV state, i.e.,
      // the consumer CCV module MUST NOT pass validator updates
      // to the underlying consensus engine
      preCCV: true,
      unbondingPeriod: p.unbondingPeriod,
      connId: connectionEnd.counterpartyConnectionIdentifier,
      providerClientState: nil,
      providerConsensusState: nil,
      counterpartyClientId: "",
      initialValSet: initialValSet,
      transferChannelId: p.transferChannelId,
    }
  } 
  else {
    // create client state
    clientState = ClientState{
      chainId: p.chainId,
      unbondingPeriod: p.unbondingPeriod,
      // the height when the client was last updated is set to the first possible height; 
      // for example, in the case of a Tendermint Client, this is Height{0, 1} (see ICS-7)
      latestHeight: 0, 
    }
    // create consensus state
    consensusState = ConsensusState{
      validatorSet: initialValSet,
    }
    // create consumer chain client and store it
    clientId = clientKeeper.CreateClient(clientState, consensusState)
    chainToClient[p.chainId] = clientId
    
    // create and store ConsumerGenesisState
    consumerGenesisState[p.chainId] = ConsumerGenesisState {
      // consumer chain MUST NOT start in pre-CCV state, i.e.,
      // the consumer CCV module MUST pass validator updates
      // to the underlying consensus engine
      preCCV: false,
      unbondingPeriod: p.unbondingPeriod,
      connId: "",
      providerClientState: getHostClientState(getCurrentHeight()),
      providerConsensusState: ownConsensusState,
      counterpartyClientId: clientId,
      initialValSet: initialValSet,
      transferChannelId: p.transferChannelId,
    }
  }

  // store lockUnbondingOnTimeout flag
  lockUnbondingOnTimeout[p.chainId] = p.lockUnbondingOnTimeout

  // add init timeout timestamp for this consumer chain
  initTimeoutTimestamps[p.chainId] = currentTimestamp().Add(initTimeout)
}
  • Caller
  • Trigger Event
    • A governance proposal ConsumerAdditionProposal p has passed (i.e., it got the necessary votes).
  • Precondition
    • currentTimestamp() > p.spawnTime.
  • Postcondition
    • If a client for p.chainId already exists, the state is not changed.
    • Otherwise,
      • the validator set of the provider chain own consensus state at current height is set as the initial validator set of the consumer chain;
      • if p.connId is set, then
        • if a connection end with ID p.connId cannot be found, the state is not changed;
        • otherwise,
          • if the connection with ID p.connId is not to the chain with ID p.chainId, the state is not changed;
          • otherwise,
            • both the client ID and connection ID are stored;
            • a ConsumerGenesisState is created and stored;
      • otherwise,
        • otherwise,
          • a client state is created with chainId = p.chainId and unbondingPeriod = p.unbondingPeriod;
          • a consensus state is created with validatorSet set to the initial validator set of the consumer chain;
          • a client of the consumer chain is created and the client ID is stored;
          • a ConsumerGenesisState is created and stored;
      • lockUnbondingOnTimeout[p.chainId] is set to p.lockUnbondingOnTimeout.
      • The init timeout timestamp is computed and stored in initTimeoutTimestamps[p.chainId].
  • Error Condition
    • None.
Note: For the case when the clientId field of the ConsumerAdditionProposal is not set, creating a client of a remote chain requires a ClientState and a ConsensusState (for an example, take a look at ICS 7). ConsensusState requires setting a validator set of the remote chain. The provider chain uses the fact that the validator set of the consumer chain is the same as its own validator set. Note: Bootstrapping the consumer CCV module requires a ConsumerGenesisState (see the CCV Data Structures section). The provider CCV module creates such a ConsumerGenesisState when handling a governance proposal ConsumerAdditionProposal. Note: If the channel initialization for a consumer chain exceeds the initTimeout period, then the provider chain removes that consumer. As a result, all further attempts on the consumer side to established the CCV channel will fail. This means that the consumer chain requires some sort of social consensus to either restart the process of becoming a consumer chain or transitioning back to a sovereign chain.

[CCV-PCF-COINIT.1]

// PCF: Provider Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onChanOpenInit(
  order: ChannelOrder,
  connectionHops: [Identifier],
  portIdentifier: Identifier,
  channelIdentifier: Identifier,
  counterpartyPortIdentifier: Identifier,
  counterpartyChannelIdentifier: Identifier,
  version: string): string {
    // the channel handshake MUST be initiated by consumer chain
    abortTransactionUnless(FALSE)
}
  • Caller
    • The provider IBC routing module.
  • Trigger Event
    • The provider IBC routing module receives a ChanOpenInit message on a port the provider CCV module is bounded to.
  • Precondition
    • True.
  • Postcondition
    • The transaction is always aborted; hence, the state is not changed.
  • Error Condition
    • None.

[CCV-PCF-COTRY.1]

// PCF: Provider Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onChanOpenTry(
  order: ChannelOrder,
  connectionHops: [Identifier],
  portIdentifier: Identifier,
  channelIdentifier: Identifier,
  counterpartyPortIdentifier: Identifier,
  counterpartyChannelIdentifier: Identifier,
  counterpartyVersion: string):  string {
    // validate parameters:
    // - only ordered channels allowed
    abortTransactionUnless(order == ORDERED)
    // - require the portIdentifier to be the port ID the CCV module is bound to
    abortTransactionUnless(portIdentifier == ProviderPortId)

    // assert that the counterpartyPortIdentifier matches 
    // the expected consumer port ID
    abortTransactionUnless(counterpartyPortIdentifier == ConsumerPortId)

    // assert that the counterpartyVersion matches the expected version
    abortTransactionUnless(counterpartyVersion == ccvVersion)
    
    // get the client state associated with the underlying client
    channelEnd = provableStore.get("channelEnds/ports/{portIdentifier}/channels/{channelIdentifier}")
    abortTransactionUnless(channelEnd != nil AND len(channelEnd.connectionHops) == 1)
    connId = channelEnd.connectionHops[0]
    connectionEnd = provableStore.get("connections/{connId}")
    clientState = provableStore.get("clients/{connectionEnd.clientIdentifier}/clientState")

    if clientState.chainId IN chainToConnection.Keys() {
      // if a connection is stored for this consumer chain, 
      // verify that the underlying connection is the expected one
      abortTransactionUnless(chainToConnection[clientState.chainId] == connId)
    }
    
    // verify that the underlying client is the expected client of the consumer chain
    abortTransactionUnless(chainToClient[clientState.chainId] == connectionEnd.clientIdentifier)

    // require that no other CCV channel exists for this consumer chain
    abortTransactionUnless(clientState.chainId NOTIN chainToChannel.Keys())

    return CCVHandshakeMetadata{
      providerDistributionAccount: GetDistributionAccountAddress(),
      version: ccvVersion
    }
}
  • Caller
    • The provider IBC routing module.
  • Trigger Event
    • The provider IBC routing module receives a ChanOpenTry message on a port the provider CCV module is bounded to.
  • Precondition
    • True.
  • Postcondition
    • The transaction is aborted if any of the following conditions are true:
      • the channel is not ordered;
      • portIdentifier != ProviderPortId;
      • counterpartyPortIdentifier != ConsumerPortId;
      • counterpartyVersion != ccvVersion;
      • no channel with portIdentifier and channelIdentifier exists;
      • the channel has more than one connection hop;
      • a connection is stored for this consumer chain and doesn’t match the underlying connection of this channel;
      • the channel is not built on top of the client created for this consumer chain;
      • another CCV channel for this consumer chain already exists.
    • A CCVHandshakeMetadata is returned, with providerDistributionAccount set to the address of the distribution module account on the provider chain and version set to ccvVersion.
    • The state is not changed.
  • Error Condition
    • None.

[CCV-PCF-COACK.1]

// PCF: Provider Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onChanOpenAck(
  portIdentifier: Identifier,
  channelIdentifier: Identifier,
  counterpartyVersion: string) {
    // the channel handshake MUST be initiated by consumer chain
    abortTransactionUnless(FALSE)
}
  • Caller
    • The provider IBC routing module.
  • Trigger Event
    • The provider IBC routing module receives a ChanOpenAck message on a port the provider CCV module is bounded to.
  • Precondition
    • True.
  • Postcondition
    • The transaction is always aborted; hence, the state is not changed.
  • Error Condition
    • None.

[CCV-PCF-COCONFIRM.1]

// PCF: Provider Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onChanOpenConfirm(
  portIdentifier: Identifier,
  channelIdentifier: Identifier) {
    // get the client state associated with the underlying client
    channelEnd = provableStore.get("channelEnds/ports/{portIdentifier}/channels/{channelIdentifier}")
    abortTransactionUnless(channelEnd != nil AND len(channelEnd.connectionHops) == 1)
    connId = channelEnd.connectionHops[0]
    connectionEnd = provableStore.get("connections/{connId}")
    clientState = provableStore.get("clients/{connectionEnd.clientIdentifier}/clientState")

    // require that no other CCV channel exists for this consumer chain;
    // note: this is a sanity check; this check should always pass by construction
    abortTransactionUnless(clientState.chainId NOTIN chainToChannel.Keys())

    // set channel mappings
    chainToConnection[clientState.chainId] = connId
    chainToChannel[clientState.chainId] = channelIdentifier
    channelToChain[channelIdentifier] = clientState.chainId
    // set initialHeights for this consumer chain
    initialHeights[chainId] = getCurrentHeight()
   
   // remove init timeout timestamp
   initTimeoutTimestamps.Remove(clientState.chainId)
}
  • Caller
    • The provider IBC routing module.
  • Trigger Event
    • The provider IBC routing module receives a ChanOpenConfirm message on a port the provider CCV module is bounded to.
  • Precondition
    • True.
  • Postcondition
    • The transaction is aborted if any of the following conditions are true:
      • no channel with portIdentifier and channelIdentifier exists;
      • the channel has more than one connection hop;
      • another CCV channel for this consumer chain already exists.
    • The connection mapping is set, i.e., chainToConnection.
    • The channel mappings are set, i.e., chainToChannel and channelToChain.
    • initialHeights[chainId] is set to the current height.
    • The init timeout timestamp for the consumer chain with ID clientState.chainId is removed.
  • Error Condition
    • None.

[CCV-CCF-INITG.1]

// CCF: Consumer Chain Function
// implements the AppModule interface
function InitGenesis(gs: ConsumerGenesisState): [ValidatorUpdate] {
  // ValidateGenesis
  // - contains a non-empty initial validator set
  abortSystemUnless(gs.initialValSet NOT empty)
  if gs.preCCV {
    // - contains a valid connId
    connectionEnd = provableStore.get("connections/{gs.connId}")
    abortSystemUnless(connectionEnd != nil)
  }
  else {
    // - contains a valid providerClientState  
    abortSystemUnless(gs.providerClientState != nil AND gs.providerClientState.Valid())
    // - contains a valid providerConsensusState
    abortSystemUnless(gs.providerConsensusState != nil AND gs.providerConsensusState.Valid())
    // - contains an initial validator set that matches 
    //   the validator set in the providerConsensusState (e.g., ICS 7)
    abortSystemUnless(gs.initialValSet == gs.providerConsensusState.validatorSet)
  }
  if gs.transferChannelId != "" {
      // - if transferChannelId is provided, it must the ID
      //   of a channel connected to the "transfer" port
      channelEnd = provableStore.get("channelEnds/ports/transfer/channels/{gs.transferChannelId}")
      abortSystemUnless(channelEnd != nil)
  }

  // bind to ConsumerPortId port 
  err = portKeeper.bindPort(ConsumerPortId)
  // check whether the capability for the port can be claimed
  abortSystemUnless(err == nil)

  // set pre-CCV state
  preCCV = gs.preCCV

  if preCCV {
    // start consumer chain in pre-CCV state;
    // store the ID of the client of the provider chain
    providerClientId = connectionEnd.clientIdentifier
  }
  else {
    // start consumer chain in normal CCV state;
    // create client of the provider chain and store the ID
    providerClientId = clientKeeper.CreateClient(gs.providerClientState, gs.providerConsensusState)
  }

  // set the consumer unbonding period
  ConsumerUnbondingPeriod = gs.unbondingTime

  // set default value for HtoVSC
  HtoVSC[getCurrentHeight()] = 0

  // set the initial validator set for the consumer chain
  foreach val IN gs.initialValSet {
    ccvValidatorSet[hash(val.pubKey)] = val
  }

  // set distribution channel ID
  distributionChannelId = gs.transferChannelId

  // initiate handshake 
  if preCCV {
    // initiate CCV channel opening handshake
    // i.e., use handleChanOpenInit as defined in ICS-26
    datagram = ChanOpenInit{
      order: ORDERED,
      connectionHops: [gs.connId],
      portIdentifier: ConsumerPortId,
      counterpartyPortIdentifier: ProviderPortId,
      version: ccvVersion,
    }
    handleChanOpenInit(datagram)
  }
  else {
    // initiate connection opening handshake
    // i.e., use handleConnOpenInit as defined in ICS-26
    datagram = ConnOpenInit{
      clientIdentifier: providerClientId,
      counterpartyClientIdentifier: gs.counterpartyClientId,
      version: "ccv"
    }
    connId = handleConnOpenInit(datagram)

    // initiate CCV channel opening handshake
    // i.e., use handleChanOpenInit as defined in ICS-26
    datagram = ChanOpenInit{
      order: ORDERED,
      connectionHops: [connId],
      portIdentifier: ConsumerPortId,
      counterpartyPortIdentifier: ProviderPortId,
      version: ccvVersion,
    }
    handleChanOpenInit(datagram)
  }

  return gs.initialValSet
}
  • Caller
    • The ABCI application.
  • Trigger Event
    • An InitChain message is received from the consensus engine; the InitChain message is sent when the consumer chain is first started.
  • Precondition
    • The consumer CCV module is in the initial state.
  • Postcondition
    • The capability for the port ConsumerPortId is claimed.
    • preCCV is set to gs.preCCV.
    • If preCCV == true, the ID of the client on which the connection with gs.connId is built is stored into providerClientId.
    • Otherwise, a client of the provider chain is created and the client ID is stored into providerClientId.
    • ConsumerUnbondingPeriod is set to gs.unbondingPeriod.
    • HtoVSC for the current block is set to 0.
    • The ccvValidatorSet mapping is populated with the initial validator set.
    • The ID of the distribution token transfer channel is set to gs.transferChannelId.
    • If preCCV == true, the CCV channel opening handshake is initialized.
    • Otherwise, the connection opening handshake is initialized.
    • The initial validator set is returned to the consensus engine.
  • Error Condition
    • The genesis state contains an empty initial validator set.
    • If the genesis state preCCV field is set to true, then the genesis state contains no valid connection ID.
    • Otherwise,
      • the genesis state contains no valid provider client state, where the validity is defined in the corresponding client specification (e.g., ICS 7;
      • the genesis state contains no valid provider consensus state, where the validity is defined in the corresponding client specification (e.g., ICS 7);
      • the genesis state contains an initial validator set that does not match the validator set in the provider consensus state;
    • The genesis state contains an invalid distribution channel ID.
    • The capability for the port ConsumerPortId cannot be claimed.
Note: CCV assumes that all the correct validators in the initial validator set of the consumer chain receive the same consumer chain binary and consumer chain genesis state. Although the mechanism of disseminating the binary and the genesis state is outside the scope of this specification, a possible approach would entail including this information in the governance proposal on the provider chain.

[CCV-CCF-COINIT.1]

// CCF: Consumer Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onChanOpenInit(
  order: ChannelOrder,
  connectionHops: [Identifier],
  portIdentifier: Identifier,
  channelIdentifier: Identifier,
  counterpartyPortIdentifier: Identifier,
  counterpartyChannelIdentifier: Identifier,
  version: string): string {
    // ensure provider channel hasn't already been created
    abortTransactionUnless(providerChannel == "")

    // validate parameters:
    // - only ordered channels allowed
    abortTransactionUnless(order == ORDERED)
    // - require the portIdentifier to be the port ID the CCV module is bound to
    abortTransactionUnless(portIdentifier == ConsumerPortId)
    // - require the version to be the expected version
    abortTransactionUnless(version == "" OR version == ccvVersion)

    // assert that the counterpartyPortIdentifier matches 
    // the expected consumer port ID
    abortTransactionUnless(counterpartyPortIdentifier == ProviderPortId)
   
    // require that the client ID of the client associated 
    // with this channel matches the expected provider client id
    channelEnd = provableStore.get("channelEnds/ports/{portIdentifier}/channels/{channelIdentifier}")
    abortTransactionUnless(channelEnd != nil AND len(channelEnd.connectionHops) == 1)
    connId = channelEnd.connectionHops[0]
    connectionEnd = provableStore.get("connections/{connId}")
    abortTransactionUnless(providerClientId != connectionEnd.clientIdentifier)

    return ccvVersion
}
  • Caller
    • The consumer IBC routing module.
  • Trigger Event
    • The consumer IBC routing module receives a ChanOpenInit message on a port the consumer CCV module is bounded to.
  • Precondition
    • True.
  • Postcondition
    • The transaction is aborted if any of the following conditions are true:
      • providerChannel is already set;
      • portIdentifier != ConsumerPortId;
      • version is set but not to the expected version;
      • counterpartyPortIdentifier != ProviderPortId;
      • the client associated with this channel is not the expected provider client.
    • ccvVersion is returned.
    • The state is not changed.
  • Error Condition
    • None.

[CCV-CCF-COTRY.1]

// CCF: Consumer Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onChanOpenTry(
  order: ChannelOrder,
  connectionHops: [Identifier],
  portIdentifier: Identifier,
  channelIdentifier: Identifier,
  counterpartyPortIdentifier: Identifier,
  counterpartyChannelIdentifier: Identifier,
  counterpartyVersion: string):  string {
    // the channel handshake MUST be initiated by consumer chain
    abortTransactionUnless(FALSE)
}
  • Caller
    • The consumer IBC routing module.
  • Trigger Event
    • The consumer IBC routing module receives a ChanOpenTry message on a port the consumer CCV module is bounded to.
  • Precondition
    • True.
  • Postcondition
    • The transaction is always aborted; hence, the state is not changed.
  • Error Condition
    • None.

[CCV-CCF-COACK.1]

// CCF: Consumer Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onChanOpenAck(
  portIdentifier: Identifier,
  channelIdentifier: Identifier,
  counterpartyVersion: string) {
    // ensure provider channel hasn't already been created
    abortTransactionUnless(providerChannel == "")

    // the version must be encoded in JSON format (as defined in ICS4)
    md = UnmarshalJSON(counterpartyVersion)

    // assert that the counterpartyVersion matches the expected version
    abortTransactionUnless(md.version == ccvVersion)

    // set the address of the distribution module account on the provider chain
    providerDistributionAccount = md.providerDistributionAccount

    if distributionChannelId == "" {
      // initiate opening handshake for the distribution token transfer channel
      // over the same connection as the CCV channel
      // i.e., use handleChanOpenInit as defined in ICS-26
      datagram = ChanOpenInit{
          order: UNORDERED,
          connectionHops: channelKeeper.GetConnectionHops(channelIdentifier), // same as the CCV channel
          portIdentifier: "transfer",
          counterpartyPortIdentifier: "transfer",
          version: "ics20-1",
      }
      distributionChannelId = handleChanOpenInit(datagram)
    }

    // set the channel as the provider channel
    providerChannel = channelIdentifier

    // send pending slash requests;
    // note: this can happen only if preCCV == false, as the ABCI application 
    // can invoke SendSlashRequest only once the chain is upgraded to 
    // a consumer chain, see BeginBlockInit below
    SendPendingSlashRequests()

    if preCCV {
      // replace valset with initial valset
      stakingKeeper.ReplaceValset(ccvValidatorSet.Values()) 
    }
}
  • Caller
    • The consumer IBC routing module.
  • Trigger Event
    • The consumer IBC routing module receives a ChanOpenAck message on a port the consumer CCV module is bounded to.
  • Precondition
    • True.
  • Postcondition
    • counterpartyVersion is unmarshaled into a CCVHandshakeMetadata structure md.
    • The transaction is aborted if any of the following conditions are true:
      • providerChannel is already set;
      • md.version != ccvVersion.
    • The address of the distribution module account on the provider chain is set to md.providerDistributionAccount.
    • If distributionChannelId is not set, the distribution token transfer channel opening handshake is initiated and distributionChannelId is set to the resulting channel ID.
    • The CCV channel is marked as established, i.e., providerChannel is set to this channel.
    • The pending slash requests are sent to the provider chain (see [CCV-CCF-SNDPESLASH.1]). Note that this can happen only if preCCV == false, as the ABCI application can invoke SendSlashRequest only once the chain is upgraded to a consumer chain (see [CCV-CCF-BBLOCK-INIT.1]).
    • If preCCV == true, the valset in the staking module is replaced with the ccvValidatorSet, i.e., the initial validator set.
  • Error Condition
    • None.

[CCV-CCF-COCONFIRM.1]

// CCF: Consumer Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onChanOpenConfirm(
  portIdentifier: Identifier,
  channelIdentifier: Identifier) {
    // the channel handshake MUST be initiated by consumer chain
    abortTransactionUnless(FALSE)
}
  • Caller
    • The consumer IBC routing module.
  • Trigger Event
    • The consumer IBC routing module receives a ChanOpenConfirm message on a port the consumer CCV module is bounded to.
  • Precondition
    • True.
  • Postcondition
    • The transaction is always aborted; hence, the state is not changed.
  • Error Condition
    • None.

[CCV-CCF-BBLOCK-INIT.1]

// CCF: Consumer Chain Function
function BeginBlockInit() {
  if preCCV {  
    ownConsensusState = getConsensusState(getCurrentHeight())
    if ownConsensusState.validatorSet == ccvValidatorSet.Values() {
      // pre-CCV state is over; upgrade chain to consumer chain
      //  - set preCCV to false
      //  - the existing staking module no longer provides 
      //    validator updates to the underlying consensus engine
      //  - the CCV module starts providing validator updates 
      //    to the underlying consensus engine
      //  - for safety, the existing staking module must be kept 
      //    for at least the unbonding period
    }
  }
}
  • Caller
    • The BeginBlock() method.
  • Trigger Event
    • A BeginBlock message is received from the consensus engine; BeginBlock messages are sent once per block.
  • Precondition
    • True.
  • Postcondition
    • If preCCV == true and the current validator set matches the ccvValidatorSet (i.e., the initial validator set), then the chain MUST be upgraded to a full consumer chain. The upgrade mechanism is outside the scope of this specification.
  • Error Condition
    • None.

Consumer Chain Removal

↑ Back to Outline

[CCV-PCF-HCRPROP.1]

// PCF: Provider Chain Function
// implements governance proposal Handler 
function HandleConsumerRemovalProposal(p: ConsumerRemovalProposal) {
    // store the proposal as a pending removal proposal
    pendingConsumerRemovalProposals.Append(p)
}
  • Caller
    • EndBlock() method of Governance module.
  • Trigger Event
    • A governance proposal ConsumerRemovalProposal has passed (i.e., it got the necessary votes).
  • Precondition
    • True.
  • Postcondition
    • The proposal is appended to the list of pending removal proposals, i.e., pendingConsumerRemovalProposals.
  • Error Condition
    • None.

[CCV-PCF-BBLOCK-CCR.1]

// PCF: Provider Chain Function
function BeginBlockCCR() {
  // iterate over the pending removal proposals 
  // and stop the consumer chain
  foreach p IN pendingConsumerRemovalProposals {
    if currentTimestamp() > p.stopTime {
      // stop the consumer chain and do not lock the unbonding
      StopConsumerChain(p.chainId, false)
      pendingConsumerRemovalProposals.Remove(p)
    }
  }
}
  • Caller
    • The BeginBlock() method.
  • Trigger Event
    • A BeginBlock message is received from the consensus engine; BeginBlock messages are sent once per block.
  • Precondition
    • True.
  • Postcondition
    • For each ConsumerRemovalProposal p in the list of pending removal proposals pendingConsumerRemovalProposals, if currentTimestamp() > p.stopTime, then
      • StopConsumerChain(p.chainId, false) is invoked;
      • p is removed from pendingConsumerRemovalProposals.
  • Error Condition
    • None.

[CCV-PCF-STCC.1]

// PCF: Provider Chain Function
function StopConsumerChain(chainId: string, lockUnbonding: Bool) {
  // check that a client for chainId exists 
  if chainId NOT IN chainToClient.Keys() {
    return
  }

  // cleanup state
  chainToClient.Remove(chainId)
  lockUnbondingOnTimeout.Remove(chainId)
  if chainId IN chainToChannel.Keys() {
    // CCV channel is established
    channelToChain.Remove(chainToChannel[chainId])
    channelKeeper.ChanCloseInit(chainToChannel[chainId])
    chainToChannel.Remove(chainId)
  }
  pendingVSCPackets.Remove(chainId)
  initialHeights.Remove(chainId)
  downtimeSlashRequests.Remove(chainId)
  initTimeoutTimestamps.Remove(chainId)
  vscSendTimestamps.Remove((chainId, *))

  if !lockUnbonding {
    // remove chainId form all outstanding unbonding operations
    foreach id IN vscToUnbondingOps[(chainId, _)] {
      unbondingOps[id].unbondingChainIds.Remove(chainId)
      // if the unbonding operation has unbonded on all consumer chains
      if unbondingOps[id].unbondingChainIds.IsEmpty() {
        // append the id of the unbonding to maturedUnbondingOps
        maturedUnbondingOps.Append(id)
        // remove unbonding operation
        unbondingOps.Remove(id)
      }
    }
    // clean up vscToUnbondingOps mapping
    vscToUnbondingOps.Remove((chainId, _))
  }
}
  • Caller
  • Trigger Event
    • One of the following events:
      • a governance proposal to stop the consumer chain with chainId has passed (i.e., it got the necessary votes);
      • a VSCPacket sent on the CCV channel to the consumer chain with chainId has timed out;
      • the channel initialization has timed out.
  • Precondition
    • True.
  • Postcondition
    • If a client for p.chainId does not exist, the state is not changed.
    • Otherwise,
      • the client ID mapped to chainId in chainToClient is removed;
      • the value mapped to chainId in lockUnbondingOnTimeout is removed;
      • if the CCV channel to the consumer chain with chainId is established, then
        • the chain ID mapped to chainToChannel[chainId] in channelToChain is removed;
        • the channel closing handshake is initiated for the CCV channel;
        • the channel ID mapped to chainId in chainToChannel is removed.
      • all the VSCPacketData mapped to chainId in pendingVSCPackets are removed;
      • the height mapped to chainId in initialHeights is removed;
      • downtimeSlashRequests[chainId] is emptied;
      • if lockUnbonding == false, then
        • chainId is removed from all outstanding unbonding operations;
        • if an outstanding unbonding operation has matured on all consumer chains,
        • the matured unbonding operation is added to maturedUnbondingOps;
        • the matured unbonding operation is removed from unbondingOps;
        • all the entries with chainId are removed from the vscToUnbondingOps mapping.
  • Error Condition
    • None
Note: Invoking StopConsumerChain(chainId, lockUnbonding) with lockUnbonding == FALSE entails that all outstanding unbonding operations can complete before ConsumerUnbondingPeriod elapses on the consumer chain with chainId. Thus, invoking StopConsumerChain(chainId, false) for any chainId MAY violate the Bond-Based Consumer Voting Power and Slashable Consumer Misbehavior properties (see the System Properties section). StopConsumerChain(chainId, false) is invoked in two scenarios (see Trigger Event above).
  • In the first scenario (i.e., a governance proposal to stop the consumer chain with chainId), the validators on the provider chain MUST make sure that it is safe to stop the consumer chain. Since a governance proposal needs a majority of the voting power to pass, the safety of invoking StopConsumerChain(chainId, false) is ensured by the Safe Blockchain assumption (see the Assumptions section).
  • The second scenario (i.e., a timeout) is only possible if the Correct Relayer assumption is violated (see the Assumptions section), which is necessary to guarantee both the Bond-Based Consumer Voting Power and Slashable Consumer Misbehavior properties (see the Assumptions section).

[CCV-PCF-EBLOCK-CCR.1]

// PCF: Provider Chain Function
function EndBlockCCR() {
  // iterate over vscSendTimestamps
  for (chainId, vscId) IN vscSendTimestamps.Keys() {
    // check get first timestamp, i.e., the smallest
    if currentTimestamp() > vscSendTimestamps[(chainId, vscId)] + vscTimeout {
      // vscTimeout expired: 
      // stop the consumer chain and use lockUnbondingOnTimeout 
      // to decide whether to lock the unbonding
      StopConsumerChain(chainId, lockUnbondingOnTimeout[chainId])
    }
  }

  // iterate over initTimeoutTimestamps
  for chainId IN initTimeoutTimestamps.Keys() {
    if currentTimestamp() > initTimeoutTimestamps[chainId] {
      // initTimeout expired:
      // stop the consumer chain and unlock the unbonding 
      StopConsumerChain(chainId, false)
    }
  }
}
  • Caller
    • The EndBlock() method.
  • Trigger Event
    • An EndBlock message is received from the consensus engine; EndBlock messages are sent once per block.
  • Precondition
    • True.
  • Postcondition
    • For each consumer chain ID chainId in vscSendTimestamps.Keys(),
      • if vscSendTimestamps[(chainId, vscId)] + vscTimeout is smaller than the current timestamp, then the consumer chain with ID chainId is stopped.
    • For each consumer chain ID chainId in initTimeoutTimestamps.Keys(),
      • if the timestamp in initTimeoutTimestamps[chainId] is smaller than the current timestamp, then the consumer chain with ID chainId is stopped.
  • Error Condition
    • None.
Note: To avoid false positives where a consumer chain is unnecessarily removed, vscTimeout MUST be larger than consumerUnbondingPeriod and SHOULD account for the time needed to relay the VSCPacket to the consumer and the corresponding VSCMaturedPacket back to the provider.

[CCV-PCF-CCINIT.1]

// PCF: Provider Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onChanCloseInit(
  portIdentifier: Identifier,
  channelIdentifier: Identifier) {
    // Disallow user-initiated channel closing
    abortTransactionUnless(FALSE)
}
  • Caller
    • The provider IBC routing module.
  • Trigger Event
    • The provider IBC routing module receives a ChanCloseInit message on a port the provider CCV module is bounded to.
  • Precondition
    • True.
  • Postcondition
    • The transaction is always aborted; hence, the state is not changed.
  • Error Condition
    • None.

[CCV-PCF-CCCONFIRM.1]

// PCF: Provider Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onChanCloseConfirm(
  portIdentifier: Identifier,
  channelIdentifier: Identifier) {
    // do nothing
}
  • Caller
    • The provider IBC routing module.
  • Trigger Event
    • The provider IBC routing module receives a ChanCloseConfirm message on a port the provider CCV module is bounded to.
  • Precondition
    • True.
  • Postcondition
    • The state is not changed.
  • Error Condition
    • None.

[CCV-CCF-BBLOCK-CCR.1]

// CCF: Consumer Chain Function
function BeginBlockCCR() {
  if providerChannel != "" AND channelKeeper.GetChannelState(providerChannel) == CLOSED {
    // the CCV channel was established, but it was then closed; 
    // the consumer chain is no longer safe

    // cleanup state, e.g., 
    // providerChannel = ""

    // shut down consumer chain
    abortSystemUnless(FALSE)
  } 
}
  • Caller
    • The BeginBlock() method.
  • Trigger Event
    • A BeginBlock message is received from the consensus engine; BeginBlock messages are sent once per block.
  • Precondition
    • True.
  • Postcondition
    • If the CCV was established, but then was moved to the CLOSED state, then the state of the consumer CCV module is cleaned up, e.g., the providerChannel is unset.
  • Error Condition
    • If the CCV was established, but then was moved to the CLOSED state.
Note: Once the CCV channel is closed, the provider chain can no longer provider security. As a result, the consumer chain MUST be shut down. For an example of how to do this in practice, see the Cosmos SDK implementation.

[CCV-CCF-CCINIT.1]

// CCF: Consumer Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onChanCloseInit(
  portIdentifier: Identifier,
  channelIdentifier: Identifier) {
    // allow relayers to close duplicate OPEN channels, 
    // if the provider channel has already been established
    if providerChannel == "" || providerChannel == channelIdentifier {
      // user cannot close channel
      abortTransactionUnless(FALSE)
    }
}
  • Caller
    • The consumer IBC routing module.
  • Trigger Event
    • The consumer IBC routing module receives a ChanCloseInit message on a port the consumer CCV module is bounded to.
  • Precondition
    • True.
  • Postcondition
    • If providerChannel is not set or providerChannel matches the ID of the channel the ChanCloseInit message was received on, then the transaction is aborted.
    • The state is not changed.
  • Error Condition
    • None.

[CCV-CCF-CCCONFIRM.1]

// CCF: Consumer Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onChanCloseConfirm(
  portIdentifier: Identifier,
  channelIdentifier: Identifier) {
    // do nothing
}
  • Caller
    • The consumer IBC routing module.
  • Trigger Event
    • The consumer IBC routing module receives a ChanCloseConfirm message on a port the consumer CCV module is bounded to.
  • Precondition
    • True.
  • Postcondition
    • The state is not changed.
  • Error Condition
    • None.

Validator Set Update

↑ Back to Outline The validator set update sub-protocol enables the provider chain
  • to update the consumer chain on the voting power granted to validators on the provider chain
  • and to ensure the correct completion of unbonding operations for validators that produce blocks on the consumer chain.

[CCV-PCF-EBLOCK-VSU.1]

// PCF: Provider Chain Function
function EndBlockVSU() {
  // notify the Staking module to complete all matured unbondings
  for id IN maturedUnbondingOps {
    stakingKeeper.UnbondingCanComplete(id)
  }
  maturedUnbondingOps.RemoveAll()

  // get list of validator updates from the provider Staking module
  valUpdates = stakingKeeper.GetValidatorUpdates()

  // iterate over all consumer chains registered with this provider chain
  foreach chainId IN chainToClient.Keys() {
    // check whether there are changes in the validator set;
    // note that this also entails unbonding operations 
    // w/o changes in the voting power of the validators in the validator set
    if len(valUpdates) != 0 OR len(vscToUnbondingOps[(chainId, vscId)]) != 0 {
      // create VSCPacket data
      data = VSCPacketData{
        id: vscId, 
        updates: valUpdates,
        downtimeSlashAcks: downtimeSlashRequests[chainId]
      }
      downtimeSlashRequests.Remove(chainId)

      // add VSCPacket data to the list of pending VSCPackets 
      pendingVSCPackets.Append(chainId, data)
    }

    // check whether there is an established CCV channel to the consumer chain
    if chainId IN chainToChannel.Keys() {
      // get the channel ID for the given consumer chain ID
      channelId = chainToChannel[chainId]

      foreach data IN pendingVSCPackets[chainId] {
        // send data using the interface exposed by ICS-4
        channelKeeper.sendPacket(
          portKeeper.getCapability(portKeeper.portPath(ProviderPortId)),
          ProviderPortId, // source port ID
          channelId, // source channel ID
          zeroTimeoutHeight,
          ccvTimeoutTimestamp,
          data
        )
        // add VSC send timestamp to vscSendTimestamps
        vscSendTimestamps[(vscId, chainId)] = currentTimestamp()
      }

      // remove pending VSCPackets
      pendingVSCPackets.Remove(chainId)
    }
  }
  // increment VSC ID
  vscId++ 
}
  • Caller
    • The EndBlock() method.
  • Trigger Event
    • An EndBlock message is received from the consensus engine; EndBlock messages are sent once per block.
  • Precondition
    • True.
  • Postcondition
    • For every matured unbonding operation in maturedUnbondingOps, the Staking module is notified that the unbonding can complete.
    • All unbonding operation in maturedUnbondingOps are removed.
    • A list of validator updates valUpdates is obtained from the provider Staking module.
    • For every consumer chain with chainId
      • If either valUpdates is not empty or there were unbonding operations initiated during this block, then
        • a VSCPacket data data is created such that data.id = vscId, data.updates = valUpdates, and data.downtimeSlashAcks = downtimeSlashRequests[chainId];
        • downtimeSlashRequests[chainId] is emptied;
        • packetData is appended to the list of pending VSCPackets associated to chainId, i.e., pendingVSCPackets[chainId].
      • If there is an established CCV channel for the consumer chain with chainId, then
        • for each VSCPacketData in the list of pending VSCPackets associated to chainId
          • a packet with the VSCPacketData is sent on the channel associated with the consumer chain with chainId;
          • vscSendTimestamps[(vscId, chainId)] is set to the current timestamp;
        • all the pending VSCPackets associated to chainId are removed.
    • vscId is incremented.
  • Error Condition
    • None.

[CCV-PCF-ACKVSC.1]

// PCF: Provider Chain Function
function onAcknowledgeVSCPacket(packet: Packet, ack: bytes) {
  // providing the VSC with id packet.data.id can fail, 
  // i.e., ack == VSCPacketError, 
  // only if the VSCPacket was sent on a channel 
  // other than the established CCV channel;
  // that should never happen, see EndBlock()
  abortSystemUnless(ack != VSCPacketError)
}
  • Caller
    • The onAcknowledgePacket() method.
  • Trigger Event
    • The provider IBC routing module receives an acknowledgement of a VSCPacket on a channel owned by the provider CCV module.
  • Precondition
    • True.
  • Postcondition
    • The state is not changed.
  • Error Condition
    • The acknowledgement is VSCPacketError.

[CCV-PCF-TOVSC.1]

// PCF: Provider Chain Function
function onTimeoutVSCPacket(packet: Packet) {
  // cleanup state
  abortTransactionUnless(packet.getDestinationChannel() IN channelToChain.Keys())
  chainId = channelToChain[packet.getDestinationChannel()]
  // stop the consumer chain and use lockUnbondingOnTimeout 
  // to decide whether to lock the unbonding
  StopConsumerChain(chainId, lockUnbondingOnTimeout[chainId])
}
  • Caller
    • The onTimeoutPacket() method.
  • Trigger Event
    • A VSCPacket sent on a channel owned by the provider CCV module timed out as a result of either
      • the timeout height or timeout timestamp passing on the consumer chain without the packet being received (see timeoutPacket defined in ICS4);
      • or the channel being closed without the packet being received (see timeoutOnClose defined in ICS4).
  • Precondition
    • The Correct Relayer assumption is violated (see the Assumptions section).
  • Postcondition
    • The transaction is aborted if the ID of the channel on which the packet was sent is not mapped to a chain ID (in channelToChain).
    • StopConsumerChain(chainId, lockUnbondingOnTimeout[chainId]) is invoked, where chainId = channelToChain[packet.getDestinationChannel()].
  • Error Condition
    • None

[CCV-PCF-RCVMAT.1]

// PCF: Provider Chain Function
function onRecvVSCMaturedPacket(packet: Packet): bytes {
  // get the ID of the consumer chain mapped to this channel ID
  abortTransactionUnless(packet.getDestinationChannel() IN channelToChain.Keys())
  chainId = channelToChain[packet.getDestinationChannel()]

  // iterate over the unbonding operations mapped to
  // this chainId and vscId (i.e., packet.data.id)
  foreach op in GetUnbondingsFromVSC(chainId, packet.data.id) {
    // remove the consumer chain from 
    // the list of consumer chain that are still unbonding
    op.unbondingChainIds.Remove(chainId)
    // if the unbonding operation has unbonded on all consumer chains
    if op.unbondingChainIds.IsEmpty() {
      // append the id of the unbonding to maturedUnbondingOps
      maturedUnbondingOps.Append(op.id)
      // remove unbonding operation
      unbondingOps.Remove(op.id)
    }
  }
  // clean up vscToUnbondingOps mapping
  vscToUnbondingOps.Remove((chainId, vscId))

  // clean up vscSendTimestamps mapping
  vscSendTimestamps.Remove((chainId, vscId))

  return VSCMaturedPacketSuccess
}
  • Caller
    • The onRecvPacket() method.
  • Trigger Event
    • The provider IBC routing module receives a VSCMaturedPacket on a channel owned by the provider CCV module.
  • Precondition
    • True.
  • Postcondition
    • The transaction is aborted if the channel on which the packet was received is not an established CCV channel (i.e., not in channelToChain).
    • chainId is set to the ID of the consumer chain mapped to the channel on which the packet was received.
    • For each unbonding operation op returned by GetUnbondingsFromVSC(chainId, packet.data.id)
      • chainId is removed from op.unbondingChainIds;
      • if op.unbondingChainIds is empty,
        • op.id is added to maturedUnbondingOps;
        • op.id is removed from unbondingOps.
    • (chainId, vscId) is removed from vscToUnbondingOps.
    • (chainId, vscId) is removed from vscSendTimestamps.
    • A successful acknowledgment is returned.
  • Error Condition
    • None.

[CCV-PCF-GETUBS.1]

// PCF: Provider Chain Function
// Utility method
function GetUnbondingsFromVSC(
  chainId: Identifier, 
  _vscId: uint64): [UnbondingOperation] {
    // get all unbonding operations associated with (chainId, _vscId)
    ops = []
    foreach id in vscToUnbondingOps[(chainId, _vscId)] {
      // get the unbonding operation with this ID
      op = unbondingOps[id]
      // append the operation to the list of operations to be returned
      ops.Append(op)
    }
    return ops
}
  • Caller
    • The onRecvVSCMaturedPacket() method.
  • Trigger Event
    • The provider IBC routing module receives a VSCMaturedPacket on a channel owned by the provider CCV module.
  • Precondition
    • The provider CCV module received a VSCMaturedPacket P from a consumer chain with ID chainId, such that P.data.id == _vscId.
  • Postcondition
    • Return the list of unbonding operations mapped to (chainId, _vscId).
  • Error Condition
    • None.

[CCV-PCF-HOOK-AFUBOPCR.1]

// PCF: Provider Chain Function
// implements a Staking module hook
function AfterUnbondingInitiated(opId: uint64) {
  // get the IDs of all consumer chains registered with this provider chain;
  // note: this includes also consumer chains in the pre-CCV state
  chainIds = chainToClient.Keys()
  if len(chainIds) > 0 {
    // create and store a new unbonding operation
    unbondingOps[opId] = UnbondingOperation{
      id: opId,
      unbondingChainIds: chainIds
    }
    // add the unbonding operation id to vscToUnbondingOps
    foreach chainId in chainIds {
      vscToUnbondingOps[(chainId, vscId)].Append(opId)
    }

    // ask the Staking module to wait for this operation 
    // to reach maturity on the consumer chains
    stakingKeeper.PutUnbondingOnHold(opId)
  }
}
  • Caller
    • The Staking module.
  • Trigger Event
    • An unbonding operation with id opId is initiated.
  • Precondition
    • True.
  • Postcondition
    • chainIds is set to the list of all consumer chains registered with this provider chain, i.e., chainToClient.Keys().
    • If there is at least one consumer chain in chainIds, then
      • an UnbondingOperation op is created and added to unbondingOps, such that op.id = opId and op.unbondingChainIds = chainIds.
      • opId is appended to every list in vscToUnbondingOps[(chainId, vscId)], where chainId is an ID of a consumer chains registered with this provider chain and vscId is the current VSC ID.
      • the PutUnbondingOnHold(opId) of the Staking module is invoked.
  • Error Condition
    • None.

[CCV-CCF-RCVVSC.1]

// CCF: Consumer Chain Function
function onRecvVSCPacket(packet: Packet): bytes {
  // check whether the packet was sent on the CCV channel
  if providerChannel != "" && providerChannel != packet.getDestinationChannel() {
    // packet sent on a channel other than the established provider channel;
    // return error acknowledgement
    return VSCPacketError
  }

  // set HtoVSC mapping
  HtoVSC[getCurrentHeight() + 1] = packet.data.id

  // store the packet data
  receivedVSCs.Append(packet.data)

  return VSCPacketSuccess
}
  • Caller
    • The onRecvPacket() method.
  • Trigger Event
    • The consumer IBC routing module receives a VSCPacket on a channel owned by the consumer CCV module.
  • Precondition
    • True.
  • Postcondition
    • If providerChannel is set and does not match the channel (with ID packet.getDestinationChannel()) on which the packet was received, then an error acknowledgement is returned.
    • Otherwise,
      • the height of the subsequent block is mapped to packet.data.id (i.e., the HtoVSC mapping) ;
      • packet.data is appended to receivedVSCs.
      • a successful acknowledgement is returned.
  • Error Condition
    • None.

[CCV-CCF-ACKMAT.1]

// CCF: Consumer Chain Function
function onAcknowledgeVSCMaturedPacket(packet: Packet, ack: bytes) {
  // notifications of VSC maturity cannot fail by construction
  abortSystemUnless(ack != VSCMaturedPacketError)
}
  • Caller
    • The onAcknowledgePacket() method.
  • Trigger Event
    • The consumer IBC routing module receives an acknowledgement of a VSCMaturedPacket on a channel owned by the consumer CCV module.
  • Precondition
    • True.
  • Postcondition
    • The state is not changed.
  • Error Condition
    • The acknowledgement is VSCMaturedPacketError.

[CCV-CCF-TOMAT.1]

// CCF: Consumer Chain Function
function onTimeoutVSCMaturedPacket(packet Packet) {
  // the CCV channel state is changed to CLOSED 
  // by the IBC handler (since the channel is ORDERED)
}
  • Caller
    • The onTimeoutPacket() method.
  • Trigger Event
    • A VSCMaturedPacket sent on a channel owned by the consumer CCV module timed out as a result of either
      • the timeout height or timeout timestamp passing on the provider chain without the packet being received (see timeoutPacket defined in ICS4);
      • or the channel being closed without the packet being received (see timeoutOnClose defined in ICS4).
  • Precondition
    • The Correct Relayer assumption is violated (see the Assumptions section).
  • Postcondition
    • The state is not changed.
  • Error Condition
    • None

[CCV-CCF-EBLOCK-VSU.1]

// CCF: Consumer Chain Function
function EndBlockVSU(): [ValidatorUpdate] {
  // unbond mature packets if the CCV channel is established
  if providerChannel != "" {
    UnbondMaturePackets()
  }

  if preCCV {
    // do nothing
    return []
  }
  else {
    // handle received VSCs
    changes = HandleReceivedVSCs()

    // update ccvValidatorSet
    UpdateValidatorSet(changes)

    // return the validator set updates
    return changes
  }
}
  • Caller
    • The EndBlock() method.
  • Trigger Event
    • An EndBlock message is received from the consensus engine; EndBlock messages are sent once per block.
  • Precondition
    • True.
  • Postcondition
    • If providerChannel != "", UnbondMaturePackets() is invoked;
    • If preCCV == true, the state is not changed.
    • Otherwise,
      • the data items in receivedVSCs are handled (see [CCV-CCF-HAREVSC.1]), which results in a list changes of validator updates;
      • UpdateValidatorSet(changes) is invoked;
      • changes is returned.
  • Error Condition
    • None.

[CCV-CCF-HAREVSC.1]

// CCF: Consumer Chain Function
function HandleReceivedVSCs(): [ValidatorUpdate] {
  changes = []
  foreach data IN receivedVSCs {
    // store the list of updates
    changes.Append(data.updates)

    // calculate and store the maturity timestamp for the VSC
    maturityTimestamp = currentTimestamp().Add(ConsumerUnbondingPeriod)
    maturingVSCs.Add(data.id, maturityTimestamp)

    // reset outstandingDowntime for validators in data.downtimeSlashAcks
    foreach valAddr IN data.downtimeSlashAcks {
      outstandingDowntime[valAddr] = FALSE
    }
  }
  // remove all entries
  receivedVSCs = []

  // aggregate the updates, 
  // i.e., keep only the latest update per validator;
  // note: in the implementation, the aggregation is done directly 
  // when receiving a VSCPacket via the AccumulateChanges method
  return changes.Aggregate()
}
  • Caller
    • The EndBlock() method.
  • Trigger Event
    • An EndBlock message is received from the consensus engine.
  • Precondition
    • preCCV == false.
  • Postcondition
    • For each data item in the list receivedVSCs,
      • data.updates are appended to changes, where changes is initially an empty list of validator updates;
      • (data.id, maturityTimestamp) is added to maturingVSCs, where maturityTimestamp = currentTimestamp() + ConsumerUnbondingPeriod;
      • for each valAddr in the slash acknowledgments received from the provider chain, outstandingDowntime[valAddr] is set to false.
    • receivedVSCs is emptied.
    • The updates in changes are aggregated, i.e., only the latest update per validator is kept, and returned.
  • Error Condition
    • None.

[CCV-CCF-UPVALS.1]

// CCF: Consumer Chain Function
function UpdateValidatorSet(changes: [ValidatorUpdate]) {
  foreach update IN changes {
    addr := hash(update.pubKey)
    if addr NOT IN ccvValidatorSet.Keys() {
      // new validator bonded;
      // note that due changes.Aggregate(), 
      // a validator can be added to the valset and 
      // then removed in the subsequent block, 
      // resulting in update.power == 0 
      if update.power > 0 {
        // add new validator to validator set
        ccvValidatorSet[addr] = update
        // call AfterCCValidatorBonded hook
        AfterCCValidatorBonded(addr)
      }
    }
    else if update.power == 0 {
      // existing validator begins unbonding
      ccvValidatorSet.Remove(addr)
      // call AfterCCValidatorBeginUnbonding hook
      AfterCCValidatorBeginUnbonding(addr)
    }
    else {
      ccvValidatorSet[addr].power = update.power
    }
  }
}
  • Caller
    • The EndBlock() method.
  • Trigger Event
    • An EndBlock message is received from the consensus engine.
  • Precondition
    • preCCV == false.
  • Postcondition
    • For each validator update in changes,
      • if the validator is not in the validator set and update.power > 0, then
        • a new validator is added to ccvValidatorSet;
        • the AfterCCValidatorBonded hook is called;
      • otherwise, if the validator’s new power is 0, then,
        • the validator is removed from ccvValidatorSet;
        • the AfterCCValidatorBeginUnbonding hook is called;
      • otherwise, the validator’s power is updated.
  • Error Condition
    • None.

[CCV-CCF-UMP.1]

// CCF: Consumer Chain Function
function UnbondMaturePackets() {
  foreach (id, ts) in maturingVSCs.SortedByMaturityTime() {
    if currentTimestamp() < ts {
      break // stop loop
    }
    // create VSCMaturedPacketData
    packetData = VSCMaturedPacketData{id: id}

    // send VSCMaturedPacketData using the interface exposed by ICS-4
    channelKeeper.sendPacket(
      portKeeper.getCapability(portKeeper.portPath(ConsumerPortId)),
      ConsumerPortId, // source port ID
      providerChannel, // source channel ID
      zeroTimeoutHeight,
      ccvTimeoutTimestamp,
      packetData
    )
          
    // remove entry from the list
    maturingVSCs.Remove(id, ts)
  }
}
  • Caller
    • The EndBlock() method.
  • Trigger Event
    • An EndBlock message is received from the consensus engine.
  • Precondition
    • The CCV channel to the provider chain is established, i.e., providerChannel != "".
  • Postcondition
    • For each (id, ts) in the list of maturing VSCs sorted by maturity timestamps
      • if currentTimestamp() < ts, the loop is stopped;
      • a VSCMaturedPacketData packet data is created;
      • a packet with the created VSCMaturedPacketData is sent to the provider chain;
      • the tuple (id, ts) is removed from maturingVSCs.
  • Error Condition
    • None.

Consumer Initiated Slashing

↑ Back to Outline

[CCV-PCF-EBLOCK-CIS.1]

// PCF: Provider Chain Function
function EndBlockCIS() {
  // set VSCtoH mapping
  VSCtoH[vscId] = getCurrentHeight() + 1
}
  • Caller
    • The EndBlock() method.
  • Trigger Event
    • An EndBlock message is received from the consensus engine; EndBlock messages are sent once per block.
  • Precondition
    • True.
  • Postcondition
    • vscId is mapped to the height of the subsequent block.
  • Error Condition
    • None.

[CCV-PCF-RCVSLASH.1]

// PCF: Provider Chain Function
function onRecvSlashPacket(packet: Packet): bytes {
  // check whether the packet was received on an established CCV channel
  if packet.getDestinationChannel() NOT IN channelToChain.Keys() {
    // packet received on a non-established channel; incorrect behavior
    return SlashPacketError
  }

  // get the height that maps to the VSC ID in the packet data
  if packet.data.vscId == 0 {
    // the infraction happened before sending any VSC to this chain
    chainId = channelToChain[packet.getDestinationChannel()]
    infractionHeight = initialHeights[chainId]
  }
  else {
    infractionHeight = VSCtoH[packet.data.vscId]
  }

  // request the Slashing module to slash the validator
  // using the slashFactor set on the provider chain
  slashFactor = slashingKeeper.GetSlashFactor(packet.data.downtime)
  slashingKeeper.Slash(
    packet.data.valAddress, 
    infractionHeight, 
    packet.data.valPower, 
    slashFactor))

  // request the Slashing module to jail the validator
  // using the jailTime set on the provider chain
  jailTime = slashingKeeper.GetJailTime(packet.data.downtime)
  slashingKeeper.JailUntil(packet.data.valAddress, currentTimestamp() + jailTime)

  if packet.data.downtime {
    // add validator to list of downtime slash requests for chainId
    downtimeSlashRequests[chainId].Append(packet.data.valAddress)
  }

  return SlashPacketSuccess
}
  • Caller
    • The onRecvPacket() method.
  • Trigger Event
    • The provider IBC routing module receives a SlashPacket on a channel owned by the provider CCV module.
  • Precondition
    • True.
  • Postcondition
    • If the channel the packet was received on is not an established CCV channel, then an error acknowledgment is returned.
    • Otherwise,
      • if packet.data.vscId == 0, infractionHeight is set to initialHeights[chainId], with chainId = channelToChain[packet.getDestinationChannel()], i.e., the height when the CCV channel to this consumer chain is established;
      • otherwise, infractionHeight is set to VSCtoH[packet.data.vscId], i.e., the height at which the voting power was last updated by the validator updates in the VSC with ID packet.data.vscId;
      • a request is made to the Slashing module to slash slashFactor of the tokens bonded at infractionHeight by the validator with address packet.data.valAddress, where slashFactor is the slashing factor set on the provider chain;
      • a request is made to the Slashing module to jail the validator with address packet.data.valAddress for a period jailTime, where jailTime is the jailing time set on the provider chain;
      • if the slash request is for downtime, the validator’s address packet.data.valAddress is added to the list of downtime slash requests from this chainId;
      • a successful acknowledgment is returned.
  • Error Condition
    • None.

[CCV-CCF-BBLOCK-CIS.1]

// CCF: Consumer Chain Function
function BeginBlockCIS() {
  HtoVSC[getCurrentHeight() + 1] = HtoVSC[getCurrentHeight()]
}
  • Caller
    • The BeginBlock() method.
  • Trigger Event
    • A BeginBlock message is received from the consensus engine; BeginBlock messages are sent once per block.
  • Precondition
    • True.
  • Postcondition
    • HtoVSC for the subsequent block height is set to the same VSC ID as the current block height.
  • Error Condition
    • None.

[CCV-CCF-ACKSLASH.1]

// CCF: Consumer Chain Function
function onAcknowledgeSlashPacket(packet: Packet, ack: bytes) {
  // slash request fail, i.e., ack == SlashPacketError, 
  // only if the SlashPacket was sent on a channel 
  // other than the established CCV channel;
  // that should never happen,
  // see SendSlashRequest() and SendPendingSlashRequests()
  abortSystemUnless(ack != SlashPacketError)
}
  • Caller
    • The onAcknowledgePacket() method.
  • Trigger Event
    • The consumer IBC routing module receives an acknowledgement of a SlashPacket on a channel owned by the consumer CCV module.
  • Precondition
    • True.
  • Postcondition
    • The state is not changed.
  • Error Condition
    • The acknowledgement is SlashPacketError.

[CCV-CCF-TOSLASH.1]

// CCF: Consumer Chain Function
function onTimeoutSlashPacket(packet Packet) {
  // the CCV channel state is changed to CLOSED 
  // by the IBC handler (since the channel is ORDERED)
}
  • Caller
    • The onTimeoutPacket() method.
  • Trigger Event
    • A SlashPacket sent on a channel owned by the consumer CCV module timed out as a result of either
      • the timeout height or timeout timestamp passing on the provider chain without the packet being received (see timeoutPacket defined in ICS4);
      • or the channel being closed without the packet being received (see timeoutOnClose defined in ICS4).
  • Precondition
    • The Correct Relayer assumption is violated (see the Assumptions section).
  • Postcondition
    • The state is not changed.
  • Error Condition
    • None

[CCV-CCF-SNDSLASH.1]

// CCF: Consumer Chain Function
// Enables consumer initiated slashing
function SendSlashRequest(
  valAddress: string, 
  power: int64, 
  infractionHeight: Height,
  downtime: Bool) {
    if downtime AND outstandingDowntime[data.valAddress] {
      // do not send multiple requests for the same downtime
      return
    }

    // create SlashPacket data
    packetData = SlashPacketData{
      valAddress: valAddress,
      valPower: power,
      vscId: HtoVSC[infractionHeight],
      downtime: downtime
    }

    // check whether the CCV channel to the provider chain is established
    if providerChannel != "" {
      // send SlashPacket data using the interface exposed by ICS-4
      channelKeeper.sendPacket(
        portKeeper.getCapability(portKeeper.portPath(ConsumerPortId)),
        ConsumerPortId, // source port ID
        providerChannel, // source channel ID
        zeroTimeoutHeight,
        ccvTimeoutTimestamp,
        packetData
      )

      if downtime {
        // set outstandingDowntime for this validator
        outstandingDowntime[data.valAddress] = TRUE
      }
    }
    else {
      // add SlashPacket data to the list of pending SlashPackets 
      req := SlashRequest{data: packetData, downtime: downtime}
      pendingSlashRequests.Append(req)
    }
}
  • Caller
    • The ABCI application (e.g., the Slashing module).
  • Trigger Event
    • Evidence of misbehavior for a validator with address valAddress was received.
  • Precondition
    • True.
  • Postcondition
    • If the request is for downtime and there is an outstanding request to slash this validator for downtime, then the state is not changed.
    • Otherwise,
      • a SlashPacket data packetData is created, such that packetData.vscId = VSCtoH[infractionHeight];
      • if the CCV channel to the provider chain is established, then
        • a packet with the packetData is sent to the provider chain;
        • if the request is for downtime, outstandingDowntime[data.valAddress] is set to true;
      • otherwise SlashRequest{data: packetData, downtime: downtime} is appended to pendingSlashRequests.
  • Error Condition
    • None.
Note: The ABCI application MUST subtract ValidatorUpdateDelay from the infraction height before invoking SendSlashRequest, where ValidatorUpdateDelay is a delay (in blocks) between when validator updates are returned to the consensus-engine and when they are applied. For example, if ValidatorUpdateDelay = x and a validator set update is returned with new validators at the end of block 10, then the new validators are expected to sign blocks beginning at block 11+x (for more details, take a look at the ABCI specification). Consequently, the consumer CCV module expects the infractionHeight parameter of the SendSlashRequest() to be set accordingly. Note: In the context of single-chain validation, slashing for downtime is an atomic operation, i.e., once the downtime is detected, the misbehaving validator is slashed and jailed immediately. Consequently, once a validator is punished for downtime, it is removed from the validator set and cannot be punished again for downtime. Since validators are not automatically added back to the validator set, it entails that the validator is aware of the punishment before it can rejoin and be potentially punished again. In the context of CCV, slashing for downtime is no longer atomic, i.e., downtime is detected on the consumer chain, but the jailing happens on the provider chain. To avoid sending multiple slash requests for the same downtime infraction, the consumer CCV module uses an outstandingDowntime flag per validator. CCV assumes that the consumer ABCI application (e.g., the slashing module) is not including the downtime of a validator with outstandingDowntime == TRUE in the evidence for downtime.

[CCV-CCF-SNDPESLASH.1]

// CCF: Consumer Chain Function
// Utility method
function SendPendingSlashRequests() {
  // iterate over every pending SlashRequest in reverse order
  foreach req IN pendingSlashRequests.Reverse() {
    if !req.downtime OR !outstandingDowntime[req.data.valAddress] {
      // send req.data using the interface exposed by ICS-4
      channelKeeper.sendPacket(
        portKeeper.getCapability(portKeeper.portPath(ConsumerPortId)),
        ConsumerPortId, // source port ID
        providerChannel, // source channel ID
        zeroTimeoutHeight,
        ccvTimeoutTimestamp,
        req.data
      )

      if req.downtime {
        // set outstandingDowntime for this validator
        outstandingDowntime[req.data.valAddress] = TRUE
      }
    }
  }
  // remove pending SlashRequest
  pendingSlashRequests.RemoveAll()
}
  • Caller
  • Trigger Event
    • The first VSCPacket is received from the provider chain.
  • Precondition
    • providerChannel != "".
  • Postcondition
    • For each slash request req in pendingSlashRequests in reverse order, such that either the slash request is not for downtime or there is no outstanding slash request for downtime,
      • a packet with the data req.data is sent to the provider chain;
      • if the request is for downtime, outstandingDowntime[req.data.valAddress] is set to true.
    • All the pending SlashRequests are removed.
  • Error Condition
    • None.
Note: Iterating over pending SlashRequests in reverse order ensures that validators that are down for multiple blocks during channel initialization will be slashed for the latest downtime evidence.

Reward Distribution

↑ Back to Outline

[CCV-CCF-EBLOCK-RD.1]

// CCF: Consumer Chain Function
function EndBlockRD() {
  if getCurrentHeight() - lastDistributionTransferHeight >= BlocksPerDistributionTransfer {
    DistributeRewards()
  }
}
  • Caller
    • The EndBlock() method.
  • Trigger Event
    • An EndBlock message is received from the consensus engine; EndBlock messages are sent once per block.
  • Precondition
    • True.
  • Postcondition
    • If getCurrentHeight() - lastDistributionTransferHeight >= BlocksPerDistributionTransfer, the DistributeRewards() method is invoked.
  • Error Condition
    • None.

[CCV-CCF-DISTRREW.1]

// CCF: Consumer Chain Function
function DistributeRewards() {
  // iterate over all different tokens in ccvAccount
  foreach (denomination, amount) IN ccvAccount.GetAllBalances() {
    // transfer token using ICS20
    transferKeeper.sendFungibleTokens(
      denomination,
      amount,
      ccvAccount, // sender
      providerDistributionAccount, // receiver
      "transfer", // transfer port
      distributionChannelId, // transfer channel ID
      zeroTimeoutHeight, // timeoutHeight
      transferTimeoutTimestamp // timeoutTimestamp
    )
  }
  lastDistributionTransferHeight = getCurrentHeight()
}
  • Caller
    • The EndBlockRD() method.
  • Trigger Event
    • An EndBlock message is received from the consensus engine.
  • Precondition
    • getCurrentHeight() - lastDistributionTransferHeight >= BlocksPerDistributionTransfer
  • Postcondition
    • For each token type defined as a pair (denomination, amount) in ccvAccount, a transfer token (as defined in ICS 20) is initiated.
    • lastDistributionTransferHeight is set to the current height.
  • Error Condition
    • None.