↑ Back to technical specification
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
.
The functions BeginBlock()
and EndBlock()
(see Implemented Interfaces) are split across the CCV sub-protocols.
// 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.
- A
- Precondition
- True.
- Postcondition
BeginBlockInit()
is invoked (see [CCV-PCF-BBLOCK-INIT.1], i.e., it contains theBeginBlock()
logic needed for the Initialization sub-protocol).BeginBlockCCR()
is invoked (see [CCV-PCF-BBLOCK-CCR.1], i.e., it contains theBeginBlock()
logic needed for the Consumer Chain Removal sub-protocol).
- Error Condition
- None.
// PCF: Provider Chain Function
// implements the AppModule interface
function EndBlock(): [ValidatorUpdate] {
EndBlockCIS()
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.
- An
- Precondition
- True.
- Postcondition
EndBlockCIS()
is invoked (see [CCV-PCF-EBLOCK-CIS.1], i.e., it contains theEndBlock()
logic needed for the Consumer Initiated Slashing sub-protocol).EndBlockVSU()
is invoked (see [CCV-PCF-EBLOCK-VSU.1], i.e., it contains theEndBlock()
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 duringEndBlock()
and then, theEndBlock()
of the provider Staking module to be executed before theEndBlock()
of the provider CCV module.
// CCF: Consumer Chain Function
// implements the AppModule interface
function BeginBlock() {
BeginBlockCCR()
BeginBlockCIS()
}
- Caller
- The ABCI application.
- Trigger Event
- A
BeginBlock
message is received from the consensus engine;BeginBlock
messages are sent once per block.
- A
- Precondition
- True.
- Postcondition
BeginBlockCCR()
is invoked (see [CCV-CCF-BBLOCK-CCR.1], i.e., it contains theBeginBlock()
logic needed for the Consumer Chain Removal sub-protocol).BeginBlockCIS()
is invoked (see [CCV-CCF-BBLOCK-CIS.1], i.e., it contains theBeginBlock()
logic needed for the Consumer Initiated Slashing sub-protocol).
- Error Condition
- None.
// 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.
- An
- Precondition
- True.
- Postcondition
EndBlockRD()
is invoked (see [CCV-PCF-EBLOCK-RD.1], i.e., it contains theEndBlock()
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 theEndBlock()
logic needed for the Validator Set Update sub-protocol).
- Error Condition
- None.
// 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 theonRecvVSCMaturedPacket
method is returned. - If the packet is a
SlashPacket
, the acknowledgement obtained from invoking theonRecvSlashPacket
method is returned. - Otherwise, an error acknowledgement is returned.
- If the packet is a
- Error Condition
- None.
// 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
, theonAcknowledgeVSCPacket
method is invoked. - Otherwise, the transaction is aborted.
- If the acknowledgement is for a
- Error Condition
- None.
// 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
- Precondition
- The Correct Relayer assumption is violated (see the Assumptions section).
- Postcondition
- If the timeout is for a
VSCPacket
, theonTimeoutVSCPacket
method is invoked. - Otherwise, the transaction is aborted.
- If the timeout is for a
- Error Condition
- None.
// 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 theonRecvVSCPacket
method is returned. - Otherwise, an error acknowledgement is returned.
- If the packet is a
- Error Condition
- None.
// 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
, theonAcknowledgeVSCMaturedPacket
method is invoked. - If the acknowledgement is for a
SlashPacket
, theonAcknowledgeSlashPacket
method is invoked. - Otherwise, the transaction is aborted.
- If the acknowledgement is for a
- Error Condition
- None.
// 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
- Precondition
- The Correct Relayer assumption is violated (see the Assumptions section).
- Postcondition
- If the timeout is for a
VSCMaturedPacket
, theonTimeoutVSCMaturedPacket
method is invoked. - If the timeout is for a
SlashPacket
, theonTimeoutSlashPacket
method is invoked. - Otherwise, the transaction is aborted.
- If the timeout is for a
- Error Condition
- None.
// computes the unbonding period on the consumer
// from the unbonding period on the provider
function ComputeConsumerUnbondingPeriod(delta: Duration): Duration {
if delta > 7*24*Hour {
return delta - 24*Hour // one day less
}
else if delta >= 24*Hour {
return delta - Hour // one hour less
}
return delta
}
- Caller
- Either
CreateConsumerClient
(see [CCV-PCF-CRCLIENT.1]) orInitGenesis
(see [CCV-CCF-INITG.1])
- Either
- Trigger Event
- A new consumer chain is created.
- Precondition
- None.
- Postcondition
- None.
- Error Condition
- None.
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).
// 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; theInitChain
message is sent when the provider chain is first started.
- An
- 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 mappingschainToChannel
,channelToChain
are set.
- The capability for the port
- 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).
- The capability for the port
// PCF: Provider Chain Function
// implements governance proposal Handler
function SpawnConsumerChainProposalHandler(p: SpawnConsumerChainProposal) {
if currentTimestamp() > p.spawnTime {
CreateConsumerClient(p)
}
else {
// store the proposal as a pending spawn proposal
pendingSpawnProposals.Append(p)
}
}
- Caller
EndBlock()
method of Governance module.
- Trigger Event
- A governance proposal
SpawnConsumerChainProposal
has passed (i.e., it got the necessary votes).
- A governance proposal
- Precondition
- True.
- Postcondition
- If the spawn time has already passed,
CreateConsumerClient(p)
is invoked, withp
theSpawnConsumerChainProposal
. - Otherwise, the proposal is appended to the list of pending spawn proposals, i.e.,
pendingSpawnProposals
.
- If the spawn time has already passed,
- Error Condition
- None.
// PCF: Provider Chain Function
// Utility method
function CreateConsumerClient(p: SpawnConsumerChainProposal) {
// check that a client for this chain does not exist
if p.chainId IN chainToClient.Keys() {
return
}
// create client state
clientState = ClientState{
chainId: p.chainId,
// use the unbonding period on the provider to compute
// the unbonding period on the consumer
unbondingPeriod: ComputeConsumerUnbondingPeriod(stakingKeeper.UnbondingTime()),
latestHeight: p.initialHeight, // the height when the client was last updated
}
// create consensus state;
// the validator set is the same as the validator set
// from own consensus state at current height
ownConsensusState = getConsensusState(getCurrentHeight())
consensusState = ConsensusState{
validatorSet: ownConsensusState.validatorSet,
}
// create consumer chain client and store it
clientId = clientKeeper.CreateClient(clientState, consensusState)
chainToClient[p.chainId] = clientId
// store lockUnbondingOnTimeout flag
lockUnbondingOnTimeout[p.chainId] = p.lockUnbondingOnTimeout
}
- Caller
- Either
SpawnConsumerChainProposalHandler
(see CCV-PCF-SPCCPROP.1) orBeginBlockInit()
(see CCV-PCF-BBLOCK-INIT.1).
- Either
- Trigger Event
- A governance proposal
SpawnConsumerChainProposal
p
has passed (i.e., it got the necessary votes).
- A governance proposal
- Precondition
currentTimestamp() > p.spawnTime
.
- Postcondition
- If a client for
p.chainId
already exists, the state is not changed. - Otherwise,
- a client state is created with
chainId = p.chainId
andunbondingPeriod
set toComputeConsumerUnbondingPeriod(stakingKeeper.UnbondingTime())
; - a consensus state is created with
validatorSet
set to the validator set the provider chain own consensus state at current height; - a client of the consumer chain is created and the client ID is added to
chainToClient
; lockUnbondingOnTimeout[p.chainId]
is set top.lockUnbondingOnTimeout
.
- a client state is created with
- If a client for
- Error Condition
- None.
Note: Creating a client of a remote chain requires a
ClientState
and aConsensusState
(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. The rest of information to create aClientState
it receives through the governance proposal.
// PCF: Provider Chain Function
function BeginBlockInit() {
// iterate over the pending spawn proposals and create
// the consumer client if the spawn time has passed
foreach p IN pendingSpawnProposals {
if currentTimestamp() > p.spawnTime {
CreateConsumerClient(p)
pendingSpawnProposals.Remove(p)
}
}
}
- Caller
- The
BeginBlock()
method.
- The
- Trigger Event
- A
BeginBlock
message is received from the consensus engine;BeginBlock
messages are sent once per block.
- A
- Precondition
- True.
- Postcondition
- For each
SpawnConsumerChainProposal
p
in the list of pending spawn proposalspendingSpawnProposals
, ifcurrentTimestamp() > p.spawnTime
, thenCreateConsumerClient(p)
is invoked;p
is removed frompendingSpawnProposals
.
- For each
- Error Condition
- None.
// 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.
- The provider IBC routing module receives a
- Precondition
- True.
- Postcondition
- The transaction is always aborted; hence, the state is not changed.
- Error Condition
- None.
// 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 this client ID in order
// to get access to the consumer chain ID
clientId = getClient(channelIdentifier)
clientState = clientKeeper.GetClientState(clientId)
// require the CCV channel to be built on top
// of the expected client of the consumer chain
abortTransactionUnless(chainToClient[clientState.chainId] == clientId)
// 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.
- The provider IBC routing module receives a
- 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
;- 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, withproviderDistributionAccount
set to the the address of the distribution module account on the provider chain andversion
set toccvVersion
. - The state is not changed.
- The transaction is aborted if any of the following conditions are true:
- Error Condition
- None.
// 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.
- The provider IBC routing module receives a
- Precondition
- True.
- Postcondition
- The transaction is always aborted; hence, the state is not changed.
- Error Condition
- None.
// PCF: Provider Chain Function
// implements the ModuleCallbacks interface defined in ICS26
function onChanOpenConfirm(
portIdentifier: Identifier,
channelIdentifier: Identifier) {
// get the client state associated with this client ID in order
// to get access to the consumer chain ID
clientId = getClient(channelIdentifier)
clientState = clientKeeper.GetClientState(clientId)
// Verify that there isn't already a CCV channel for the consumer chain
// If there is, then close the channel.
if clientState.chainId IN chainToChannel {
channelKeeper.ChanCloseInit(channelIdentifier)
abortTransactionUnless(FALSE)
}
// set channel mappings
chainToChannel[clientState.chainId] = channelIdentifier
channelToChain[channelIdentifier] = clientState.chainId
// set initialHeights for this consumer chain
initialHeights[chainId] = getCurrentHeight()
}
- 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.
- The provider IBC routing module receives a
- Precondition
- True.
- Postcondition
- If a CCV channel for this consumer chain already exists, then
- the channel closing handshake is initiated for the underlying channel;
- the transaction is aborted.
- Otherwise,
- the channel mappings are set, i.e.,
chainToChannel
andchannelToChain
; initialHeights[chainId]
is set to the current height.
- the channel mappings are set, i.e.,
- If a CCV channel for this consumer chain already exists, then
- Error Condition
- None.
// CCF: Consumer Chain Function
// implements the AppModule interface
function InitGenesis(gs: ConsumerGenesisState): [ValidatorUpdate] {
// ValidateGenesis
// - 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 a non-empty initial validator set
abortSystemUnless(gs.initialValSet NOT empty)
// - contains an initial validator set that matches
// the validator set in the providerConsensusState (e.g., ICS 7)
abortSystemUnless(gs.initialValSet == gs.providerConsensusState.validatorSet)
// bind to ConsumerPortId port
err = portKeeper.bindPort(ConsumerPortId)
// check whether the capability for the port can be claimed
abortSystemUnless(err == nil)
// create client of the provider chain
clientId = clientKeeper.CreateClient(gs.providerClientState, gs.providerConsensusState)
// store the ID of the client of the provider chain
providerClient = clientId
// compute (and store) the consumer unbonding period from the provider unbonding period
consumerUnbondingPeriod = ComputeConsumerUnbondingPeriod(gs.providerClientState.unbondingTime)
// set default value for HtoVSC
HtoVSC[getCurrentHeight()] = 0
// set the initial validator set for the consumer chain
foreach val IN gs.initialValSet {
ccVal := CrossChainValidator{
address: hash(val.pubKey),
power: val.power
}
validatorSet[ccVal.address] = ccVal
}
return gs.initialValSet
}
- Caller
- The ABCI application.
- Trigger Event
- An
InitChain
message is received from the consensus engine; theInitChain
message is sent when the consumer chain is first started.
- An
- Precondition
- The consumer CCV module is in the initial state.
- Postcondition
- The capability for the port
ConsumerPortId
is claimed. - A client of the provider chain is created and the client ID is stored into
providerClient
. consumerUnbondingPeriod
is set toComputeConsumerUnbondingPeriod(gs.providerClientState.unbondingTime)
.HtoVSC
for the current block is set to0
.- The
validatorSet
mapping is populated with the initial validator set. - The initial validator set is returned to the consensus engine.
- The capability for the port
- Error Condition
- 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 empty initial validator set.
- The genesis state contains an initial validator set that does not match the validator set in the provider consensus state.
- 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.
// 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
clientId = getClient(channelIdentifier)
abortTransactionUnless(providerClient != clientId)
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.
- The consumer IBC routing module receives a
- 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.
- The transaction is aborted if any of the following conditions are true:
- Error Condition
- None.
// 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.
- The consumer IBC routing module receives a
- Precondition
- True.
- Postcondition
- The transaction is always aborted; hence, the state is not changed.
- Error Condition
- None.
// 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
// initiate opening handshake for the distribution token transfer channel
// over the same connection as the CCV channel
// i.e., ChanOpenInit (as required by ICS20)
distributionChannelId = channelKeeper.ChanOpenInit(
UNORDERED, // order
channelKeeper.GetConnectionHops(channelIdentifier), // connectionHops: same as the CCV channel
"transfer", // portIdentifier
"transfer", // counterpartyPortIdentifier
"ics20-1" // version
)
}
- 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.
- The consumer IBC routing module receives a
- Precondition
- True.
- Postcondition
counterpartyVersion
is unmarshaled into aCCVHandshakeMetadata
structuremd
.- 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
. - The distribution token transfer channel opening handshake is initiated and
distributionChannelId
is set to the resulting channel ID.
- Error Condition
- None.
Note: The initialization sub-protocol on the consumer chain finalizes on receiving the first
VSCPacket
and settingproviderChannel
to the ID of the channel on which it receives the packet (seeonRecvVSCPacket
method).
// 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.
- The consumer IBC routing module receives a
- Precondition
- True.
- Postcondition
- The transaction is always aborted; hence, the state is not changed.
- Error Condition
- None.
// PCF: Provider Chain Function
// implements governance proposal Handler
function StopConsumerChainProposalHandler(p: StopConsumerChainProposal) {
if currentTimestamp() >= p.stopTime {
// stop the consumer chain and do not lock the unbonding
StopConsumerChain(p.chainId, false)
}
else {
// store the proposal as a pending stop proposal
pendingStopProposals.Append(p)
}
}
- Caller
EndBlock()
method of Governance module.
- Trigger Event
- A governance proposal
StopConsumerChainProposal
has passed (i.e., it got the necessary votes).
- A governance proposal
- Precondition
- True.
- Postcondition
- If the spawn time has already passed,
StopConsumerChain(p.chainId, false)
is invoked, withp
theStopConsumerChainProposal
. - Otherwise, the proposal is appended to the list of pending stop proposals, i.e.,
pendingStopProposals
.
- If the spawn time has already passed,
- Error Condition
- None.
// 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)
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() {
// notify the Staking module that the unbonding can complete
stakingKeeper.UnbondingCanComplete(id)
// remove unbonding operation
unbondingOps.Remove(id)
}
}
// clean up vscToUnbondingOps mapping
vscToUnbondingOps.Remove((chainId, _))
}
}
- Caller
StopConsumerChainProposalHandler
(see CCV-PCF-STCCPROP.1) orBeginBlockCCR()
(see CCV-PCF-BBLOCK-CCR.1) oronTimeoutVSCPacket()
(see CCV-PCF-TOVSC.1).
- Trigger Event
- Either a governance proposal to stop the consumer chain with
chainId
has passed (i.e., it got the necessary votes) or a packet sent on the CCV channel to the consumer chain withchainId
has timed out.
- Either a governance proposal to stop the consumer chain with
- Precondition
- True.
- Postcondition
- If a client for
p.chainId
does not exist, the state is not changed. - Otherwise,
- the client ID mapped to
chainId
inchainToClient
is removed; - the value mapped to
chainId
inlockUnbondingOnTimeout
is removed; - if the CCV channel to the consumer chain with
chainId
is established, then- the chain ID mapped to
chainToChannel[chainId]
inchannelToChain
is removed; - the channel closing handshake is initiated for the CCV channel;
- the channel ID mapped to
chainId
inchainToChannel
is removed.
- the chain ID mapped to
- all the
VSCPacketData
mapped tochainId
inpendingVSCPackets
are removed; - the height mapped to
chainId
ininitialHeights
is removed; downtimeSlashRequests[chainId]
is emptied;- if
lockUnbonding == false
, thenchainId
is removed from all outstanding unbonding operations;- if an outstanding unbonding operation has matured on all consumer chains,
- the
UnbondingCanComplete()
method of the Staking module is invoked; - the unbonding operation is removed from
unbondingOps
; - all the entries with
chainId
are removed from thevscToUnbondingOps
mapping.
- the client ID mapped to
- If a client for
- Error Condition
- None
Note: Invoking
StopConsumerChain(chainId, lockUnbonding)
withlockUnbonding == FALSE
entails that all outstanding unbonding operations can complete beforeconsumerUnbondingPeriod
elapses on the consumer chain withchainId
. Thus, invokingStopConsumerChain(chainId, false)
for anychainId
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 invokingStopConsumerChain(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).
// PCF: Provider Chain Function
function BeginBlockCCR() {
// iterate over the pending stop proposals
// and stop the consumer chain
foreach p IN pendingStopProposals {
if currentTimestamp() > p.stopTime {
// stop the consumer chain and do not lock the unbonding
StopConsumerChain(p.chainId, false)
pendingStopProposals.Remove(p)
}
}
}
- Caller
- The
BeginBlock()
method.
- The
- Trigger Event
- A
BeginBlock
message is received from the consensus engine;BeginBlock
messages are sent once per block.
- A
- Precondition
- True.
- Postcondition
- For each
StopConsumerChainProposal
p
in the list of pending spawn proposalspendingStopProposals
, ifcurrentTimestamp() > p.stopTime
, thenStopConsumerChain(p.chainId, false)
is invoked;p
is removed frompendingStopProposals
.
- For each
- Error Condition
- None.
// 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.
- The provider IBC routing module receives a
- Precondition
- True.
- Postcondition
- The transaction is always aborted; hence, the state is not changed.
- Error Condition
- None.
// 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.
- The provider IBC routing module receives a
- Precondition
- True.
- Postcondition
- The state is not changed.
- Error Condition
- None.
// 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.
- The
- Trigger Event
- A
BeginBlock
message is received from the consensus engine;BeginBlock
messages are sent once per block.
- A
- 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., theproviderChannel
is unset.
- If the CCV was established, but then was moved to the
- Error Condition
- If the CCV was established, but then was moved to the
CLOSED
state.
- If the CCV was established, but then was moved to the
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.
// 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.
- The consumer IBC routing module receives a
- Precondition
- True.
- Postcondition
- If
providerChannel
is not set orproviderChannel
matches the ID of the channel theChanCloseInit
message was received on, then the transaction is aborted. - The state is not changed.
- If
- Error Condition
- None.
// 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.
- The consumer IBC routing module receives a
- Precondition
- True.
- Postcondition
- The state is not changed.
- Error Condition
- None.
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.
// PCF: Provider Chain Function
function EndBlockVSU() {
// 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
)
}
// remove pending VSCPackets
pendingVSCPackets.Remove(chainId)
}
}
// increment VSC ID
vscId++
}
- Caller
- The
EndBlock()
method.
- The
- Trigger Event
- An
EndBlock
message is received from the consensus engine;EndBlock
messages are sent once per block.
- An
- Precondition
- True.
- Postcondition
- 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
datadata
is created such thatdata.id = vscId
,data.updates = valUpdates
, anddata.downtimeSlashAcks = downtimeSlashRequests[chainId]
; downtimeSlashRequests[chainId]
is emptied;packetData
is appended to the list of pendingVSCPacket
s associated tochainId
, i.e.,pendingVSCPackets[chainId]
.
- a
- If there is an established CCV channel for the the consumer chain with
chainId
, then- for each
VSCPacketData
in the list of pending VSCPackets associated tochainId
- a packet with the
VSCPacketData
is sent on the channel associated with the consumer chain withchainId
;
- a packet with the
- all the pending VSCPackets associated to
chainId
are removed.
- for each
- If either
vscId
is incremented.
- A list of validator updates
- Error Condition
- None.
// 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.
- The
- Trigger Event
- The provider IBC routing module receives an acknowledgement of a
VSCPacket
on a channel owned by the provider CCV module.
- The provider IBC routing module receives an acknowledgement of a
- Precondition
- True.
- Postcondition
- The state is not changed.
- Error Condition
- The acknowledgement is
VSCPacketError
.
- The acknowledgement is
// 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.
- The
- Trigger Event
- A
VSCPacket
sent on a channel owned by the provider CCV module timed out as a result of either
- A
- 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, wherechainId = channelToChain[packet.getDestinationChannel()]
.
- The transaction is aborted if the ID of the channel on which the packet was sent is not mapped to a chain ID (in
- Error Condition
- None
// 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() {
// notify the Staking module that the unbonding can complete
stakingKeeper.UnbondingCanComplete(op.id)
// remove unbonding operation
unbondingOps.Remove(op.id)
}
}
// clean up vscToUnbondingOps mapping
vscToUnbondingOps.Remove((chainId, vscId))
return VSCMaturedPacketSuccess
}
- Caller
- The
onRecvPacket()
method.
- The
- Trigger Event
- The provider IBC routing module receives a
VSCMaturedPacket
on a channel owned by the provider CCV module.
- The provider IBC routing module receives a
- 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 byGetUnbondingsFromVSC(chainId, packet.data.id)
chainId
is removed fromop.unbondingChainIds
;- if
op.unbondingChainIds
is empty,- the
UnbondingCanComplete()
method of the Staking module is invoked; - the entry
op
is removed fromunbondingOps
.
- the
(chainId, vscId)
is removed fromvscToUnbondingOps
.- A successful acknowledgment is returned.
- The transaction is aborted if the channel on which the packet was received is not an established CCV channel (i.e., not in
- Error Condition
- None.
// 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.
- The
- Trigger Event
- The provider IBC routing module receives a
VSCMaturedPacket
on a channel owned by the provider CCV module.
- The provider IBC routing module receives a
- Precondition
- The provider CCV module received a
VSCMaturedPacket
P
from a consumer chain with IDchainId
, such thatP.data.id == _vscId
.
- The provider CCV module received a
- Postcondition
- Return the list of unbonding operations mapped to
(chainId, _vscId)
.
- Return the list of unbonding operations mapped to
- Error Condition
- None.
// 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
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.
- An unbonding operation with id
- 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 tounbondingOps
, such thatop.id = opId
andop.unbondingChainIds = chainIds
. opId
is appended to every list invscToUnbondingOps[(chainId, vscId)]
, wherechainId
is an ID of a consumer chains registered with this provider chain andvscId
is the current VSC ID.- the
PutUnbondingOnHold(opId)
of the Staking module is invoked.
- an
- Error Condition
- None.
// 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;
// close channel and return error acknowledgement
channelKeeper.ChanCloseInit(packet.getDestinationChannel())
return VSCPacketError
}
// set HtoVSC mapping
HtoVSC[getCurrentHeight() + 1] = packet.data.id
// check whether the CCV channel is established
if providerChannel == "" {
// set the channel as the provider channel
providerChannel = packet.getDestinationChannel()
// send pending slash requests
SendPendingSlashRequests()
}
// store the list of updates from the packet
pendingChanges.Append(packet.data.updates)
// calculate and store the maturity timestamp for the VSC
maturityTimestamp = currentTimestamp().Add(consumerUnbondingPeriod)
maturingVSCs.Add(packet.data.id, maturityTimestamp)
// reset outstandingDowntime for validators in packet.data.downtimeSlashAcks
foreach valAddr IN packet.data.downtimeSlashAcks {
outstandingDowntime[valAddr] = FALSE
}
return VSCPacketSuccess
}
- Caller
- The
onRecvPacket()
method.
- The
- Trigger Event
- The consumer IBC routing module receives a
VSCPacket
on a channel owned by the consumer CCV module.
- The consumer IBC routing module receives a
- Precondition
- True.
- Postcondition
- If
providerChannel
is set and does not match the channel (with IDpacket.getDestinationChannel()
) on which the packet was received, then- the closing handshake for the channel with ID
packet.getDestinationChannel()
is initiated; - an error acknowledgement is returned.
- the closing handshake for the channel with ID
- Otherwise,
- the height of the subsequent block is mapped to
packet.data.id
(i.e., theHtoVSC
mapping) ; - if
providerChannel
is not set, then- the CCV channel is marked as established, i.e.,
providerChannel = packet.getDestinationChannel()
; - the pending slash requests are sent to the provider chain (see CCV-CCF-SNDPESLASH.1);
- the CCV channel is marked as established, i.e.,
packet.data.updates
are appended topendingChanges
;(packet.data.id, maturityTimestamp)
is added tomaturingVSCs
, wherematurityTimestamp = currentTimestamp() + consumerUnbondingPeriod
;- for each
valAddr
in the slash acknowledgments received from the provider chain,outstandingDowntime[valAddr]
is set to false; - a successful acknowledgement is returned.
- the height of the subsequent block is mapped to
- If
- Error Condition
- None.
// 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.
- The
- Trigger Event
- The consumer IBC routing module receives an acknowledgement of a
VSCMaturedPacket
on a channel owned by the consumer CCV module.
- The consumer IBC routing module receives an acknowledgement of a
- Precondition
- True.
- Postcondition
- The state is not changed.
- Error Condition
- The acknowledgement is
VSCMaturedPacketError
.
- The acknowledgement is
// 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.
- The
- Trigger Event
- A
VSCMaturedPacket
sent on a channel owned by the consumer CCV module timed out as a result of either
- A
- Precondition
- The Correct Relayer assumption is violated (see the Assumptions section).
- Postcondition
- The state is not changed.
- Error Condition
- None
// CCF: Consumer Chain Function
function EndBlockVSU(): [ValidatorUpdate] {
// unbond mature packets if the CCV channel is established
if providerChannel != "" {
UnbondMaturePackets()
}
if pendingChanges.IsEmpty() {
// do nothing
return []
}
// aggregate the pending changes
changes = pendingChanges.Aggregate()
// Note: in the implementation, the aggregation is done directly
// when receiving a VSCPacket via the AccumulateChanges method.
// remove all pending changes
pendingChanges.RemoveAll()
// update validatorSet
UpdateValidatorSet(changes)
// return the validator set updates
return changes
}
- Caller
- The
EndBlock()
method.
- The
- Trigger Event
- An
EndBlock
message is received from the consensus engine;EndBlock
messages are sent once per block.
- An
- Precondition
- True.
- Postcondition
- If
providerChannel != ""
,UnbondMaturePackets()
is invoked; - If
pendingChanges
is empty, the state is not changed. - Otherwise,
- the pending changes are aggregated and stored in
changes
; pendingChanges
is emptied;UpdateValidatorSet(changes)
is invoked;changes
is returned.
- the pending changes are aggregated and stored in
- If
- Error Condition
- None.
// CCF: Consumer Chain Function
function UpdateValidatorSet(changes: [ValidatorUpdate]) {
foreach update IN changes {
addr := hash(update.pubKey)
if addr NOT IN validatorSet.Keys() {
// new validator bonded;
// note that due pendingChanges.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 CrossChainValidator to validator set
val := CrossChainValidator{
address: addr,
power: update.power
}
validatorSet[addr] = val
// call AfterCCValidatorBonded hook
AfterCCValidatorBonded(addr)
}
}
else if update.power == 0 {
// existing validator begins unbonding
validatorSet.Remove(addr)
// call AfterCCValidatorBeginUnbonding hook
AfterCCValidatorBeginUnbonding(addr)
}
else {
validatorSet[addr].power = update.power
}
}
}
- Caller
- The
EndBlock()
method.
- The
- Trigger Event
- An
EndBlock
message is received from the consensus engine.
- An
- Precondition
changes
contains the aggregated validator updates frompendingChanges
before it was emptied.
- Postcondition
- For each validator
update
inchanges
,- if the validator is not in the validator set and
update.power > 0
, then- a new
CrossChainValidator
is added tovalidatorSet
; - the
AfterCCValidatorBonded
hook is called;
- a new
- otherwise, if the validator's new power is
0
, then,- the validator is removed from
validatorSet
; - the
AfterCCValidatorBeginUnbonding
hook is called;
- the validator is removed from
- otherwise, the validator's power is updated.
- if the validator is not in the validator set and
- For each validator
- Error Condition
- None.
// 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.
- The
- Trigger Event
- An
EndBlock
message is received from the consensus engine.
- An
- Precondition
- The CCV channel to the provider chain is established, i.e.,
providerChannel != ""
.
- The CCV channel to the provider chain is established, i.e.,
- 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 frommaturingVSCs
.
- if
- For each
- Error Condition
- None.
// PCF: Provider Chain Function
function EndBlockCIS() {
// set VSCtoH mapping
VSCtoH[vscId] = getCurrentHeight() + 1
}
- Caller
- The
EndBlock()
method.
- The
- Trigger Event
- An
EndBlock
message is received from the consensus engine;EndBlock
messages are sent once per block.
- An
- Precondition
- True.
- Postcondition
vscId
is mapped to the height of the subsequent block.
- Error Condition
- None.
// 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
channelKeeper.ChanCloseInit(packet.getDestinationChannel())
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.
- The
- Trigger Event
- The provider IBC routing module receives a
SlashPacket
on a channel owned by the provider CCV module.
- The provider IBC routing module receives a
- Precondition
- True.
- Postcondition
- If the channel the packet was received on is not an established CCV channel, then
- the channel closing handshake is initiated;
- an error acknowledgment is returned.
- Otherwise,
- if
packet.data.vscId == 0
,infractionHeight
is set toinitialHeights[chainId]
, withchainId = channelToChain[packet.getDestinationChannel()]
, i.e., the height when the CCV channel to this consumer chain is established; - otherwise,
infractionHeight
is set toVSCtoH[packet.data.vscId]
, i.e., the height at which the voting power was last updated by the validator updates in the VSC with IDpacket.data.vscId
; - a request is made to the Slashing module to slash
slashFactor
of the tokens bonded atinfractionHeight
by the validator with addresspacket.data.valAddress
, whereslashFactor
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 periodjailTime
, wherejailTime
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 thischainId
; - a successful acknowledgment is returned.
- if
- If the channel the packet was received on is not an established CCV channel, then
- Error Condition
- None.
// CCF: Consumer Chain Function
function BeginBlockCIS() {
HtoVSC[getCurrentHeight() + 1] = HtoVSC[getCurrentHeight()]
}
- Caller
- The
BeginBlock()
method.
- The
- Trigger Event
- A
BeginBlock
message is received from the consensus engine;BeginBlock
messages are sent once per block.
- A
- Precondition
- True.
- Postcondition
HtoVSC
for the subsequent block height is set to the same VSC ID as the current block height.
- Error Condition
- None.
// 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.
- The
- Trigger Event
- The consumer IBC routing module receives an acknowledgement of a
SlashPacket
on a channel owned by the consumer CCV module.
- The consumer IBC routing module receives an acknowledgement of a
- Precondition
- True.
- Postcondition
- The state is not changed.
- Error Condition
- The acknowledgement is
SlashPacketError
.
- The acknowledgement is
// 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.
- The
- Trigger Event
- A
SlashPacket
sent on a channel owned by the consumer CCV module timed out as a result of either
- A
- Precondition
- The Correct Relayer assumption is violated (see the Assumptions section).
- Postcondition
- The state is not changed.
- Error Condition
- None
// 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.
- Evidence of misbehavior for a validator with address
- 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
datapacketData
is created, such thatpacketData.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;
- a packet with the
- otherwise
SlashRequest{data: packetData, downtime: downtime}
is appended topendingSlashRequests
.
- a
- Error Condition
- None.
Note: The ABCI application MUST subtract
ValidatorUpdateDelay
from the infraction height before invokingSendSlashRequest
, whereValidatorUpdateDelay
is a delay (in blocks) between when validator updates are returned to the consensus-engine and when they are applied. For example, ifValidatorUpdateDelay = x
and a validator set update is returned with new validators at the end of block10
, then the new validators are expected to sign blocks beginning at block11+x
(for more details, take a look at the ABCI specification).Consequently, the consumer CCV module expects the
infractionHeight
parameter of theSendSlashRequest()
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 withoutstandingDowntime == TRUE
in the evidence for downtime.
// 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
- The
onRecvVSCPacket()
method (see CCV-CCF-RCVVSC.1).
- The
- Trigger Event
- The first
VSCPacket
is received from the provider chain.
- The first
- Precondition
providerChannel != ""
.
- Postcondition
- For each slash request
req
inpendingSlashRequests
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.
- a packet with the data
- All the pending
SlashRequest
s are removed.
- For each slash request
- Error Condition
- None.
Note: Iterating over pending
SlashRequest
s in reverse order ensures that validators that are down for multiple blocks during channel initialization will be slashed for the latest downtime evidence.
// CCF: Consumer Chain Function
function EndBlockRD() {
if getCurrentHeight() - lastDistributionTransferHeight >= BlocksPerDistributionTransfer {
DistributeRewards()
}
}
- Caller
- The
EndBlock()
method.
- The
- Trigger Event
- An
EndBlock
message is received from the consensus engine;EndBlock
messages are sent once per block.
- An
- Precondition
- True.
- Postcondition
- If
getCurrentHeight() - lastDistributionTransferHeight >= BlocksPerDistributionTransfer
, theDistributeRewards()
method is invoked.
- If
- Error Condition
- None.
// 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.
- The
- Trigger Event
- An
EndBlock
message is received from the consensus engine.
- An
- Precondition
getCurrentHeight() - lastDistributionTransferHeight >= BlocksPerDistributionTransfer
- Postcondition
- For each token type defined as a pair
(denomination, amount)
inccvAccount
, a transfer token (as defined in ICS 20) is initiated. lastDistributionTransferHeight
is set to the current height.
- For each token type defined as a pair
- Error Condition
- None.