From e2b640762d366da7cf67ac415517a07e45ac56b2 Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Thu, 17 Aug 2023 15:48:22 +0200
Subject: [PATCH 01/35] Start new attempt on Quint model of ICS
---
.../core/quint_model/extraSpells.qnt | 62 ++++++++
tests/difference/core/quint_model/staking.qnt | 138 ++++++++++++++++++
2 files changed, 200 insertions(+)
create mode 100644 tests/difference/core/quint_model/extraSpells.qnt
create mode 100644 tests/difference/core/quint_model/staking.qnt
diff --git a/tests/difference/core/quint_model/extraSpells.qnt b/tests/difference/core/quint_model/extraSpells.qnt
new file mode 100644
index 0000000000..bdc6fd0bf2
--- /dev/null
+++ b/tests/difference/core/quint_model/extraSpells.qnt
@@ -0,0 +1,62 @@
+module extraSpells {
+
+ /// Removes a set of map entry.
+ ///
+ /// - @param __map a map to remove an entry from
+ /// - @param __keys a set of keys to remove from the map
+ /// - @returns a new map that contains all entries of __map
+ /// that do not have a key in __keys
+ pure def mapRemoveAll(__map: a -> b, __keys: Set[a]): a -> b = {
+ __map.keys().exclude(__keys).mapBy(__k => __map.get(__k))
+ }
+
+ run mapRemoveAllTest =
+ val m = Map(3 -> 4, 5 -> 6, 7 -> 8)
+ all {
+ assert(m.mapRemoveAll(Set(5, 7)) == Map(3 -> 4)),
+ assert(m.mapRemoveAll(Set(5, 99999)) == Map(3 -> 4, 7 -> 8)),
+ }
+
+ pure def listSorted(__list: List[a], __lt: (a, a) => bool): List[a] = {
+ pure def __countSmaller(__j: int): int = {
+ pure val __jth = __list[__j]
+ __list.indices().filter(__i =>
+ __lt(__list[__i], __jth) or (__list[__i] == __jth and __i < __j)
+ )
+ .size()
+ }
+
+ pure val __permutation = __list.indices().mapBy(__i => __countSmaller(__i))
+ __list.foldl([], (__l, __i) => __l.append(__list[__permutation.get(__i)]))
+ }
+
+ //// Returns a list of all elements of a set.
+ ////
+ //// - @param __set a set
+ //// - @returns a list of all elements of __set
+ pure def toList(__set: Set[a]): List[a] = {
+ __set.fold(List(), (__l, __e) => __l.append(__e))
+ }
+
+ //// Returns a set of the elements in the list.
+ ////
+ //// - @param __list a list
+ //// - @returns a set of the elements in __list
+ pure def toSet(__list: List[a]): Set[a] = {
+ __list.foldl(Set(), (__s, __e) => __s.union(Set(__e)))
+ }
+
+ run toListAndSetTest =
+ all {
+ assert(Set(3, 2, 1).toList().toSet() == Set(1, 2, 3)),
+ assert(List(2,3,1).toSet() == Set(1, 2, 3)),
+ assert(List(2,3,1).toSet() == List(3,2,1).toSet()),
+ assert(toList(Set()) == List()),
+ assert(toSet(List()) == Set())
+ }
+
+ pure def add(__set: Set[a], elem: a): Set[a] = {
+ __set.union(Set(elem))
+ }
+
+}
\ No newline at end of file
diff --git a/tests/difference/core/quint_model/staking.qnt b/tests/difference/core/quint_model/staking.qnt
new file mode 100644
index 0000000000..bcb944cd41
--- /dev/null
+++ b/tests/difference/core/quint_model/staking.qnt
@@ -0,0 +1,138 @@
+module ccv {
+ import extraSpells.* from "./extraSpells"
+
+ // Things that are not modelled:
+ // * Reward distribution
+
+ // TYPE DEFINITIONS
+ type Chain = str
+ type Validator = {
+ identifier: str,
+ // for simplciity, voting power is equal to amount of tokens
+ votingPower: int
+ }
+ type Height = int
+ type Timestamp = int
+
+ type Infraction =
+ {
+ validator: Validator,
+ infractionType: str,
+ // slash fraction in percent, i.e. 80 means 80%
+ slashFraction: int,
+ // the height at which the infraction was committed
+ infractionHeight: Height,
+ // the timestamp at which the infraction was committed
+ infractionTime: Timestamp,
+ // the chain on which the infraction was committed
+ originChain: Chain,
+ }
+
+ type Block =
+ {
+ validatorSet: Set[Validator],
+ blockHeight: Height,
+ blockTime: Timestamp,
+ chain: Chain,
+ // Infrations comitted on this chain in this block.
+ comittedInfractions: Set[Infraction],
+ // Evidence received by this chain in this block.
+ receivedEvidence: Set[Infraction] // only used on the provider
+ }
+
+ type Slash =
+ {
+ validator: Validator,
+ slashAmount: int,
+ slashHeight: Height,
+ slashTime: Timestamp,
+ chain: Chain,
+ infraction: Infraction
+ }
+
+
+ // MODEL PARAMETERS
+ const UnbondingPeriod: int
+
+ // MODEL STATE
+
+ // The singular provider chain.
+ var providerChain: Chain
+
+ // The set of (possible) consumer chains that are being tracked.
+ var chains: Set[Chain]
+
+ // Stores, for each chain, the list of blocks over its entire existence.
+ // Each height should occur at most once, and no height should be skipped.
+ // Blocks should be ordered by height in descending order.
+ var blockHistories: Chain -> List[Block]
+
+ // Stores, for each chain, whether it is currently a consumer chain.
+ var consumerStatus: Chain -> bool
+
+ var slashes: Set[Slash]
+
+ // UTILITY FUNCTIONS
+ def getCurrentHeight(chain: Chain): Height = {
+ blockHistories.get(chain).head().blockHeight
+ }
+
+ def getLatestBlock(chain: Chain): Block = {
+ blockHistories.get(chain).head()
+ }
+
+ def isConsumer(chain: Chain): bool = {
+ consumerStatus.get(chain)
+ }
+
+ def wasValidatorSetOnProvider(validatorSet: Set[Validator]): bool = {
+ blockHistories.get(providerChain).toSet().exists(
+ block => block.validatorSet == validatorSet
+ )
+ }
+
+ // PROPERTIES
+
+ // Every validator set on any consumer chain MUST either be or
+ // have been a validator set on the provider chain.
+ val ValidatorSetReplication: bool =
+ chains.filter(chain => chain.isConsumer()).forall(
+ chain => chain.getLatestBlock().validatorSet.wasValidatorSetOnProvider()
+ )
+
+ // TODO: This is extremely convoluted, maybe coming later?
+ val BondBasedConsumerVotingPower: bool =
+ true
+
+ // If a validator val commits an infraction, with a slashing fraction of sf,
+ // on a consumer chain cc at a block height hi,
+ // then any evidence of misbehavior that is received by cc at height he,
+ // such that ts(he) < ts(hi) + UnbondingPeriod, MUST results in exactly the amount of tokens
+ // sf*Token(Power(cc,hi,val)) to be slashed on the provider chain.
+ // Furthermore, val MUST NOT be slashed more than once for the same misbehavior.
+ val SlashableConsumerMisbehaviour: bool =
+ val latestProviderBlock = providerChain.getLatestBlock()
+ latestProviderBlock.receivedEvidence.forall(
+ // for each evidence the provider chain received
+ evidence =>
+ // if the evidence is received before it is outdated
+ (evidence.infractionTime < latestProviderBlock.blockTime - UnbondingPeriod)
+ implies
+ // the provider needs to slash the validator
+ val expectedSlash =
+ {
+ validator: evidence.validator,
+ // the amount depends on the voting power during the infraction
+ slashAmount: evidence.validator.votingPower * evidence.slashFraction,
+ slashHeight: latestProviderBlock.blockHeight,
+ slashTime: latestProviderBlock.blockTime,
+ chain: providerChain,
+ infraction: evidence
+ }
+ slashes.exists(slash => slash == expectedSlash)
+ )
+
+ val ValidatorUpdateInclusion: bool =
+ true
+
+}
\ No newline at end of file
From 283dc0a01ac950c39a06b8f5e797f0ed6bf92ec3 Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Wed, 23 Aug 2023 18:04:29 +0200
Subject: [PATCH 02/35] Advance quint model
---
.../core/quint_model/extraSpells.qnt | 129 +++++++
tests/difference/core/quint_model/model.qnt | 327 ++++++++++++++++++
tests/difference/core/quint_model/staking.qnt | 138 --------
3 files changed, 456 insertions(+), 138 deletions(-)
create mode 100644 tests/difference/core/quint_model/model.qnt
delete mode 100644 tests/difference/core/quint_model/staking.qnt
diff --git a/tests/difference/core/quint_model/extraSpells.qnt b/tests/difference/core/quint_model/extraSpells.qnt
index bdc6fd0bf2..a32a74ad8f 100644
--- a/tests/difference/core/quint_model/extraSpells.qnt
+++ b/tests/difference/core/quint_model/extraSpells.qnt
@@ -1,5 +1,134 @@
module extraSpells {
+ pure def prepend(__list: List[a], __elem: a): List[a] = {
+ List(__elem).concat(__list)
+ }
+
+ run prependTest = all {
+ assert(List(2,3,4).prepend(1) == List(1,2,3,4)),
+ assert(List().prepend(1) == List(1)),
+ }
+
+ /// An annotation for writing preconditions.
+ /// - @param __cond condition to check
+ /// - @returns true if and only if __cond evaluates to true
+ pure def require(__cond: bool): bool = __cond
+
+ run requireTest = all {
+ assert(require(4 > 3)),
+ assert(not(require(false))),
+ }
+
+ /// A convenience operator that returns a string error code,
+ /// if the condition does not hold true.
+ ///
+ /// - @param __cond condition to check
+ /// - @param __error a non-empty error message
+ /// - @returns "", when __cond holds true; otherwise __error
+ pure def requires(__cond: bool, __error: str): str = {
+ if (__cond) "" else __error
+ }
+
+ run requiresTest = all {
+ assert(requires(4 > 3, "4 > 3") == ""),
+ assert(requires(4 < 3, "false: 4 < 3") == "false: 4 < 3"),
+ }
+
+
+ /// Compute the maximum of two integers.
+ ///
+ /// - @param __i first integer
+ /// - @param __j second integer
+ /// - @returns the maximum of __i and __j
+ pure def max(__i: int, __j: int): int = {
+ if (__i > __j) __i else __j
+ }
+
+ run maxTest = all {
+ assert(max(3, 4) == 4),
+ assert(max(6, 3) == 6),
+ assert(max(10, 10) == 10),
+ assert(max(-3, -5) == -3),
+ assert(max(-5, -3) == -3),
+ }
+
+ /// Compute the absolute value of an integer
+ ///
+ /// - @param __i : an integer whose absolute value we are interested in
+ /// - @returns |__i|, the absolute value of __i
+ pure def abs(__i: int): int = {
+ if (__i < 0) -__i else __i
+ }
+
+ run absTest = all {
+ assert(abs(3) == 3),
+ assert(abs(-3) == 3),
+ assert(abs(0) == 0),
+ }
+
+ /// Remove a set element.
+ ///
+ /// - @param __set a set to remove an element from
+ /// - @param __elem an element to remove
+ /// - @returns a new set that contains all elements of __set but __elem
+ pure def setRemove(__set: Set[a], __elem: a): Set[a] = {
+ __set.exclude(Set(__elem))
+ }
+
+ run setRemoveTest = all {
+ assert(Set(2, 4) == Set(2, 3, 4).setRemove(3)),
+ assert(Set() == Set().setRemove(3)),
+ }
+
+ /// Test whether a key is present in a map
+ ///
+ /// - @param __map a map to query
+ /// - @param __key the key to look for
+ /// - @returns true if and only __map has an entry associated with __key
+ pure def has(__map: a -> b, __key: a): bool = {
+ __map.keys().contains(__key)
+ }
+
+ run hasTest = all {
+ assert(Map(2 -> 3, 4 -> 5).has(2)),
+ assert(not(Map(2 -> 3, 4 -> 5).has(6))),
+ }
+
+ /// Get the map value associated with a key, or the default,
+ /// if the key is not present.
+ ///
+ /// - @param __map the map to query
+ /// - @param __key the key to search for
+ /// - @returns the value associated with the key, if __key is
+ /// present in the map, and __default otherwise
+ pure def getOrElse(__map: a -> b, __key: a, __default: b): b = {
+ if (__map.has(__key)) {
+ __map.get(__key)
+ } else {
+ __default
+ }
+ }
+
+ run getOrElseTest = all {
+ assert(Map(2 -> 3, 4 -> 5).getOrElse(2, 0) == 3),
+ assert(Map(2 -> 3, 4 -> 5).getOrElse(7, 11) == 11),
+ }
+
+ /// Remove a map entry.
+ ///
+ /// - @param __map a map to remove an entry from
+ /// - @param __key the key of an entry to remove
+ /// - @returns a new map that contains all entries of __map
+ /// that do not have the key __key
+ pure def mapRemove(__map: a -> b, __key: a): a -> b = {
+ __map.keys().setRemove(__key).mapBy(__k => __map.get(__k))
+ }
+
+ run mapRemoveTest = all {
+ assert(Map(3 -> 4, 7 -> 8) == Map(3 -> 4, 5 -> 6, 7 -> 8).mapRemove(5)),
+ assert(Map() == Map().mapRemove(3)),
+ }
+
/// Removes a set of map entry.
///
/// - @param __map a map to remove an entry from
diff --git a/tests/difference/core/quint_model/model.qnt b/tests/difference/core/quint_model/model.qnt
new file mode 100644
index 0000000000..6c78f6fb64
--- /dev/null
+++ b/tests/difference/core/quint_model/model.qnt
@@ -0,0 +1,327 @@
+module ccv {
+ import extraSpells.* from "./extraSpells"
+
+ // Things that are not modelled:
+ // * Reward distribution
+ // * Starting/Stopping chains during execution
+ // * Slashes
+
+ // TYPE DEFINITIONS
+ type Node = str
+ type Chain = str
+ type ValidatorSet = Node -> int
+ type Height = int
+ type Timestamp = int
+
+ // --PACKETS
+ type VSCPacket =
+ {
+ // the identifier for this packet
+ id: int,
+ // the new validator set. in the implementation, this would be a list of validator updates
+ validatorSet: ValidatorSet
+ }
+
+ type VSCMaturedPacket =
+ {
+ // the id of the VSCPacket that matured
+ id: int
+ }
+
+ // MODEL PARAMETERS
+ // length of the unbonding period on each chain
+ const UnbondingPeriod: Chain -> int
+
+ // set of identifiers of potential nodes
+ const Nodes: Set[Node]
+
+ // the set of consumer chains
+ const ConsumerChains: Set[Chain]
+
+ // The singular provider chain.
+ const ProviderChain: Chain
+
+ pure val chains = ConsumerChains.union(Set(ProviderChain))
+
+ // the time until a packet times out
+ const PacketTimeout: int
+
+ // the time until a consumer chain is dropped by the provider due to inactivity
+ const InactivityTimeout: int
+
+
+ // MODEL STATE
+ // --SHARED STATE
+
+ // Stores, for each chain, the list of voting powers that corresponded to voting powers
+ // at blocks over its entire existence.
+ // Voting powers should be ordered by recency in descending order.
+ var votingPowerHistories: Chain -> List[ValidatorSet]
+
+ // the current validator set on each chain.
+ // this will be included in the next block, but might not be final yet,
+ // e.g. there may be more modifications in the current block.
+ var runningValidatorSet: Chain -> ValidatorSet
+
+ // the current timestamp for each chain
+ var curChainTimes: Chain -> Timestamp
+
+ // stores, for each chain, its current status -
+ // unused, running, or stopped
+ var consumerStatus: Chain -> str
+
+ // --CHANNELS
+ // Stores, for each consumer chain, the list of packets that have been sent to the provider chain
+ // and have not been received yet.
+ var outstandingPacketsToProvider: Chain -> List[VSCMaturedPacket]
+
+ // Stores, for each consumer chain, the list of packets that have been sent to the consumer chain
+ // and have not been received yet.
+ var outstandingPacketsToConsumer: Chain -> List[VSCPacket]
+
+
+ // --CONSUMER STATE
+ // Stores the maturation times for VSCPackets received by consumers
+ var maturationTimes: Chain -> (VSCPacket -> Timestamp)
+
+ // --PROVIDER STATE
+ // the set of VSCMaturedPackets received by the provider chain
+ var receivedMaturations: Set[VSCMaturedPacket]
+
+
+ // UTILITY FUNCTIONS & ACTIONS
+ def wasValidatorSetOnProvider(validatorSet: ValidatorSet): bool = {
+ votingPowerHistories.get(ProviderChain).toSet().exists(
+ historicalValSet => historicalValSet == validatorSet
+ )
+ }
+
+ def getCurrentValidatorSet(chain: Chain): ValidatorSet =
+ votingPowerHistories.get(chain).head()
+
+ def getUpdatedValidatorSet(oldValidatorSet: ValidatorSet, validator: Node, newVotingPower: int): ValidatorSet =
+ if (newVotingPower > 0)
+ oldValidatorSet.set(validator, newVotingPower)
+ else
+ oldValidatorSet.mapRemove(validator)
+
+ // utility action that leaves all provider state untouched
+ action PROVIDER_NOOP(): bool =
+ all {
+ receivedMaturations' = receivedMaturations,
+ }
+
+ // utility action that leaves all consumer state untouched
+ action CONSUMER_NOOP(): bool =
+ all {
+ maturationTimes' = maturationTimes,
+ }
+
+
+ // MODEL ACTIONS
+
+ // the power of a validator on the provider chain is changed to the given amount. We do not care how this happens,
+ // e.g. via undelegations, or delegations, ...
+ action votingPowerChange(validator: Node, amount: int): bool =
+ // for the provider chain, we need to adjust the voting power history
+ // by adding a new set
+ all {
+ amount >= 0,
+ val newValidatorSet = getCurrentValidatorSet(ProviderChain).getUpdatedValidatorSet(validator, amount)
+ // set the running validator set on the provider chain, but don't update the history yet
+ runningValidatorSet' = runningValidatorSet.set(ProviderChain, newValidatorSet),
+ // no packets are sent yet, these are only sent on endAndBeginBlock
+ outstandingPacketsToConsumer' = outstandingPacketsToConsumer,
+ outstandingPacketsToProvider' = outstandingPacketsToProvider,
+ receivedMaturations' = receivedMaturations,
+ CONSUMER_NOOP,
+ // voting power history is only updated on endAndBeginBlock
+ votingPowerHistories' = votingPowerHistories,
+ }
+
+ // deliver the next outstanding packet from the consumer to the provider.
+ // since this model assumes a single provider chain, this just takes a single chain as argument.
+ action recvPacketOnProvider(consumer: Chain): bool = all {
+ // ensure there is a packet to be received
+ outstandingPacketsToProvider.get(consumer).length() > 0,
+ // remove packet from outstanding packets
+ val newPacketQueue = outstandingPacketsToProvider.get(consumer).tail()
+ outstandingPacketsToProvider' = outstandingPacketsToProvider.set(consumer, newPacketQueue),
+ // register the packet as received
+ val maturedPacket = outstandingPacketsToProvider.get(consumer).head()
+ receivedMaturations' = receivedMaturations.add(maturedPacket),
+ CONSUMER_NOOP,
+ outstandingPacketsToConsumer' = outstandingPacketsToConsumer,
+ votingPowerHistories' = votingPowerHistories,
+ // no validator set changes are made
+ runningValidatorSet' = runningValidatorSet,
+ }
+
+ // deliver the next outstanding packet from the provider to the consumer.
+ // since this model assumes a single provider chain, this just takes a single chain as argument.
+ action recvPacketOnConsumer(consumer: Chain): bool = all {
+ // ensure there is a packet to be received
+ outstandingPacketsToConsumer.get(consumer).length() > 0,
+ // remove packet from outstanding packets
+ val newPacketQueue = outstandingPacketsToConsumer.get(consumer).tail()
+ outstandingPacketsToConsumer' = outstandingPacketsToConsumer.set(consumer, newPacketQueue),
+ val packet = outstandingPacketsToConsumer.get(consumer).head()
+ all {
+ // update the running validator set, but not the history yet,
+ // as that only happens when the next block is started
+ runningValidatorSet' = runningValidatorSet.set(consumer, packet.validatorSet),
+ // add the new packet and store its maturation time
+ val newMaturationTimes = maturationTimes.get(consumer).set(packet, curChainTimes.get(consumer) + UnbondingPeriod.get(consumer))
+ maturationTimes' = maturationTimes.set(consumer, newMaturationTimes)
+ },
+ PROVIDER_NOOP,
+ votingPowerHistories' = votingPowerHistories,
+ outstandingPacketsToProvider' = outstandingPacketsToProvider,
+ }
+
+ // ends the current block and starts the next block for a given chain.
+ action endAndBeginBlock(chain: Chain): bool = any {
+ all {
+ chain == ProviderChain,
+ endAndBeginBlockForProvider,
+ },
+ all {
+ chain != ProviderChain,
+ endAndBeginBlockForConsumer(chain),
+ }
+ }
+
+ // gets the updated history for the current chain when ending a block, i.e. the
+ // running validator set is added to the history if different from the last one.
+ def getUpdatedHistory(chain: Chain): List[ValidatorSet] =
+ // update voting power history if the validator set changed
+ val newValidatorSet = runningValidatorSet.get(ProviderChain)
+ val oldValidatorSet = votingPowerHistories.get(ProviderChain).head()
+ if (newValidatorSet != oldValidatorSet)
+ votingPowerHistories.get(ProviderChain).prepend(newValidatorSet)
+ else
+ votingPowerHistories.get(ProviderChain)
+
+
+ action endAndBeginBlockForProvider(): bool = all {
+ // update the voting power history
+ votingPowerHistories' = votingPowerHistories.set(ProviderChain, getUpdatedHistory(ProviderChain)),
+ // the running validator set is now for sure the current validator set,
+ // so start with it in the next block
+ runningValidatorSet' = runningValidatorSet,
+ // send VSCPackets to consumers
+ outstandingPacketsToConsumer' =
+ // if running validator set is not equal to the last one in the history
+ if (runningValidatorSet.get(ProviderChain) != votingPowerHistories.get(ProviderChain).head())
+ // then send a packet to each consumer
+ outstandingPacketsToConsumer.keys().mapBy(
+ (consumer) =>
+ val packetQueue = outstandingPacketsToConsumer.get(consumer)
+ packetQueue.append(
+ {
+ id: packetQueue.length(),
+ validatorSet: runningValidatorSet.get(ProviderChain)
+ }
+ )
+ )
+ else
+ // otherwise, don't send any packets
+ outstandingPacketsToConsumer,
+ CONSUMER_NOOP,
+ // no packets are sent to the provider
+ outstandingPacketsToProvider' = outstandingPacketsToProvider,
+ // do not receive any maturations
+ receivedMaturations' = receivedMaturations,
+ }
+
+ action endAndBeginBlockForConsumer(consumer: Chain): bool = all {
+ ConsumerChains.contains(consumer),
+ // update the voting power history
+ votingPowerHistories' = votingPowerHistories.set(consumer, getUpdatedHistory(consumer)),
+ // the running validator set is now for sure the current validator set,
+ // so start with it in the next block
+ runningValidatorSet' = runningValidatorSet,
+ // compute mature packets whose maturation time has passed
+ val maturedPackets = maturationTimes.get(consumer).keys().filter(
+ packet =>
+ val maturationTime = maturationTimes.get(consumer).get(packet)
+ maturationTime <= curChainTimes.get(consumer)
+ )
+ all {
+ // remove matured packets from the maturation times
+ maturationTimes' = maturationTimes.set(consumer, maturationTimes.get(consumer).mapRemoveAll(maturedPackets)),
+ // send matured packets
+ outstandingPacketsToProvider' = outstandingPacketsToProvider.set(
+ consumer,
+ // construct VSCMaturedPackets from the matured VSCPackets
+ outstandingPacketsToProvider.get(consumer).concat(
+ maturedPackets.map(packet => {id: packet.id}).toList()
+ )
+ )
+ },
+ PROVIDER_NOOP,
+ // no packets are sent to consumer or received by it
+ outstandingPacketsToConsumer' = outstandingPacketsToConsumer,
+ }
+
+ // the timestamp for each chain is advanced nondeterministically
+ action advanceTimestamps(): bool =
+ curChainTimes' = curChainTimes.keys().mapBy(
+ (chain) =>
+ nondet advancement = oneOf(0.to(100))
+ curChainTimes.get(chain) + advancement
+ )
+
+ // each consumer chain may nondeterministically advance in the order
+ // unused -> initializing -> running -> stopped
+ // some events may necessitate a transition, e.g. timeouts
+ action AdvanceConsumers: bool =
+ consumerStatus' = consumerStatus.keys().mapBy(
+ (chain) =>
+ val curStatus = consumerStatus.get(chain)
+ // if the current status is unused, it may transition to running
+ if (curStatus == "unused") {
+ nondet transition = oneOf(0.to(1))
+ if (transition == 0)
+ {
+ "running"
+ } else {
+ "unused"
+ }
+ }
+ else if (curStatus == "running") {
+ // the chain may transition to stopped.
+ // it is *forced* to stop if a packet timed out,
+ // or if the inactivity timeout has passed
+ } else {
+ "unused"
+ }
+
+ // if the current status is initializing, it may transition to running
+ )
+
+
+ action step: bool =
+ all {
+ // timestamps can be advanced in each step
+ advanceTimestamps,
+ any {
+ nondet node = oneOf(Nodes)
+ nondet amount = oneOf(1.to(10))
+ votingPowerChange(node, amount),
+ recvPacketOnProvider(oneOf(ConsumerChains)),
+ recvPacketOnConsumer(oneOf(ConsumerChains)),
+ nondet chain = oneOf(chains)
+ endAndBeginBlock(chain),
+ }
+ }
+
+ // PROPERTIES
+
+ // Every validator set on any consumer chain MUST either be or
+ // have been a validator set on the provider chain.
+ val ValidatorSetReplication: bool =
+ chains.forall(
+ chain => chain.getCurrentValidatorSet().wasValidatorSetOnProvider()
+ )
+}
\ No newline at end of file
diff --git a/tests/difference/core/quint_model/staking.qnt b/tests/difference/core/quint_model/staking.qnt
deleted file mode 100644
index bcb944cd41..0000000000
--- a/tests/difference/core/quint_model/staking.qnt
+++ /dev/null
@@ -1,138 +0,0 @@
-module ccv {
- import extraSpells.* from "./extraSpells"
-
- // Things that are not modelled:
- // * Reward distribution
-
- // TYPE DEFINITIONS
- type Chain = str
- type Validator = {
- identifier: str,
- // for simplciity, voting power is equal to amount of tokens
- votingPower: int
- }
- type Height = int
- type Timestamp = int
-
- type Infraction =
- {
- validator: Validator,
- infractionType: str,
- // slash fraction in percent, i.e. 80 means 80%
- slashFraction: int,
- // the height at which the infraction was committed
- infractionHeight: Height,
- // the timestamp at which the infraction was committed
- infractionTime: Timestamp,
- // the chain on which the infraction was committed
- originChain: Chain,
- }
-
- type Block =
- {
- validatorSet: Set[Validator],
- blockHeight: Height,
- blockTime: Timestamp,
- chain: Chain,
- // Infrations comitted on this chain in this block.
- comittedInfractions: Set[Infraction],
- // Evidence received by this chain in this block.
- receivedEvidence: Set[Infraction] // only used on the provider
- }
-
- type Slash =
- {
- validator: Validator,
- slashAmount: int,
- slashHeight: Height,
- slashTime: Timestamp,
- chain: Chain,
- infraction: Infraction
- }
-
-
- // MODEL PARAMETERS
- const UnbondingPeriod: int
-
- // MODEL STATE
-
- // The singular provider chain.
- var providerChain: Chain
-
- // The set of (possible) consumer chains that are being tracked.
- var chains: Set[Chain]
-
- // Stores, for each chain, the list of blocks over its entire existence.
- // Each height should occur at most once, and no height should be skipped.
- // Blocks should be ordered by height in descending order.
- var blockHistories: Chain -> List[Block]
-
- // Stores, for each chain, whether it is currently a consumer chain.
- var consumerStatus: Chain -> bool
-
- var slashes: Set[Slash]
-
- // UTILITY FUNCTIONS
- def getCurrentHeight(chain: Chain): Height = {
- blockHistories.get(chain).head().blockHeight
- }
-
- def getLatestBlock(chain: Chain): Block = {
- blockHistories.get(chain).head()
- }
-
- def isConsumer(chain: Chain): bool = {
- consumerStatus.get(chain)
- }
-
- def wasValidatorSetOnProvider(validatorSet: Set[Validator]): bool = {
- blockHistories.get(providerChain).toSet().exists(
- block => block.validatorSet == validatorSet
- )
- }
-
- // PROPERTIES
-
- // Every validator set on any consumer chain MUST either be or
- // have been a validator set on the provider chain.
- val ValidatorSetReplication: bool =
- chains.filter(chain => chain.isConsumer()).forall(
- chain => chain.getLatestBlock().validatorSet.wasValidatorSetOnProvider()
- )
-
- // TODO: This is extremely convoluted, maybe coming later?
- val BondBasedConsumerVotingPower: bool =
- true
-
- // If a validator val commits an infraction, with a slashing fraction of sf,
- // on a consumer chain cc at a block height hi,
- // then any evidence of misbehavior that is received by cc at height he,
- // such that ts(he) < ts(hi) + UnbondingPeriod, MUST results in exactly the amount of tokens
- // sf*Token(Power(cc,hi,val)) to be slashed on the provider chain.
- // Furthermore, val MUST NOT be slashed more than once for the same misbehavior.
- val SlashableConsumerMisbehaviour: bool =
- val latestProviderBlock = providerChain.getLatestBlock()
- latestProviderBlock.receivedEvidence.forall(
- // for each evidence the provider chain received
- evidence =>
- // if the evidence is received before it is outdated
- (evidence.infractionTime < latestProviderBlock.blockTime - UnbondingPeriod)
- implies
- // the provider needs to slash the validator
- val expectedSlash =
- {
- validator: evidence.validator,
- // the amount depends on the voting power during the infraction
- slashAmount: evidence.validator.votingPower * evidence.slashFraction,
- slashHeight: latestProviderBlock.blockHeight,
- slashTime: latestProviderBlock.blockTime,
- chain: providerChain,
- infraction: evidence
- }
- slashes.exists(slash => slash == expectedSlash)
- )
-
- val ValidatorUpdateInclusion: bool =
- true
-
-}
\ No newline at end of file
From 2155a4f15e5d89032b6d9807c5a61049b57d94c0 Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Thu, 24 Aug 2023 13:17:19 +0200
Subject: [PATCH 03/35] Add first finished draft of model
---
tests/difference/core/quint_model/model.qnt | 180 ++++++++++++++++----
1 file changed, 146 insertions(+), 34 deletions(-)
diff --git a/tests/difference/core/quint_model/model.qnt b/tests/difference/core/quint_model/model.qnt
index 6c78f6fb64..b89f816b9d 100644
--- a/tests/difference/core/quint_model/model.qnt
+++ b/tests/difference/core/quint_model/model.qnt
@@ -19,36 +19,54 @@ module ccv {
// the identifier for this packet
id: int,
// the new validator set. in the implementation, this would be a list of validator updates
- validatorSet: ValidatorSet
+ validatorSet: ValidatorSet,
+ // the time, that when passed on the receiver chain, will mean the packet is considered timed out
+ timeout: Timestamp
}
type VSCMaturedPacket =
{
// the id of the VSCPacket that matured
- id: int
+ id: int,
+ // the time, that when passed on the receiver chain, will mean the packet is considered timed out
+ timeout: Timestamp
}
// MODEL PARAMETERS
- // length of the unbonding period on each chain
- const UnbondingPeriod: Chain -> int
+
// set of identifiers of potential nodes
- const Nodes: Set[Node]
+ pure val Nodes: Set[Node] =
+ Set("A", "B", "C", "D", "E", "F", "G", "H", "I", "J")
// the set of consumer chains
- const ConsumerChains: Set[Chain]
+ pure val ConsumerChains: Set[Chain] =
+ Set("chain1", "chain2", "chain3")
// The singular provider chain.
- const ProviderChain: Chain
-
+ pure val ProviderChain: Chain =
+ "provider"
+
pure val chains = ConsumerChains.union(Set(ProviderChain))
+ // length of the unbonding period on each chain
+ pure val UnbondingPeriod: Chain -> int = chains.mapBy(
+ (chain) =>
+ 10
+ )
+
// the time until a packet times out
- const PacketTimeout: int
+ pure val PacketTimeout: int =
+ 5
// the time until a consumer chain is dropped by the provider due to inactivity
- const InactivityTimeout: int
+ pure val InactivityTimeout: int =
+ 10
+ // consumer statuses
+ pure val STOPPED = "stopped"
+ pure val RUNNING = "running"
+ pure val UNUSED = "unused"
// MODEL STATE
// --SHARED STATE
@@ -88,6 +106,9 @@ module ccv {
// the set of VSCMaturedPackets received by the provider chain
var receivedMaturations: Set[VSCMaturedPacket]
+ // stores which VSC Packets have been sent to compare with receivedMaturations to detect timeouts due to non-responsiveness
+ var sentVSCPackets: Chain -> Set[VSCPacket]
+
// UTILITY FUNCTIONS & ACTIONS
def wasValidatorSetOnProvider(validatorSet: ValidatorSet): bool = {
@@ -105,6 +126,34 @@ module ccv {
else
oldValidatorSet.mapRemove(validator)
+ // returns true if the consumer has timed out and should be dropped
+ def consumerTimedOut(consumer: Chain): bool =
+ any {
+ // either a package from provider to consumer has timed out
+ outstandingPacketsToConsumer.get(consumer).select(
+ packet => packet.timeout <= curChainTimes.get(consumer)
+ ).length() > 0,
+ // or a package from consumer to provider has timed out
+ outstandingPacketsToProvider.get(consumer).select(
+ packet => packet.timeout <= curChainTimes.get(ProviderChain)
+ ).length() > 0,
+ // or the inactivity timeout has passed since a VSCPacket was sent to the consumer, but the
+ // provider has not received a VSCMaturedPacket for it
+ val packetsWithoutResponse = sentVSCPackets.get(consumer).filter( // get packets without response
+ packet =>
+ not(receivedMaturations.exists(
+ maturedPacket => maturedPacket.id == packet.id
+ ))
+ )
+ // among those, get packets where inactivity timeout has passed
+ packetsWithoutResponse.filter(
+ packet =>
+ val sentAt = curChainTimes.get(ProviderChain) - PacketTimeout // compute when the packet was sent
+ val timesOutAt = sentAt + InactivityTimeout // compute when the packet times out
+ timesOutAt <= curChainTimes.get(ProviderChain)
+ ).size() > 0
+ }
+
// utility action that leaves all provider state untouched
action PROVIDER_NOOP(): bool =
all {
@@ -213,14 +262,15 @@ module ccv {
outstandingPacketsToConsumer' =
// if running validator set is not equal to the last one in the history
if (runningValidatorSet.get(ProviderChain) != votingPowerHistories.get(ProviderChain).head())
- // then send a packet to each consumer
- outstandingPacketsToConsumer.keys().mapBy(
+ // then send a packet to each running consumer
+ outstandingPacketsToConsumer.keys().filter(consumer => consumerStatus.get(consumer) == RUNNING).mapBy(
(consumer) =>
val packetQueue = outstandingPacketsToConsumer.get(consumer)
packetQueue.append(
{
id: packetQueue.length(),
- validatorSet: runningValidatorSet.get(ProviderChain)
+ validatorSet: runningValidatorSet.get(ProviderChain),
+ timeout: curChainTimes.get(ProviderChain) + PacketTimeout
}
)
)
@@ -255,7 +305,7 @@ module ccv {
consumer,
// construct VSCMaturedPackets from the matured VSCPackets
outstandingPacketsToProvider.get(consumer).concat(
- maturedPackets.map(packet => {id: packet.id}).toList()
+ maturedPackets.map(packet => {id: packet.id, timeout: 5}).toList()
)
)
},
@@ -265,46 +315,59 @@ module ccv {
}
// the timestamp for each chain is advanced nondeterministically
- action advanceTimestamps(): bool =
+ action AdvanceTimestamps(): bool =
curChainTimes' = curChainTimes.keys().mapBy(
(chain) =>
nondet advancement = oneOf(0.to(100))
curChainTimes.get(chain) + advancement
- )
+ )
- // each consumer chain may nondeterministically advance in the order
- // unused -> initializing -> running -> stopped
- // some events may necessitate a transition, e.g. timeouts
- action AdvanceConsumers: bool =
+ // each consumer chain may advance in the order
+ // some events may necessitate a transition, e.g. timeouts.
+ // shouldAdvance gives, for each consumer chain, whether it should advance if possible.
+ // if a chain has to advance, e.g. due to timeouts, or may not advance, the value will have no effect.
+ action AdvanceConsumers(shouldAdvance: Chain -> bool): bool =
consumerStatus' = consumerStatus.keys().mapBy(
- (chain) =>
+ chain =>
val curStatus = consumerStatus.get(chain)
- // if the current status is unused, it may transition to running
- if (curStatus == "unused") {
- nondet transition = oneOf(0.to(1))
- if (transition == 0)
+ if (curStatus == UNUSED) {
+ if (shouldAdvance.get(chain))
{
- "running"
+ RUNNING
} else {
- "unused"
+ UNUSED
}
}
- else if (curStatus == "running") {
+ else if (curStatus == RUNNING) {
// the chain may transition to stopped.
// it is *forced* to stop if a packet timed out,
// or if the inactivity timeout has passed
+ if(consumerTimedOut(chain)) {
+ STOPPED
+ } else {
+ if (shouldAdvance.get(chain)) {
+ RUNNING
+ } else {
+ STOPPED
+ }
+ }
} else {
- "unused"
+ // stopped chains cannot restart, we assume a new chain would be started in that case
+ STOPPED
}
-
- // if the current status is initializing, it may transition to running
)
+ // stores the VSCPackets sent in this step in sentVSCPackets
+ action StoreSentPackets: bool =
+ sentVSCPackets' = sentVSCPackets.keys().mapBy(
+ (chain) =>
+ sentVSCPackets.get(chain).union(outstandingPacketsToConsumer.get(chain).toSet())
+ )
action step: bool =
- all {
+ all {
// timestamps can be advanced in each step
- advanceTimestamps,
+ AdvanceTimestamps,
any {
nondet node = oneOf(Nodes)
nondet amount = oneOf(1.to(10))
@@ -313,7 +376,56 @@ module ccv {
recvPacketOnConsumer(oneOf(ConsumerChains)),
nondet chain = oneOf(chains)
endAndBeginBlock(chain),
- }
+ },
+ StoreSentPackets,
+ val shouldAdvance = ConsumerChains.mapBy(
+ chain =>
+ nondet should = oneOf(Set(true, false))
+ should
+ )
+ AdvanceConsumers(shouldAdvance),
+ }
+
+
+ def getArbitraryValidatorSet(): ValidatorSet =
+ val numValidators = oneOf(1.to(Nodes.size()))
+ // toList has nondeterministic behaviour, so this gets arbitrary validators
+ val validators = Nodes.toList().slice(0, numValidators).toSet()
+ validators.mapBy(
+ validator =>
+ nondet votingPower = oneOf(1.to(10))
+ votingPower
+ )
+
+ action init: bool =
+ all {
+ // voting power histories are empty
+ votingPowerHistories' =
+ chains.mapBy(
+ (chain) =>
+ List()
+ ),
+ // provider chain gets an arbitrary validator set, consumer chains have none
+ runningValidatorSet' =
+ chains.mapBy(
+ (chain) =>
+ if (chain == ProviderChain) getArbitraryValidatorSet else Map()
+ ),
+ // each chain starts at time 0
+ curChainTimes' = chains.mapBy(
+ (chain) => 0
+ ),
+ // all consumer chains are unused
+ consumerStatus' = chains.mapBy(chain => UNUSED),
+ // no packets are outstanding
+ outstandingPacketsToProvider' = chains.mapBy(chain => List()),
+ outstandingPacketsToConsumer' = chains.mapBy(chain => List()),
+ // no maturations have been received by provider
+ receivedMaturations' = Set(),
+ // no packets have been sent to consumers
+ sentVSCPackets' = chains.mapBy(chain => Set()),
+ // no packets have been received by consumers, so no maturation times set
+ maturationTimes' = chains.mapBy(chain => Map()),
}
// PROPERTIES
From 6ca65105e8b044c03b9d0006ed212009239512af Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Fri, 25 Aug 2023 09:53:47 +0200
Subject: [PATCH 04/35] Add test run to model
---
tests/difference/core/quint_model/model.qnt | 149 ++++++++++++++++----
1 file changed, 125 insertions(+), 24 deletions(-)
diff --git a/tests/difference/core/quint_model/model.qnt b/tests/difference/core/quint_model/model.qnt
index b89f816b9d..aa3378bca6 100644
--- a/tests/difference/core/quint_model/model.qnt
+++ b/tests/difference/core/quint_model/model.qnt
@@ -68,6 +68,23 @@ module ccv {
pure val RUNNING = "running"
pure val UNUSED = "unused"
+ // utility: a map assigning each chain to 0, used for not advancing timestamps
+ pure val NoTimeAdvancement: Chain -> int = chains.mapBy(
+ (chain) =>
+ 0
+ )
+
+
+ // utility: a map assigning each chain to false, used for not advancing consumer status
+ pure val NoStatusAdvancement: Chain -> bool = chains.mapBy(
+ (chain) =>
+ false
+ )
+
+ // utility: the set of consumers currently running
+ val RunningConsumers: Set[Chain] =
+ ConsumerChains.filter(chain => consumerStatus.get(chain) == RUNNING)
+
// MODEL STATE
// --SHARED STATE
@@ -186,6 +203,12 @@ module ccv {
CONSUMER_NOOP,
// voting power history is only updated on endAndBeginBlock
votingPowerHistories' = votingPowerHistories,
+ // consumer statusses do not change
+ consumerStatus' = consumerStatus,
+ // chain times do not change
+ curChainTimes' = curChainTimes,
+ // update the packet store on the provider
+ StoreSentPackets,
}
// deliver the next outstanding packet from the consumer to the provider.
@@ -204,6 +227,12 @@ module ccv {
votingPowerHistories' = votingPowerHistories,
// no validator set changes are made
runningValidatorSet' = runningValidatorSet,
+ // consumer statusses do not change
+ consumerStatus' = consumerStatus,
+ // chain times do not change
+ curChainTimes' = curChainTimes,
+ // update the packet store on the provider
+ StoreSentPackets,
}
// deliver the next outstanding packet from the provider to the consumer.
@@ -226,6 +255,12 @@ module ccv {
PROVIDER_NOOP,
votingPowerHistories' = votingPowerHistories,
outstandingPacketsToProvider' = outstandingPacketsToProvider,
+ // consumer statusses do not change
+ consumerStatus' = consumerStatus,
+ // chain times do not change
+ curChainTimes' = curChainTimes,
+ // update the packet store on the provider
+ StoreSentPackets,
}
// ends the current block and starts the next block for a given chain.
@@ -282,6 +317,12 @@ module ccv {
outstandingPacketsToProvider' = outstandingPacketsToProvider,
// do not receive any maturations
receivedMaturations' = receivedMaturations,
+ // consumer statusses do not change
+ consumerStatus' = consumerStatus,
+ // chain times do not change
+ curChainTimes' = curChainTimes,
+ // update the packet store on the provider
+ StoreSentPackets,
}
action endAndBeginBlockForConsumer(consumer: Chain): bool = all {
@@ -312,22 +353,49 @@ module ccv {
PROVIDER_NOOP,
// no packets are sent to consumer or received by it
outstandingPacketsToConsumer' = outstandingPacketsToConsumer,
+ // consumer statusses do not change
+ consumerStatus' = consumerStatus,
+ // chain times do not change
+ curChainTimes' = curChainTimes,
+ // update the packet store on the provider
+ StoreSentPackets,
}
- // the timestamp for each chain is advanced nondeterministically
- action AdvanceTimestamps(): bool =
- curChainTimes' = curChainTimes.keys().mapBy(
- (chain) =>
- nondet advancement = oneOf(0.to(100))
- curChainTimes.get(chain) + advancement
- )
+ // advance timestamps for maps nondeterministically
+ action AdvanceTime(): bool =
+ val advanceAmounts = curChainTimes.keys().mapBy(
+ chain =>
+ nondet amount = oneOf(1.to(10))
+ amount
+ )
+ AdvanceTimeByMap(advanceAmounts)
+
+ // the timestamp for each chain is advanced by the given amount
+ action AdvanceTimeByMap(advancementAmount: Chain -> int): bool = all
+ {
+ curChainTimes' = curChainTimes.keys().mapBy(
+ chain =>
+ curChainTimes.get(chain) + advancementAmount.get(chain)
+ ),
+ // all other variables are left untouched
+ votingPowerHistories' = votingPowerHistories,
+ runningValidatorSet' = runningValidatorSet,
+ outstandingPacketsToProvider' = outstandingPacketsToProvider,
+ outstandingPacketsToConsumer' = outstandingPacketsToConsumer,
+ receivedMaturations' = receivedMaturations,
+ maturationTimes' = maturationTimes,
+ // chain times do not change
+ consumerStatus' = consumerStatus,
+ // update the packet store on the provider
+ StoreSentPackets,
+ }
// each consumer chain may advance in the order
// some events may necessitate a transition, e.g. timeouts.
// shouldAdvance gives, for each consumer chain, whether it should advance if possible.
// if a chain has to advance, e.g. due to timeouts, or may not advance, the value will have no effect.
action AdvanceConsumers(shouldAdvance: Chain -> bool): bool =
- consumerStatus' = consumerStatus.keys().mapBy(
+ val newConsumerStatus = consumerStatus.keys().mapBy(
chain =>
val curStatus = consumerStatus.get(chain)
if (curStatus == UNUSED) {
@@ -355,7 +423,30 @@ module ccv {
// stopped chains cannot restart, we assume a new chain would be started in that case
STOPPED
}
- )
+ )
+ all {
+ consumerStatus' = newConsumerStatus,
+ // all other variables are left untouched
+ votingPowerHistories' = votingPowerHistories,
+ runningValidatorSet' = runningValidatorSet.keys().mapBy(
+ chain =>
+ if (newConsumerStatus.get(chain) == RUNNING and consumerStatus.get(chain) == UNUSED)
+ // consumers that went from unused to running start with the current validator set on the provider
+ {
+ runningValidatorSet.get(ProviderChain)
+ } else {
+ runningValidatorSet.get(chain)
+ }
+ ),
+ outstandingPacketsToProvider' = outstandingPacketsToProvider,
+ outstandingPacketsToConsumer' = outstandingPacketsToConsumer,
+ receivedMaturations' = receivedMaturations,
+ maturationTimes' = maturationTimes,
+ // chain times do not change
+ curChainTimes' = curChainTimes,
+ // update the packet store on the provider
+ StoreSentPackets,
+ }
// stores the VSCPackets sent in this step in sentVSCPackets
action StoreSentPackets: bool =
@@ -364,20 +455,17 @@ module ccv {
sentVSCPackets.get(chain).union(outstandingPacketsToConsumer.get(chain).toSet())
)
- action step: bool =
- all {
- // timestamps can be advanced in each step
- AdvanceTimestamps,
- any {
- nondet node = oneOf(Nodes)
- nondet amount = oneOf(1.to(10))
- votingPowerChange(node, amount),
- recvPacketOnProvider(oneOf(ConsumerChains)),
- recvPacketOnConsumer(oneOf(ConsumerChains)),
- nondet chain = oneOf(chains)
- endAndBeginBlock(chain),
- },
- StoreSentPackets,
+
+ // the main step action
+ action step: bool = any {
+ AdvanceTime,
+ nondet node = oneOf(Nodes)
+ nondet amount = oneOf(1.to(10))
+ votingPowerChange(node, amount),
+ recvPacketOnProvider(oneOf(ConsumerChains)),
+ recvPacketOnConsumer(oneOf(ConsumerChains)),
+ nondet chain = oneOf(chains)
+ endAndBeginBlock(chain),
val shouldAdvance = ConsumerChains.mapBy(
chain =>
nondet should = oneOf(Set(true, false))
@@ -385,7 +473,6 @@ module ccv {
)
AdvanceConsumers(shouldAdvance),
}
-
def getArbitraryValidatorSet(): ValidatorSet =
val numValidators = oneOf(1.to(Nodes.size()))
@@ -397,6 +484,7 @@ module ccv {
votingPower
)
+ // INITIALIZATION
action init: bool =
all {
// voting power histories are empty
@@ -436,4 +524,17 @@ module ccv {
chains.forall(
chain => chain.getCurrentValidatorSet().wasValidatorSetOnProvider()
)
+
+ // TESTS
+ run VSCHappyPath: bool = {
+ init
+ // trigger a votingPowerChange on the provider chain
+ .then(votingPowerChange("A", 10))
+ // endAndBeginBlock on provider. No consumer chains are running, so no packets are sent
+ .then(endAndBeginBlock(ProviderChain))
+ // advance chain1 to running
+ .then(AdvanceConsumers(NoStatusAdvancement.set("chain1", true)))
+ }
+
+
}
\ No newline at end of file
From e27e74b98c6f4bb2da7fd7da3124619a341c19f5 Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Fri, 25 Aug 2023 15:20:01 +0200
Subject: [PATCH 05/35] Rename model, add test, use powerset for nondeterminism
---
.../core/quint_model/{model.qnt => ccv.qnt} | 95 ++++++++++++++-----
1 file changed, 72 insertions(+), 23 deletions(-)
rename tests/difference/core/quint_model/{model.qnt => ccv.qnt} (86%)
diff --git a/tests/difference/core/quint_model/model.qnt b/tests/difference/core/quint_model/ccv.qnt
similarity index 86%
rename from tests/difference/core/quint_model/model.qnt
rename to tests/difference/core/quint_model/ccv.qnt
index aa3378bca6..86dbccf523 100644
--- a/tests/difference/core/quint_model/model.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -74,6 +74,20 @@ module ccv {
0
)
+ // utility: a struct summarizing the current state
+ val state =
+ {
+ votingPowerHistories: votingPowerHistories,
+ runningValidatorSet: runningValidatorSet,
+ curChainTimes: curChainTimes,
+ consumerStatus: consumerStatus,
+ outstandingPacketsToProvider: outstandingPacketsToProvider,
+ outstandingPacketsToConsumer: outstandingPacketsToConsumer,
+ maturationTimes: maturationTimes,
+ receivedMaturations: receivedMaturations,
+ sentVSCPackets: sentVSCPackets,
+ }
+
// utility: a map assigning each chain to false, used for not advancing consumer status
pure val NoStatusAdvancement: Chain -> bool = chains.mapBy(
@@ -249,7 +263,7 @@ module ccv {
// as that only happens when the next block is started
runningValidatorSet' = runningValidatorSet.set(consumer, packet.validatorSet),
// add the new packet and store its maturation time
- val newMaturationTimes = maturationTimes.get(consumer).set(packet, curChainTimes.get(consumer) + UnbondingPeriod.get(consumer))
+ val newMaturationTimes = maturationTimes.get(consumer).put(packet, curChainTimes.get(consumer) + UnbondingPeriod.get(consumer))
maturationTimes' = maturationTimes.set(consumer, newMaturationTimes)
},
PROVIDER_NOOP,
@@ -298,16 +312,20 @@ module ccv {
// if running validator set is not equal to the last one in the history
if (runningValidatorSet.get(ProviderChain) != votingPowerHistories.get(ProviderChain).head())
// then send a packet to each running consumer
- outstandingPacketsToConsumer.keys().filter(consumer => consumerStatus.get(consumer) == RUNNING).mapBy(
+ outstandingPacketsToConsumer.keys().mapBy(
(consumer) =>
val packetQueue = outstandingPacketsToConsumer.get(consumer)
- packetQueue.append(
- {
- id: packetQueue.length(),
- validatorSet: runningValidatorSet.get(ProviderChain),
- timeout: curChainTimes.get(ProviderChain) + PacketTimeout
- }
- )
+ if (consumerStatus.get(consumer) == RUNNING) {
+ packetQueue.append(
+ {
+ id: packetQueue.length(),
+ validatorSet: runningValidatorSet.get(ProviderChain),
+ timeout: curChainTimes.get(ProviderChain) + PacketTimeout
+ }
+ )
+ } else {
+ packetQueue
+ }
)
else
// otherwise, don't send any packets
@@ -474,10 +492,12 @@ module ccv {
AdvanceConsumers(shouldAdvance),
}
+ pure val nodePowerSet = Nodes.powerset()
+
def getArbitraryValidatorSet(): ValidatorSet =
- val numValidators = oneOf(1.to(Nodes.size()))
+ nondet numValidators = oneOf(1.to(Nodes.size()))
// toList has nondeterministic behaviour, so this gets arbitrary validators
- val validators = Nodes.toList().slice(0, numValidators).toSet()
+ nondet validators = oneOf(nodePowerSet.filter(s => s.size() == numValidators))
validators.mapBy(
validator =>
nondet votingPower = oneOf(1.to(10))
@@ -487,18 +507,18 @@ module ccv {
// INITIALIZATION
action init: bool =
all {
- // voting power histories are empty
- votingPowerHistories' =
- chains.mapBy(
- (chain) =>
- List()
- ),
+ val validatorSets = chains.mapBy(
+ (chain) =>
// provider chain gets an arbitrary validator set, consumer chains have none
- runningValidatorSet' =
- chains.mapBy(
+ if (chain == ProviderChain) getArbitraryValidatorSet else Map()
+ )
+ all {
+ votingPowerHistories' = chains.mapBy(
(chain) =>
- if (chain == ProviderChain) getArbitraryValidatorSet else Map()
+ List(validatorSets.get(chain))
),
+ runningValidatorSet' = validatorSets,
+ },
// each chain starts at time 0
curChainTimes' = chains.mapBy(
(chain) => 0
@@ -526,14 +546,43 @@ module ccv {
)
// TESTS
- run VSCHappyPath: bool = {
+ run VSCHappyPathTest: bool = {
init
// trigger a votingPowerChange on the provider chain
.then(votingPowerChange("A", 10))
// endAndBeginBlock on provider. No consumer chains are running, so no packets are sent
.then(endAndBeginBlock(ProviderChain))
- // advance chain1 to running
- .then(AdvanceConsumers(NoStatusAdvancement.set("chain1", true)))
+ .then(all {
+ // no packet was sent
+ assert(outstandingPacketsToConsumer.get("chain1").length() == 0),
+ // advance chain1 to running
+ AdvanceConsumers(NoStatusAdvancement.set("chain1", true))
+ })
+ // consumer chain should have current validator set from provider
+ .then(
+ all {
+ // since consumer chain just started, its assumed to have the validator set from provider
+ assert(runningValidatorSet.get("chain1") == runningValidatorSet.get(ProviderChain)),
+ // trigger a votingPowerChange on the provider chain
+ votingPowerChange("B", 10)
+ }
+ )
+ .then(endAndBeginBlock(ProviderChain))
+ // now the provider should send a packet on block end
+ .then(all {
+ // a packet was sent
+ assert(outstandingPacketsToConsumer.get("chain1").length() == 1),
+ // deliver the packet to the consumer
+ recvPacketOnConsumer("chain1")
+ })
+ .then(all {
+ // the consumer should have the new validator set
+ assert(runningValidatorSet.get("chain1") == runningValidatorSet.get(ProviderChain)),
+ // the old validator set should be in the history
+ assert(votingPowerHistories.get("chain1").head() == Map("A" -> 10)),
+ // put a last action to satisfy the action effect
+ AdvanceConsumers(NoStatusAdvancement)
+ })
}
From 50fae345fa1f3a7f42ad933ff1e33390e3003c3e Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Fri, 25 Aug 2023 17:49:42 +0200
Subject: [PATCH 06/35] Reintroduce vsc changed tracking variables
---
tests/difference/core/quint_model/ccv.qnt | 110 +++++++++++++---------
1 file changed, 67 insertions(+), 43 deletions(-)
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index 86dbccf523..131a490d8a 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -140,6 +140,12 @@ module ccv {
// stores which VSC Packets have been sent to compare with receivedMaturations to detect timeouts due to non-responsiveness
var sentVSCPackets: Chain -> Set[VSCPacket]
+ // stores whether, in this step, the validator set considered to be changed.
+ // this is needed because the validator set might be considered to have changed, even though
+ // it is still technically identical at our level of abstraction, e.g. a validator power change on the provider
+ // might leave the validator set the same because a delegation and undelegation cancel each other out.
+ var providerValidatorSetChangedInThisBlock: bool
+
// UTILITY FUNCTIONS & ACTIONS
def wasValidatorSetOnProvider(validatorSet: ValidatorSet): bool = {
@@ -153,7 +159,7 @@ module ccv {
def getUpdatedValidatorSet(oldValidatorSet: ValidatorSet, validator: Node, newVotingPower: int): ValidatorSet =
if (newVotingPower > 0)
- oldValidatorSet.set(validator, newVotingPower)
+ oldValidatorSet.put(validator, newVotingPower)
else
oldValidatorSet.mapRemove(validator)
@@ -211,7 +217,7 @@ module ccv {
// set the running validator set on the provider chain, but don't update the history yet
runningValidatorSet' = runningValidatorSet.set(ProviderChain, newValidatorSet),
// no packets are sent yet, these are only sent on endAndBeginBlock
- outstandingPacketsToConsumer' = outstandingPacketsToConsumer,
+ RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
outstandingPacketsToProvider' = outstandingPacketsToProvider,
receivedMaturations' = receivedMaturations,
CONSUMER_NOOP,
@@ -221,8 +227,8 @@ module ccv {
consumerStatus' = consumerStatus,
// chain times do not change
curChainTimes' = curChainTimes,
- // update the packet store on the provider
- StoreSentPackets,
+ // the validator set is considered to have changed
+ providerValidatorSetChangedInThisBlock' = true,
}
// deliver the next outstanding packet from the consumer to the provider.
@@ -237,7 +243,7 @@ module ccv {
val maturedPacket = outstandingPacketsToProvider.get(consumer).head()
receivedMaturations' = receivedMaturations.add(maturedPacket),
CONSUMER_NOOP,
- outstandingPacketsToConsumer' = outstandingPacketsToConsumer,
+ RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
votingPowerHistories' = votingPowerHistories,
// no validator set changes are made
runningValidatorSet' = runningValidatorSet,
@@ -245,8 +251,8 @@ module ccv {
consumerStatus' = consumerStatus,
// chain times do not change
curChainTimes' = curChainTimes,
- // update the packet store on the provider
- StoreSentPackets,
+ // the validator set was not changed by this action (but might have been changed before in this block)
+ providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
}
// deliver the next outstanding packet from the provider to the consumer.
@@ -256,7 +262,8 @@ module ccv {
outstandingPacketsToConsumer.get(consumer).length() > 0,
// remove packet from outstanding packets
val newPacketQueue = outstandingPacketsToConsumer.get(consumer).tail()
- outstandingPacketsToConsumer' = outstandingPacketsToConsumer.set(consumer, newPacketQueue),
+ val newOutstandingPackets = outstandingPacketsToConsumer.set(consumer, newPacketQueue)
+ RegisterNewOutstandingPackets(newOutstandingPackets),
val packet = outstandingPacketsToConsumer.get(consumer).head()
all {
// update the running validator set, but not the history yet,
@@ -273,8 +280,8 @@ module ccv {
consumerStatus' = consumerStatus,
// chain times do not change
curChainTimes' = curChainTimes,
- // update the packet store on the provider
- StoreSentPackets,
+ // the validator set was not changed by this action (but might have been changed before in this block)
+ providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
}
// ends the current block and starts the next block for a given chain.
@@ -308,9 +315,9 @@ module ccv {
// so start with it in the next block
runningValidatorSet' = runningValidatorSet,
// send VSCPackets to consumers
- outstandingPacketsToConsumer' =
- // if running validator set is not equal to the last one in the history
- if (runningValidatorSet.get(ProviderChain) != votingPowerHistories.get(ProviderChain).head())
+ val newOutstandingPackets =
+ // if running validator set is considered to have changed
+ if (providerValidatorSetChangedInThisBlock)
// then send a packet to each running consumer
outstandingPacketsToConsumer.keys().mapBy(
(consumer) =>
@@ -329,7 +336,8 @@ module ccv {
)
else
// otherwise, don't send any packets
- outstandingPacketsToConsumer,
+ outstandingPacketsToConsumer
+ RegisterNewOutstandingPackets(newOutstandingPackets),
CONSUMER_NOOP,
// no packets are sent to the provider
outstandingPacketsToProvider' = outstandingPacketsToProvider,
@@ -339,8 +347,8 @@ module ccv {
consumerStatus' = consumerStatus,
// chain times do not change
curChainTimes' = curChainTimes,
- // update the packet store on the provider
- StoreSentPackets,
+ // the validator set was definitely not changed in the new block yet, so set to false
+ providerValidatorSetChangedInThisBlock' = false
}
action endAndBeginBlockForConsumer(consumer: Chain): bool = all {
@@ -370,13 +378,14 @@ module ccv {
},
PROVIDER_NOOP,
// no packets are sent to consumer or received by it
- outstandingPacketsToConsumer' = outstandingPacketsToConsumer,
+ RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
// consumer statusses do not change
consumerStatus' = consumerStatus,
// chain times do not change
curChainTimes' = curChainTimes,
- // update the packet store on the provider
- StoreSentPackets,
+ // the validator set was not changed by this action (but might have been changed before in this block)
+ // also, this is only a new block for a consumer, so the change variable shouldn't be reset
+ providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
}
// advance timestamps for maps nondeterministically
@@ -399,13 +408,13 @@ module ccv {
votingPowerHistories' = votingPowerHistories,
runningValidatorSet' = runningValidatorSet,
outstandingPacketsToProvider' = outstandingPacketsToProvider,
- outstandingPacketsToConsumer' = outstandingPacketsToConsumer,
+ RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
receivedMaturations' = receivedMaturations,
maturationTimes' = maturationTimes,
// chain times do not change
consumerStatus' = consumerStatus,
- // update the packet store on the provider
- StoreSentPackets,
+ // the validator set was not changed by this action (but might have been changed before in this block)
+ providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
}
// each consumer chain may advance in the order
@@ -457,20 +466,28 @@ module ccv {
}
),
outstandingPacketsToProvider' = outstandingPacketsToProvider,
- outstandingPacketsToConsumer' = outstandingPacketsToConsumer,
+ RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
receivedMaturations' = receivedMaturations,
maturationTimes' = maturationTimes,
// chain times do not change
curChainTimes' = curChainTimes,
- // update the packet store on the provider
- StoreSentPackets,
+ // the validator set was not changed by this action (but might have been changed before in this block)
+ providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
+ }
+
+ // Updates the outstandingPacketsToConsumer and sentVSCPackets variables
+ action RegisterNewOutstandingPackets(newOutstandingPackets: Chain -> List[VSCPacket]): bool =
+ all {
+ outstandingPacketsToConsumer' = newOutstandingPackets,
+ StoreSentPackets(newOutstandingPackets),
}
+
// stores the VSCPackets sent in this step in sentVSCPackets
- action StoreSentPackets: bool =
+ action StoreSentPackets(newOutstandingPackets: Chain -> List[VSCPacket]): bool =
sentVSCPackets' = sentVSCPackets.keys().mapBy(
(chain) =>
- sentVSCPackets.get(chain).union(outstandingPacketsToConsumer.get(chain).toSet())
+ sentVSCPackets.get(chain).union(newOutstandingPackets.get(chain).toSet())
)
@@ -534,6 +551,8 @@ module ccv {
sentVSCPackets' = chains.mapBy(chain => Set()),
// no packets have been received by consumers, so no maturation times set
maturationTimes' = chains.mapBy(chain => Map()),
+ // validator set was not changed yet
+ providerValidatorSetChangedInThisBlock' = false
}
// PROPERTIES
@@ -567,22 +586,27 @@ module ccv {
votingPowerChange("B", 10)
}
)
- .then(endAndBeginBlock(ProviderChain))
- // now the provider should send a packet on block end
- .then(all {
- // a packet was sent
- assert(outstandingPacketsToConsumer.get("chain1").length() == 1),
- // deliver the packet to the consumer
- recvPacketOnConsumer("chain1")
- })
- .then(all {
- // the consumer should have the new validator set
- assert(runningValidatorSet.get("chain1") == runningValidatorSet.get(ProviderChain)),
- // the old validator set should be in the history
- assert(votingPowerHistories.get("chain1").head() == Map("A" -> 10)),
- // put a last action to satisfy the action effect
- AdvanceConsumers(NoStatusAdvancement)
- })
+ .then(
+ val valSet = runningValidatorSet.get(ProviderChain)
+ endAndBeginBlock(ProviderChain)
+ // now the provider should send a packet on block end
+ .then(all {
+ // a packet was sent
+ assert(outstandingPacketsToConsumer.get("chain1").length() == 1),
+ // deliver the packet to the consumer
+ recvPacketOnConsumer("chain1")
+ })
+ .then(
+ // consumer needs to end a block before it has the new validator set
+ endAndBeginBlock("chain1")
+ )
+ .then(all {
+ // the consumer should have the new validator set
+ assert(runningValidatorSet.get("chain1") == valSet),
+ // put a last action to satisfy the action effect
+ AdvanceConsumers(NoStatusAdvancement)
+ })
+ )
}
From 78e890aef2d319ffaab3347583eda01205315dbb Mon Sep 17 00:00:00 2001
From: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com>
Date: Fri, 25 Aug 2023 18:05:24 +0200
Subject: [PATCH 07/35] Add text with what expliticly is modelled
---
tests/difference/core/quint_model/ccv.qnt | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index 131a490d8a..7984d3b01b 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -5,6 +5,11 @@ module ccv {
// * Reward distribution
// * Starting/Stopping chains during execution
// * Slashes
+
+ // Things that explicitly are modelled:
+ // * Validator set changes are propagated from provider to consumers
+ // * VSC packets mature
+ // * Consumers can be forcefully dropped due to timeouts, or stop on their own volition
// TYPE DEFINITIONS
type Node = str
@@ -610,4 +615,4 @@ module ccv {
}
-}
\ No newline at end of file
+}
From a1d57ef47f88dd34c66dd5f61b99073767a7fdd7 Mon Sep 17 00:00:00 2001
From: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com>
Date: Fri, 25 Aug 2023 20:36:40 +0200
Subject: [PATCH 08/35] Add bluespec to ccv.qnt
---
tests/difference/core/quint_model/ccv.qnt | 1 +
1 file changed, 1 insertion(+)
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index 7984d3b01b..d18930ae1d 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -1,3 +1,4 @@
+-*- mode: Bluespec; -*-
module ccv {
import extraSpells.* from "./extraSpells"
From 6c91866a573bcca553fdfbf00af14d3aac815d48 Mon Sep 17 00:00:00 2001
From: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com>
Date: Fri, 25 Aug 2023 20:37:05 +0200
Subject: [PATCH 09/35] Add bluespec to expraSpells.qnt
---
tests/difference/core/quint_model/extraSpells.qnt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/tests/difference/core/quint_model/extraSpells.qnt b/tests/difference/core/quint_model/extraSpells.qnt
index a32a74ad8f..a959db5251 100644
--- a/tests/difference/core/quint_model/extraSpells.qnt
+++ b/tests/difference/core/quint_model/extraSpells.qnt
@@ -1,3 +1,4 @@
+-*- mode: Bluespec; -*-
module extraSpells {
pure def prepend(__list: List[a], __elem: a): List[a] = {
@@ -188,4 +189,4 @@ module extraSpells {
__set.union(Set(elem))
}
-}
\ No newline at end of file
+}
From 2126eac396d18977b5408e3ee550a20157359dbd Mon Sep 17 00:00:00 2001
From: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com>
Date: Tue, 5 Sep 2023 13:03:45 +0200
Subject: [PATCH 10/35] Add docstring to extraSpells module
---
tests/difference/core/quint_model/extraSpells.qnt | 1 +
1 file changed, 1 insertion(+)
diff --git a/tests/difference/core/quint_model/extraSpells.qnt b/tests/difference/core/quint_model/extraSpells.qnt
index a959db5251..cd2105beb7 100644
--- a/tests/difference/core/quint_model/extraSpells.qnt
+++ b/tests/difference/core/quint_model/extraSpells.qnt
@@ -1,4 +1,5 @@
-*- mode: Bluespec; -*-
+// This module is just a library with utility functions (sometimes called spells in Quint).
module extraSpells {
pure def prepend(__list: List[a], __elem: a): List[a] = {
From 1320b9517982bff596ecfc0ac99755dc75c5f1e1 Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Mon, 18 Sep 2023 18:23:31 +0200
Subject: [PATCH 11/35] Start rewriting model
---
tests/difference/core/quint_model/ccv.qnt | 1241 +++++++++--------
.../core/quint_model/extraSpells.qnt | 1 -
...ActionMarshalling-20230912123337-6230.fail | 18 +
...ActionMarshalling-20230912123517-6794.fail | 14 +
...ActionMarshalling-20230912123556-6970.fail | 14 +
...ActionMarshalling-20230912123609-7116.fail | 18 +
...ActionMarshalling-20230912123822-8026.fail | 8 +
...ctionMarshalling-20230913151826-12656.fail | 8 +
...ctionMarshalling-20230913151917-13146.fail | 8 +
...eadAndWriteTrace-20230913151920-13146.fail | 11 +
10 files changed, 790 insertions(+), 551 deletions(-)
create mode 100644 tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123337-6230.fail
create mode 100644 tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123517-6794.fail
create mode 100644 tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123556-6970.fail
create mode 100644 tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123609-7116.fail
create mode 100644 tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123822-8026.fail
create mode 100644 tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230913151826-12656.fail
create mode 100644 tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230913151917-13146.fail
create mode 100644 tests/e2e/testdata/rapid/TestReadAndWriteTrace/TestReadAndWriteTrace-20230913151920-13146.fail
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index d18930ae1d..4cbc327ac4 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -1,5 +1,4 @@
--*- mode: Bluespec; -*-
-module ccv {
+module ccv_logic {
import extraSpells.* from "./extraSpells"
// Things that are not modelled:
@@ -12,12 +11,17 @@ module ccv {
// * VSC packets mature
// * Consumers can be forcefully dropped due to timeouts, or stop on their own volition
+ // ===================
// TYPE DEFINITIONS
+ // ===================
type Node = str
type Chain = str
- type ValidatorSet = Node -> int
+ type Power = int
+ type ValidatorSet = Node -> Power
type Height = int
type Timestamp = int
+ // a list of validator sets per blocks, ordered by recency
+ type VotingPowerHistory = List[ValidatorSet]
// --PACKETS
type VSCPacket =
@@ -38,582 +42,719 @@ module ccv {
timeout: Timestamp
}
- // MODEL PARAMETERS
-
-
- // set of identifiers of potential nodes
- pure val Nodes: Set[Node] =
- Set("A", "B", "C", "D", "E", "F", "G", "H", "I", "J")
-
- // the set of consumer chains
- pure val ConsumerChains: Set[Chain] =
- Set("chain1", "chain2", "chain3")
+ // possible consumer statuses
+ pure val STOPPED = "stopped" // the chain was once a consumer chain, but has been dropped by the provider.
+ pure val RUNNING = "running" //Â the chain is currently a consumer chain. Running chains are those that get sent VSCPackets.
+ pure val UNUSED = "unused" // the chain has never been a consumer chain, and is available to become one.
+ // When a chain is dropped, it cannot become a consumer again - we assume that would be done by another consumer becoming running.
+
+
+ // state that each chain needs to store, whether consumer or provider.
+ type ChainState = {
+ // Stores the list of voting powers that corresponded to voting powers
+ // at blocks over the chains entire existence.
+ // Voting powers should be ordered by recency in descending order.
+ votingPowerHistory: VotingPowerHistory,
+
+ // the current validator set on each chain.
+ // this will be included in the next block, but might not be final yet,
+ // e.g. there may be more modifications in the current block.
+ currentValidatorSet: ValidatorSet,
+
+ // the latest timestamp that was comitted on chain
+ lastTimestamp: Timestamp,
+ }
- // The singular provider chain.
- pure val ProviderChain: Chain =
- "provider"
-
- pure val chains = ConsumerChains.union(Set(ProviderChain))
-
- // length of the unbonding period on each chain
- pure val UnbondingPeriod: Chain -> int = chains.mapBy(
- (chain) =>
- 10
- )
-
- // the time until a packet times out
- pure val PacketTimeout: int =
- 5
-
- // the time until a consumer chain is dropped by the provider due to inactivity
- pure val InactivityTimeout: int =
- 10
-
- // consumer statuses
- pure val STOPPED = "stopped"
- pure val RUNNING = "running"
- pure val UNUSED = "unused"
-
- // utility: a map assigning each chain to 0, used for not advancing timestamps
- pure val NoTimeAdvancement: Chain -> int = chains.mapBy(
- (chain) =>
- 0
- )
-
- // utility: a struct summarizing the current state
- val state =
+ // Defines the current state of the provider chain. Essentially, all information here is stored by the provider on-chain (or could be derived purely by information that is on-chain).
+ type ProviderState =
{
- votingPowerHistories: votingPowerHistories,
- runningValidatorSet: runningValidatorSet,
- curChainTimes: curChainTimes,
- consumerStatus: consumerStatus,
- outstandingPacketsToProvider: outstandingPacketsToProvider,
- outstandingPacketsToConsumer: outstandingPacketsToConsumer,
- maturationTimes: maturationTimes,
- receivedMaturations: receivedMaturations,
- sentVSCPackets: sentVSCPackets,
- }
-
-
- // utility: a map assigning each chain to false, used for not advancing consumer status
- pure val NoStatusAdvancement: Chain -> bool = chains.mapBy(
- (chain) =>
- false
- )
-
- // utility: the set of consumers currently running
- val RunningConsumers: Set[Chain] =
- ConsumerChains.filter(chain => consumerStatus.get(chain) == RUNNING)
-
- // MODEL STATE
- // --SHARED STATE
-
- // Stores, for each chain, the list of voting powers that corresponded to voting powers
- // at blocks over its entire existence.
- // Voting powers should be ordered by recency in descending order.
- var votingPowerHistories: Chain -> List[ValidatorSet]
-
- // the current validator set on each chain.
- // this will be included in the next block, but might not be final yet,
- // e.g. there may be more modifications in the current block.
- var runningValidatorSet: Chain -> ValidatorSet
-
- // the current timestamp for each chain
- var curChainTimes: Chain -> Timestamp
-
- // stores, for each chain, its current status -
- // unused, running, or stopped
- var consumerStatus: Chain -> str
-
- // --CHANNELS
- // Stores, for each consumer chain, the list of packets that have been sent to the provider chain
- // and have not been received yet.
- var outstandingPacketsToProvider: Chain -> List[VSCMaturedPacket]
+ // the state that each chain needs to store
+ chainState: ChainState,
- // Stores, for each consumer chain, the list of packets that have been sent to the consumer chain
- // and have not been received yet.
- var outstandingPacketsToConsumer: Chain -> List[VSCPacket]
+ // Stores, for each consumer chain, the list of packets that have been sent to the consumer chain
+ // and have not been received yet.
+ // In the implementation, this would roughly be the unacknowledged packets on an ibc channel.
+ outstandingPacketsToConsumer: Chain -> List[VSCPacket],
+ // the set of received VSCMaturedPackets
+ receivedMaturations: Set[VSCMaturedPacket],
- // --CONSUMER STATE
- // Stores the maturation times for VSCPackets received by consumers
- var maturationTimes: Chain -> (VSCPacket -> Timestamp)
+ // stores which VSC Packets have been sent to compare with receivedMaturations to detect timeouts due to non-responsiveness
+ sentVSCPackets: Chain -> Set[VSCPacket],
- // --PROVIDER STATE
- // the set of VSCMaturedPackets received by the provider chain
- var receivedMaturations: Set[VSCMaturedPacket]
-
- // stores which VSC Packets have been sent to compare with receivedMaturations to detect timeouts due to non-responsiveness
- var sentVSCPackets: Chain -> Set[VSCPacket]
-
- // stores whether, in this step, the validator set considered to be changed.
- // this is needed because the validator set might be considered to have changed, even though
- // it is still technically identical at our level of abstraction, e.g. a validator power change on the provider
- // might leave the validator set the same because a delegation and undelegation cancel each other out.
- var providerValidatorSetChangedInThisBlock: bool
-
-
- // UTILITY FUNCTIONS & ACTIONS
- def wasValidatorSetOnProvider(validatorSet: ValidatorSet): bool = {
- votingPowerHistories.get(ProviderChain).toSet().exists(
- historicalValSet => historicalValSet == validatorSet
- )
- }
-
- def getCurrentValidatorSet(chain: Chain): ValidatorSet =
- votingPowerHistories.get(chain).head()
-
- def getUpdatedValidatorSet(oldValidatorSet: ValidatorSet, validator: Node, newVotingPower: int): ValidatorSet =
- if (newVotingPower > 0)
- oldValidatorSet.put(validator, newVotingPower)
- else
- oldValidatorSet.mapRemove(validator)
-
- // returns true if the consumer has timed out and should be dropped
- def consumerTimedOut(consumer: Chain): bool =
- any {
- // either a package from provider to consumer has timed out
- outstandingPacketsToConsumer.get(consumer).select(
- packet => packet.timeout <= curChainTimes.get(consumer)
- ).length() > 0,
- // or a package from consumer to provider has timed out
- outstandingPacketsToProvider.get(consumer).select(
- packet => packet.timeout <= curChainTimes.get(ProviderChain)
- ).length() > 0,
- // or the inactivity timeout has passed since a VSCPacket was sent to the consumer, but the
- // provider has not received a VSCMaturedPacket for it
- val packetsWithoutResponse = sentVSCPackets.get(consumer).filter( // get packets without response
- packet =>
- not(receivedMaturations.exists(
- maturedPacket => maturedPacket.id == packet.id
- ))
- )
- // among those, get packets where inactivity timeout has passed
- packetsWithoutResponse.filter(
- packet =>
- val sentAt = curChainTimes.get(ProviderChain) - PacketTimeout // compute when the packet was sent
- val timesOutAt = sentAt + InactivityTimeout // compute when the packet times out
- timesOutAt <= curChainTimes.get(ProviderChain)
- ).size() > 0
- }
-
- // utility action that leaves all provider state untouched
- action PROVIDER_NOOP(): bool =
- all {
- receivedMaturations' = receivedMaturations,
- }
+ // stores whether, in this block, the validator set has changed.
+ // this is needed because the validator set might be considered to have changed, even though
+ // it is still technically identical at our level of abstraction, e.g. a validator power change on the provider
+ // might leave the validator set the same because a delegation and undelegation cancel each other out.
+ providerValidatorSetChangedInThisBlock: bool,
- // utility action that leaves all consumer state untouched
- action CONSUMER_NOOP(): bool =
- all {
- maturationTimes' = maturationTimes,
+ // stores, for each consumer chain, its current status -
+ // unused, running, or stopped
+ consumerStatus: Chain -> str,
}
-
- // MODEL ACTIONS
+ // Defines the current state of a consumer chain. This information is accessible to that consumer on-chain.
+ type ConsumerState = {
+ // the state that each chain needs to store
+ chainState: ChainState,
- // the power of a validator on the provider chain is changed to the given amount. We do not care how this happens,
- // e.g. via undelegations, or delegations, ...
- action votingPowerChange(validator: Node, amount: int): bool =
- // for the provider chain, we need to adjust the voting power history
- // by adding a new set
- all {
- amount >= 0,
- val newValidatorSet = getCurrentValidatorSet(ProviderChain).getUpdatedValidatorSet(validator, amount)
- // set the running validator set on the provider chain, but don't update the history yet
- runningValidatorSet' = runningValidatorSet.set(ProviderChain, newValidatorSet),
- // no packets are sent yet, these are only sent on endAndBeginBlock
- RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
- outstandingPacketsToProvider' = outstandingPacketsToProvider,
- receivedMaturations' = receivedMaturations,
- CONSUMER_NOOP,
- // voting power history is only updated on endAndBeginBlock
- votingPowerHistories' = votingPowerHistories,
- // consumer statusses do not change
- consumerStatus' = consumerStatus,
- // chain times do not change
- curChainTimes' = curChainTimes,
- // the validator set is considered to have changed
- providerValidatorSetChangedInThisBlock' = true,
- }
+ // Stores the maturation times for VSCPackets received by this consumer
+ maturationTimes: VSCPacket -> Timestamp,
- // deliver the next outstanding packet from the consumer to the provider.
- // since this model assumes a single provider chain, this just takes a single chain as argument.
- action recvPacketOnProvider(consumer: Chain): bool = all {
- // ensure there is a packet to be received
- outstandingPacketsToProvider.get(consumer).length() > 0,
- // remove packet from outstanding packets
- val newPacketQueue = outstandingPacketsToProvider.get(consumer).tail()
- outstandingPacketsToProvider' = outstandingPacketsToProvider.set(consumer, newPacketQueue),
- // register the packet as received
- val maturedPacket = outstandingPacketsToProvider.get(consumer).head()
- receivedMaturations' = receivedMaturations.add(maturedPacket),
- CONSUMER_NOOP,
- RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
- votingPowerHistories' = votingPowerHistories,
- // no validator set changes are made
- runningValidatorSet' = runningValidatorSet,
- // consumer statusses do not change
- consumerStatus' = consumerStatus,
- // chain times do not change
- curChainTimes' = curChainTimes,
- // the validator set was not changed by this action (but might have been changed before in this block)
- providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
- }
+ // Stores the list of packets that have been sent to the provider chain by this consumer
+ // and have not been received yet.
+ // In the implementation, essentially unacknowledged IBC packets.
+ outstandingPacketsToProvider: Chain -> List[VSCMaturedPacket],
- // deliver the next outstanding packet from the provider to the consumer.
- // since this model assumes a single provider chain, this just takes a single chain as argument.
- action recvPacketOnConsumer(consumer: Chain): bool = all {
- // ensure there is a packet to be received
- outstandingPacketsToConsumer.get(consumer).length() > 0,
- // remove packet from outstanding packets
- val newPacketQueue = outstandingPacketsToConsumer.get(consumer).tail()
- val newOutstandingPackets = outstandingPacketsToConsumer.set(consumer, newPacketQueue)
- RegisterNewOutstandingPackets(newOutstandingPackets),
- val packet = outstandingPacketsToConsumer.get(consumer).head()
- all {
- // update the running validator set, but not the history yet,
- // as that only happens when the next block is started
- runningValidatorSet' = runningValidatorSet.set(consumer, packet.validatorSet),
- // add the new packet and store its maturation time
- val newMaturationTimes = maturationTimes.get(consumer).put(packet, curChainTimes.get(consumer) + UnbondingPeriod.get(consumer))
- maturationTimes' = maturationTimes.set(consumer, newMaturationTimes)
- },
- PROVIDER_NOOP,
- votingPowerHistories' = votingPowerHistories,
- outstandingPacketsToProvider' = outstandingPacketsToProvider,
- // consumer statusses do not change
- consumerStatus' = consumerStatus,
- // chain times do not change
- curChainTimes' = curChainTimes,
- // the validator set was not changed by this action (but might have been changed before in this block)
- providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
+ // Stores the list of voting powers that corresponded to voting powers
+ // at blocks over the chains entire existence.
+ // Voting powers should be ordered by recency in descending order.
+ votingPowerHistory: VotingPowerHistory,
}
- // ends the current block and starts the next block for a given chain.
- action endAndBeginBlock(chain: Chain): bool = any {
- all {
- chain == ProviderChain,
- endAndBeginBlockForProvider,
- },
- all {
- chain != ProviderChain,
- endAndBeginBlockForConsumer(chain),
- }
+ // the state of the protocol consists of the composition of the state of one provider chain with potentially many consumer chains.
+ type ProtocolState = {
+ providerState: ProviderState,
+ // the state of each consumer chain.
+ // note that we assume that this contains all consumer chains that may ever exist,
+ // and consumer chains that are currently not running will have providerState.consumerStatus == UNUSED or STOPPED
+ consumerStates: Set[ConsumerState]
}
- // gets the updated history for the current chain when ending a block, i.e. the
- // running validator set is added to the history if different from the last one.
- def getUpdatedHistory(chain: Chain): List[ValidatorSet] =
- // update voting power history if the validator set changed
- val newValidatorSet = runningValidatorSet.get(ProviderChain)
- val oldValidatorSet = votingPowerHistories.get(ProviderChain).head()
- if (newValidatorSet != oldValidatorSet)
- votingPowerHistories.get(ProviderChain).prepend(newValidatorSet)
- else
- votingPowerHistories.get(ProviderChain)
-
-
- action endAndBeginBlockForProvider(): bool = all {
- // update the voting power history
- votingPowerHistories' = votingPowerHistories.set(ProviderChain, getUpdatedHistory(ProviderChain)),
- // the running validator set is now for sure the current validator set,
- // so start with it in the next block
- runningValidatorSet' = runningValidatorSet,
- // send VSCPackets to consumers
- val newOutstandingPackets =
- // if running validator set is considered to have changed
- if (providerValidatorSetChangedInThisBlock)
- // then send a packet to each running consumer
- outstandingPacketsToConsumer.keys().mapBy(
- (consumer) =>
- val packetQueue = outstandingPacketsToConsumer.get(consumer)
- if (consumerStatus.get(consumer) == RUNNING) {
- packetQueue.append(
- {
- id: packetQueue.length(),
- validatorSet: runningValidatorSet.get(ProviderChain),
- timeout: curChainTimes.get(ProviderChain) + PacketTimeout
- }
- )
- } else {
- packetQueue
- }
- )
- else
- // otherwise, don't send any packets
- outstandingPacketsToConsumer
- RegisterNewOutstandingPackets(newOutstandingPackets),
- CONSUMER_NOOP,
- // no packets are sent to the provider
- outstandingPacketsToProvider' = outstandingPacketsToProvider,
- // do not receive any maturations
- receivedMaturations' = receivedMaturations,
- // consumer statusses do not change
- consumerStatus' = consumerStatus,
- // chain times do not change
- curChainTimes' = curChainTimes,
- // the validator set was definitely not changed in the new block yet, so set to false
- providerValidatorSetChangedInThisBlock' = false
+ type Error = {
+ message: str
}
- action endAndBeginBlockForConsumer(consumer: Chain): bool = all {
- ConsumerChains.contains(consumer),
- // update the voting power history
- votingPowerHistories' = votingPowerHistories.set(consumer, getUpdatedHistory(consumer)),
- // the running validator set is now for sure the current validator set,
- // so start with it in the next block
- runningValidatorSet' = runningValidatorSet,
- // compute mature packets whose maturation time has passed
- val maturedPackets = maturationTimes.get(consumer).keys().filter(
- packet =>
- val maturationTime = maturationTimes.get(consumer).get(packet)
- maturationTime <= curChainTimes.get(consumer)
- )
- all {
- // remove matured packets from the maturation times
- maturationTimes' = maturationTimes.set(consumer, maturationTimes.get(consumer).mapRemoveAll(maturedPackets)),
- // send matured packets
- outstandingPacketsToProvider' = outstandingPacketsToProvider.set(
- consumer,
- // construct VSCMaturedPackets from the matured VSCPackets
- outstandingPacketsToProvider.get(consumer).concat(
- maturedPackets.map(packet => {id: packet.id, timeout: 5}).toList()
- )
- )
- },
- PROVIDER_NOOP,
- // no packets are sent to consumer or received by it
- RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
- // consumer statusses do not change
- consumerStatus' = consumerStatus,
- // chain times do not change
- curChainTimes' = curChainTimes,
- // the validator set was not changed by this action (but might have been changed before in this block)
- // also, this is only a new block for a consumer, so the change variable shouldn't be reset
- providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
+ // we return either a result or an error.
+ // if hasError is true, newState may be arbitrary, but the error will be meaningful.
+ // if hasError is false, error may be arbitrary, but newState will be meaningful.
+ type Result = {
+ hasError: bool,
+ newState: ProtocolState,
+ error: Error
}
- // advance timestamps for maps nondeterministically
- action AdvanceTime(): bool =
- val advanceAmounts = curChainTimes.keys().mapBy(
- chain =>
- nondet amount = oneOf(1.to(10))
- amount
- )
- AdvanceTimeByMap(advanceAmounts)
-
- // the timestamp for each chain is advanced by the given amount
- action AdvanceTimeByMap(advancementAmount: Chain -> int): bool = all
+ pure def Ok(newState: ProtocolState): Result = {
{
- curChainTimes' = curChainTimes.keys().mapBy(
- chain =>
- curChainTimes.get(chain) + advancementAmount.get(chain)
- ),
- // all other variables are left untouched
- votingPowerHistories' = votingPowerHistories,
- runningValidatorSet' = runningValidatorSet,
- outstandingPacketsToProvider' = outstandingPacketsToProvider,
- RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
- receivedMaturations' = receivedMaturations,
- maturationTimes' = maturationTimes,
- // chain times do not change
- consumerStatus' = consumerStatus,
- // the validator set was not changed by this action (but might have been changed before in this block)
- providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
- }
-
- // each consumer chain may advance in the order
- // some events may necessitate a transition, e.g. timeouts.
- // shouldAdvance gives, for each consumer chain, whether it should advance if possible.
- // if a chain has to advance, e.g. due to timeouts, or may not advance, the value will have no effect.
- action AdvanceConsumers(shouldAdvance: Chain -> bool): bool =
- val newConsumerStatus = consumerStatus.keys().mapBy(
- chain =>
- val curStatus = consumerStatus.get(chain)
- if (curStatus == UNUSED) {
- if (shouldAdvance.get(chain))
- {
- RUNNING
- } else {
- UNUSED
- }
- }
- else if (curStatus == RUNNING) {
- // the chain may transition to stopped.
- // it is *forced* to stop if a packet timed out,
- // or if the inactivity timeout has passed
- if(consumerTimedOut(chain)) {
- STOPPED
- } else {
- if (shouldAdvance.get(chain)) {
- RUNNING
- } else {
- STOPPED
- }
- }
- } else {
- // stopped chains cannot restart, we assume a new chain would be started in that case
- STOPPED
+ hasError: false,
+ newState: newState,
+ error: {
+ message: ""
}
- )
- all {
- consumerStatus' = newConsumerStatus,
- // all other variables are left untouched
- votingPowerHistories' = votingPowerHistories,
- runningValidatorSet' = runningValidatorSet.keys().mapBy(
- chain =>
- if (newConsumerStatus.get(chain) == RUNNING and consumerStatus.get(chain) == UNUSED)
- // consumers that went from unused to running start with the current validator set on the provider
- {
- runningValidatorSet.get(ProviderChain)
- } else {
- runningValidatorSet.get(chain)
- }
- ),
- outstandingPacketsToProvider' = outstandingPacketsToProvider,
- RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
- receivedMaturations' = receivedMaturations,
- maturationTimes' = maturationTimes,
- // chain times do not change
- curChainTimes' = curChainTimes,
- // the validator set was not changed by this action (but might have been changed before in this block)
- providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
}
+ }
- // Updates the outstandingPacketsToConsumer and sentVSCPackets variables
- action RegisterNewOutstandingPackets(newOutstandingPackets: Chain -> List[VSCPacket]): bool =
- all {
- outstandingPacketsToConsumer' = newOutstandingPackets,
- StoreSentPackets(newOutstandingPackets),
+ pure def Err(msg: str): Result = {
+ {
+ hasError: true,
+ newState: {
+ providerState: {
+ chainState: {
+ votingPowerHistory: List(),
+ currentValidatorSet: Map(),
+ lastTimestamp: 0
+ },
+ outstandingPacketsToConsumer: Map(),
+ receivedMaturations: Set(),
+ sentVSCPackets: Map(),
+ providerValidatorSetChangedInThisBlock: false,
+ consumerStatus: Map()
+ },
+ consumerStates: Set()
+ },
+ error: {
+ message: msg
+ }
}
+ }
+ // ===================
+ // PROTOCOL LOGIC
+ // ===================
- // stores the VSCPackets sent in this step in sentVSCPackets
- action StoreSentPackets(newOutstandingPackets: Chain -> List[VSCPacket]): bool =
- sentVSCPackets' = sentVSCPackets.keys().mapBy(
- (chain) =>
- sentVSCPackets.get(chain).union(newOutstandingPackets.get(chain).toSet())
- )
-
+ pure def getUpdatedValidatorSet(oldValidatorSet: ValidatorSet, validator: Node, newVotingPower: int): ValidatorSet =
+ if (newVotingPower > 0)
+ oldValidatorSet.put(validator, newVotingPower)
+ else
+ oldValidatorSet.mapRemove(validator)
- // the main step action
- action step: bool = any {
- AdvanceTime,
- nondet node = oneOf(Nodes)
- nondet amount = oneOf(1.to(10))
- votingPowerChange(node, amount),
- recvPacketOnProvider(oneOf(ConsumerChains)),
- recvPacketOnConsumer(oneOf(ConsumerChains)),
- nondet chain = oneOf(chains)
- endAndBeginBlock(chain),
- val shouldAdvance = ConsumerChains.mapBy(
- chain =>
- nondet should = oneOf(Set(true, false))
- should
- )
- AdvanceConsumers(shouldAdvance),
- }
-
- pure val nodePowerSet = Nodes.powerset()
-
- def getArbitraryValidatorSet(): ValidatorSet =
- nondet numValidators = oneOf(1.to(Nodes.size()))
- // toList has nondeterministic behaviour, so this gets arbitrary validators
- nondet validators = oneOf(nodePowerSet.filter(s => s.size() == numValidators))
- validators.mapBy(
- validator =>
- nondet votingPower = oneOf(1.to(10))
- votingPower
- )
-
- // INITIALIZATION
- action init: bool =
- all {
- val validatorSets = chains.mapBy(
- (chain) =>
- // provider chain gets an arbitrary validator set, consumer chains have none
- if (chain == ProviderChain) getArbitraryValidatorSet else Map()
- )
- all {
- votingPowerHistories' = chains.mapBy(
- (chain) =>
- List(validatorSets.get(chain))
- ),
- runningValidatorSet' = validatorSets,
- },
- // each chain starts at time 0
- curChainTimes' = chains.mapBy(
- (chain) => 0
- ),
- // all consumer chains are unused
- consumerStatus' = chains.mapBy(chain => UNUSED),
- // no packets are outstanding
- outstandingPacketsToProvider' = chains.mapBy(chain => List()),
- outstandingPacketsToConsumer' = chains.mapBy(chain => List()),
- // no maturations have been received by provider
- receivedMaturations' = Set(),
- // no packets have been sent to consumers
- sentVSCPackets' = chains.mapBy(chain => Set()),
- // no packets have been received by consumers, so no maturation times set
- maturationTimes' = chains.mapBy(chain => Map()),
- // validator set was not changed yet
- providerValidatorSetChangedInThisBlock' = false
- }
+ // the power of a validator on the provider chain is changed to the given amount. We do not care how this happens,
+ // e.g. via undelegations, or delegations, ...
+ pure def votingPowerChange(currentState: ProtocolState, validator: Node, amount: int): Result =
+ // adjust the current validator set on the provider - it is not entered in the voting history yet because that happens only on block end
+ val currentValidatorSet = currentState.providerState.currentValidatorSet
+ // val newValidatorSet = getUpdatedValidatorSet(currentValidatorSet, validator, amount)
+ // val newProviderState = currentState.providerState.with(
+ // "currentValidatorSet", newValidatorSet
+ // )
+ // val newState = currentState.with(
+ // "providerState", newProviderState
+ // )
+ Err("not implemented")
+}
- // PROPERTIES
-
- // Every validator set on any consumer chain MUST either be or
- // have been a validator set on the provider chain.
- val ValidatorSetReplication: bool =
- chains.forall(
- chain => chain.getCurrentValidatorSet().wasValidatorSetOnProvider()
- )
-
- // TESTS
- run VSCHappyPathTest: bool = {
- init
- // trigger a votingPowerChange on the provider chain
- .then(votingPowerChange("A", 10))
- // endAndBeginBlock on provider. No consumer chains are running, so no packets are sent
- .then(endAndBeginBlock(ProviderChain))
- .then(all {
- // no packet was sent
- assert(outstandingPacketsToConsumer.get("chain1").length() == 0),
- // advance chain1 to running
- AdvanceConsumers(NoStatusAdvancement.set("chain1", true))
- })
- // consumer chain should have current validator set from provider
- .then(
- all {
- // since consumer chain just started, its assumed to have the validator set from provider
- assert(runningValidatorSet.get("chain1") == runningValidatorSet.get(ProviderChain)),
- // trigger a votingPowerChange on the provider chain
- votingPowerChange("B", 10)
- }
- )
- .then(
- val valSet = runningValidatorSet.get(ProviderChain)
- endAndBeginBlock(ProviderChain)
- // now the provider should send a packet on block end
- .then(all {
- // a packet was sent
- assert(outstandingPacketsToConsumer.get("chain1").length() == 1),
- // deliver the packet to the consumer
- recvPacketOnConsumer("chain1")
- })
- .then(
- // consumer needs to end a block before it has the new validator set
- endAndBeginBlock("chain1")
- )
- .then(all {
- // the consumer should have the new validator set
- assert(runningValidatorSet.get("chain1") == valSet),
- // put a last action to satisfy the action effect
- AdvanceConsumers(NoStatusAdvancement)
- })
- )
- }
+module ccv_tests {
+ import ccv_logic.*
+
+ // // UTILITY FUNCTIONS & ACTIONS
+ // def wasValidatorSetOnProvider(validatorSet: ValidatorSet): bool = {
+ // votingPowerHistories.get(ProviderChain).toSet().exists(
+ // historicalValSet => historicalValSet == validatorSet
+ // )
+ // }
+
+ // def getCurrentValidatorSet(chain: Chain): ValidatorSet =
+ // votingPowerHistories.get(chain).head()
+
+ // // returns true if the consumer has timed out and should be dropped
+ // def consumerTimedOut(consumer: Chain): bool =
+ // any {
+ // // either a package from provider to consumer has timed out
+ // outstandingPacketsToConsumer.get(consumer).select(
+ // packet => packet.timeout <= curChainTimes.get(consumer)
+ // ).length() > 0,
+ // // or a package from consumer to provider has timed out
+ // outstandingPacketsToProvider.get(consumer).select(
+ // packet => packet.timeout <= curChainTimes.get(ProviderChain)
+ // ).length() > 0,
+ // // or the inactivity timeout has passed since a VSCPacket was sent to the consumer, but the
+ // // provider has not received a VSCMaturedPacket for it
+ // val packetsWithoutResponse = sentVSCPackets.get(consumer).filter( // get packets without response
+ // packet =>
+ // not(receivedMaturations.exists(
+ // maturedPacket => maturedPacket.id == packet.id
+ // ))
+ // )
+ // // among those, get packets where inactivity timeout has passed
+ // packetsWithoutResponse.filter(
+ // packet =>
+ // val sentAt = curChainTimes.get(ProviderChain) - PacketTimeout // compute when the packet was sent
+ // val timesOutAt = sentAt + InactivityTimeout // compute when the packet times out
+ // timesOutAt <= curChainTimes.get(ProviderChain)
+ // ).size() > 0
+ // }
+
+ // // utility action that leaves all provider state untouched
+ // action PROVIDER_NOOP(): bool =
+ // all {
+ // receivedMaturations' = receivedMaturations,
+ // }
+
+ // // utility action that leaves all consumer state untouched
+ // action CONSUMER_NOOP(): bool =
+ // all {
+ // maturationTimes' = maturationTimes,
+ // }
+
+ // // MODEL ACTIONS
+
+ // // the power of a validator on the provider chain is changed to the given amount. We do not care how this happens,
+ // // e.g. via undelegations, or delegations, ...
+ // action doVotingPowerChange(validator: Node, amount: int): bool =
+ // // for the provider chain, we need to adjust the voting power history
+ // // by adding a new set
+ // all {
+ // amount >= 0,
+ // val newValidatorSet = getCurrentValidatorSet(ProviderChain).getUpdatedValidatorSet(validator, amount)
+ // // set the running validator set on the provider chain, but don't update the history yet
+ // runningValidatorSet' = runningValidatorSet.set(ProviderChain, newValidatorSet),
+ // // no packets are sent yet, these are only sent on endAndBeginBlock
+ // RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
+ // outstandingPacketsToProvider' = outstandingPacketsToProvider,
+ // receivedMaturations' = receivedMaturations,
+ // CONSUMER_NOOP,
+ // // voting power history is only updated on endAndBeginBlock
+ // votingPowerHistories' = votingPowerHistories,
+ // // consumer statusses do not change
+ // consumerStatus' = consumerStatus,
+ // // chain times do not change
+ // curChainTimes' = curChainTimes,
+ // // the validator set is considered to have changed
+ // providerValidatorSetChangedInThisBlock' = true,
+ // }
+
+ // // deliver the next outstanding packet from the consumer to the provider.
+ // // since this model assumes a single provider chain, this just takes a single chain as argument.
+ // action recvPacketOnProvider(consumer: Chain): bool = all {
+ // // ensure there is a packet to be received
+ // outstandingPacketsToProvider.get(consumer).length() > 0,
+ // // remove packet from outstanding packets
+ // val newPacketQueue = outstandingPacketsToProvider.get(consumer).tail()
+ // outstandingPacketsToProvider' = outstandingPacketsToProvider.set(consumer, newPacketQueue),
+ // // register the packet as received
+ // val maturedPacket = outstandingPacketsToProvider.get(consumer).head()
+ // receivedMaturations' = receivedMaturations.add(maturedPacket),
+ // CONSUMER_NOOP,
+ // RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
+ // votingPowerHistories' = votingPowerHistories,
+ // // no validator set changes are made
+ // runningValidatorSet' = runningValidatorSet,
+ // // consumer statusses do not change
+ // consumerStatus' = consumerStatus,
+ // // chain times do not change
+ // curChainTimes' = curChainTimes,
+ // // the validator set was not changed by this action (but might have been changed before in this block)
+ // providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
+ // }
+
+ // // deliver the next outstanding packet from the provider to the consumer.
+ // // since this model assumes a single provider chain, this just takes a single chain as argument.
+ // action recvPacketOnConsumer(consumer: Chain): bool = all {
+ // // ensure there is a packet to be received
+ // outstandingPacketsToConsumer.get(consumer).length() > 0,
+ // // remove packet from outstanding packets
+ // val newPacketQueue = outstandingPacketsToConsumer.get(consumer).tail()
+ // val newOutstandingPackets = outstandingPacketsToConsumer.set(consumer, newPacketQueue)
+ // RegisterNewOutstandingPackets(newOutstandingPackets),
+ // val packet = outstandingPacketsToConsumer.get(consumer).head()
+ // all {
+ // // update the running validator set, but not the history yet,
+ // // as that only happens when the next block is started
+ // runningValidatorSet' = runningValidatorSet.set(consumer, packet.validatorSet),
+ // // add the new packet and store its maturation time
+ // val newMaturationTimes = maturationTimes.get(consumer).put(packet, curChainTimes.get(consumer) + UnbondingPeriod.get(consumer))
+ // maturationTimes' = maturationTimes.set(consumer, newMaturationTimes)
+ // },
+ // PROVIDER_NOOP,
+ // votingPowerHistories' = votingPowerHistories,
+ // outstandingPacketsToProvider' = outstandingPacketsToProvider,
+ // // consumer statusses do not change
+ // consumerStatus' = consumerStatus,
+ // // chain times do not change
+ // curChainTimes' = curChainTimes,
+ // // the validator set was not changed by this action (but might have been changed before in this block)
+ // providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
+ // }
+
+ // // ends the current block and starts the next block for a given chain.
+ // action endAndBeginBlock(chain: Chain): bool = any {
+ // all {
+ // chain == ProviderChain,
+ // endAndBeginBlockForProvider,
+ // },
+ // all {
+ // chain != ProviderChain,
+ // endAndBeginBlockForConsumer(chain),
+ // }
+ // }
+
+ // // gets the updated history for the current chain when ending a block, i.e. the
+ // // running validator set is added to the history if different from the last one.
+ // def getUpdatedHistory(chain: Chain): List[ValidatorSet] =
+ // // update voting power history if the validator set changed
+ // val newValidatorSet = runningValidatorSet.get(ProviderChain)
+ // val oldValidatorSet = votingPowerHistories.get(ProviderChain).head()
+ // if (newValidatorSet != oldValidatorSet)
+ // votingPowerHistories.get(ProviderChain).prepend(newValidatorSet)
+ // else
+ // votingPowerHistories.get(ProviderChain)
+
+
+ // action endAndBeginBlockForProvider(): bool = all {
+ // // update the voting power history
+ // votingPowerHistories' = votingPowerHistories.set(ProviderChain, getUpdatedHistory(ProviderChain)),
+ // // the running validator set is now for sure the current validator set,
+ // // so start with it in the next block
+ // runningValidatorSet' = runningValidatorSet,
+ // // send VSCPackets to consumers
+ // val newOutstandingPackets =
+ // // if running validator set is considered to have changed
+ // if (providerValidatorSetChangedInThisBlock)
+ // // then send a packet to each running consumer
+ // outstandingPacketsToConsumer.keys().mapBy(
+ // (consumer) =>
+ // val packetQueue = outstandingPacketsToConsumer.get(consumer)
+ // if (consumerStatus.get(consumer) == RUNNING) {
+ // packetQueue.append(
+ // {
+ // id: packetQueue.length(),
+ // validatorSet: runningValidatorSet.get(ProviderChain),
+ // timeout: curChainTimes.get(ProviderChain) + PacketTimeout
+ // }
+ // )
+ // } else {
+ // packetQueue
+ // }
+ // )
+ // else
+ // // otherwise, don't send any packets
+ // outstandingPacketsToConsumer
+ // RegisterNewOutstandingPackets(newOutstandingPackets),
+ // CONSUMER_NOOP,
+ // // no packets are sent to the provider
+ // outstandingPacketsToProvider' = outstandingPacketsToProvider,
+ // // do not receive any maturations
+ // receivedMaturations' = receivedMaturations,
+ // // consumer statusses do not change
+ // consumerStatus' = consumerStatus,
+ // // chain times do not change
+ // curChainTimes' = curChainTimes,
+ // // the validator set was definitely not changed in the new block yet, so set to false
+ // providerValidatorSetChangedInThisBlock' = false
+ // }
+
+ // action endAndBeginBlockForConsumer(consumer: Chain): bool = all {
+ // ConsumerChains.contains(consumer),
+ // // update the voting power history
+ // votingPowerHistories' = votingPowerHistories.set(consumer, getUpdatedHistory(consumer)),
+ // // the running validator set is now for sure the current validator set,
+ // // so start with it in the next block
+ // runningValidatorSet' = runningValidatorSet,
+ // // compute mature packets whose maturation time has passed
+ // val maturedPackets = maturationTimes.get(consumer).keys().filter(
+ // packet =>
+ // val maturationTime = maturationTimes.get(consumer).get(packet)
+ // maturationTime <= curChainTimes.get(consumer)
+ // )
+ // all {
+ // // remove matured packets from the maturation times
+ // maturationTimes' = maturationTimes.set(consumer, maturationTimes.get(consumer).mapRemoveAll(maturedPackets)),
+ // // send matured packets
+ // outstandingPacketsToProvider' = outstandingPacketsToProvider.set(
+ // consumer,
+ // // construct VSCMaturedPackets from the matured VSCPackets
+ // outstandingPacketsToProvider.get(consumer).concat(
+ // maturedPackets.map(packet => {id: packet.id, timeout: 5}).toList()
+ // )
+ // )
+ // },
+ // PROVIDER_NOOP,
+ // // no packets are sent to consumer or received by it
+ // RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
+ // // consumer statusses do not change
+ // consumerStatus' = consumerStatus,
+ // // chain times do not change
+ // curChainTimes' = curChainTimes,
+ // // the validator set was not changed by this action (but might have been changed before in this block)
+ // // also, this is only a new block for a consumer, so the change variable shouldn't be reset
+ // providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
+ // }
+
+ // // advance timestamps for maps nondeterministically
+ // action AdvanceTime(): bool =
+ // val advanceAmounts = curChainTimes.keys().mapBy(
+ // chain =>
+ // nondet amount = oneOf(1.to(10))
+ // amount
+ // )
+ // AdvanceTimeByMap(advanceAmounts)
+
+ // // the timestamp for each chain is advanced by the given amount
+ // action AdvanceTimeByMap(advancementAmount: Chain -> int): bool = all
+ // {
+ // curChainTimes' = curChainTimes.keys().mapBy(
+ // chain =>
+ // curChainTimes.get(chain) + advancementAmount.get(chain)
+ // ),
+ // // all other variables are left untouched
+ // votingPowerHistories' = votingPowerHistories,
+ // runningValidatorSet' = runningValidatorSet,
+ // outstandingPacketsToProvider' = outstandingPacketsToProvider,
+ // RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
+ // receivedMaturations' = receivedMaturations,
+ // maturationTimes' = maturationTimes,
+ // // chain times do not change
+ // consumerStatus' = consumerStatus,
+ // // the validator set was not changed by this action (but might have been changed before in this block)
+ // providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
+ // }
+
+ // // each consumer chain may advance in the order
+ // // some events may necessitate a transition, e.g. timeouts.
+ // // shouldAdvance gives, for each consumer chain, whether it should advance if possible.
+ // // if a chain has to advance, e.g. due to timeouts, or may not advance, the value will have no effect.
+ // action AdvanceConsumers(shouldAdvance: Chain -> bool): bool =
+ // val newConsumerStatus = consumerStatus.keys().mapBy(
+ // chain =>
+ // val curStatus = consumerStatus.get(chain)
+ // if (curStatus == UNUSED) {
+ // if (shouldAdvance.get(chain))
+ // {
+ // RUNNING
+ // } else {
+ // UNUSED
+ // }
+ // }
+ // else if (curStatus == RUNNING) {
+ // // the chain may transition to stopped.
+ // // it is *forced* to stop if a packet timed out,
+ // // or if the inactivity timeout has passed
+ // if(consumerTimedOut(chain)) {
+ // STOPPED
+ // } else {
+ // if (shouldAdvance.get(chain)) {
+ // RUNNING
+ // } else {
+ // STOPPED
+ // }
+ // }
+ // } else {
+ // // stopped chains cannot restart, we assume a new chain would be started in that case
+ // STOPPED
+ // }
+ // )
+ // all {
+ // consumerStatus' = newConsumerStatus,
+ // // all other variables are left untouched
+ // votingPowerHistories' = votingPowerHistories,
+ // runningValidatorSet' = runningValidatorSet.keys().mapBy(
+ // chain =>
+ // if (newConsumerStatus.get(chain) == RUNNING and consumerStatus.get(chain) == UNUSED)
+ // // consumers that went from unused to running start with the current validator set on the provider
+ // {
+ // runningValidatorSet.get(ProviderChain)
+ // } else {
+ // runningValidatorSet.get(chain)
+ // }
+ // ),
+ // outstandingPacketsToProvider' = outstandingPacketsToProvider,
+ // RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
+ // receivedMaturations' = receivedMaturations,
+ // maturationTimes' = maturationTimes,
+ // // chain times do not change
+ // curChainTimes' = curChainTimes,
+ // // the validator set was not changed by this action (but might have been changed before in this block)
+ // providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
+ // }
+
+ // // Updates the outstandingPacketsToConsumer and sentVSCPackets variables
+ // action RegisterNewOutstandingPackets(newOutstandingPackets: Chain -> List[VSCPacket]): bool =
+ // all {
+ // outstandingPacketsToConsumer' = newOutstandingPackets,
+ // StoreSentPackets(newOutstandingPackets),
+ // }
+
+
+ // // stores the VSCPackets sent in this step in sentVSCPackets
+ // action StoreSentPackets(newOutstandingPackets: Chain -> List[VSCPacket]): bool =
+ // sentVSCPackets' = sentVSCPackets.keys().mapBy(
+ // (chain) =>
+ // sentVSCPackets.get(chain).union(newOutstandingPackets.get(chain).toSet())
+ // )
+
+ // // the main step action
+ // action step: bool = any {
+ // AdvanceTime,
+ // nondet node = oneOf(Nodes)
+ // nondet amount = oneOf(1.to(10))
+ // votingPowerChange(node, amount),
+ // recvPacketOnProvider(oneOf(ConsumerChains)),
+ // recvPacketOnConsumer(oneOf(ConsumerChains)),
+ // nondet chain = oneOf(chains)
+ // endAndBeginBlock(chain),
+ // val shouldAdvance = ConsumerChains.mapBy(
+ // chain =>
+ // nondet should = oneOf(Set(true, false))
+ // should
+ // )
+ // AdvanceConsumers(shouldAdvance),
+ // }
+
+ // pure val nodePowerSet = Nodes.powerset()
+
+ // def getArbitraryValidatorSet(): ValidatorSet =
+ // nondet numValidators = oneOf(1.to(Nodes.size()))
+ // // toList has nondeterministic behaviour, so this gets arbitrary validators
+ // nondet validators = oneOf(nodePowerSet.filter(s => s.size() == numValidators))
+ // validators.mapBy(
+ // validator =>
+ // nondet votingPower = oneOf(1.to(10))
+ // votingPower
+ // )
+
+ // // INITIALIZATION
+ // action init: bool =
+ // all {
+ // val validatorSets = chains.mapBy(
+ // (chain) =>
+ // // provider chain gets an arbitrary validator set, consumer chains have none
+ // if (chain == ProviderChain) getArbitraryValidatorSet else Map()
+ // )
+ // all {
+ // votingPowerHistories' = chains.mapBy(
+ // (chain) =>
+ // List(validatorSets.get(chain))
+ // ),
+ // runningValidatorSet' = validatorSets,
+ // },
+ // // each chain starts at time 0
+ // curChainTimes' = chains.mapBy(
+ // (chain) => 0
+ // ),
+ // // all consumer chains are unused
+ // consumerStatus' = chains.mapBy(chain => UNUSED),
+ // // no packets are outstanding
+ // outstandingPacketsToProvider' = chains.mapBy(chain => List()),
+ // outstandingPacketsToConsumer' = chains.mapBy(chain => List()),
+ // // no maturations have been received by provider
+ // receivedMaturations' = Set(),
+ // // no packets have been sent to consumers
+ // sentVSCPackets' = chains.mapBy(chain => Set()),
+ // // no packets have been received by consumers, so no maturation times set
+ // maturationTimes' = chains.mapBy(chain => Map()),
+ // // validator set was not changed yet
+ // providerValidatorSetChangedInThisBlock' = false
+ // }
+
+ // // PROPERTIES
+
+ // // Every validator set on any consumer chain MUST either be or
+ // // have been a validator set on the provider chain.
+ // val ValidatorSetReplication: bool =
+ // chains.forall(
+ // chain => chain.getCurrentValidatorSet().wasValidatorSetOnProvider()
+ // )
+
+ // // TESTS
+ // run VSCHappyPathTest: bool = {
+ // init
+ // // trigger a votingPowerChange on the provider chain
+ // .then(votingPowerChange("A", 10))
+ // // endAndBeginBlock on provider. No consumer chains are running, so no packets are sent
+ // .then(endAndBeginBlock(ProviderChain))
+ // .then(all {
+ // // no packet was sent
+ // assert(outstandingPacketsToConsumer.get("chain1").length() == 0),
+ // // advance chain1 to running
+ // AdvanceConsumers(NoStatusAdvancement.set("chain1", true))
+ // })
+ // // consumer chain should have current validator set from provider
+ // .then(
+ // all {
+ // // since consumer chain just started, its assumed to have the validator set from provider
+ // assert(runningValidatorSet.get("chain1") == runningValidatorSet.get(ProviderChain)),
+ // // trigger a votingPowerChange on the provider chain
+ // votingPowerChange("B", 10)
+ // }
+ // )
+ // .then(
+ // val valSet = runningValidatorSet.get(ProviderChain)
+ // endAndBeginBlock(ProviderChain)
+ // // now the provider should send a packet on block end
+ // .then(all {
+ // // a packet was sent
+ // assert(outstandingPacketsToConsumer.get("chain1").length() == 1),
+ // // deliver the packet to the consumer
+ // recvPacketOnConsumer("chain1")
+ // })
+ // .then(
+ // // consumer needs to end a block before it has the new validator set
+ // endAndBeginBlock("chain1")
+ // )
+ // .then(all {
+ // // the consumer should have the new validator set
+ // assert(runningValidatorSet.get("chain1") == valSet),
+ // // put a last action to satisfy the action effect
+ // AdvanceConsumers(NoStatusAdvancement)
+ // })
+ // )
+ // }
+
+ // // utility: the set of consumers currently running
+ // val RunningConsumers: Set[Chain] =
+ // ConsumerChains.filter(chain => consumerStatus.get(chain) == RUNNING)
+
+ // // MODEL STATE
+ // // --SHARED STATE
+
+ // // Stores, for each chain, the list of voting powers that corresponded to voting powers
+ // // at blocks over its entire existence.
+ // // Voting powers should be ordered by recency in descending order.
+ // var votingPowerHistories: Chain -> List[ValidatorSet]
+
+ // // the current validator set on each chain.
+ // // this will be included in the next block, but might not be final yet,
+ // // e.g. there may be more modifications in the current block.
+ // var runningValidatorSet: Chain -> ValidatorSet
+
+ // // the current timestamp for each chain
+ // var curChainTimes: Chain -> Timestamp
+
+ // // stores, for each chain, its current status -
+ // // unused, running, or stopped
+ // var consumerStatus: Chain -> str
+
+ // // --CHANNELS
+ // // Stores, for each consumer chain, the list of packets that have been sent to the provider chain
+ // // and have not been received yet.
+ // var outstandingPacketsToProvider: Chain -> List[VSCMaturedPacket]
+
+ // // Stores, for each consumer chain, the list of packets that have been sent to the consumer chain
+ // // and have not been received yet.
+ // var outstandingPacketsToConsumer: Chain -> List[VSCPacket]
+
+
+ // // --CONSUMER STATE
+ // // Stores the maturation times for VSCPackets received by consumers
+ // var maturationTimes: Chain -> (VSCPacket -> Timestamp)
+
+ // // --PROVIDER STATE
+ // // the set of VSCMaturedPackets received by the provider chain
+ // var receivedMaturations: Set[VSCMaturedPacket]
+
+ // // stores which VSC Packets have been sent to compare with receivedMaturations to detect timeouts due to non-responsiveness
+ // var sentVSCPackets: Chain -> Set[VSCPacket]
+
+ // // stores whether, in this step, the validator set considered to be changed.
+ // // this is needed because the validator set might be considered to have changed, even though
+ // // it is still technically identical at our level of abstraction, e.g. a validator power change on the provider
+ // // might leave the validator set the same because a delegation and undelegation cancel each other out.
+ // var providerValidatorSetChangedInThisBlock: bool
+
+ // // utility: a struct summarizing the current state
+ // val state =
+ // {
+ // votingPowerHistories: votingPowerHistories,
+ // runningValidatorSet: runningValidatorSet,
+ // curChainTimes: curChainTimes,
+ // consumerStatus: consumerStatus,
+ // outstandingPacketsToProvider: outstandingPacketsToProvider,
+ // outstandingPacketsToConsumer: outstandingPacketsToConsumer,
+ // maturationTimes: maturationTimes,
+ // receivedMaturations: receivedMaturations,
+ // sentVSCPackets: sentVSCPackets,
+ // }
+
+ // // set of identifiers of potential nodes
+ // pure val Nodes: Set[Node] =
+ // Set("A", "B", "C", "D", "E", "F", "G", "H", "I", "J")
+
+ // // the set of consumer chains
+ // pure val ConsumerChains: Set[Chain] =
+ // Set("chain1", "chain2", "chain3")
+
+ // // The singular provider chain.
+ // pure val ProviderChain: Chain =
+ // "provider"
+
+ // pure val chains = ConsumerChains.union(Set(ProviderChain))
+
+ // // length of the unbonding period on each chain
+ // pure val UnbondingPeriod: Chain -> int = chains.mapBy(
+ // (chain) =>
+ // 10
+ // )
+
+ // // the time until a packet times out
+ // pure val PacketTimeout: int =
+ // 5
+
+ // // the time until a consumer chain is dropped by the provider due to inactivity
+ // pure val InactivityTimeout: int =
+ // 10
+
+ // // utility: a map assigning each chain to 0, used for not advancing timestamps
+ // pure val NoTimeAdvancement: Chain -> int = chains.mapBy(
+ // (chain) =>
+ // 0
+ // )
+
+
+ // // utility: a map assigning each chain to false, used for not advancing consumer status
+ // pure val NoStatusAdvancement: Chain -> bool = chains.mapBy(
+ // (chain) =>
+ // false
+ // )
}
diff --git a/tests/difference/core/quint_model/extraSpells.qnt b/tests/difference/core/quint_model/extraSpells.qnt
index cd2105beb7..5c98f7a384 100644
--- a/tests/difference/core/quint_model/extraSpells.qnt
+++ b/tests/difference/core/quint_model/extraSpells.qnt
@@ -1,4 +1,3 @@
--*- mode: Bluespec; -*-
// This module is just a library with utility functions (sometimes called spells in Quint).
module extraSpells {
diff --git a/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123337-6230.fail b/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123337-6230.fail
new file mode 100644
index 0000000000..f2c09a9e15
--- /dev/null
+++ b/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123337-6230.fail
@@ -0,0 +1,18 @@
+# 2023/09/12 12:33:37 TestActionMarshalling [rapid] draw Action: main.StartChainAction{Chain:"", Validators:[]main.StartChainValidator{}, GenesisChanges:"", SkipGentx:false}
+# 2023/09/12 12:33:37 TestActionMarshalling error marshalling and unmarshalling chain state: any(
+# - main.StartChainAction{Validators: []main.StartChainValidator{}},
+# + map[string]any{
+# + "Chain": string(""),
+# + "GenesisChanges": string(""),
+# + "SkipGentx": bool(false),
+# + "Validators": []any{},
+# + },
+# )
+#
+v0.4.8#6863100771198181688
+0x0
+0x1
+0x0
+0x0
+0x0
+0x0
\ No newline at end of file
diff --git a/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123517-6794.fail b/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123517-6794.fail
new file mode 100644
index 0000000000..9547963143
--- /dev/null
+++ b/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123517-6794.fail
@@ -0,0 +1,14 @@
+# 2023/09/12 12:35:17 TestActionMarshalling [rapid] draw Action: main.SendTokensAction{Chain:"", From:"", To:"", Amount:0x0}
+# 2023/09/12 12:35:17 TestActionMarshalling error marshalling and unmarshalling action: got (+), want (-): Â Â any(
+# -Â main.SendTokensAction{},
+# +Â map[string]any{"Amount": float64(0), "Chain": string(""), "From": string(""), "To": string("")},
+# Â Â )
+#
+v0.4.8#14250065908211800578
+0x0
+0x0
+0x0
+0x0
+0x0
+0x0
+0x0
\ No newline at end of file
diff --git a/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123556-6970.fail b/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123556-6970.fail
new file mode 100644
index 0000000000..4ddf365f5c
--- /dev/null
+++ b/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123556-6970.fail
@@ -0,0 +1,14 @@
+# 2023/09/12 12:35:56 TestActionMarshalling [rapid] draw Action: main.SendTokensAction{Chain:"", From:"", To:"", Amount:0x0}
+# 2023/09/12 12:35:56 TestActionMarshalling error marshalling and unmarshalling action: got (-), want (+): Â Â any(
+# -Â main.SendTokensAction{},
+# +Â map[string]any{"Amount": float64(0), "Chain": string(""), "From": string(""), "To": string("")},
+# Â Â )
+#
+v0.4.8#9176735344445930654
+0x0
+0x0
+0x0
+0x0
+0x0
+0x0
+0x0
\ No newline at end of file
diff --git a/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123609-7116.fail b/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123609-7116.fail
new file mode 100644
index 0000000000..89dcbcc8b2
--- /dev/null
+++ b/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123609-7116.fail
@@ -0,0 +1,18 @@
+# 2023/09/12 12:36:09 TestActionMarshalling [rapid] draw Action: main.StartChainAction{Chain:"", Validators:[]main.StartChainValidator{}, GenesisChanges:"", SkipGentx:false}
+# 2023/09/12 12:36:09 TestActionMarshalling error marshalling and unmarshalling action: got (-), want (+): any(
+# - main.StartChainAction{Validators: []main.StartChainValidator{}},
+# + map[string]any{
+# + "Chain": string(""),
+# + "GenesisChanges": string(""),
+# + "SkipGentx": bool(false),
+# + "Validators": []any{},
+# + },
+# )
+#
+v0.4.8#17927886955469684979
+0x0
+0x1
+0x0
+0x0
+0x0
+0x0
\ No newline at end of file
diff --git a/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123822-8026.fail b/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123822-8026.fail
new file mode 100644
index 0000000000..d2493d87a3
--- /dev/null
+++ b/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123822-8026.fail
@@ -0,0 +1,8 @@
+# 2023/09/12 12:38:22 TestActionMarshalling [rapid] draw Action: main.addChainToRelayerAction{Chain:"", Validator:""}
+# 2023/09/12 12:38:22 TestActionMarshalling error marshalling and unmarshalling action: error unmarshalling action inside step: unknown action name: main.addChainToRelayerAction
+#
+v0.4.8#17613601115647214278
+0x9867f1f40f739
+0x9
+0x0
+0x0
\ No newline at end of file
diff --git a/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230913151826-12656.fail b/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230913151826-12656.fail
new file mode 100644
index 0000000000..10fd2ac639
--- /dev/null
+++ b/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230913151826-12656.fail
@@ -0,0 +1,8 @@
+# 2023/09/13 15:18:26 TestActionMarshalling [rapid] draw Action: main.lightClientEquivocationAttackAction{Validator:"", Chain:""}
+# 2023/09/13 15:18:26 TestActionMarshalling error marshalling and unmarshalling action: error unmarshalling action inside step: unknown action name: main.lightClientEquivocationAttackAction
+#
+v0.4.8#7694661539125204314
+0xc05c654ab866b
+0x1f
+0x0
+0x0
\ No newline at end of file
diff --git a/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230913151917-13146.fail b/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230913151917-13146.fail
new file mode 100644
index 0000000000..6fb9bfa9f7
--- /dev/null
+++ b/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230913151917-13146.fail
@@ -0,0 +1,8 @@
+# 2023/09/13 15:19:17 TestActionMarshalling [rapid] draw Action: main.lightClientLunaticAttackAction{Validator:"", Chain:""}
+# 2023/09/13 15:19:17 TestActionMarshalling error marshalling and unmarshalling action: error unmarshalling action inside step: unknown action name: main.lightClientLunaticAttackAction
+#
+v0.4.8#10796173543550944397
+0x1f2b5a0e61c3a6
+0x0
+0x0
+0x0
\ No newline at end of file
diff --git a/tests/e2e/testdata/rapid/TestReadAndWriteTrace/TestReadAndWriteTrace-20230913151920-13146.fail b/tests/e2e/testdata/rapid/TestReadAndWriteTrace/TestReadAndWriteTrace-20230913151920-13146.fail
new file mode 100644
index 0000000000..f8e2127feb
--- /dev/null
+++ b/tests/e2e/testdata/rapid/TestReadAndWriteTrace/TestReadAndWriteTrace-20230913151920-13146.fail
@@ -0,0 +1,11 @@
+# 2023/09/13 15:19:20 TestReadAndWriteTrace [rapid] draw Trace: []main.Step{main.Step{Action:main.lightClientEquivocationAttackAction{Validator:"", Chain:""}, State:main.State{}}}
+# 2023/09/13 15:19:20 TestReadAndWriteTrace error writing and reading trace: error reading trace from file: unknown action name: main.lightClientEquivocationAttackAction
+#
+v0.4.8#17668525343613541116
+0x5555555555555
+0xc05c654ab866b
+0x1f
+0x0
+0x0
+0x0
+0x0
\ No newline at end of file
From 5fa449c8d8684b52146b2a765988ec44883b92a4 Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Mon, 18 Sep 2023 18:23:42 +0200
Subject: [PATCH 12/35] Revert "Start rewriting model"
This reverts commit 1320b9517982bff596ecfc0ac99755dc75c5f1e1.
---
tests/difference/core/quint_model/ccv.qnt | 1241 ++++++++---------
.../core/quint_model/extraSpells.qnt | 1 +
...ActionMarshalling-20230912123337-6230.fail | 18 -
...ActionMarshalling-20230912123517-6794.fail | 14 -
...ActionMarshalling-20230912123556-6970.fail | 14 -
...ActionMarshalling-20230912123609-7116.fail | 18 -
...ActionMarshalling-20230912123822-8026.fail | 8 -
...ctionMarshalling-20230913151826-12656.fail | 8 -
...ctionMarshalling-20230913151917-13146.fail | 8 -
...eadAndWriteTrace-20230913151920-13146.fail | 11 -
10 files changed, 551 insertions(+), 790 deletions(-)
delete mode 100644 tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123337-6230.fail
delete mode 100644 tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123517-6794.fail
delete mode 100644 tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123556-6970.fail
delete mode 100644 tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123609-7116.fail
delete mode 100644 tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123822-8026.fail
delete mode 100644 tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230913151826-12656.fail
delete mode 100644 tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230913151917-13146.fail
delete mode 100644 tests/e2e/testdata/rapid/TestReadAndWriteTrace/TestReadAndWriteTrace-20230913151920-13146.fail
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index 4cbc327ac4..d18930ae1d 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -1,4 +1,5 @@
-module ccv_logic {
+-*- mode: Bluespec; -*-
+module ccv {
import extraSpells.* from "./extraSpells"
// Things that are not modelled:
@@ -11,17 +12,12 @@ module ccv_logic {
// * VSC packets mature
// * Consumers can be forcefully dropped due to timeouts, or stop on their own volition
- // ===================
// TYPE DEFINITIONS
- // ===================
type Node = str
type Chain = str
- type Power = int
- type ValidatorSet = Node -> Power
+ type ValidatorSet = Node -> int
type Height = int
type Timestamp = int
- // a list of validator sets per blocks, ordered by recency
- type VotingPowerHistory = List[ValidatorSet]
// --PACKETS
type VSCPacket =
@@ -42,719 +38,582 @@ module ccv_logic {
timeout: Timestamp
}
- // possible consumer statuses
- pure val STOPPED = "stopped" // the chain was once a consumer chain, but has been dropped by the provider.
- pure val RUNNING = "running" //Â the chain is currently a consumer chain. Running chains are those that get sent VSCPackets.
- pure val UNUSED = "unused" // the chain has never been a consumer chain, and is available to become one.
- // When a chain is dropped, it cannot become a consumer again - we assume that would be done by another consumer becoming running.
-
-
- // state that each chain needs to store, whether consumer or provider.
- type ChainState = {
- // Stores the list of voting powers that corresponded to voting powers
- // at blocks over the chains entire existence.
- // Voting powers should be ordered by recency in descending order.
- votingPowerHistory: VotingPowerHistory,
-
- // the current validator set on each chain.
- // this will be included in the next block, but might not be final yet,
- // e.g. there may be more modifications in the current block.
- currentValidatorSet: ValidatorSet,
-
- // the latest timestamp that was comitted on chain
- lastTimestamp: Timestamp,
- }
+ // MODEL PARAMETERS
+
+
+ // set of identifiers of potential nodes
+ pure val Nodes: Set[Node] =
+ Set("A", "B", "C", "D", "E", "F", "G", "H", "I", "J")
+
+ // the set of consumer chains
+ pure val ConsumerChains: Set[Chain] =
+ Set("chain1", "chain2", "chain3")
- // Defines the current state of the provider chain. Essentially, all information here is stored by the provider on-chain (or could be derived purely by information that is on-chain).
- type ProviderState =
+ // The singular provider chain.
+ pure val ProviderChain: Chain =
+ "provider"
+
+ pure val chains = ConsumerChains.union(Set(ProviderChain))
+
+ // length of the unbonding period on each chain
+ pure val UnbondingPeriod: Chain -> int = chains.mapBy(
+ (chain) =>
+ 10
+ )
+
+ // the time until a packet times out
+ pure val PacketTimeout: int =
+ 5
+
+ // the time until a consumer chain is dropped by the provider due to inactivity
+ pure val InactivityTimeout: int =
+ 10
+
+ // consumer statuses
+ pure val STOPPED = "stopped"
+ pure val RUNNING = "running"
+ pure val UNUSED = "unused"
+
+ // utility: a map assigning each chain to 0, used for not advancing timestamps
+ pure val NoTimeAdvancement: Chain -> int = chains.mapBy(
+ (chain) =>
+ 0
+ )
+
+ // utility: a struct summarizing the current state
+ val state =
{
- // the state that each chain needs to store
- chainState: ChainState,
+ votingPowerHistories: votingPowerHistories,
+ runningValidatorSet: runningValidatorSet,
+ curChainTimes: curChainTimes,
+ consumerStatus: consumerStatus,
+ outstandingPacketsToProvider: outstandingPacketsToProvider,
+ outstandingPacketsToConsumer: outstandingPacketsToConsumer,
+ maturationTimes: maturationTimes,
+ receivedMaturations: receivedMaturations,
+ sentVSCPackets: sentVSCPackets,
+ }
+
+
+ // utility: a map assigning each chain to false, used for not advancing consumer status
+ pure val NoStatusAdvancement: Chain -> bool = chains.mapBy(
+ (chain) =>
+ false
+ )
+
+ // utility: the set of consumers currently running
+ val RunningConsumers: Set[Chain] =
+ ConsumerChains.filter(chain => consumerStatus.get(chain) == RUNNING)
+
+ // MODEL STATE
+ // --SHARED STATE
+
+ // Stores, for each chain, the list of voting powers that corresponded to voting powers
+ // at blocks over its entire existence.
+ // Voting powers should be ordered by recency in descending order.
+ var votingPowerHistories: Chain -> List[ValidatorSet]
+
+ // the current validator set on each chain.
+ // this will be included in the next block, but might not be final yet,
+ // e.g. there may be more modifications in the current block.
+ var runningValidatorSet: Chain -> ValidatorSet
+
+ // the current timestamp for each chain
+ var curChainTimes: Chain -> Timestamp
+
+ // stores, for each chain, its current status -
+ // unused, running, or stopped
+ var consumerStatus: Chain -> str
+
+ // --CHANNELS
+ // Stores, for each consumer chain, the list of packets that have been sent to the provider chain
+ // and have not been received yet.
+ var outstandingPacketsToProvider: Chain -> List[VSCMaturedPacket]
- // Stores, for each consumer chain, the list of packets that have been sent to the consumer chain
- // and have not been received yet.
- // In the implementation, this would roughly be the unacknowledged packets on an ibc channel.
- outstandingPacketsToConsumer: Chain -> List[VSCPacket],
+ // Stores, for each consumer chain, the list of packets that have been sent to the consumer chain
+ // and have not been received yet.
+ var outstandingPacketsToConsumer: Chain -> List[VSCPacket]
- // the set of received VSCMaturedPackets
- receivedMaturations: Set[VSCMaturedPacket],
- // stores which VSC Packets have been sent to compare with receivedMaturations to detect timeouts due to non-responsiveness
- sentVSCPackets: Chain -> Set[VSCPacket],
+ // --CONSUMER STATE
+ // Stores the maturation times for VSCPackets received by consumers
+ var maturationTimes: Chain -> (VSCPacket -> Timestamp)
- // stores whether, in this block, the validator set has changed.
- // this is needed because the validator set might be considered to have changed, even though
- // it is still technically identical at our level of abstraction, e.g. a validator power change on the provider
- // might leave the validator set the same because a delegation and undelegation cancel each other out.
- providerValidatorSetChangedInThisBlock: bool,
+ // --PROVIDER STATE
+ // the set of VSCMaturedPackets received by the provider chain
+ var receivedMaturations: Set[VSCMaturedPacket]
+
+ // stores which VSC Packets have been sent to compare with receivedMaturations to detect timeouts due to non-responsiveness
+ var sentVSCPackets: Chain -> Set[VSCPacket]
+
+ // stores whether, in this step, the validator set considered to be changed.
+ // this is needed because the validator set might be considered to have changed, even though
+ // it is still technically identical at our level of abstraction, e.g. a validator power change on the provider
+ // might leave the validator set the same because a delegation and undelegation cancel each other out.
+ var providerValidatorSetChangedInThisBlock: bool
+
+
+ // UTILITY FUNCTIONS & ACTIONS
+ def wasValidatorSetOnProvider(validatorSet: ValidatorSet): bool = {
+ votingPowerHistories.get(ProviderChain).toSet().exists(
+ historicalValSet => historicalValSet == validatorSet
+ )
+ }
+
+ def getCurrentValidatorSet(chain: Chain): ValidatorSet =
+ votingPowerHistories.get(chain).head()
+
+ def getUpdatedValidatorSet(oldValidatorSet: ValidatorSet, validator: Node, newVotingPower: int): ValidatorSet =
+ if (newVotingPower > 0)
+ oldValidatorSet.put(validator, newVotingPower)
+ else
+ oldValidatorSet.mapRemove(validator)
+
+ // returns true if the consumer has timed out and should be dropped
+ def consumerTimedOut(consumer: Chain): bool =
+ any {
+ // either a package from provider to consumer has timed out
+ outstandingPacketsToConsumer.get(consumer).select(
+ packet => packet.timeout <= curChainTimes.get(consumer)
+ ).length() > 0,
+ // or a package from consumer to provider has timed out
+ outstandingPacketsToProvider.get(consumer).select(
+ packet => packet.timeout <= curChainTimes.get(ProviderChain)
+ ).length() > 0,
+ // or the inactivity timeout has passed since a VSCPacket was sent to the consumer, but the
+ // provider has not received a VSCMaturedPacket for it
+ val packetsWithoutResponse = sentVSCPackets.get(consumer).filter( // get packets without response
+ packet =>
+ not(receivedMaturations.exists(
+ maturedPacket => maturedPacket.id == packet.id
+ ))
+ )
+ // among those, get packets where inactivity timeout has passed
+ packetsWithoutResponse.filter(
+ packet =>
+ val sentAt = curChainTimes.get(ProviderChain) - PacketTimeout // compute when the packet was sent
+ val timesOutAt = sentAt + InactivityTimeout // compute when the packet times out
+ timesOutAt <= curChainTimes.get(ProviderChain)
+ ).size() > 0
+ }
+
+ // utility action that leaves all provider state untouched
+ action PROVIDER_NOOP(): bool =
+ all {
+ receivedMaturations' = receivedMaturations,
+ }
- // stores, for each consumer chain, its current status -
- // unused, running, or stopped
- consumerStatus: Chain -> str,
+ // utility action that leaves all consumer state untouched
+ action CONSUMER_NOOP(): bool =
+ all {
+ maturationTimes' = maturationTimes,
}
+
- // Defines the current state of a consumer chain. This information is accessible to that consumer on-chain.
- type ConsumerState = {
- // the state that each chain needs to store
- chainState: ChainState,
+ // MODEL ACTIONS
- // Stores the maturation times for VSCPackets received by this consumer
- maturationTimes: VSCPacket -> Timestamp,
+ // the power of a validator on the provider chain is changed to the given amount. We do not care how this happens,
+ // e.g. via undelegations, or delegations, ...
+ action votingPowerChange(validator: Node, amount: int): bool =
+ // for the provider chain, we need to adjust the voting power history
+ // by adding a new set
+ all {
+ amount >= 0,
+ val newValidatorSet = getCurrentValidatorSet(ProviderChain).getUpdatedValidatorSet(validator, amount)
+ // set the running validator set on the provider chain, but don't update the history yet
+ runningValidatorSet' = runningValidatorSet.set(ProviderChain, newValidatorSet),
+ // no packets are sent yet, these are only sent on endAndBeginBlock
+ RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
+ outstandingPacketsToProvider' = outstandingPacketsToProvider,
+ receivedMaturations' = receivedMaturations,
+ CONSUMER_NOOP,
+ // voting power history is only updated on endAndBeginBlock
+ votingPowerHistories' = votingPowerHistories,
+ // consumer statusses do not change
+ consumerStatus' = consumerStatus,
+ // chain times do not change
+ curChainTimes' = curChainTimes,
+ // the validator set is considered to have changed
+ providerValidatorSetChangedInThisBlock' = true,
+ }
- // Stores the list of packets that have been sent to the provider chain by this consumer
- // and have not been received yet.
- // In the implementation, essentially unacknowledged IBC packets.
- outstandingPacketsToProvider: Chain -> List[VSCMaturedPacket],
+ // deliver the next outstanding packet from the consumer to the provider.
+ // since this model assumes a single provider chain, this just takes a single chain as argument.
+ action recvPacketOnProvider(consumer: Chain): bool = all {
+ // ensure there is a packet to be received
+ outstandingPacketsToProvider.get(consumer).length() > 0,
+ // remove packet from outstanding packets
+ val newPacketQueue = outstandingPacketsToProvider.get(consumer).tail()
+ outstandingPacketsToProvider' = outstandingPacketsToProvider.set(consumer, newPacketQueue),
+ // register the packet as received
+ val maturedPacket = outstandingPacketsToProvider.get(consumer).head()
+ receivedMaturations' = receivedMaturations.add(maturedPacket),
+ CONSUMER_NOOP,
+ RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
+ votingPowerHistories' = votingPowerHistories,
+ // no validator set changes are made
+ runningValidatorSet' = runningValidatorSet,
+ // consumer statusses do not change
+ consumerStatus' = consumerStatus,
+ // chain times do not change
+ curChainTimes' = curChainTimes,
+ // the validator set was not changed by this action (but might have been changed before in this block)
+ providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
+ }
- // Stores the list of voting powers that corresponded to voting powers
- // at blocks over the chains entire existence.
- // Voting powers should be ordered by recency in descending order.
- votingPowerHistory: VotingPowerHistory,
+ // deliver the next outstanding packet from the provider to the consumer.
+ // since this model assumes a single provider chain, this just takes a single chain as argument.
+ action recvPacketOnConsumer(consumer: Chain): bool = all {
+ // ensure there is a packet to be received
+ outstandingPacketsToConsumer.get(consumer).length() > 0,
+ // remove packet from outstanding packets
+ val newPacketQueue = outstandingPacketsToConsumer.get(consumer).tail()
+ val newOutstandingPackets = outstandingPacketsToConsumer.set(consumer, newPacketQueue)
+ RegisterNewOutstandingPackets(newOutstandingPackets),
+ val packet = outstandingPacketsToConsumer.get(consumer).head()
+ all {
+ // update the running validator set, but not the history yet,
+ // as that only happens when the next block is started
+ runningValidatorSet' = runningValidatorSet.set(consumer, packet.validatorSet),
+ // add the new packet and store its maturation time
+ val newMaturationTimes = maturationTimes.get(consumer).put(packet, curChainTimes.get(consumer) + UnbondingPeriod.get(consumer))
+ maturationTimes' = maturationTimes.set(consumer, newMaturationTimes)
+ },
+ PROVIDER_NOOP,
+ votingPowerHistories' = votingPowerHistories,
+ outstandingPacketsToProvider' = outstandingPacketsToProvider,
+ // consumer statusses do not change
+ consumerStatus' = consumerStatus,
+ // chain times do not change
+ curChainTimes' = curChainTimes,
+ // the validator set was not changed by this action (but might have been changed before in this block)
+ providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
}
- // the state of the protocol consists of the composition of the state of one provider chain with potentially many consumer chains.
- type ProtocolState = {
- providerState: ProviderState,
- // the state of each consumer chain.
- // note that we assume that this contains all consumer chains that may ever exist,
- // and consumer chains that are currently not running will have providerState.consumerStatus == UNUSED or STOPPED
- consumerStates: Set[ConsumerState]
+ // ends the current block and starts the next block for a given chain.
+ action endAndBeginBlock(chain: Chain): bool = any {
+ all {
+ chain == ProviderChain,
+ endAndBeginBlockForProvider,
+ },
+ all {
+ chain != ProviderChain,
+ endAndBeginBlockForConsumer(chain),
+ }
}
- type Error = {
- message: str
+ // gets the updated history for the current chain when ending a block, i.e. the
+ // running validator set is added to the history if different from the last one.
+ def getUpdatedHistory(chain: Chain): List[ValidatorSet] =
+ // update voting power history if the validator set changed
+ val newValidatorSet = runningValidatorSet.get(ProviderChain)
+ val oldValidatorSet = votingPowerHistories.get(ProviderChain).head()
+ if (newValidatorSet != oldValidatorSet)
+ votingPowerHistories.get(ProviderChain).prepend(newValidatorSet)
+ else
+ votingPowerHistories.get(ProviderChain)
+
+
+ action endAndBeginBlockForProvider(): bool = all {
+ // update the voting power history
+ votingPowerHistories' = votingPowerHistories.set(ProviderChain, getUpdatedHistory(ProviderChain)),
+ // the running validator set is now for sure the current validator set,
+ // so start with it in the next block
+ runningValidatorSet' = runningValidatorSet,
+ // send VSCPackets to consumers
+ val newOutstandingPackets =
+ // if running validator set is considered to have changed
+ if (providerValidatorSetChangedInThisBlock)
+ // then send a packet to each running consumer
+ outstandingPacketsToConsumer.keys().mapBy(
+ (consumer) =>
+ val packetQueue = outstandingPacketsToConsumer.get(consumer)
+ if (consumerStatus.get(consumer) == RUNNING) {
+ packetQueue.append(
+ {
+ id: packetQueue.length(),
+ validatorSet: runningValidatorSet.get(ProviderChain),
+ timeout: curChainTimes.get(ProviderChain) + PacketTimeout
+ }
+ )
+ } else {
+ packetQueue
+ }
+ )
+ else
+ // otherwise, don't send any packets
+ outstandingPacketsToConsumer
+ RegisterNewOutstandingPackets(newOutstandingPackets),
+ CONSUMER_NOOP,
+ // no packets are sent to the provider
+ outstandingPacketsToProvider' = outstandingPacketsToProvider,
+ // do not receive any maturations
+ receivedMaturations' = receivedMaturations,
+ // consumer statusses do not change
+ consumerStatus' = consumerStatus,
+ // chain times do not change
+ curChainTimes' = curChainTimes,
+ // the validator set was definitely not changed in the new block yet, so set to false
+ providerValidatorSetChangedInThisBlock' = false
}
- // we return either a result or an error.
- // if hasError is true, newState may be arbitrary, but the error will be meaningful.
- // if hasError is false, error may be arbitrary, but newState will be meaningful.
- type Result = {
- hasError: bool,
- newState: ProtocolState,
- error: Error
+ action endAndBeginBlockForConsumer(consumer: Chain): bool = all {
+ ConsumerChains.contains(consumer),
+ // update the voting power history
+ votingPowerHistories' = votingPowerHistories.set(consumer, getUpdatedHistory(consumer)),
+ // the running validator set is now for sure the current validator set,
+ // so start with it in the next block
+ runningValidatorSet' = runningValidatorSet,
+ // compute mature packets whose maturation time has passed
+ val maturedPackets = maturationTimes.get(consumer).keys().filter(
+ packet =>
+ val maturationTime = maturationTimes.get(consumer).get(packet)
+ maturationTime <= curChainTimes.get(consumer)
+ )
+ all {
+ // remove matured packets from the maturation times
+ maturationTimes' = maturationTimes.set(consumer, maturationTimes.get(consumer).mapRemoveAll(maturedPackets)),
+ // send matured packets
+ outstandingPacketsToProvider' = outstandingPacketsToProvider.set(
+ consumer,
+ // construct VSCMaturedPackets from the matured VSCPackets
+ outstandingPacketsToProvider.get(consumer).concat(
+ maturedPackets.map(packet => {id: packet.id, timeout: 5}).toList()
+ )
+ )
+ },
+ PROVIDER_NOOP,
+ // no packets are sent to consumer or received by it
+ RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
+ // consumer statusses do not change
+ consumerStatus' = consumerStatus,
+ // chain times do not change
+ curChainTimes' = curChainTimes,
+ // the validator set was not changed by this action (but might have been changed before in this block)
+ // also, this is only a new block for a consumer, so the change variable shouldn't be reset
+ providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
}
- pure def Ok(newState: ProtocolState): Result = {
+ // advance timestamps for maps nondeterministically
+ action AdvanceTime(): bool =
+ val advanceAmounts = curChainTimes.keys().mapBy(
+ chain =>
+ nondet amount = oneOf(1.to(10))
+ amount
+ )
+ AdvanceTimeByMap(advanceAmounts)
+
+ // the timestamp for each chain is advanced by the given amount
+ action AdvanceTimeByMap(advancementAmount: Chain -> int): bool = all
{
- hasError: false,
- newState: newState,
- error: {
- message: ""
- }
+ curChainTimes' = curChainTimes.keys().mapBy(
+ chain =>
+ curChainTimes.get(chain) + advancementAmount.get(chain)
+ ),
+ // all other variables are left untouched
+ votingPowerHistories' = votingPowerHistories,
+ runningValidatorSet' = runningValidatorSet,
+ outstandingPacketsToProvider' = outstandingPacketsToProvider,
+ RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
+ receivedMaturations' = receivedMaturations,
+ maturationTimes' = maturationTimes,
+ // chain times do not change
+ consumerStatus' = consumerStatus,
+ // the validator set was not changed by this action (but might have been changed before in this block)
+ providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
}
- }
- pure def Err(msg: str): Result = {
- {
- hasError: true,
- newState: {
- providerState: {
- chainState: {
- votingPowerHistory: List(),
- currentValidatorSet: Map(),
- lastTimestamp: 0
- },
- outstandingPacketsToConsumer: Map(),
- receivedMaturations: Set(),
- sentVSCPackets: Map(),
- providerValidatorSetChangedInThisBlock: false,
- consumerStatus: Map()
- },
- consumerStates: Set()
- },
- error: {
- message: msg
+ // each consumer chain may advance in the order
+ // some events may necessitate a transition, e.g. timeouts.
+ // shouldAdvance gives, for each consumer chain, whether it should advance if possible.
+ // if a chain has to advance, e.g. due to timeouts, or may not advance, the value will have no effect.
+ action AdvanceConsumers(shouldAdvance: Chain -> bool): bool =
+ val newConsumerStatus = consumerStatus.keys().mapBy(
+ chain =>
+ val curStatus = consumerStatus.get(chain)
+ if (curStatus == UNUSED) {
+ if (shouldAdvance.get(chain))
+ {
+ RUNNING
+ } else {
+ UNUSED
+ }
}
+ else if (curStatus == RUNNING) {
+ // the chain may transition to stopped.
+ // it is *forced* to stop if a packet timed out,
+ // or if the inactivity timeout has passed
+ if(consumerTimedOut(chain)) {
+ STOPPED
+ } else {
+ if (shouldAdvance.get(chain)) {
+ RUNNING
+ } else {
+ STOPPED
+ }
+ }
+ } else {
+ // stopped chains cannot restart, we assume a new chain would be started in that case
+ STOPPED
+ }
+ )
+ all {
+ consumerStatus' = newConsumerStatus,
+ // all other variables are left untouched
+ votingPowerHistories' = votingPowerHistories,
+ runningValidatorSet' = runningValidatorSet.keys().mapBy(
+ chain =>
+ if (newConsumerStatus.get(chain) == RUNNING and consumerStatus.get(chain) == UNUSED)
+ // consumers that went from unused to running start with the current validator set on the provider
+ {
+ runningValidatorSet.get(ProviderChain)
+ } else {
+ runningValidatorSet.get(chain)
+ }
+ ),
+ outstandingPacketsToProvider' = outstandingPacketsToProvider,
+ RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
+ receivedMaturations' = receivedMaturations,
+ maturationTimes' = maturationTimes,
+ // chain times do not change
+ curChainTimes' = curChainTimes,
+ // the validator set was not changed by this action (but might have been changed before in this block)
+ providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
}
- }
- // ===================
- // PROTOCOL LOGIC
- // ===================
+ // Updates the outstandingPacketsToConsumer and sentVSCPackets variables
+ action RegisterNewOutstandingPackets(newOutstandingPackets: Chain -> List[VSCPacket]): bool =
+ all {
+ outstandingPacketsToConsumer' = newOutstandingPackets,
+ StoreSentPackets(newOutstandingPackets),
+ }
- pure def getUpdatedValidatorSet(oldValidatorSet: ValidatorSet, validator: Node, newVotingPower: int): ValidatorSet =
- if (newVotingPower > 0)
- oldValidatorSet.put(validator, newVotingPower)
- else
- oldValidatorSet.mapRemove(validator)
-
- // the power of a validator on the provider chain is changed to the given amount. We do not care how this happens,
- // e.g. via undelegations, or delegations, ...
- pure def votingPowerChange(currentState: ProtocolState, validator: Node, amount: int): Result =
- // adjust the current validator set on the provider - it is not entered in the voting history yet because that happens only on block end
- val currentValidatorSet = currentState.providerState.currentValidatorSet
- // val newValidatorSet = getUpdatedValidatorSet(currentValidatorSet, validator, amount)
- // val newProviderState = currentState.providerState.with(
- // "currentValidatorSet", newValidatorSet
- // )
- // val newState = currentState.with(
- // "providerState", newProviderState
- // )
- Err("not implemented")
-}
-module ccv_tests {
- import ccv_logic.*
-
- // // UTILITY FUNCTIONS & ACTIONS
- // def wasValidatorSetOnProvider(validatorSet: ValidatorSet): bool = {
- // votingPowerHistories.get(ProviderChain).toSet().exists(
- // historicalValSet => historicalValSet == validatorSet
- // )
- // }
-
- // def getCurrentValidatorSet(chain: Chain): ValidatorSet =
- // votingPowerHistories.get(chain).head()
-
- // // returns true if the consumer has timed out and should be dropped
- // def consumerTimedOut(consumer: Chain): bool =
- // any {
- // // either a package from provider to consumer has timed out
- // outstandingPacketsToConsumer.get(consumer).select(
- // packet => packet.timeout <= curChainTimes.get(consumer)
- // ).length() > 0,
- // // or a package from consumer to provider has timed out
- // outstandingPacketsToProvider.get(consumer).select(
- // packet => packet.timeout <= curChainTimes.get(ProviderChain)
- // ).length() > 0,
- // // or the inactivity timeout has passed since a VSCPacket was sent to the consumer, but the
- // // provider has not received a VSCMaturedPacket for it
- // val packetsWithoutResponse = sentVSCPackets.get(consumer).filter( // get packets without response
- // packet =>
- // not(receivedMaturations.exists(
- // maturedPacket => maturedPacket.id == packet.id
- // ))
- // )
- // // among those, get packets where inactivity timeout has passed
- // packetsWithoutResponse.filter(
- // packet =>
- // val sentAt = curChainTimes.get(ProviderChain) - PacketTimeout // compute when the packet was sent
- // val timesOutAt = sentAt + InactivityTimeout // compute when the packet times out
- // timesOutAt <= curChainTimes.get(ProviderChain)
- // ).size() > 0
- // }
-
- // // utility action that leaves all provider state untouched
- // action PROVIDER_NOOP(): bool =
- // all {
- // receivedMaturations' = receivedMaturations,
- // }
-
- // // utility action that leaves all consumer state untouched
- // action CONSUMER_NOOP(): bool =
- // all {
- // maturationTimes' = maturationTimes,
- // }
+ // stores the VSCPackets sent in this step in sentVSCPackets
+ action StoreSentPackets(newOutstandingPackets: Chain -> List[VSCPacket]): bool =
+ sentVSCPackets' = sentVSCPackets.keys().mapBy(
+ (chain) =>
+ sentVSCPackets.get(chain).union(newOutstandingPackets.get(chain).toSet())
+ )
+
+ // the main step action
+ action step: bool = any {
+ AdvanceTime,
+ nondet node = oneOf(Nodes)
+ nondet amount = oneOf(1.to(10))
+ votingPowerChange(node, amount),
+ recvPacketOnProvider(oneOf(ConsumerChains)),
+ recvPacketOnConsumer(oneOf(ConsumerChains)),
+ nondet chain = oneOf(chains)
+ endAndBeginBlock(chain),
+ val shouldAdvance = ConsumerChains.mapBy(
+ chain =>
+ nondet should = oneOf(Set(true, false))
+ should
+ )
+ AdvanceConsumers(shouldAdvance),
+ }
+
+ pure val nodePowerSet = Nodes.powerset()
+
+ def getArbitraryValidatorSet(): ValidatorSet =
+ nondet numValidators = oneOf(1.to(Nodes.size()))
+ // toList has nondeterministic behaviour, so this gets arbitrary validators
+ nondet validators = oneOf(nodePowerSet.filter(s => s.size() == numValidators))
+ validators.mapBy(
+ validator =>
+ nondet votingPower = oneOf(1.to(10))
+ votingPower
+ )
+
+ // INITIALIZATION
+ action init: bool =
+ all {
+ val validatorSets = chains.mapBy(
+ (chain) =>
+ // provider chain gets an arbitrary validator set, consumer chains have none
+ if (chain == ProviderChain) getArbitraryValidatorSet else Map()
+ )
+ all {
+ votingPowerHistories' = chains.mapBy(
+ (chain) =>
+ List(validatorSets.get(chain))
+ ),
+ runningValidatorSet' = validatorSets,
+ },
+ // each chain starts at time 0
+ curChainTimes' = chains.mapBy(
+ (chain) => 0
+ ),
+ // all consumer chains are unused
+ consumerStatus' = chains.mapBy(chain => UNUSED),
+ // no packets are outstanding
+ outstandingPacketsToProvider' = chains.mapBy(chain => List()),
+ outstandingPacketsToConsumer' = chains.mapBy(chain => List()),
+ // no maturations have been received by provider
+ receivedMaturations' = Set(),
+ // no packets have been sent to consumers
+ sentVSCPackets' = chains.mapBy(chain => Set()),
+ // no packets have been received by consumers, so no maturation times set
+ maturationTimes' = chains.mapBy(chain => Map()),
+ // validator set was not changed yet
+ providerValidatorSetChangedInThisBlock' = false
+ }
+
+ // PROPERTIES
+
+ // Every validator set on any consumer chain MUST either be or
+ // have been a validator set on the provider chain.
+ val ValidatorSetReplication: bool =
+ chains.forall(
+ chain => chain.getCurrentValidatorSet().wasValidatorSetOnProvider()
+ )
+
+ // TESTS
+ run VSCHappyPathTest: bool = {
+ init
+ // trigger a votingPowerChange on the provider chain
+ .then(votingPowerChange("A", 10))
+ // endAndBeginBlock on provider. No consumer chains are running, so no packets are sent
+ .then(endAndBeginBlock(ProviderChain))
+ .then(all {
+ // no packet was sent
+ assert(outstandingPacketsToConsumer.get("chain1").length() == 0),
+ // advance chain1 to running
+ AdvanceConsumers(NoStatusAdvancement.set("chain1", true))
+ })
+ // consumer chain should have current validator set from provider
+ .then(
+ all {
+ // since consumer chain just started, its assumed to have the validator set from provider
+ assert(runningValidatorSet.get("chain1") == runningValidatorSet.get(ProviderChain)),
+ // trigger a votingPowerChange on the provider chain
+ votingPowerChange("B", 10)
+ }
+ )
+ .then(
+ val valSet = runningValidatorSet.get(ProviderChain)
+ endAndBeginBlock(ProviderChain)
+ // now the provider should send a packet on block end
+ .then(all {
+ // a packet was sent
+ assert(outstandingPacketsToConsumer.get("chain1").length() == 1),
+ // deliver the packet to the consumer
+ recvPacketOnConsumer("chain1")
+ })
+ .then(
+ // consumer needs to end a block before it has the new validator set
+ endAndBeginBlock("chain1")
+ )
+ .then(all {
+ // the consumer should have the new validator set
+ assert(runningValidatorSet.get("chain1") == valSet),
+ // put a last action to satisfy the action effect
+ AdvanceConsumers(NoStatusAdvancement)
+ })
+ )
+ }
- // // MODEL ACTIONS
-
- // // the power of a validator on the provider chain is changed to the given amount. We do not care how this happens,
- // // e.g. via undelegations, or delegations, ...
- // action doVotingPowerChange(validator: Node, amount: int): bool =
- // // for the provider chain, we need to adjust the voting power history
- // // by adding a new set
- // all {
- // amount >= 0,
- // val newValidatorSet = getCurrentValidatorSet(ProviderChain).getUpdatedValidatorSet(validator, amount)
- // // set the running validator set on the provider chain, but don't update the history yet
- // runningValidatorSet' = runningValidatorSet.set(ProviderChain, newValidatorSet),
- // // no packets are sent yet, these are only sent on endAndBeginBlock
- // RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
- // outstandingPacketsToProvider' = outstandingPacketsToProvider,
- // receivedMaturations' = receivedMaturations,
- // CONSUMER_NOOP,
- // // voting power history is only updated on endAndBeginBlock
- // votingPowerHistories' = votingPowerHistories,
- // // consumer statusses do not change
- // consumerStatus' = consumerStatus,
- // // chain times do not change
- // curChainTimes' = curChainTimes,
- // // the validator set is considered to have changed
- // providerValidatorSetChangedInThisBlock' = true,
- // }
-
- // // deliver the next outstanding packet from the consumer to the provider.
- // // since this model assumes a single provider chain, this just takes a single chain as argument.
- // action recvPacketOnProvider(consumer: Chain): bool = all {
- // // ensure there is a packet to be received
- // outstandingPacketsToProvider.get(consumer).length() > 0,
- // // remove packet from outstanding packets
- // val newPacketQueue = outstandingPacketsToProvider.get(consumer).tail()
- // outstandingPacketsToProvider' = outstandingPacketsToProvider.set(consumer, newPacketQueue),
- // // register the packet as received
- // val maturedPacket = outstandingPacketsToProvider.get(consumer).head()
- // receivedMaturations' = receivedMaturations.add(maturedPacket),
- // CONSUMER_NOOP,
- // RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
- // votingPowerHistories' = votingPowerHistories,
- // // no validator set changes are made
- // runningValidatorSet' = runningValidatorSet,
- // // consumer statusses do not change
- // consumerStatus' = consumerStatus,
- // // chain times do not change
- // curChainTimes' = curChainTimes,
- // // the validator set was not changed by this action (but might have been changed before in this block)
- // providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
- // }
-
- // // deliver the next outstanding packet from the provider to the consumer.
- // // since this model assumes a single provider chain, this just takes a single chain as argument.
- // action recvPacketOnConsumer(consumer: Chain): bool = all {
- // // ensure there is a packet to be received
- // outstandingPacketsToConsumer.get(consumer).length() > 0,
- // // remove packet from outstanding packets
- // val newPacketQueue = outstandingPacketsToConsumer.get(consumer).tail()
- // val newOutstandingPackets = outstandingPacketsToConsumer.set(consumer, newPacketQueue)
- // RegisterNewOutstandingPackets(newOutstandingPackets),
- // val packet = outstandingPacketsToConsumer.get(consumer).head()
- // all {
- // // update the running validator set, but not the history yet,
- // // as that only happens when the next block is started
- // runningValidatorSet' = runningValidatorSet.set(consumer, packet.validatorSet),
- // // add the new packet and store its maturation time
- // val newMaturationTimes = maturationTimes.get(consumer).put(packet, curChainTimes.get(consumer) + UnbondingPeriod.get(consumer))
- // maturationTimes' = maturationTimes.set(consumer, newMaturationTimes)
- // },
- // PROVIDER_NOOP,
- // votingPowerHistories' = votingPowerHistories,
- // outstandingPacketsToProvider' = outstandingPacketsToProvider,
- // // consumer statusses do not change
- // consumerStatus' = consumerStatus,
- // // chain times do not change
- // curChainTimes' = curChainTimes,
- // // the validator set was not changed by this action (but might have been changed before in this block)
- // providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
- // }
-
- // // ends the current block and starts the next block for a given chain.
- // action endAndBeginBlock(chain: Chain): bool = any {
- // all {
- // chain == ProviderChain,
- // endAndBeginBlockForProvider,
- // },
- // all {
- // chain != ProviderChain,
- // endAndBeginBlockForConsumer(chain),
- // }
- // }
-
- // // gets the updated history for the current chain when ending a block, i.e. the
- // // running validator set is added to the history if different from the last one.
- // def getUpdatedHistory(chain: Chain): List[ValidatorSet] =
- // // update voting power history if the validator set changed
- // val newValidatorSet = runningValidatorSet.get(ProviderChain)
- // val oldValidatorSet = votingPowerHistories.get(ProviderChain).head()
- // if (newValidatorSet != oldValidatorSet)
- // votingPowerHistories.get(ProviderChain).prepend(newValidatorSet)
- // else
- // votingPowerHistories.get(ProviderChain)
-
-
- // action endAndBeginBlockForProvider(): bool = all {
- // // update the voting power history
- // votingPowerHistories' = votingPowerHistories.set(ProviderChain, getUpdatedHistory(ProviderChain)),
- // // the running validator set is now for sure the current validator set,
- // // so start with it in the next block
- // runningValidatorSet' = runningValidatorSet,
- // // send VSCPackets to consumers
- // val newOutstandingPackets =
- // // if running validator set is considered to have changed
- // if (providerValidatorSetChangedInThisBlock)
- // // then send a packet to each running consumer
- // outstandingPacketsToConsumer.keys().mapBy(
- // (consumer) =>
- // val packetQueue = outstandingPacketsToConsumer.get(consumer)
- // if (consumerStatus.get(consumer) == RUNNING) {
- // packetQueue.append(
- // {
- // id: packetQueue.length(),
- // validatorSet: runningValidatorSet.get(ProviderChain),
- // timeout: curChainTimes.get(ProviderChain) + PacketTimeout
- // }
- // )
- // } else {
- // packetQueue
- // }
- // )
- // else
- // // otherwise, don't send any packets
- // outstandingPacketsToConsumer
- // RegisterNewOutstandingPackets(newOutstandingPackets),
- // CONSUMER_NOOP,
- // // no packets are sent to the provider
- // outstandingPacketsToProvider' = outstandingPacketsToProvider,
- // // do not receive any maturations
- // receivedMaturations' = receivedMaturations,
- // // consumer statusses do not change
- // consumerStatus' = consumerStatus,
- // // chain times do not change
- // curChainTimes' = curChainTimes,
- // // the validator set was definitely not changed in the new block yet, so set to false
- // providerValidatorSetChangedInThisBlock' = false
- // }
-
- // action endAndBeginBlockForConsumer(consumer: Chain): bool = all {
- // ConsumerChains.contains(consumer),
- // // update the voting power history
- // votingPowerHistories' = votingPowerHistories.set(consumer, getUpdatedHistory(consumer)),
- // // the running validator set is now for sure the current validator set,
- // // so start with it in the next block
- // runningValidatorSet' = runningValidatorSet,
- // // compute mature packets whose maturation time has passed
- // val maturedPackets = maturationTimes.get(consumer).keys().filter(
- // packet =>
- // val maturationTime = maturationTimes.get(consumer).get(packet)
- // maturationTime <= curChainTimes.get(consumer)
- // )
- // all {
- // // remove matured packets from the maturation times
- // maturationTimes' = maturationTimes.set(consumer, maturationTimes.get(consumer).mapRemoveAll(maturedPackets)),
- // // send matured packets
- // outstandingPacketsToProvider' = outstandingPacketsToProvider.set(
- // consumer,
- // // construct VSCMaturedPackets from the matured VSCPackets
- // outstandingPacketsToProvider.get(consumer).concat(
- // maturedPackets.map(packet => {id: packet.id, timeout: 5}).toList()
- // )
- // )
- // },
- // PROVIDER_NOOP,
- // // no packets are sent to consumer or received by it
- // RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
- // // consumer statusses do not change
- // consumerStatus' = consumerStatus,
- // // chain times do not change
- // curChainTimes' = curChainTimes,
- // // the validator set was not changed by this action (but might have been changed before in this block)
- // // also, this is only a new block for a consumer, so the change variable shouldn't be reset
- // providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
- // }
-
- // // advance timestamps for maps nondeterministically
- // action AdvanceTime(): bool =
- // val advanceAmounts = curChainTimes.keys().mapBy(
- // chain =>
- // nondet amount = oneOf(1.to(10))
- // amount
- // )
- // AdvanceTimeByMap(advanceAmounts)
-
- // // the timestamp for each chain is advanced by the given amount
- // action AdvanceTimeByMap(advancementAmount: Chain -> int): bool = all
- // {
- // curChainTimes' = curChainTimes.keys().mapBy(
- // chain =>
- // curChainTimes.get(chain) + advancementAmount.get(chain)
- // ),
- // // all other variables are left untouched
- // votingPowerHistories' = votingPowerHistories,
- // runningValidatorSet' = runningValidatorSet,
- // outstandingPacketsToProvider' = outstandingPacketsToProvider,
- // RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
- // receivedMaturations' = receivedMaturations,
- // maturationTimes' = maturationTimes,
- // // chain times do not change
- // consumerStatus' = consumerStatus,
- // // the validator set was not changed by this action (but might have been changed before in this block)
- // providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
- // }
-
- // // each consumer chain may advance in the order
- // // some events may necessitate a transition, e.g. timeouts.
- // // shouldAdvance gives, for each consumer chain, whether it should advance if possible.
- // // if a chain has to advance, e.g. due to timeouts, or may not advance, the value will have no effect.
- // action AdvanceConsumers(shouldAdvance: Chain -> bool): bool =
- // val newConsumerStatus = consumerStatus.keys().mapBy(
- // chain =>
- // val curStatus = consumerStatus.get(chain)
- // if (curStatus == UNUSED) {
- // if (shouldAdvance.get(chain))
- // {
- // RUNNING
- // } else {
- // UNUSED
- // }
- // }
- // else if (curStatus == RUNNING) {
- // // the chain may transition to stopped.
- // // it is *forced* to stop if a packet timed out,
- // // or if the inactivity timeout has passed
- // if(consumerTimedOut(chain)) {
- // STOPPED
- // } else {
- // if (shouldAdvance.get(chain)) {
- // RUNNING
- // } else {
- // STOPPED
- // }
- // }
- // } else {
- // // stopped chains cannot restart, we assume a new chain would be started in that case
- // STOPPED
- // }
- // )
- // all {
- // consumerStatus' = newConsumerStatus,
- // // all other variables are left untouched
- // votingPowerHistories' = votingPowerHistories,
- // runningValidatorSet' = runningValidatorSet.keys().mapBy(
- // chain =>
- // if (newConsumerStatus.get(chain) == RUNNING and consumerStatus.get(chain) == UNUSED)
- // // consumers that went from unused to running start with the current validator set on the provider
- // {
- // runningValidatorSet.get(ProviderChain)
- // } else {
- // runningValidatorSet.get(chain)
- // }
- // ),
- // outstandingPacketsToProvider' = outstandingPacketsToProvider,
- // RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
- // receivedMaturations' = receivedMaturations,
- // maturationTimes' = maturationTimes,
- // // chain times do not change
- // curChainTimes' = curChainTimes,
- // // the validator set was not changed by this action (but might have been changed before in this block)
- // providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
- // }
-
- // // Updates the outstandingPacketsToConsumer and sentVSCPackets variables
- // action RegisterNewOutstandingPackets(newOutstandingPackets: Chain -> List[VSCPacket]): bool =
- // all {
- // outstandingPacketsToConsumer' = newOutstandingPackets,
- // StoreSentPackets(newOutstandingPackets),
- // }
-
-
- // // stores the VSCPackets sent in this step in sentVSCPackets
- // action StoreSentPackets(newOutstandingPackets: Chain -> List[VSCPacket]): bool =
- // sentVSCPackets' = sentVSCPackets.keys().mapBy(
- // (chain) =>
- // sentVSCPackets.get(chain).union(newOutstandingPackets.get(chain).toSet())
- // )
-
- // // the main step action
- // action step: bool = any {
- // AdvanceTime,
- // nondet node = oneOf(Nodes)
- // nondet amount = oneOf(1.to(10))
- // votingPowerChange(node, amount),
- // recvPacketOnProvider(oneOf(ConsumerChains)),
- // recvPacketOnConsumer(oneOf(ConsumerChains)),
- // nondet chain = oneOf(chains)
- // endAndBeginBlock(chain),
- // val shouldAdvance = ConsumerChains.mapBy(
- // chain =>
- // nondet should = oneOf(Set(true, false))
- // should
- // )
- // AdvanceConsumers(shouldAdvance),
- // }
-
- // pure val nodePowerSet = Nodes.powerset()
-
- // def getArbitraryValidatorSet(): ValidatorSet =
- // nondet numValidators = oneOf(1.to(Nodes.size()))
- // // toList has nondeterministic behaviour, so this gets arbitrary validators
- // nondet validators = oneOf(nodePowerSet.filter(s => s.size() == numValidators))
- // validators.mapBy(
- // validator =>
- // nondet votingPower = oneOf(1.to(10))
- // votingPower
- // )
-
- // // INITIALIZATION
- // action init: bool =
- // all {
- // val validatorSets = chains.mapBy(
- // (chain) =>
- // // provider chain gets an arbitrary validator set, consumer chains have none
- // if (chain == ProviderChain) getArbitraryValidatorSet else Map()
- // )
- // all {
- // votingPowerHistories' = chains.mapBy(
- // (chain) =>
- // List(validatorSets.get(chain))
- // ),
- // runningValidatorSet' = validatorSets,
- // },
- // // each chain starts at time 0
- // curChainTimes' = chains.mapBy(
- // (chain) => 0
- // ),
- // // all consumer chains are unused
- // consumerStatus' = chains.mapBy(chain => UNUSED),
- // // no packets are outstanding
- // outstandingPacketsToProvider' = chains.mapBy(chain => List()),
- // outstandingPacketsToConsumer' = chains.mapBy(chain => List()),
- // // no maturations have been received by provider
- // receivedMaturations' = Set(),
- // // no packets have been sent to consumers
- // sentVSCPackets' = chains.mapBy(chain => Set()),
- // // no packets have been received by consumers, so no maturation times set
- // maturationTimes' = chains.mapBy(chain => Map()),
- // // validator set was not changed yet
- // providerValidatorSetChangedInThisBlock' = false
- // }
-
- // // PROPERTIES
-
- // // Every validator set on any consumer chain MUST either be or
- // // have been a validator set on the provider chain.
- // val ValidatorSetReplication: bool =
- // chains.forall(
- // chain => chain.getCurrentValidatorSet().wasValidatorSetOnProvider()
- // )
-
- // // TESTS
- // run VSCHappyPathTest: bool = {
- // init
- // // trigger a votingPowerChange on the provider chain
- // .then(votingPowerChange("A", 10))
- // // endAndBeginBlock on provider. No consumer chains are running, so no packets are sent
- // .then(endAndBeginBlock(ProviderChain))
- // .then(all {
- // // no packet was sent
- // assert(outstandingPacketsToConsumer.get("chain1").length() == 0),
- // // advance chain1 to running
- // AdvanceConsumers(NoStatusAdvancement.set("chain1", true))
- // })
- // // consumer chain should have current validator set from provider
- // .then(
- // all {
- // // since consumer chain just started, its assumed to have the validator set from provider
- // assert(runningValidatorSet.get("chain1") == runningValidatorSet.get(ProviderChain)),
- // // trigger a votingPowerChange on the provider chain
- // votingPowerChange("B", 10)
- // }
- // )
- // .then(
- // val valSet = runningValidatorSet.get(ProviderChain)
- // endAndBeginBlock(ProviderChain)
- // // now the provider should send a packet on block end
- // .then(all {
- // // a packet was sent
- // assert(outstandingPacketsToConsumer.get("chain1").length() == 1),
- // // deliver the packet to the consumer
- // recvPacketOnConsumer("chain1")
- // })
- // .then(
- // // consumer needs to end a block before it has the new validator set
- // endAndBeginBlock("chain1")
- // )
- // .then(all {
- // // the consumer should have the new validator set
- // assert(runningValidatorSet.get("chain1") == valSet),
- // // put a last action to satisfy the action effect
- // AdvanceConsumers(NoStatusAdvancement)
- // })
- // )
- // }
-
- // // utility: the set of consumers currently running
- // val RunningConsumers: Set[Chain] =
- // ConsumerChains.filter(chain => consumerStatus.get(chain) == RUNNING)
-
- // // MODEL STATE
- // // --SHARED STATE
-
- // // Stores, for each chain, the list of voting powers that corresponded to voting powers
- // // at blocks over its entire existence.
- // // Voting powers should be ordered by recency in descending order.
- // var votingPowerHistories: Chain -> List[ValidatorSet]
-
- // // the current validator set on each chain.
- // // this will be included in the next block, but might not be final yet,
- // // e.g. there may be more modifications in the current block.
- // var runningValidatorSet: Chain -> ValidatorSet
-
- // // the current timestamp for each chain
- // var curChainTimes: Chain -> Timestamp
-
- // // stores, for each chain, its current status -
- // // unused, running, or stopped
- // var consumerStatus: Chain -> str
-
- // // --CHANNELS
- // // Stores, for each consumer chain, the list of packets that have been sent to the provider chain
- // // and have not been received yet.
- // var outstandingPacketsToProvider: Chain -> List[VSCMaturedPacket]
-
- // // Stores, for each consumer chain, the list of packets that have been sent to the consumer chain
- // // and have not been received yet.
- // var outstandingPacketsToConsumer: Chain -> List[VSCPacket]
-
-
- // // --CONSUMER STATE
- // // Stores the maturation times for VSCPackets received by consumers
- // var maturationTimes: Chain -> (VSCPacket -> Timestamp)
-
- // // --PROVIDER STATE
- // // the set of VSCMaturedPackets received by the provider chain
- // var receivedMaturations: Set[VSCMaturedPacket]
-
- // // stores which VSC Packets have been sent to compare with receivedMaturations to detect timeouts due to non-responsiveness
- // var sentVSCPackets: Chain -> Set[VSCPacket]
-
- // // stores whether, in this step, the validator set considered to be changed.
- // // this is needed because the validator set might be considered to have changed, even though
- // // it is still technically identical at our level of abstraction, e.g. a validator power change on the provider
- // // might leave the validator set the same because a delegation and undelegation cancel each other out.
- // var providerValidatorSetChangedInThisBlock: bool
-
- // // utility: a struct summarizing the current state
- // val state =
- // {
- // votingPowerHistories: votingPowerHistories,
- // runningValidatorSet: runningValidatorSet,
- // curChainTimes: curChainTimes,
- // consumerStatus: consumerStatus,
- // outstandingPacketsToProvider: outstandingPacketsToProvider,
- // outstandingPacketsToConsumer: outstandingPacketsToConsumer,
- // maturationTimes: maturationTimes,
- // receivedMaturations: receivedMaturations,
- // sentVSCPackets: sentVSCPackets,
- // }
-
- // // set of identifiers of potential nodes
- // pure val Nodes: Set[Node] =
- // Set("A", "B", "C", "D", "E", "F", "G", "H", "I", "J")
-
- // // the set of consumer chains
- // pure val ConsumerChains: Set[Chain] =
- // Set("chain1", "chain2", "chain3")
-
- // // The singular provider chain.
- // pure val ProviderChain: Chain =
- // "provider"
-
- // pure val chains = ConsumerChains.union(Set(ProviderChain))
-
- // // length of the unbonding period on each chain
- // pure val UnbondingPeriod: Chain -> int = chains.mapBy(
- // (chain) =>
- // 10
- // )
-
- // // the time until a packet times out
- // pure val PacketTimeout: int =
- // 5
-
- // // the time until a consumer chain is dropped by the provider due to inactivity
- // pure val InactivityTimeout: int =
- // 10
-
- // // utility: a map assigning each chain to 0, used for not advancing timestamps
- // pure val NoTimeAdvancement: Chain -> int = chains.mapBy(
- // (chain) =>
- // 0
- // )
-
-
- // // utility: a map assigning each chain to false, used for not advancing consumer status
- // pure val NoStatusAdvancement: Chain -> bool = chains.mapBy(
- // (chain) =>
- // false
- // )
}
diff --git a/tests/difference/core/quint_model/extraSpells.qnt b/tests/difference/core/quint_model/extraSpells.qnt
index 5c98f7a384..cd2105beb7 100644
--- a/tests/difference/core/quint_model/extraSpells.qnt
+++ b/tests/difference/core/quint_model/extraSpells.qnt
@@ -1,3 +1,4 @@
+-*- mode: Bluespec; -*-
// This module is just a library with utility functions (sometimes called spells in Quint).
module extraSpells {
diff --git a/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123337-6230.fail b/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123337-6230.fail
deleted file mode 100644
index f2c09a9e15..0000000000
--- a/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123337-6230.fail
+++ /dev/null
@@ -1,18 +0,0 @@
-# 2023/09/12 12:33:37 TestActionMarshalling [rapid] draw Action: main.StartChainAction{Chain:"", Validators:[]main.StartChainValidator{}, GenesisChanges:"", SkipGentx:false}
-# 2023/09/12 12:33:37 TestActionMarshalling error marshalling and unmarshalling chain state: any(
-# - main.StartChainAction{Validators: []main.StartChainValidator{}},
-# + map[string]any{
-# + "Chain": string(""),
-# + "GenesisChanges": string(""),
-# + "SkipGentx": bool(false),
-# + "Validators": []any{},
-# + },
-# )
-#
-v0.4.8#6863100771198181688
-0x0
-0x1
-0x0
-0x0
-0x0
-0x0
\ No newline at end of file
diff --git a/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123517-6794.fail b/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123517-6794.fail
deleted file mode 100644
index 9547963143..0000000000
--- a/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123517-6794.fail
+++ /dev/null
@@ -1,14 +0,0 @@
-# 2023/09/12 12:35:17 TestActionMarshalling [rapid] draw Action: main.SendTokensAction{Chain:"", From:"", To:"", Amount:0x0}
-# 2023/09/12 12:35:17 TestActionMarshalling error marshalling and unmarshalling action: got (+), want (-): Â Â any(
-# -Â main.SendTokensAction{},
-# +Â map[string]any{"Amount": float64(0), "Chain": string(""), "From": string(""), "To": string("")},
-# Â Â )
-#
-v0.4.8#14250065908211800578
-0x0
-0x0
-0x0
-0x0
-0x0
-0x0
-0x0
\ No newline at end of file
diff --git a/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123556-6970.fail b/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123556-6970.fail
deleted file mode 100644
index 4ddf365f5c..0000000000
--- a/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123556-6970.fail
+++ /dev/null
@@ -1,14 +0,0 @@
-# 2023/09/12 12:35:56 TestActionMarshalling [rapid] draw Action: main.SendTokensAction{Chain:"", From:"", To:"", Amount:0x0}
-# 2023/09/12 12:35:56 TestActionMarshalling error marshalling and unmarshalling action: got (-), want (+): Â Â any(
-# -Â main.SendTokensAction{},
-# +Â map[string]any{"Amount": float64(0), "Chain": string(""), "From": string(""), "To": string("")},
-# Â Â )
-#
-v0.4.8#9176735344445930654
-0x0
-0x0
-0x0
-0x0
-0x0
-0x0
-0x0
\ No newline at end of file
diff --git a/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123609-7116.fail b/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123609-7116.fail
deleted file mode 100644
index 89dcbcc8b2..0000000000
--- a/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123609-7116.fail
+++ /dev/null
@@ -1,18 +0,0 @@
-# 2023/09/12 12:36:09 TestActionMarshalling [rapid] draw Action: main.StartChainAction{Chain:"", Validators:[]main.StartChainValidator{}, GenesisChanges:"", SkipGentx:false}
-# 2023/09/12 12:36:09 TestActionMarshalling error marshalling and unmarshalling action: got (-), want (+): any(
-# - main.StartChainAction{Validators: []main.StartChainValidator{}},
-# + map[string]any{
-# + "Chain": string(""),
-# + "GenesisChanges": string(""),
-# + "SkipGentx": bool(false),
-# + "Validators": []any{},
-# + },
-# )
-#
-v0.4.8#17927886955469684979
-0x0
-0x1
-0x0
-0x0
-0x0
-0x0
\ No newline at end of file
diff --git a/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123822-8026.fail b/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123822-8026.fail
deleted file mode 100644
index d2493d87a3..0000000000
--- a/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230912123822-8026.fail
+++ /dev/null
@@ -1,8 +0,0 @@
-# 2023/09/12 12:38:22 TestActionMarshalling [rapid] draw Action: main.addChainToRelayerAction{Chain:"", Validator:""}
-# 2023/09/12 12:38:22 TestActionMarshalling error marshalling and unmarshalling action: error unmarshalling action inside step: unknown action name: main.addChainToRelayerAction
-#
-v0.4.8#17613601115647214278
-0x9867f1f40f739
-0x9
-0x0
-0x0
\ No newline at end of file
diff --git a/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230913151826-12656.fail b/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230913151826-12656.fail
deleted file mode 100644
index 10fd2ac639..0000000000
--- a/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230913151826-12656.fail
+++ /dev/null
@@ -1,8 +0,0 @@
-# 2023/09/13 15:18:26 TestActionMarshalling [rapid] draw Action: main.lightClientEquivocationAttackAction{Validator:"", Chain:""}
-# 2023/09/13 15:18:26 TestActionMarshalling error marshalling and unmarshalling action: error unmarshalling action inside step: unknown action name: main.lightClientEquivocationAttackAction
-#
-v0.4.8#7694661539125204314
-0xc05c654ab866b
-0x1f
-0x0
-0x0
\ No newline at end of file
diff --git a/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230913151917-13146.fail b/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230913151917-13146.fail
deleted file mode 100644
index 6fb9bfa9f7..0000000000
--- a/tests/e2e/testdata/rapid/TestActionMarshalling/TestActionMarshalling-20230913151917-13146.fail
+++ /dev/null
@@ -1,8 +0,0 @@
-# 2023/09/13 15:19:17 TestActionMarshalling [rapid] draw Action: main.lightClientLunaticAttackAction{Validator:"", Chain:""}
-# 2023/09/13 15:19:17 TestActionMarshalling error marshalling and unmarshalling action: error unmarshalling action inside step: unknown action name: main.lightClientLunaticAttackAction
-#
-v0.4.8#10796173543550944397
-0x1f2b5a0e61c3a6
-0x0
-0x0
-0x0
\ No newline at end of file
diff --git a/tests/e2e/testdata/rapid/TestReadAndWriteTrace/TestReadAndWriteTrace-20230913151920-13146.fail b/tests/e2e/testdata/rapid/TestReadAndWriteTrace/TestReadAndWriteTrace-20230913151920-13146.fail
deleted file mode 100644
index f8e2127feb..0000000000
--- a/tests/e2e/testdata/rapid/TestReadAndWriteTrace/TestReadAndWriteTrace-20230913151920-13146.fail
+++ /dev/null
@@ -1,11 +0,0 @@
-# 2023/09/13 15:19:20 TestReadAndWriteTrace [rapid] draw Trace: []main.Step{main.Step{Action:main.lightClientEquivocationAttackAction{Validator:"", Chain:""}, State:main.State{}}}
-# 2023/09/13 15:19:20 TestReadAndWriteTrace error writing and reading trace: error reading trace from file: unknown action name: main.lightClientEquivocationAttackAction
-#
-v0.4.8#17668525343613541116
-0x5555555555555
-0xc05c654ab866b
-0x1f
-0x0
-0x0
-0x0
-0x0
\ No newline at end of file
From c05d9e682e055f458d64977c6da8917b5ba0c802 Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Mon, 18 Sep 2023 18:24:33 +0200
Subject: [PATCH 13/35] Start rewriting quint model
---
tests/difference/core/quint_model/ccv.qnt | 1241 +++++++++--------
.../core/quint_model/extraSpells.qnt | 1 -
2 files changed, 691 insertions(+), 551 deletions(-)
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index d18930ae1d..4cbc327ac4 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -1,5 +1,4 @@
--*- mode: Bluespec; -*-
-module ccv {
+module ccv_logic {
import extraSpells.* from "./extraSpells"
// Things that are not modelled:
@@ -12,12 +11,17 @@ module ccv {
// * VSC packets mature
// * Consumers can be forcefully dropped due to timeouts, or stop on their own volition
+ // ===================
// TYPE DEFINITIONS
+ // ===================
type Node = str
type Chain = str
- type ValidatorSet = Node -> int
+ type Power = int
+ type ValidatorSet = Node -> Power
type Height = int
type Timestamp = int
+ // a list of validator sets per blocks, ordered by recency
+ type VotingPowerHistory = List[ValidatorSet]
// --PACKETS
type VSCPacket =
@@ -38,582 +42,719 @@ module ccv {
timeout: Timestamp
}
- // MODEL PARAMETERS
-
-
- // set of identifiers of potential nodes
- pure val Nodes: Set[Node] =
- Set("A", "B", "C", "D", "E", "F", "G", "H", "I", "J")
-
- // the set of consumer chains
- pure val ConsumerChains: Set[Chain] =
- Set("chain1", "chain2", "chain3")
+ // possible consumer statuses
+ pure val STOPPED = "stopped" // the chain was once a consumer chain, but has been dropped by the provider.
+ pure val RUNNING = "running" //Â the chain is currently a consumer chain. Running chains are those that get sent VSCPackets.
+ pure val UNUSED = "unused" // the chain has never been a consumer chain, and is available to become one.
+ // When a chain is dropped, it cannot become a consumer again - we assume that would be done by another consumer becoming running.
+
+
+ // state that each chain needs to store, whether consumer or provider.
+ type ChainState = {
+ // Stores the list of voting powers that corresponded to voting powers
+ // at blocks over the chains entire existence.
+ // Voting powers should be ordered by recency in descending order.
+ votingPowerHistory: VotingPowerHistory,
+
+ // the current validator set on each chain.
+ // this will be included in the next block, but might not be final yet,
+ // e.g. there may be more modifications in the current block.
+ currentValidatorSet: ValidatorSet,
+
+ // the latest timestamp that was comitted on chain
+ lastTimestamp: Timestamp,
+ }
- // The singular provider chain.
- pure val ProviderChain: Chain =
- "provider"
-
- pure val chains = ConsumerChains.union(Set(ProviderChain))
-
- // length of the unbonding period on each chain
- pure val UnbondingPeriod: Chain -> int = chains.mapBy(
- (chain) =>
- 10
- )
-
- // the time until a packet times out
- pure val PacketTimeout: int =
- 5
-
- // the time until a consumer chain is dropped by the provider due to inactivity
- pure val InactivityTimeout: int =
- 10
-
- // consumer statuses
- pure val STOPPED = "stopped"
- pure val RUNNING = "running"
- pure val UNUSED = "unused"
-
- // utility: a map assigning each chain to 0, used for not advancing timestamps
- pure val NoTimeAdvancement: Chain -> int = chains.mapBy(
- (chain) =>
- 0
- )
-
- // utility: a struct summarizing the current state
- val state =
+ // Defines the current state of the provider chain. Essentially, all information here is stored by the provider on-chain (or could be derived purely by information that is on-chain).
+ type ProviderState =
{
- votingPowerHistories: votingPowerHistories,
- runningValidatorSet: runningValidatorSet,
- curChainTimes: curChainTimes,
- consumerStatus: consumerStatus,
- outstandingPacketsToProvider: outstandingPacketsToProvider,
- outstandingPacketsToConsumer: outstandingPacketsToConsumer,
- maturationTimes: maturationTimes,
- receivedMaturations: receivedMaturations,
- sentVSCPackets: sentVSCPackets,
- }
-
-
- // utility: a map assigning each chain to false, used for not advancing consumer status
- pure val NoStatusAdvancement: Chain -> bool = chains.mapBy(
- (chain) =>
- false
- )
-
- // utility: the set of consumers currently running
- val RunningConsumers: Set[Chain] =
- ConsumerChains.filter(chain => consumerStatus.get(chain) == RUNNING)
-
- // MODEL STATE
- // --SHARED STATE
-
- // Stores, for each chain, the list of voting powers that corresponded to voting powers
- // at blocks over its entire existence.
- // Voting powers should be ordered by recency in descending order.
- var votingPowerHistories: Chain -> List[ValidatorSet]
-
- // the current validator set on each chain.
- // this will be included in the next block, but might not be final yet,
- // e.g. there may be more modifications in the current block.
- var runningValidatorSet: Chain -> ValidatorSet
-
- // the current timestamp for each chain
- var curChainTimes: Chain -> Timestamp
-
- // stores, for each chain, its current status -
- // unused, running, or stopped
- var consumerStatus: Chain -> str
-
- // --CHANNELS
- // Stores, for each consumer chain, the list of packets that have been sent to the provider chain
- // and have not been received yet.
- var outstandingPacketsToProvider: Chain -> List[VSCMaturedPacket]
+ // the state that each chain needs to store
+ chainState: ChainState,
- // Stores, for each consumer chain, the list of packets that have been sent to the consumer chain
- // and have not been received yet.
- var outstandingPacketsToConsumer: Chain -> List[VSCPacket]
+ // Stores, for each consumer chain, the list of packets that have been sent to the consumer chain
+ // and have not been received yet.
+ // In the implementation, this would roughly be the unacknowledged packets on an ibc channel.
+ outstandingPacketsToConsumer: Chain -> List[VSCPacket],
+ // the set of received VSCMaturedPackets
+ receivedMaturations: Set[VSCMaturedPacket],
- // --CONSUMER STATE
- // Stores the maturation times for VSCPackets received by consumers
- var maturationTimes: Chain -> (VSCPacket -> Timestamp)
+ // stores which VSC Packets have been sent to compare with receivedMaturations to detect timeouts due to non-responsiveness
+ sentVSCPackets: Chain -> Set[VSCPacket],
- // --PROVIDER STATE
- // the set of VSCMaturedPackets received by the provider chain
- var receivedMaturations: Set[VSCMaturedPacket]
-
- // stores which VSC Packets have been sent to compare with receivedMaturations to detect timeouts due to non-responsiveness
- var sentVSCPackets: Chain -> Set[VSCPacket]
-
- // stores whether, in this step, the validator set considered to be changed.
- // this is needed because the validator set might be considered to have changed, even though
- // it is still technically identical at our level of abstraction, e.g. a validator power change on the provider
- // might leave the validator set the same because a delegation and undelegation cancel each other out.
- var providerValidatorSetChangedInThisBlock: bool
-
-
- // UTILITY FUNCTIONS & ACTIONS
- def wasValidatorSetOnProvider(validatorSet: ValidatorSet): bool = {
- votingPowerHistories.get(ProviderChain).toSet().exists(
- historicalValSet => historicalValSet == validatorSet
- )
- }
-
- def getCurrentValidatorSet(chain: Chain): ValidatorSet =
- votingPowerHistories.get(chain).head()
-
- def getUpdatedValidatorSet(oldValidatorSet: ValidatorSet, validator: Node, newVotingPower: int): ValidatorSet =
- if (newVotingPower > 0)
- oldValidatorSet.put(validator, newVotingPower)
- else
- oldValidatorSet.mapRemove(validator)
-
- // returns true if the consumer has timed out and should be dropped
- def consumerTimedOut(consumer: Chain): bool =
- any {
- // either a package from provider to consumer has timed out
- outstandingPacketsToConsumer.get(consumer).select(
- packet => packet.timeout <= curChainTimes.get(consumer)
- ).length() > 0,
- // or a package from consumer to provider has timed out
- outstandingPacketsToProvider.get(consumer).select(
- packet => packet.timeout <= curChainTimes.get(ProviderChain)
- ).length() > 0,
- // or the inactivity timeout has passed since a VSCPacket was sent to the consumer, but the
- // provider has not received a VSCMaturedPacket for it
- val packetsWithoutResponse = sentVSCPackets.get(consumer).filter( // get packets without response
- packet =>
- not(receivedMaturations.exists(
- maturedPacket => maturedPacket.id == packet.id
- ))
- )
- // among those, get packets where inactivity timeout has passed
- packetsWithoutResponse.filter(
- packet =>
- val sentAt = curChainTimes.get(ProviderChain) - PacketTimeout // compute when the packet was sent
- val timesOutAt = sentAt + InactivityTimeout // compute when the packet times out
- timesOutAt <= curChainTimes.get(ProviderChain)
- ).size() > 0
- }
-
- // utility action that leaves all provider state untouched
- action PROVIDER_NOOP(): bool =
- all {
- receivedMaturations' = receivedMaturations,
- }
+ // stores whether, in this block, the validator set has changed.
+ // this is needed because the validator set might be considered to have changed, even though
+ // it is still technically identical at our level of abstraction, e.g. a validator power change on the provider
+ // might leave the validator set the same because a delegation and undelegation cancel each other out.
+ providerValidatorSetChangedInThisBlock: bool,
- // utility action that leaves all consumer state untouched
- action CONSUMER_NOOP(): bool =
- all {
- maturationTimes' = maturationTimes,
+ // stores, for each consumer chain, its current status -
+ // unused, running, or stopped
+ consumerStatus: Chain -> str,
}
-
- // MODEL ACTIONS
+ // Defines the current state of a consumer chain. This information is accessible to that consumer on-chain.
+ type ConsumerState = {
+ // the state that each chain needs to store
+ chainState: ChainState,
- // the power of a validator on the provider chain is changed to the given amount. We do not care how this happens,
- // e.g. via undelegations, or delegations, ...
- action votingPowerChange(validator: Node, amount: int): bool =
- // for the provider chain, we need to adjust the voting power history
- // by adding a new set
- all {
- amount >= 0,
- val newValidatorSet = getCurrentValidatorSet(ProviderChain).getUpdatedValidatorSet(validator, amount)
- // set the running validator set on the provider chain, but don't update the history yet
- runningValidatorSet' = runningValidatorSet.set(ProviderChain, newValidatorSet),
- // no packets are sent yet, these are only sent on endAndBeginBlock
- RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
- outstandingPacketsToProvider' = outstandingPacketsToProvider,
- receivedMaturations' = receivedMaturations,
- CONSUMER_NOOP,
- // voting power history is only updated on endAndBeginBlock
- votingPowerHistories' = votingPowerHistories,
- // consumer statusses do not change
- consumerStatus' = consumerStatus,
- // chain times do not change
- curChainTimes' = curChainTimes,
- // the validator set is considered to have changed
- providerValidatorSetChangedInThisBlock' = true,
- }
+ // Stores the maturation times for VSCPackets received by this consumer
+ maturationTimes: VSCPacket -> Timestamp,
- // deliver the next outstanding packet from the consumer to the provider.
- // since this model assumes a single provider chain, this just takes a single chain as argument.
- action recvPacketOnProvider(consumer: Chain): bool = all {
- // ensure there is a packet to be received
- outstandingPacketsToProvider.get(consumer).length() > 0,
- // remove packet from outstanding packets
- val newPacketQueue = outstandingPacketsToProvider.get(consumer).tail()
- outstandingPacketsToProvider' = outstandingPacketsToProvider.set(consumer, newPacketQueue),
- // register the packet as received
- val maturedPacket = outstandingPacketsToProvider.get(consumer).head()
- receivedMaturations' = receivedMaturations.add(maturedPacket),
- CONSUMER_NOOP,
- RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
- votingPowerHistories' = votingPowerHistories,
- // no validator set changes are made
- runningValidatorSet' = runningValidatorSet,
- // consumer statusses do not change
- consumerStatus' = consumerStatus,
- // chain times do not change
- curChainTimes' = curChainTimes,
- // the validator set was not changed by this action (but might have been changed before in this block)
- providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
- }
+ // Stores the list of packets that have been sent to the provider chain by this consumer
+ // and have not been received yet.
+ // In the implementation, essentially unacknowledged IBC packets.
+ outstandingPacketsToProvider: Chain -> List[VSCMaturedPacket],
- // deliver the next outstanding packet from the provider to the consumer.
- // since this model assumes a single provider chain, this just takes a single chain as argument.
- action recvPacketOnConsumer(consumer: Chain): bool = all {
- // ensure there is a packet to be received
- outstandingPacketsToConsumer.get(consumer).length() > 0,
- // remove packet from outstanding packets
- val newPacketQueue = outstandingPacketsToConsumer.get(consumer).tail()
- val newOutstandingPackets = outstandingPacketsToConsumer.set(consumer, newPacketQueue)
- RegisterNewOutstandingPackets(newOutstandingPackets),
- val packet = outstandingPacketsToConsumer.get(consumer).head()
- all {
- // update the running validator set, but not the history yet,
- // as that only happens when the next block is started
- runningValidatorSet' = runningValidatorSet.set(consumer, packet.validatorSet),
- // add the new packet and store its maturation time
- val newMaturationTimes = maturationTimes.get(consumer).put(packet, curChainTimes.get(consumer) + UnbondingPeriod.get(consumer))
- maturationTimes' = maturationTimes.set(consumer, newMaturationTimes)
- },
- PROVIDER_NOOP,
- votingPowerHistories' = votingPowerHistories,
- outstandingPacketsToProvider' = outstandingPacketsToProvider,
- // consumer statusses do not change
- consumerStatus' = consumerStatus,
- // chain times do not change
- curChainTimes' = curChainTimes,
- // the validator set was not changed by this action (but might have been changed before in this block)
- providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
+ // Stores the list of voting powers that corresponded to voting powers
+ // at blocks over the chains entire existence.
+ // Voting powers should be ordered by recency in descending order.
+ votingPowerHistory: VotingPowerHistory,
}
- // ends the current block and starts the next block for a given chain.
- action endAndBeginBlock(chain: Chain): bool = any {
- all {
- chain == ProviderChain,
- endAndBeginBlockForProvider,
- },
- all {
- chain != ProviderChain,
- endAndBeginBlockForConsumer(chain),
- }
+ // the state of the protocol consists of the composition of the state of one provider chain with potentially many consumer chains.
+ type ProtocolState = {
+ providerState: ProviderState,
+ // the state of each consumer chain.
+ // note that we assume that this contains all consumer chains that may ever exist,
+ // and consumer chains that are currently not running will have providerState.consumerStatus == UNUSED or STOPPED
+ consumerStates: Set[ConsumerState]
}
- // gets the updated history for the current chain when ending a block, i.e. the
- // running validator set is added to the history if different from the last one.
- def getUpdatedHistory(chain: Chain): List[ValidatorSet] =
- // update voting power history if the validator set changed
- val newValidatorSet = runningValidatorSet.get(ProviderChain)
- val oldValidatorSet = votingPowerHistories.get(ProviderChain).head()
- if (newValidatorSet != oldValidatorSet)
- votingPowerHistories.get(ProviderChain).prepend(newValidatorSet)
- else
- votingPowerHistories.get(ProviderChain)
-
-
- action endAndBeginBlockForProvider(): bool = all {
- // update the voting power history
- votingPowerHistories' = votingPowerHistories.set(ProviderChain, getUpdatedHistory(ProviderChain)),
- // the running validator set is now for sure the current validator set,
- // so start with it in the next block
- runningValidatorSet' = runningValidatorSet,
- // send VSCPackets to consumers
- val newOutstandingPackets =
- // if running validator set is considered to have changed
- if (providerValidatorSetChangedInThisBlock)
- // then send a packet to each running consumer
- outstandingPacketsToConsumer.keys().mapBy(
- (consumer) =>
- val packetQueue = outstandingPacketsToConsumer.get(consumer)
- if (consumerStatus.get(consumer) == RUNNING) {
- packetQueue.append(
- {
- id: packetQueue.length(),
- validatorSet: runningValidatorSet.get(ProviderChain),
- timeout: curChainTimes.get(ProviderChain) + PacketTimeout
- }
- )
- } else {
- packetQueue
- }
- )
- else
- // otherwise, don't send any packets
- outstandingPacketsToConsumer
- RegisterNewOutstandingPackets(newOutstandingPackets),
- CONSUMER_NOOP,
- // no packets are sent to the provider
- outstandingPacketsToProvider' = outstandingPacketsToProvider,
- // do not receive any maturations
- receivedMaturations' = receivedMaturations,
- // consumer statusses do not change
- consumerStatus' = consumerStatus,
- // chain times do not change
- curChainTimes' = curChainTimes,
- // the validator set was definitely not changed in the new block yet, so set to false
- providerValidatorSetChangedInThisBlock' = false
+ type Error = {
+ message: str
}
- action endAndBeginBlockForConsumer(consumer: Chain): bool = all {
- ConsumerChains.contains(consumer),
- // update the voting power history
- votingPowerHistories' = votingPowerHistories.set(consumer, getUpdatedHistory(consumer)),
- // the running validator set is now for sure the current validator set,
- // so start with it in the next block
- runningValidatorSet' = runningValidatorSet,
- // compute mature packets whose maturation time has passed
- val maturedPackets = maturationTimes.get(consumer).keys().filter(
- packet =>
- val maturationTime = maturationTimes.get(consumer).get(packet)
- maturationTime <= curChainTimes.get(consumer)
- )
- all {
- // remove matured packets from the maturation times
- maturationTimes' = maturationTimes.set(consumer, maturationTimes.get(consumer).mapRemoveAll(maturedPackets)),
- // send matured packets
- outstandingPacketsToProvider' = outstandingPacketsToProvider.set(
- consumer,
- // construct VSCMaturedPackets from the matured VSCPackets
- outstandingPacketsToProvider.get(consumer).concat(
- maturedPackets.map(packet => {id: packet.id, timeout: 5}).toList()
- )
- )
- },
- PROVIDER_NOOP,
- // no packets are sent to consumer or received by it
- RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
- // consumer statusses do not change
- consumerStatus' = consumerStatus,
- // chain times do not change
- curChainTimes' = curChainTimes,
- // the validator set was not changed by this action (but might have been changed before in this block)
- // also, this is only a new block for a consumer, so the change variable shouldn't be reset
- providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
+ // we return either a result or an error.
+ // if hasError is true, newState may be arbitrary, but the error will be meaningful.
+ // if hasError is false, error may be arbitrary, but newState will be meaningful.
+ type Result = {
+ hasError: bool,
+ newState: ProtocolState,
+ error: Error
}
- // advance timestamps for maps nondeterministically
- action AdvanceTime(): bool =
- val advanceAmounts = curChainTimes.keys().mapBy(
- chain =>
- nondet amount = oneOf(1.to(10))
- amount
- )
- AdvanceTimeByMap(advanceAmounts)
-
- // the timestamp for each chain is advanced by the given amount
- action AdvanceTimeByMap(advancementAmount: Chain -> int): bool = all
+ pure def Ok(newState: ProtocolState): Result = {
{
- curChainTimes' = curChainTimes.keys().mapBy(
- chain =>
- curChainTimes.get(chain) + advancementAmount.get(chain)
- ),
- // all other variables are left untouched
- votingPowerHistories' = votingPowerHistories,
- runningValidatorSet' = runningValidatorSet,
- outstandingPacketsToProvider' = outstandingPacketsToProvider,
- RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
- receivedMaturations' = receivedMaturations,
- maturationTimes' = maturationTimes,
- // chain times do not change
- consumerStatus' = consumerStatus,
- // the validator set was not changed by this action (but might have been changed before in this block)
- providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
- }
-
- // each consumer chain may advance in the order
- // some events may necessitate a transition, e.g. timeouts.
- // shouldAdvance gives, for each consumer chain, whether it should advance if possible.
- // if a chain has to advance, e.g. due to timeouts, or may not advance, the value will have no effect.
- action AdvanceConsumers(shouldAdvance: Chain -> bool): bool =
- val newConsumerStatus = consumerStatus.keys().mapBy(
- chain =>
- val curStatus = consumerStatus.get(chain)
- if (curStatus == UNUSED) {
- if (shouldAdvance.get(chain))
- {
- RUNNING
- } else {
- UNUSED
- }
- }
- else if (curStatus == RUNNING) {
- // the chain may transition to stopped.
- // it is *forced* to stop if a packet timed out,
- // or if the inactivity timeout has passed
- if(consumerTimedOut(chain)) {
- STOPPED
- } else {
- if (shouldAdvance.get(chain)) {
- RUNNING
- } else {
- STOPPED
- }
- }
- } else {
- // stopped chains cannot restart, we assume a new chain would be started in that case
- STOPPED
+ hasError: false,
+ newState: newState,
+ error: {
+ message: ""
}
- )
- all {
- consumerStatus' = newConsumerStatus,
- // all other variables are left untouched
- votingPowerHistories' = votingPowerHistories,
- runningValidatorSet' = runningValidatorSet.keys().mapBy(
- chain =>
- if (newConsumerStatus.get(chain) == RUNNING and consumerStatus.get(chain) == UNUSED)
- // consumers that went from unused to running start with the current validator set on the provider
- {
- runningValidatorSet.get(ProviderChain)
- } else {
- runningValidatorSet.get(chain)
- }
- ),
- outstandingPacketsToProvider' = outstandingPacketsToProvider,
- RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
- receivedMaturations' = receivedMaturations,
- maturationTimes' = maturationTimes,
- // chain times do not change
- curChainTimes' = curChainTimes,
- // the validator set was not changed by this action (but might have been changed before in this block)
- providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
}
+ }
- // Updates the outstandingPacketsToConsumer and sentVSCPackets variables
- action RegisterNewOutstandingPackets(newOutstandingPackets: Chain -> List[VSCPacket]): bool =
- all {
- outstandingPacketsToConsumer' = newOutstandingPackets,
- StoreSentPackets(newOutstandingPackets),
+ pure def Err(msg: str): Result = {
+ {
+ hasError: true,
+ newState: {
+ providerState: {
+ chainState: {
+ votingPowerHistory: List(),
+ currentValidatorSet: Map(),
+ lastTimestamp: 0
+ },
+ outstandingPacketsToConsumer: Map(),
+ receivedMaturations: Set(),
+ sentVSCPackets: Map(),
+ providerValidatorSetChangedInThisBlock: false,
+ consumerStatus: Map()
+ },
+ consumerStates: Set()
+ },
+ error: {
+ message: msg
+ }
}
+ }
+ // ===================
+ // PROTOCOL LOGIC
+ // ===================
- // stores the VSCPackets sent in this step in sentVSCPackets
- action StoreSentPackets(newOutstandingPackets: Chain -> List[VSCPacket]): bool =
- sentVSCPackets' = sentVSCPackets.keys().mapBy(
- (chain) =>
- sentVSCPackets.get(chain).union(newOutstandingPackets.get(chain).toSet())
- )
-
+ pure def getUpdatedValidatorSet(oldValidatorSet: ValidatorSet, validator: Node, newVotingPower: int): ValidatorSet =
+ if (newVotingPower > 0)
+ oldValidatorSet.put(validator, newVotingPower)
+ else
+ oldValidatorSet.mapRemove(validator)
- // the main step action
- action step: bool = any {
- AdvanceTime,
- nondet node = oneOf(Nodes)
- nondet amount = oneOf(1.to(10))
- votingPowerChange(node, amount),
- recvPacketOnProvider(oneOf(ConsumerChains)),
- recvPacketOnConsumer(oneOf(ConsumerChains)),
- nondet chain = oneOf(chains)
- endAndBeginBlock(chain),
- val shouldAdvance = ConsumerChains.mapBy(
- chain =>
- nondet should = oneOf(Set(true, false))
- should
- )
- AdvanceConsumers(shouldAdvance),
- }
-
- pure val nodePowerSet = Nodes.powerset()
-
- def getArbitraryValidatorSet(): ValidatorSet =
- nondet numValidators = oneOf(1.to(Nodes.size()))
- // toList has nondeterministic behaviour, so this gets arbitrary validators
- nondet validators = oneOf(nodePowerSet.filter(s => s.size() == numValidators))
- validators.mapBy(
- validator =>
- nondet votingPower = oneOf(1.to(10))
- votingPower
- )
-
- // INITIALIZATION
- action init: bool =
- all {
- val validatorSets = chains.mapBy(
- (chain) =>
- // provider chain gets an arbitrary validator set, consumer chains have none
- if (chain == ProviderChain) getArbitraryValidatorSet else Map()
- )
- all {
- votingPowerHistories' = chains.mapBy(
- (chain) =>
- List(validatorSets.get(chain))
- ),
- runningValidatorSet' = validatorSets,
- },
- // each chain starts at time 0
- curChainTimes' = chains.mapBy(
- (chain) => 0
- ),
- // all consumer chains are unused
- consumerStatus' = chains.mapBy(chain => UNUSED),
- // no packets are outstanding
- outstandingPacketsToProvider' = chains.mapBy(chain => List()),
- outstandingPacketsToConsumer' = chains.mapBy(chain => List()),
- // no maturations have been received by provider
- receivedMaturations' = Set(),
- // no packets have been sent to consumers
- sentVSCPackets' = chains.mapBy(chain => Set()),
- // no packets have been received by consumers, so no maturation times set
- maturationTimes' = chains.mapBy(chain => Map()),
- // validator set was not changed yet
- providerValidatorSetChangedInThisBlock' = false
- }
+ // the power of a validator on the provider chain is changed to the given amount. We do not care how this happens,
+ // e.g. via undelegations, or delegations, ...
+ pure def votingPowerChange(currentState: ProtocolState, validator: Node, amount: int): Result =
+ // adjust the current validator set on the provider - it is not entered in the voting history yet because that happens only on block end
+ val currentValidatorSet = currentState.providerState.currentValidatorSet
+ // val newValidatorSet = getUpdatedValidatorSet(currentValidatorSet, validator, amount)
+ // val newProviderState = currentState.providerState.with(
+ // "currentValidatorSet", newValidatorSet
+ // )
+ // val newState = currentState.with(
+ // "providerState", newProviderState
+ // )
+ Err("not implemented")
+}
- // PROPERTIES
-
- // Every validator set on any consumer chain MUST either be or
- // have been a validator set on the provider chain.
- val ValidatorSetReplication: bool =
- chains.forall(
- chain => chain.getCurrentValidatorSet().wasValidatorSetOnProvider()
- )
-
- // TESTS
- run VSCHappyPathTest: bool = {
- init
- // trigger a votingPowerChange on the provider chain
- .then(votingPowerChange("A", 10))
- // endAndBeginBlock on provider. No consumer chains are running, so no packets are sent
- .then(endAndBeginBlock(ProviderChain))
- .then(all {
- // no packet was sent
- assert(outstandingPacketsToConsumer.get("chain1").length() == 0),
- // advance chain1 to running
- AdvanceConsumers(NoStatusAdvancement.set("chain1", true))
- })
- // consumer chain should have current validator set from provider
- .then(
- all {
- // since consumer chain just started, its assumed to have the validator set from provider
- assert(runningValidatorSet.get("chain1") == runningValidatorSet.get(ProviderChain)),
- // trigger a votingPowerChange on the provider chain
- votingPowerChange("B", 10)
- }
- )
- .then(
- val valSet = runningValidatorSet.get(ProviderChain)
- endAndBeginBlock(ProviderChain)
- // now the provider should send a packet on block end
- .then(all {
- // a packet was sent
- assert(outstandingPacketsToConsumer.get("chain1").length() == 1),
- // deliver the packet to the consumer
- recvPacketOnConsumer("chain1")
- })
- .then(
- // consumer needs to end a block before it has the new validator set
- endAndBeginBlock("chain1")
- )
- .then(all {
- // the consumer should have the new validator set
- assert(runningValidatorSet.get("chain1") == valSet),
- // put a last action to satisfy the action effect
- AdvanceConsumers(NoStatusAdvancement)
- })
- )
- }
+module ccv_tests {
+ import ccv_logic.*
+
+ // // UTILITY FUNCTIONS & ACTIONS
+ // def wasValidatorSetOnProvider(validatorSet: ValidatorSet): bool = {
+ // votingPowerHistories.get(ProviderChain).toSet().exists(
+ // historicalValSet => historicalValSet == validatorSet
+ // )
+ // }
+
+ // def getCurrentValidatorSet(chain: Chain): ValidatorSet =
+ // votingPowerHistories.get(chain).head()
+
+ // // returns true if the consumer has timed out and should be dropped
+ // def consumerTimedOut(consumer: Chain): bool =
+ // any {
+ // // either a package from provider to consumer has timed out
+ // outstandingPacketsToConsumer.get(consumer).select(
+ // packet => packet.timeout <= curChainTimes.get(consumer)
+ // ).length() > 0,
+ // // or a package from consumer to provider has timed out
+ // outstandingPacketsToProvider.get(consumer).select(
+ // packet => packet.timeout <= curChainTimes.get(ProviderChain)
+ // ).length() > 0,
+ // // or the inactivity timeout has passed since a VSCPacket was sent to the consumer, but the
+ // // provider has not received a VSCMaturedPacket for it
+ // val packetsWithoutResponse = sentVSCPackets.get(consumer).filter( // get packets without response
+ // packet =>
+ // not(receivedMaturations.exists(
+ // maturedPacket => maturedPacket.id == packet.id
+ // ))
+ // )
+ // // among those, get packets where inactivity timeout has passed
+ // packetsWithoutResponse.filter(
+ // packet =>
+ // val sentAt = curChainTimes.get(ProviderChain) - PacketTimeout // compute when the packet was sent
+ // val timesOutAt = sentAt + InactivityTimeout // compute when the packet times out
+ // timesOutAt <= curChainTimes.get(ProviderChain)
+ // ).size() > 0
+ // }
+
+ // // utility action that leaves all provider state untouched
+ // action PROVIDER_NOOP(): bool =
+ // all {
+ // receivedMaturations' = receivedMaturations,
+ // }
+
+ // // utility action that leaves all consumer state untouched
+ // action CONSUMER_NOOP(): bool =
+ // all {
+ // maturationTimes' = maturationTimes,
+ // }
+
+ // // MODEL ACTIONS
+
+ // // the power of a validator on the provider chain is changed to the given amount. We do not care how this happens,
+ // // e.g. via undelegations, or delegations, ...
+ // action doVotingPowerChange(validator: Node, amount: int): bool =
+ // // for the provider chain, we need to adjust the voting power history
+ // // by adding a new set
+ // all {
+ // amount >= 0,
+ // val newValidatorSet = getCurrentValidatorSet(ProviderChain).getUpdatedValidatorSet(validator, amount)
+ // // set the running validator set on the provider chain, but don't update the history yet
+ // runningValidatorSet' = runningValidatorSet.set(ProviderChain, newValidatorSet),
+ // // no packets are sent yet, these are only sent on endAndBeginBlock
+ // RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
+ // outstandingPacketsToProvider' = outstandingPacketsToProvider,
+ // receivedMaturations' = receivedMaturations,
+ // CONSUMER_NOOP,
+ // // voting power history is only updated on endAndBeginBlock
+ // votingPowerHistories' = votingPowerHistories,
+ // // consumer statusses do not change
+ // consumerStatus' = consumerStatus,
+ // // chain times do not change
+ // curChainTimes' = curChainTimes,
+ // // the validator set is considered to have changed
+ // providerValidatorSetChangedInThisBlock' = true,
+ // }
+
+ // // deliver the next outstanding packet from the consumer to the provider.
+ // // since this model assumes a single provider chain, this just takes a single chain as argument.
+ // action recvPacketOnProvider(consumer: Chain): bool = all {
+ // // ensure there is a packet to be received
+ // outstandingPacketsToProvider.get(consumer).length() > 0,
+ // // remove packet from outstanding packets
+ // val newPacketQueue = outstandingPacketsToProvider.get(consumer).tail()
+ // outstandingPacketsToProvider' = outstandingPacketsToProvider.set(consumer, newPacketQueue),
+ // // register the packet as received
+ // val maturedPacket = outstandingPacketsToProvider.get(consumer).head()
+ // receivedMaturations' = receivedMaturations.add(maturedPacket),
+ // CONSUMER_NOOP,
+ // RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
+ // votingPowerHistories' = votingPowerHistories,
+ // // no validator set changes are made
+ // runningValidatorSet' = runningValidatorSet,
+ // // consumer statusses do not change
+ // consumerStatus' = consumerStatus,
+ // // chain times do not change
+ // curChainTimes' = curChainTimes,
+ // // the validator set was not changed by this action (but might have been changed before in this block)
+ // providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
+ // }
+
+ // // deliver the next outstanding packet from the provider to the consumer.
+ // // since this model assumes a single provider chain, this just takes a single chain as argument.
+ // action recvPacketOnConsumer(consumer: Chain): bool = all {
+ // // ensure there is a packet to be received
+ // outstandingPacketsToConsumer.get(consumer).length() > 0,
+ // // remove packet from outstanding packets
+ // val newPacketQueue = outstandingPacketsToConsumer.get(consumer).tail()
+ // val newOutstandingPackets = outstandingPacketsToConsumer.set(consumer, newPacketQueue)
+ // RegisterNewOutstandingPackets(newOutstandingPackets),
+ // val packet = outstandingPacketsToConsumer.get(consumer).head()
+ // all {
+ // // update the running validator set, but not the history yet,
+ // // as that only happens when the next block is started
+ // runningValidatorSet' = runningValidatorSet.set(consumer, packet.validatorSet),
+ // // add the new packet and store its maturation time
+ // val newMaturationTimes = maturationTimes.get(consumer).put(packet, curChainTimes.get(consumer) + UnbondingPeriod.get(consumer))
+ // maturationTimes' = maturationTimes.set(consumer, newMaturationTimes)
+ // },
+ // PROVIDER_NOOP,
+ // votingPowerHistories' = votingPowerHistories,
+ // outstandingPacketsToProvider' = outstandingPacketsToProvider,
+ // // consumer statusses do not change
+ // consumerStatus' = consumerStatus,
+ // // chain times do not change
+ // curChainTimes' = curChainTimes,
+ // // the validator set was not changed by this action (but might have been changed before in this block)
+ // providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
+ // }
+
+ // // ends the current block and starts the next block for a given chain.
+ // action endAndBeginBlock(chain: Chain): bool = any {
+ // all {
+ // chain == ProviderChain,
+ // endAndBeginBlockForProvider,
+ // },
+ // all {
+ // chain != ProviderChain,
+ // endAndBeginBlockForConsumer(chain),
+ // }
+ // }
+
+ // // gets the updated history for the current chain when ending a block, i.e. the
+ // // running validator set is added to the history if different from the last one.
+ // def getUpdatedHistory(chain: Chain): List[ValidatorSet] =
+ // // update voting power history if the validator set changed
+ // val newValidatorSet = runningValidatorSet.get(ProviderChain)
+ // val oldValidatorSet = votingPowerHistories.get(ProviderChain).head()
+ // if (newValidatorSet != oldValidatorSet)
+ // votingPowerHistories.get(ProviderChain).prepend(newValidatorSet)
+ // else
+ // votingPowerHistories.get(ProviderChain)
+
+
+ // action endAndBeginBlockForProvider(): bool = all {
+ // // update the voting power history
+ // votingPowerHistories' = votingPowerHistories.set(ProviderChain, getUpdatedHistory(ProviderChain)),
+ // // the running validator set is now for sure the current validator set,
+ // // so start with it in the next block
+ // runningValidatorSet' = runningValidatorSet,
+ // // send VSCPackets to consumers
+ // val newOutstandingPackets =
+ // // if running validator set is considered to have changed
+ // if (providerValidatorSetChangedInThisBlock)
+ // // then send a packet to each running consumer
+ // outstandingPacketsToConsumer.keys().mapBy(
+ // (consumer) =>
+ // val packetQueue = outstandingPacketsToConsumer.get(consumer)
+ // if (consumerStatus.get(consumer) == RUNNING) {
+ // packetQueue.append(
+ // {
+ // id: packetQueue.length(),
+ // validatorSet: runningValidatorSet.get(ProviderChain),
+ // timeout: curChainTimes.get(ProviderChain) + PacketTimeout
+ // }
+ // )
+ // } else {
+ // packetQueue
+ // }
+ // )
+ // else
+ // // otherwise, don't send any packets
+ // outstandingPacketsToConsumer
+ // RegisterNewOutstandingPackets(newOutstandingPackets),
+ // CONSUMER_NOOP,
+ // // no packets are sent to the provider
+ // outstandingPacketsToProvider' = outstandingPacketsToProvider,
+ // // do not receive any maturations
+ // receivedMaturations' = receivedMaturations,
+ // // consumer statusses do not change
+ // consumerStatus' = consumerStatus,
+ // // chain times do not change
+ // curChainTimes' = curChainTimes,
+ // // the validator set was definitely not changed in the new block yet, so set to false
+ // providerValidatorSetChangedInThisBlock' = false
+ // }
+
+ // action endAndBeginBlockForConsumer(consumer: Chain): bool = all {
+ // ConsumerChains.contains(consumer),
+ // // update the voting power history
+ // votingPowerHistories' = votingPowerHistories.set(consumer, getUpdatedHistory(consumer)),
+ // // the running validator set is now for sure the current validator set,
+ // // so start with it in the next block
+ // runningValidatorSet' = runningValidatorSet,
+ // // compute mature packets whose maturation time has passed
+ // val maturedPackets = maturationTimes.get(consumer).keys().filter(
+ // packet =>
+ // val maturationTime = maturationTimes.get(consumer).get(packet)
+ // maturationTime <= curChainTimes.get(consumer)
+ // )
+ // all {
+ // // remove matured packets from the maturation times
+ // maturationTimes' = maturationTimes.set(consumer, maturationTimes.get(consumer).mapRemoveAll(maturedPackets)),
+ // // send matured packets
+ // outstandingPacketsToProvider' = outstandingPacketsToProvider.set(
+ // consumer,
+ // // construct VSCMaturedPackets from the matured VSCPackets
+ // outstandingPacketsToProvider.get(consumer).concat(
+ // maturedPackets.map(packet => {id: packet.id, timeout: 5}).toList()
+ // )
+ // )
+ // },
+ // PROVIDER_NOOP,
+ // // no packets are sent to consumer or received by it
+ // RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
+ // // consumer statusses do not change
+ // consumerStatus' = consumerStatus,
+ // // chain times do not change
+ // curChainTimes' = curChainTimes,
+ // // the validator set was not changed by this action (but might have been changed before in this block)
+ // // also, this is only a new block for a consumer, so the change variable shouldn't be reset
+ // providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
+ // }
+
+ // // advance timestamps for maps nondeterministically
+ // action AdvanceTime(): bool =
+ // val advanceAmounts = curChainTimes.keys().mapBy(
+ // chain =>
+ // nondet amount = oneOf(1.to(10))
+ // amount
+ // )
+ // AdvanceTimeByMap(advanceAmounts)
+
+ // // the timestamp for each chain is advanced by the given amount
+ // action AdvanceTimeByMap(advancementAmount: Chain -> int): bool = all
+ // {
+ // curChainTimes' = curChainTimes.keys().mapBy(
+ // chain =>
+ // curChainTimes.get(chain) + advancementAmount.get(chain)
+ // ),
+ // // all other variables are left untouched
+ // votingPowerHistories' = votingPowerHistories,
+ // runningValidatorSet' = runningValidatorSet,
+ // outstandingPacketsToProvider' = outstandingPacketsToProvider,
+ // RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
+ // receivedMaturations' = receivedMaturations,
+ // maturationTimes' = maturationTimes,
+ // // chain times do not change
+ // consumerStatus' = consumerStatus,
+ // // the validator set was not changed by this action (but might have been changed before in this block)
+ // providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
+ // }
+
+ // // each consumer chain may advance in the order
+ // // some events may necessitate a transition, e.g. timeouts.
+ // // shouldAdvance gives, for each consumer chain, whether it should advance if possible.
+ // // if a chain has to advance, e.g. due to timeouts, or may not advance, the value will have no effect.
+ // action AdvanceConsumers(shouldAdvance: Chain -> bool): bool =
+ // val newConsumerStatus = consumerStatus.keys().mapBy(
+ // chain =>
+ // val curStatus = consumerStatus.get(chain)
+ // if (curStatus == UNUSED) {
+ // if (shouldAdvance.get(chain))
+ // {
+ // RUNNING
+ // } else {
+ // UNUSED
+ // }
+ // }
+ // else if (curStatus == RUNNING) {
+ // // the chain may transition to stopped.
+ // // it is *forced* to stop if a packet timed out,
+ // // or if the inactivity timeout has passed
+ // if(consumerTimedOut(chain)) {
+ // STOPPED
+ // } else {
+ // if (shouldAdvance.get(chain)) {
+ // RUNNING
+ // } else {
+ // STOPPED
+ // }
+ // }
+ // } else {
+ // // stopped chains cannot restart, we assume a new chain would be started in that case
+ // STOPPED
+ // }
+ // )
+ // all {
+ // consumerStatus' = newConsumerStatus,
+ // // all other variables are left untouched
+ // votingPowerHistories' = votingPowerHistories,
+ // runningValidatorSet' = runningValidatorSet.keys().mapBy(
+ // chain =>
+ // if (newConsumerStatus.get(chain) == RUNNING and consumerStatus.get(chain) == UNUSED)
+ // // consumers that went from unused to running start with the current validator set on the provider
+ // {
+ // runningValidatorSet.get(ProviderChain)
+ // } else {
+ // runningValidatorSet.get(chain)
+ // }
+ // ),
+ // outstandingPacketsToProvider' = outstandingPacketsToProvider,
+ // RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
+ // receivedMaturations' = receivedMaturations,
+ // maturationTimes' = maturationTimes,
+ // // chain times do not change
+ // curChainTimes' = curChainTimes,
+ // // the validator set was not changed by this action (but might have been changed before in this block)
+ // providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
+ // }
+
+ // // Updates the outstandingPacketsToConsumer and sentVSCPackets variables
+ // action RegisterNewOutstandingPackets(newOutstandingPackets: Chain -> List[VSCPacket]): bool =
+ // all {
+ // outstandingPacketsToConsumer' = newOutstandingPackets,
+ // StoreSentPackets(newOutstandingPackets),
+ // }
+
+
+ // // stores the VSCPackets sent in this step in sentVSCPackets
+ // action StoreSentPackets(newOutstandingPackets: Chain -> List[VSCPacket]): bool =
+ // sentVSCPackets' = sentVSCPackets.keys().mapBy(
+ // (chain) =>
+ // sentVSCPackets.get(chain).union(newOutstandingPackets.get(chain).toSet())
+ // )
+
+ // // the main step action
+ // action step: bool = any {
+ // AdvanceTime,
+ // nondet node = oneOf(Nodes)
+ // nondet amount = oneOf(1.to(10))
+ // votingPowerChange(node, amount),
+ // recvPacketOnProvider(oneOf(ConsumerChains)),
+ // recvPacketOnConsumer(oneOf(ConsumerChains)),
+ // nondet chain = oneOf(chains)
+ // endAndBeginBlock(chain),
+ // val shouldAdvance = ConsumerChains.mapBy(
+ // chain =>
+ // nondet should = oneOf(Set(true, false))
+ // should
+ // )
+ // AdvanceConsumers(shouldAdvance),
+ // }
+
+ // pure val nodePowerSet = Nodes.powerset()
+
+ // def getArbitraryValidatorSet(): ValidatorSet =
+ // nondet numValidators = oneOf(1.to(Nodes.size()))
+ // // toList has nondeterministic behaviour, so this gets arbitrary validators
+ // nondet validators = oneOf(nodePowerSet.filter(s => s.size() == numValidators))
+ // validators.mapBy(
+ // validator =>
+ // nondet votingPower = oneOf(1.to(10))
+ // votingPower
+ // )
+
+ // // INITIALIZATION
+ // action init: bool =
+ // all {
+ // val validatorSets = chains.mapBy(
+ // (chain) =>
+ // // provider chain gets an arbitrary validator set, consumer chains have none
+ // if (chain == ProviderChain) getArbitraryValidatorSet else Map()
+ // )
+ // all {
+ // votingPowerHistories' = chains.mapBy(
+ // (chain) =>
+ // List(validatorSets.get(chain))
+ // ),
+ // runningValidatorSet' = validatorSets,
+ // },
+ // // each chain starts at time 0
+ // curChainTimes' = chains.mapBy(
+ // (chain) => 0
+ // ),
+ // // all consumer chains are unused
+ // consumerStatus' = chains.mapBy(chain => UNUSED),
+ // // no packets are outstanding
+ // outstandingPacketsToProvider' = chains.mapBy(chain => List()),
+ // outstandingPacketsToConsumer' = chains.mapBy(chain => List()),
+ // // no maturations have been received by provider
+ // receivedMaturations' = Set(),
+ // // no packets have been sent to consumers
+ // sentVSCPackets' = chains.mapBy(chain => Set()),
+ // // no packets have been received by consumers, so no maturation times set
+ // maturationTimes' = chains.mapBy(chain => Map()),
+ // // validator set was not changed yet
+ // providerValidatorSetChangedInThisBlock' = false
+ // }
+
+ // // PROPERTIES
+
+ // // Every validator set on any consumer chain MUST either be or
+ // // have been a validator set on the provider chain.
+ // val ValidatorSetReplication: bool =
+ // chains.forall(
+ // chain => chain.getCurrentValidatorSet().wasValidatorSetOnProvider()
+ // )
+
+ // // TESTS
+ // run VSCHappyPathTest: bool = {
+ // init
+ // // trigger a votingPowerChange on the provider chain
+ // .then(votingPowerChange("A", 10))
+ // // endAndBeginBlock on provider. No consumer chains are running, so no packets are sent
+ // .then(endAndBeginBlock(ProviderChain))
+ // .then(all {
+ // // no packet was sent
+ // assert(outstandingPacketsToConsumer.get("chain1").length() == 0),
+ // // advance chain1 to running
+ // AdvanceConsumers(NoStatusAdvancement.set("chain1", true))
+ // })
+ // // consumer chain should have current validator set from provider
+ // .then(
+ // all {
+ // // since consumer chain just started, its assumed to have the validator set from provider
+ // assert(runningValidatorSet.get("chain1") == runningValidatorSet.get(ProviderChain)),
+ // // trigger a votingPowerChange on the provider chain
+ // votingPowerChange("B", 10)
+ // }
+ // )
+ // .then(
+ // val valSet = runningValidatorSet.get(ProviderChain)
+ // endAndBeginBlock(ProviderChain)
+ // // now the provider should send a packet on block end
+ // .then(all {
+ // // a packet was sent
+ // assert(outstandingPacketsToConsumer.get("chain1").length() == 1),
+ // // deliver the packet to the consumer
+ // recvPacketOnConsumer("chain1")
+ // })
+ // .then(
+ // // consumer needs to end a block before it has the new validator set
+ // endAndBeginBlock("chain1")
+ // )
+ // .then(all {
+ // // the consumer should have the new validator set
+ // assert(runningValidatorSet.get("chain1") == valSet),
+ // // put a last action to satisfy the action effect
+ // AdvanceConsumers(NoStatusAdvancement)
+ // })
+ // )
+ // }
+
+ // // utility: the set of consumers currently running
+ // val RunningConsumers: Set[Chain] =
+ // ConsumerChains.filter(chain => consumerStatus.get(chain) == RUNNING)
+
+ // // MODEL STATE
+ // // --SHARED STATE
+
+ // // Stores, for each chain, the list of voting powers that corresponded to voting powers
+ // // at blocks over its entire existence.
+ // // Voting powers should be ordered by recency in descending order.
+ // var votingPowerHistories: Chain -> List[ValidatorSet]
+
+ // // the current validator set on each chain.
+ // // this will be included in the next block, but might not be final yet,
+ // // e.g. there may be more modifications in the current block.
+ // var runningValidatorSet: Chain -> ValidatorSet
+
+ // // the current timestamp for each chain
+ // var curChainTimes: Chain -> Timestamp
+
+ // // stores, for each chain, its current status -
+ // // unused, running, or stopped
+ // var consumerStatus: Chain -> str
+
+ // // --CHANNELS
+ // // Stores, for each consumer chain, the list of packets that have been sent to the provider chain
+ // // and have not been received yet.
+ // var outstandingPacketsToProvider: Chain -> List[VSCMaturedPacket]
+
+ // // Stores, for each consumer chain, the list of packets that have been sent to the consumer chain
+ // // and have not been received yet.
+ // var outstandingPacketsToConsumer: Chain -> List[VSCPacket]
+
+
+ // // --CONSUMER STATE
+ // // Stores the maturation times for VSCPackets received by consumers
+ // var maturationTimes: Chain -> (VSCPacket -> Timestamp)
+
+ // // --PROVIDER STATE
+ // // the set of VSCMaturedPackets received by the provider chain
+ // var receivedMaturations: Set[VSCMaturedPacket]
+
+ // // stores which VSC Packets have been sent to compare with receivedMaturations to detect timeouts due to non-responsiveness
+ // var sentVSCPackets: Chain -> Set[VSCPacket]
+
+ // // stores whether, in this step, the validator set considered to be changed.
+ // // this is needed because the validator set might be considered to have changed, even though
+ // // it is still technically identical at our level of abstraction, e.g. a validator power change on the provider
+ // // might leave the validator set the same because a delegation and undelegation cancel each other out.
+ // var providerValidatorSetChangedInThisBlock: bool
+
+ // // utility: a struct summarizing the current state
+ // val state =
+ // {
+ // votingPowerHistories: votingPowerHistories,
+ // runningValidatorSet: runningValidatorSet,
+ // curChainTimes: curChainTimes,
+ // consumerStatus: consumerStatus,
+ // outstandingPacketsToProvider: outstandingPacketsToProvider,
+ // outstandingPacketsToConsumer: outstandingPacketsToConsumer,
+ // maturationTimes: maturationTimes,
+ // receivedMaturations: receivedMaturations,
+ // sentVSCPackets: sentVSCPackets,
+ // }
+
+ // // set of identifiers of potential nodes
+ // pure val Nodes: Set[Node] =
+ // Set("A", "B", "C", "D", "E", "F", "G", "H", "I", "J")
+
+ // // the set of consumer chains
+ // pure val ConsumerChains: Set[Chain] =
+ // Set("chain1", "chain2", "chain3")
+
+ // // The singular provider chain.
+ // pure val ProviderChain: Chain =
+ // "provider"
+
+ // pure val chains = ConsumerChains.union(Set(ProviderChain))
+
+ // // length of the unbonding period on each chain
+ // pure val UnbondingPeriod: Chain -> int = chains.mapBy(
+ // (chain) =>
+ // 10
+ // )
+
+ // // the time until a packet times out
+ // pure val PacketTimeout: int =
+ // 5
+
+ // // the time until a consumer chain is dropped by the provider due to inactivity
+ // pure val InactivityTimeout: int =
+ // 10
+
+ // // utility: a map assigning each chain to 0, used for not advancing timestamps
+ // pure val NoTimeAdvancement: Chain -> int = chains.mapBy(
+ // (chain) =>
+ // 0
+ // )
+
+
+ // // utility: a map assigning each chain to false, used for not advancing consumer status
+ // pure val NoStatusAdvancement: Chain -> bool = chains.mapBy(
+ // (chain) =>
+ // false
+ // )
}
diff --git a/tests/difference/core/quint_model/extraSpells.qnt b/tests/difference/core/quint_model/extraSpells.qnt
index cd2105beb7..5c98f7a384 100644
--- a/tests/difference/core/quint_model/extraSpells.qnt
+++ b/tests/difference/core/quint_model/extraSpells.qnt
@@ -1,4 +1,3 @@
--*- mode: Bluespec; -*-
// This module is just a library with utility functions (sometimes called spells in Quint).
module extraSpells {
From 4bbe033ddf663526c2904f842ec301efe8a7996f Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Tue, 19 Sep 2023 17:00:27 +0200
Subject: [PATCH 14/35] Continue seperating logic in Quint model
---
tests/difference/core/quint_model/ccv.qnt | 114 ++++++++++++++++++----
1 file changed, 97 insertions(+), 17 deletions(-)
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index 4cbc327ac4..5df198fd73 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -103,8 +103,9 @@ module ccv_logic {
// Stores the list of packets that have been sent to the provider chain by this consumer
// and have not been received yet.
+ // ordered by recency, so the head is the oldest packet.
// In the implementation, essentially unacknowledged IBC packets.
- outstandingPacketsToProvider: Chain -> List[VSCMaturedPacket],
+ outstandingPacketsToProvider: List[VSCMaturedPacket],
// Stores the list of voting powers that corresponded to voting powers
// at blocks over the chains entire existence.
@@ -118,7 +119,7 @@ module ccv_logic {
// the state of each consumer chain.
// note that we assume that this contains all consumer chains that may ever exist,
// and consumer chains that are currently not running will have providerState.consumerStatus == UNUSED or STOPPED
- consumerStates: Set[ConsumerState]
+ consumerStates: Chain -> ConsumerState
}
type Error = {
@@ -160,7 +161,7 @@ module ccv_logic {
providerValidatorSetChangedInThisBlock: false,
consumerStatus: Map()
},
- consumerStates: Set()
+ consumerStates: Map(),
},
error: {
message: msg
@@ -171,26 +172,105 @@ module ccv_logic {
// ===================
// PROTOCOL LOGIC
// ===================
+
+ // the power of a validator on the provider chain is changed to the given amount. We do not care how this happens,
+ // e.g. via undelegations, or delegations, ...
+ pure def votingPowerChange(currentState: ProtocolState, validator: Node, amount: int): Result = {
+ if (amount < 0) {
+ Err("Voting power changes must be positive")
+ } else {
+ pure val currentValidatorSet = currentState.providerState.chainState.currentValidatorSet
+ pure val newValidatorSet = getUpdatedValidatorSet(currentValidatorSet, validator, amount)
+ pure val newState = setProviderValidatorSet(currentState, newValidatorSet)
+ Ok(newState)
+ }
+ }
+
+ // Delivers the next queued VSCMaturedPacket from a consumer chain to the provider chain.
+ // Only argument is the consumer chain, from which the packet will be delivered.
+ pure def deliverPacketToProvider(currentState: ProtocolState, sender: Chain): Result = {
+ if (not(isCurrentlyConsumer(sender, currentState))) {
+ Err("Sender is not currently a consumer - must have 'running' status!")
+ } else if (length(currentState.consumerStates.get(sender).outstandingPacketsToProvider) == 0) {
+ Err("No outstanding packets to deliver")
+ } else {
+ val packet = currentState.consumerStates.get(sender).outstandingPacketsToProvider.head()
+ val result = recvPacketOnProvider(currentState, sender, packet)
+ val tmpState = result.newState
+ if (result.hasError) {
+ Err(result.error.message)
+ } else {
+ val result2 = removeOutstandingPacketFromConsumer(tmpState, sender)
+ val tmpState2 = result2.newState
+ val err2 = result2.error
+ if (result2.hasError) {
+ Err(err2.message)
+ } else {
+ Ok(tmpState2)
+ }
+ }
+ }
+ }
+
+ // ===================
+ // UTILITY FUNCTIONS
+ // ===================
+
+ // receives a given packet on the provider. The arguments are the consumer chain that sent the packet, and the packet itself.
+ pure def recvPacketOnProvider(currentState: ProtocolState, sender: Chain, packet: VSCMaturedPacket): Result = {
+ if (not(isCurrentlyConsumer(sender, currentState))) {
+ Err("Sender is not currently a consumer - must have 'running' status!")
+ } else {
+ val currentReceivedMaturations = currentState.providerState.receivedMaturations
+ val newReceivedMaturations = currentReceivedMaturations.add(packet)
+ val newProviderState = currentState.providerState.with(
+ "receivedMaturations", newReceivedMaturations
+ )
+ val newState = currentState.with(
+ "providerState", newProviderState
+ )
+ Ok(newState)
+ }
+ }
+
+ // removes the oldest outstanding packet from the consumer. on-chain, this would happen when the packet is acknowledged.
+ // only the oldest packet can be removed, since we model ordered channels.
+ pure def removeOutstandingPacketFromConsumer(currentState: ProtocolState, sender: Chain): Result = {
+ val currentOutstandingPackets = currentState.consumerStates.get(sender).outstandingPacketsToProvider
+ val newOutstandingPackets = currentOutstandingPackets.tail()
+ val newConsumerState = currentState.consumerState.get(sender).with(
+ "outstandingPacketsToProvider", newOutstandingPackets
+ )
+ val newConsumerStates = currentState.consumerStates.set(sender, newConsumerState)
+ val newState = currentState.with(
+ "consumerStates", newConsumerStates
+ )
+ Ok(newState)
+ // Err("Not implemented")
+ }
pure def getUpdatedValidatorSet(oldValidatorSet: ValidatorSet, validator: Node, newVotingPower: int): ValidatorSet =
if (newVotingPower > 0)
oldValidatorSet.put(validator, newVotingPower)
else
oldValidatorSet.mapRemove(validator)
-
- // the power of a validator on the provider chain is changed to the given amount. We do not care how this happens,
- // e.g. via undelegations, or delegations, ...
- pure def votingPowerChange(currentState: ProtocolState, validator: Node, amount: int): Result =
- // adjust the current validator set on the provider - it is not entered in the voting history yet because that happens only on block end
- val currentValidatorSet = currentState.providerState.currentValidatorSet
- // val newValidatorSet = getUpdatedValidatorSet(currentValidatorSet, validator, amount)
- // val newProviderState = currentState.providerState.with(
- // "currentValidatorSet", newValidatorSet
- // )
- // val newState = currentState.with(
- // "providerState", newProviderState
- // )
- Err("not implemented")
+
+ pure def setProviderValidatorSet(currentState: ProtocolState, newValidatorSet: ValidatorSet): ProtocolState = {
+ pure val newChainState = currentState.providerState.chainState.with(
+ "currentValidatorSet", newValidatorSet
+ )
+ currentState.with(
+ "providerState",
+ currentState.providerState.with(
+ "chainState", newChainState
+ )
+ )
+ }
+
+ pure def isCurrentlyConsumer(chain: Chain, currentState: ProtocolState): bool = {
+ val status = currentState.providerState.consumerStatus.get(chain)
+ status == RUNNING
+ }
}
module ccv_tests {
From 5ebab39b7aacc30fa2a5963d1187860c706953d4 Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Wed, 20 Sep 2023 13:23:55 +0200
Subject: [PATCH 15/35] Start debugging cryptic error message
---
tests/difference/core/quint_model/ccv.qnt | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index 5df198fd73..d0924f9e50 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -106,11 +106,6 @@ module ccv_logic {
// ordered by recency, so the head is the oldest packet.
// In the implementation, essentially unacknowledged IBC packets.
outstandingPacketsToProvider: List[VSCMaturedPacket],
-
- // Stores the list of voting powers that corresponded to voting powers
- // at blocks over the chains entire existence.
- // Voting powers should be ordered by recency in descending order.
- votingPowerHistory: VotingPowerHistory,
}
// the state of the protocol consists of the composition of the state of one provider chain with potentially many consumer chains.
@@ -153,7 +148,7 @@ module ccv_logic {
chainState: {
votingPowerHistory: List(),
currentValidatorSet: Map(),
- lastTimestamp: 0
+ lastTimestamp: 0,
},
outstandingPacketsToConsumer: Map(),
receivedMaturations: Set(),
From 5bca6fc7e3a50c03dba32a7ad7df2a0361cbba2d Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Wed, 20 Sep 2023 14:38:51 +0200
Subject: [PATCH 16/35] Start adding endAndBeginBlock defs
---
tests/difference/core/quint_model/ccv.qnt | 123 ++++++++++++++++--
.../core/quint_model/extraSpells.qnt | 13 --
2 files changed, 112 insertions(+), 24 deletions(-)
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index d0924f9e50..1c250f94b8 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -9,7 +9,6 @@ module ccv_logic {
// Things that explicitly are modelled:
// * Validator set changes are propagated from provider to consumers
// * VSC packets mature
- // * Consumers can be forcefully dropped due to timeouts, or stop on their own volition
// ===================
// TYPE DEFINITIONS
@@ -23,11 +22,10 @@ module ccv_logic {
// a list of validator sets per blocks, ordered by recency
type VotingPowerHistory = List[ValidatorSet]
- // --PACKETS
type VSCPacket =
{
// the identifier for this packet
- id: int,
+ VscId: int,
// the new validator set. in the implementation, this would be a list of validator updates
validatorSet: ValidatorSet,
// the time, that when passed on the receiver chain, will mean the packet is considered timed out
@@ -41,12 +39,6 @@ module ccv_logic {
// the time, that when passed on the receiver chain, will mean the packet is considered timed out
timeout: Timestamp
}
-
- // possible consumer statuses
- pure val STOPPED = "stopped" // the chain was once a consumer chain, but has been dropped by the provider.
- pure val RUNNING = "running" //Â the chain is currently a consumer chain. Running chains are those that get sent VSCPackets.
- pure val UNUSED = "unused" // the chain has never been a consumer chain, and is available to become one.
- // When a chain is dropped, it cannot become a consumer again - we assume that would be done by another consumer becoming running.
// state that each chain needs to store, whether consumer or provider.
@@ -164,6 +156,23 @@ module ccv_logic {
}
}
+ // possible consumer statuses
+ pure val STOPPED = "stopped" // the chain was once a consumer chain, but has been dropped by the provider.
+ pure val RUNNING = "running" //Â the chain is currently a consumer chain. Running chains are those that get sent VSCPackets.
+ pure val UNUSED = "unused" // the chain has never been a consumer chain, and is available to become one.
+ // When a chain is dropped, it cannot become a consumer again - we assume that would be done by another consumer becoming running.
+
+ // the provider chain.
+ // given as a pure val so that we can switch cases based on
+ // whether a chain is the provider or not
+ pure val PROVIDER_CHAIN = "provider"
+
+ // ===================
+ // PROTOCOL PARAMETERS
+ // ===================
+
+ const UnbondingPeriodPerChain: Chain -> int
+
// ===================
// PROTOCOL LOGIC
// ===================
@@ -207,11 +216,99 @@ module ccv_logic {
}
}
+ pure def endAndBeginBlockForProvider(
+ currentState: ProtocolState,
+ chain: Chain,
+ timeAdvancement: Timestamp,
+ newConsumerStatusses: Chain -> ConsumerState): Result = {
+ // commit the current running validator set on chain
+ val currentProviderState = currentState.providerState
+ val newChainState = currentState.providerState.chainState.with(
+ "votingPowerHistory", currentState.providerState.chainState.votingPowerHistory.prepend(
+ currentState.providerState.chainState.currentValidatorSet
+ )
+ ).with(
+ "lastTimestamp", currentState.providerState.chainState.lastTimestamp + 1
+ )
+ Err("not implemented")
+ // votingPowerHistories' = votingPowerHistories.set(ProviderChain, getUpdatedHistory(ProviderChain)),
+ // // the running validator set is now for sure the current validator set,
+ // // so start with it in the next block
+ // runningValidatorSet' = runningValidatorSet,
+ // // send VSCPackets to consumers
+ // val newOutstandingPackets =
+ // // if running validator set is considered to have changed
+ // if (providerValidatorSetChangedInThisBlock)
+ // // then send a packet to each running consumer
+ // outstandingPacketsToConsumer.keys().mapBy(
+ // (consumer) =>
+ // val packetQueue = outstandingPacketsToConsumer.get(consumer)
+ // if (consumerStatus.get(consumer) == RUNNING) {
+ // packetQueue.append(
+ // {
+ // id: packetQueue.length(),
+ // validatorSet: runningValidatorSet.get(ProviderChain),
+ // timeout: curChainTimes.get(ProviderChain) + PacketTimeout
+ // }
+ // )
+ // } else {
+ // packetQueue
+ // }
+ // )
+ // else
+ // // otherwise, don't send any packets
+ // outstandingPacketsToConsumer
+ // RegisterNewOutstandingPackets(newOutstandingPackets),
+ // CONSUMER_NOOP,
+ // // no packets are sent to the provider
+ // outstandingPacketsToProvider' = outstandingPacketsToProvider,
+ // // do not receive any maturations
+ // receivedMaturations' = receivedMaturations,
+ // // consumer statusses do not change
+ // consumerStatus' = consumerStatus,
+ // // chain times do not change
+ // curChainTimes' = curChainTimes,
+ // // the validator set was definitely not changed in the new block yet, so set to false
+ // providerValidatorSetChangedInThisBlock' = false
+ }
+
// ===================
// UTILITY FUNCTIONS
// ===================
+ // receives a given packet (sent by the provider) on the consumer. The arguments are the consumer chain that is receiving the packet, and the packet itself.
+ // To receive a packet, modify the running validator set (not the one entered into the block yet,
+ // but the candidate that would be put into the block if it ended now)
+ // and store the maturation time for the packet.
+ pure def recvPacketOnConsumer(currentState: ProtocolState, receiver: Chain, packet: VSCPacket): Result = {
+ if(not(isCurrentlyConsumer(receiver, currentState))) {
+ Err("Receiver is not currently a consumer - must have 'running' status!")
+ } else {
+ // update the running validator set, but not the history yet,
+ // as that only happens when the next block is started
+ val currentConsumerState = currentState.consumerStates.get(receiver)
+ val newConsumerState = currentConsumerState.with(
+ "chainState",
+ currentConsumerState.chainState.with(
+ "currentValidatorSet", packet.validatorSet
+ )
+ ).with(
+ "maturationTimes",
+ currentConsumerState.maturationTimes.put(
+ packet,
+ currentConsumerState.chainState.lastTimestamp + UnbondingPeriodPerChain.get(receiver)
+ )
+ )
+ val newConsumerStates = currentState.consumerStates.set(receiver, newConsumerState)
+ val newState = currentState.with(
+ "consumerStates", newConsumerStates
+ )
+ Ok(newState)
+ }
+ }
+
// receives a given packet on the provider. The arguments are the consumer chain that sent the packet, and the packet itself.
+ // To receive a packet, add it to the list of received maturations.
pure def recvPacketOnProvider(currentState: ProtocolState, sender: Chain, packet: VSCMaturedPacket): Result = {
if (not(isCurrentlyConsumer(sender, currentState))) {
Err("Sender is not currently a consumer - must have 'running' status!")
@@ -233,7 +330,7 @@ module ccv_logic {
pure def removeOutstandingPacketFromConsumer(currentState: ProtocolState, sender: Chain): Result = {
val currentOutstandingPackets = currentState.consumerStates.get(sender).outstandingPacketsToProvider
val newOutstandingPackets = currentOutstandingPackets.tail()
- val newConsumerState = currentState.consumerState.get(sender).with(
+ val newConsumerState = currentState.consumerStates.get(sender).with(
"outstandingPacketsToProvider", newOutstandingPackets
)
val newConsumerStates = currentState.consumerStates.set(sender, newConsumerState)
@@ -241,15 +338,18 @@ module ccv_logic {
"consumerStates", newConsumerStates
)
Ok(newState)
- // Err("Not implemented")
}
+ // Updates the given oldValidatorSet by setting the validator to newVotingPower.
+ // If newVotingPower is zero, the validator is removed.
pure def getUpdatedValidatorSet(oldValidatorSet: ValidatorSet, validator: Node, newVotingPower: int): ValidatorSet =
if (newVotingPower > 0)
oldValidatorSet.put(validator, newVotingPower)
else
oldValidatorSet.mapRemove(validator)
+ // Returns a ProtocolState where the current validator set on the provider is set to
+ // newValidatorSet.
pure def setProviderValidatorSet(currentState: ProtocolState, newValidatorSet: ValidatorSet): ProtocolState = {
pure val newChainState = currentState.providerState.chainState.with(
"currentValidatorSet", newValidatorSet
@@ -262,6 +362,7 @@ module ccv_logic {
)
}
+ // Returns true if the given chain is currently a running consumer, false otherwise.
pure def isCurrentlyConsumer(chain: Chain, currentState: ProtocolState): bool = {
val status = currentState.providerState.consumerStatus.get(chain)
status == RUNNING
diff --git a/tests/difference/core/quint_model/extraSpells.qnt b/tests/difference/core/quint_model/extraSpells.qnt
index 5c98f7a384..32f565b16a 100644
--- a/tests/difference/core/quint_model/extraSpells.qnt
+++ b/tests/difference/core/quint_model/extraSpells.qnt
@@ -146,19 +146,6 @@ module extraSpells {
assert(m.mapRemoveAll(Set(5, 7)) == Map(3 -> 4)),
assert(m.mapRemoveAll(Set(5, 99999)) == Map(3 -> 4, 7 -> 8)),
}
-
- pure def listSorted(__list: List[a], __lt: (a, a) => bool): List[a] = {
- pure def __countSmaller(__j: int): int = {
- pure val __jth = __list[__j]
- __list.indices().filter(__i =>
- __lt(__list[__i], __jth) or (__list[__i] == __jth and __i < __j)
- )
- .size()
- }
-
- pure val __permutation = __list.indices().mapBy(__i => __countSmaller(__i))
- __list.foldl([], (__l, __i) => __l.append(__list[__permutation.get(__i)]))
- }
//// Returns a list of all elements of a set.
////
From 4f77d68b9ad06525aa7241f392e6754de364f90c Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Thu, 21 Sep 2023 10:10:28 +0200
Subject: [PATCH 17/35] Diagnose Quint parser bug
---
tests/difference/core/quint_model/ccv.qnt | 162 ++++++++++++++--------
1 file changed, 105 insertions(+), 57 deletions(-)
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index 1c250f94b8..d8e6bcbece 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -9,6 +9,10 @@ module ccv_logic {
// Things that explicitly are modelled:
// * Validator set changes are propagated from provider to consumers
// * VSC packets mature
+
+ // We assume that packet receive + ack happen synchronously,
+ // i.e. when the packet is delivered, the ack is delivered right afterwards.
+ // This is because it is nontrivial in practice to get a relayer to relay a packet, but not its ack.
// ===================
// TYPE DEFINITIONS
@@ -16,6 +20,7 @@ module ccv_logic {
type Node = str
type Chain = str
type Power = int
+ type VSCId = int
type ValidatorSet = Node -> Power
type Height = int
type Timestamp = int
@@ -25,19 +30,19 @@ module ccv_logic {
type VSCPacket =
{
// the identifier for this packet
- VscId: int,
+ id: VSCId,
// the new validator set. in the implementation, this would be a list of validator updates
validatorSet: ValidatorSet,
- // the time, that when passed on the receiver chain, will mean the packet is considered timed out
- timeout: Timestamp
+ // the time at which the packet was sent. used to check whether packets have timed out.
+ sendingTime: Timestamp
}
type VSCMaturedPacket =
{
// the id of the VSCPacket that matured
- id: int,
- // the time, that when passed on the receiver chain, will mean the packet is considered timed out
- timeout: Timestamp
+ id: VSCId,
+ // the time at which the packet was sent. used to check whether packets have timed out.
+ sendingTime: Timestamp
}
@@ -74,6 +79,9 @@ module ccv_logic {
// stores which VSC Packets have been sent to compare with receivedMaturations to detect timeouts due to non-responsiveness
sentVSCPackets: Chain -> Set[VSCPacket],
+ // a mapping from (chainId, vscId) tuples to the timestamps of sent VSCPackets.
+ vscSendTimestamps: (Chain, VSCId) -> Timestamp,
+
// stores whether, in this block, the validator set has changed.
// this is needed because the validator set might be considered to have changed, even though
// it is still technically identical at our level of abstraction, e.g. a validator power change on the provider
@@ -83,6 +91,10 @@ module ccv_logic {
// stores, for each consumer chain, its current status -
// unused, running, or stopped
consumerStatus: Chain -> str,
+
+ // a monotonic strictly increasing and positive ID that is used
+ // to uniquely identify the VSCs sent to the consumer chains.
+ runningVscId: int,
}
// Defines the current state of a consumer chain. This information is accessible to that consumer on-chain.
@@ -105,7 +117,7 @@ module ccv_logic {
providerState: ProviderState,
// the state of each consumer chain.
// note that we assume that this contains all consumer chains that may ever exist,
- // and consumer chains that are currently not running will have providerState.consumerStatus == UNUSED or STOPPED
+ // and consumer chains that are currently not running will have providerState.consumerStatus == UNUSED or STOPPED.
consumerStates: Chain -> ConsumerState
}
@@ -146,7 +158,9 @@ module ccv_logic {
receivedMaturations: Set(),
sentVSCPackets: Map(),
providerValidatorSetChangedInThisBlock: false,
- consumerStatus: Map()
+ consumerStatus: Map(),
+ runningVscId: 0,
+ vscSendTimestamps: Map(),
},
consumerStates: Map(),
},
@@ -171,8 +185,16 @@ module ccv_logic {
// PROTOCOL PARAMETERS
// ===================
+ // For each chain, this defines the time between the initiation of an unbonding and its maturation.
const UnbondingPeriodPerChain: Chain -> int
+ // The maximum time duration between sending any VSCPacket to any consumer chain and receiving the
+ // corresponding VSCMaturedPacket, without timing out the consumer chain and consequently removing it.
+ const VscTimeout: int
+
+ // The timeoutTimestamp for sending CCV packets.
+ const CcvTimeoutTimestamp: int
+
// ===================
// PROTOCOL LOGIC
// ===================
@@ -193,7 +215,7 @@ module ccv_logic {
// Delivers the next queued VSCMaturedPacket from a consumer chain to the provider chain.
// Only argument is the consumer chain, from which the packet will be delivered.
pure def deliverPacketToProvider(currentState: ProtocolState, sender: Chain): Result = {
- if (not(isCurrentlyConsumer(sender, currentState))) {
+ if (not(isCurrentlyConsumer(sender, currentState.providerState))) {
Err("Sender is not currently a consumer - must have 'running' status!")
} else if (length(currentState.consumerStates.get(sender).outstandingPacketsToProvider) == 0) {
Err("No outstanding packets to deliver")
@@ -216,60 +238,79 @@ module ccv_logic {
}
}
+ // returns the providerState with the following modifications:
+ // * sends VSCPackets to all running consumers
+ // * increments the runningVscId
+ // This should only be called when the provider chain is ending a block,
+ // and only when the running validator set is considered to have changed
+ // and there is a consumer to send a packet to.
+ pure def sendVscPackets(providerState: ProviderState): ProviderState = {
+ providerState.with(
+ // send VSCPackets to consumers
+ "outstandingPacketsToConsumer",
+ // if running validator set is considered to have changed and there is a consumer to send a packet to
+ if (providerState.providerValidatorSetChangedInThisBlock
+ and getRunningConsumers(providerState).size() > 0) {
+ // then send a packet to each running consumer
+ providerState.consumerStatus.keys().mapBy(
+ // go through all potential consumers
+ (consumer) =>
+ val packetQueue = providerState.outstandingPacketsToConsumer.get(consumer)
+ // if the consumer is running, send a packet
+ if (isCurrentlyConsumer(consumer, providerState)) {
+ packetQueue.append(
+ {
+ id: providerState.runningVscId,
+ validatorSet: providerState.chainState.currentValidatorSet,
+ sendingTime: providerState.chainState.lastTimestamp
+ }
+ )
+ } else {
+ // otherwise, leave the queue as-is
+ packetQueue
+ }
+ )
+ } else {
+ // running validator set is not considered to have changed
+ // ...so don't send any packets
+ providerState.outstandingPacketsToConsumer
+ }
+ ).with(
+ // the validator set has not changed yet in the new block
+ "providerValidatorSetChangedInThisBlock", false
+ ).with(
+ "runningVscId", providerState.runningVscId + 1
+ )
+ }
+
+ // Ends a block on the provider. This means that the current validator set is committed on chain,
+ // packets are queued, and the next block is started.
pure def endAndBeginBlockForProvider(
currentState: ProtocolState,
chain: Chain,
- timeAdvancement: Timestamp,
- newConsumerStatusses: Chain -> ConsumerState): Result = {
+ // by how much the timestamp should be advanced,
+ // i.e. the timestamp for the next block is oldTimestamp + timeAdvancement
+ timeAdvancement: Timestamp): Result = {
// commit the current running validator set on chain
val currentProviderState = currentState.providerState
- val newChainState = currentState.providerState.chainState.with(
- "votingPowerHistory", currentState.providerState.chainState.votingPowerHistory.prepend(
- currentState.providerState.chainState.currentValidatorSet
+ val newChainState = currentProviderState.chainState.with(
+ "votingPowerHistory", currentProviderState.chainState.votingPowerHistory.prepend(
+ currentProviderState.chainState.currentValidatorSet
)
).with(
- "lastTimestamp", currentState.providerState.chainState.lastTimestamp + 1
+ // advance the time
+ "lastTimestamp", currentProviderState.chainState.lastTimestamp + timeAdvancement
)
+ val newProviderState = currentProviderState.with(
+ "chainState", newChainState
+ )
+ val providerStateAfterSending =
+ if (currentProviderState.providerValidatorSetChangedInThisBlock and getRunningConsumers(currentState.providerState).size() > 0) {
+ newProviderState.sendVscPackets()
+ } else {
+ newProviderState
+ }
Err("not implemented")
- // votingPowerHistories' = votingPowerHistories.set(ProviderChain, getUpdatedHistory(ProviderChain)),
- // // the running validator set is now for sure the current validator set,
- // // so start with it in the next block
- // runningValidatorSet' = runningValidatorSet,
- // // send VSCPackets to consumers
- // val newOutstandingPackets =
- // // if running validator set is considered to have changed
- // if (providerValidatorSetChangedInThisBlock)
- // // then send a packet to each running consumer
- // outstandingPacketsToConsumer.keys().mapBy(
- // (consumer) =>
- // val packetQueue = outstandingPacketsToConsumer.get(consumer)
- // if (consumerStatus.get(consumer) == RUNNING) {
- // packetQueue.append(
- // {
- // id: packetQueue.length(),
- // validatorSet: runningValidatorSet.get(ProviderChain),
- // timeout: curChainTimes.get(ProviderChain) + PacketTimeout
- // }
- // )
- // } else {
- // packetQueue
- // }
- // )
- // else
- // // otherwise, don't send any packets
- // outstandingPacketsToConsumer
- // RegisterNewOutstandingPackets(newOutstandingPackets),
- // CONSUMER_NOOP,
- // // no packets are sent to the provider
- // outstandingPacketsToProvider' = outstandingPacketsToProvider,
- // // do not receive any maturations
- // receivedMaturations' = receivedMaturations,
- // // consumer statusses do not change
- // consumerStatus' = consumerStatus,
- // // chain times do not change
- // curChainTimes' = curChainTimes,
- // // the validator set was definitely not changed in the new block yet, so set to false
- // providerValidatorSetChangedInThisBlock' = false
}
// ===================
@@ -281,7 +322,7 @@ module ccv_logic {
// but the candidate that would be put into the block if it ended now)
// and store the maturation time for the packet.
pure def recvPacketOnConsumer(currentState: ProtocolState, receiver: Chain, packet: VSCPacket): Result = {
- if(not(isCurrentlyConsumer(receiver, currentState))) {
+ if(not(isCurrentlyConsumer(receiver, currentState.providerState))) {
Err("Receiver is not currently a consumer - must have 'running' status!")
} else {
// update the running validator set, but not the history yet,
@@ -363,10 +404,17 @@ module ccv_logic {
}
// Returns true if the given chain is currently a running consumer, false otherwise.
- pure def isCurrentlyConsumer(chain: Chain, currentState: ProtocolState): bool = {
- val status = currentState.providerState.consumerStatus.get(chain)
+ pure def isCurrentlyConsumer(chain: Chain, providerState: ProviderState): bool = {
+ val status = providerState.consumerStatus.get(chain)
status == RUNNING
}
+
+ // Returns the set of all consumer chains that currently have the status RUNNING.
+ pure def getRunningConsumers(providerState: ProviderState): Set[Chain] = {
+ providerState.consumerStatus.keys().filter(
+ chain => providerState.consumerStatus.get(chain) == RUNNING
+ )
+ }
}
module ccv_tests {
From fa233d655ff0a86073f47bdebe7fce5054b7c6b7 Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Thu, 21 Sep 2023 10:12:59 +0200
Subject: [PATCH 18/35] Fix type in Quint
---
tests/difference/core/quint_model/ccv.qnt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index d8e6bcbece..ac5fe631a0 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -351,7 +351,7 @@ module ccv_logic {
// receives a given packet on the provider. The arguments are the consumer chain that sent the packet, and the packet itself.
// To receive a packet, add it to the list of received maturations.
pure def recvPacketOnProvider(currentState: ProtocolState, sender: Chain, packet: VSCMaturedPacket): Result = {
- if (not(isCurrentlyConsumer(sender, currentState))) {
+ if (not(isCurrentlyConsumer(sender, currentState.providerState))) {
Err("Sender is not currently a consumer - must have 'running' status!")
} else {
val currentReceivedMaturations = currentState.providerState.receivedMaturations
From 66663bfdf6e8c142553334f20ecc6e98fcde8a52 Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Thu, 21 Sep 2023 14:15:30 +0200
Subject: [PATCH 19/35] Add endBlock actions
---
tests/difference/core/quint_model/ccv.qnt | 190 ++++++++++++++++++----
1 file changed, 155 insertions(+), 35 deletions(-)
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index ac5fe631a0..586c132ac6 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -196,7 +196,8 @@ module ccv_logic {
const CcvTimeoutTimestamp: int
// ===================
- // PROTOCOL LOGIC
+ // PROTOCOL LOGIC contains the meat of the protocol
+ // functions here roughly correspond to API calls that can be triggered from external sources
// ===================
// the power of a validator on the provider chain is changed to the given amount. We do not care how this happens,
@@ -238,6 +239,159 @@ module ccv_logic {
}
}
+ // Ends a block on the provider. This means that the current validator set is committed on chain,
+ // packets are queued, and the next block is started.
+ pure def endAndBeginBlockForProvider(
+ currentState: ProtocolState,
+ // by how much the timestamp should be advanced,
+ // i.e. the timestamp for the next block is oldTimestamp + timeAdvancement
+ timeAdvancement: Timestamp,
+ // a set of consumers that were unused before, but should be set to running now.
+ consumersToStart: Set[Chain],
+ // a set of consumers that were running before, but should be set to stopped now.
+ consumersToStop: Set[Chain]): Result = {
+ // commit the current running validator set on chain
+ val currentProviderState = currentState.providerState
+ val newChainState = currentProviderState.chainState.endAndBeginBlockShared(timeAdvancement)
+ // modify the states of the consumers that should be started/stopped
+ val res = currentProviderState.consumerStatus.getNewConsumerStatusMap(consumersToStart, consumersToStop)
+ val newConsumerStatus = res._1
+ val err = res._2
+ if (err != "") {
+ Err(err)
+ } else {
+ val newProviderState = currentProviderState.with(
+ "chainState", newChainState
+ ).with(
+ "consumerStatus", newConsumerStatus
+ )
+ val providerStateAfterSending =
+ if (currentProviderState.providerValidatorSetChangedInThisBlock and getRunningConsumers(currentState.providerState).size() > 0) {
+ newProviderState.sendVscPackets()
+ } else {
+ newProviderState
+ }
+ Err("not implemented")
+ }
+ }
+
+ pure def endAndBeginBlockForConsumer(
+ currentState: ProtocolState,
+ chain: Chain,
+ // by how much the timestamp of the chain should be advanced for the next block
+ timeAdvancement: Timestamp): Result = {
+ if (currentState.consumerStates.keys().contains(chain)) {
+ Err("chain is not a consumer")
+ } else {
+ // if the chain is not a consumer, return an error
+ val currentConsumerState = currentState.consumerStates.get(chain)
+ val newChainState = currentConsumerState.chainState.endAndBeginBlockShared(timeAdvancement)
+ val newConsumerState = currentConsumerState.with(
+ "chainState", newChainState
+ )
+ val maturedPackets = newConsumerState.maturationTimes.keys().filter(
+ packet =>
+ val maturationTime = newConsumerState.maturationTimes.get(packet)
+ maturationTime <= newChainState.lastTimestamp
+ )
+ val newMaturationTimes = newConsumerState.maturationTimes.mapRemoveAll(maturedPackets)
+ val newOutstandingPackets = newConsumerState.outstandingPacketsToProvider.concat(
+ maturedPackets.map(
+ packet => {
+ id: packet.id,
+ sendingTime: newConsumerState.chainState.lastTimestamp
+ }
+ ).toList()
+ )
+ val newConsumerState2 = newConsumerState.with(
+ "maturationTimes", newMaturationTimes
+ ).with(
+ "outstandingPacketsToProvider", newOutstandingPackets
+ )
+ val newConsumerStates = currentState.consumerStates.set(chain, newConsumerState2)
+ val newState = currentState.with(
+ "consumerStates", newConsumerStates
+ )
+ Ok(newState)
+ }
+ }
+
+ // ===================
+ // UTILITY FUNCTIONS
+ // which do not hold the core logic of the protocol, but are still part of it
+ // ===================
+
+ pure def getRunningConsumersFromMap(consumerStatus: Chain -> str): Set[Chain] = {
+ consumerStatus.keys().filter(
+ chain => consumerStatus.get(chain) == RUNNING
+ )
+ }
+
+ pure def getStoppedConsumersFromMap(consumerStatus: Chain -> str): Set[Chain] = {
+ consumerStatus.keys().filter(
+ chain => consumerStatus.get(chain) == STOPPED
+ )
+ }
+
+ // Returns the new ConsumerStatusMap according to the consumers to start/stop.
+ // The second return is an error string: If it is not equal to "",
+ // it contains an error message, and the first return should be ignored.
+ pure def getNewConsumerStatusMap(
+ currentConsumerStatusMap: Chain -> str,
+ consumersToStart: Set[Chain],
+ consumersToStop: Set[Chain]): (Chain -> str, str) = {
+ val runningConsumers = getRunningConsumersFromMap(currentConsumerStatusMap)
+ val stoppedConsumers = getStoppedConsumersFromMap(currentConsumerStatusMap)
+ // if a consumer is both started and stopped, this is an error
+ if (consumersToStart.intersect(consumersToStop).size() > 0) {
+ (currentConsumerStatusMap, "Cannot start and stop a consumer at the same time")
+ } else {
+ // if a consumer is started, it must be unused
+ if (consumersToStart.intersect(runningConsumers).size() > 0) {
+ (currentConsumerStatusMap, "Cannot start a consumer that is already running")
+ } else {
+ // if a consumer is stopped, it must be running
+ if (consumersToStop.intersect(stoppedConsumers).size() > 0) {
+ (currentConsumerStatusMap, "Cannot stop a consumer that is not running")
+ } else {
+ // if a consumer is started, it must be unused
+ val newConsumerStatusMap = currentConsumerStatusMap.keys().mapBy(
+ (chain) =>
+ if (consumersToStart.contains(chain)) {
+ RUNNING
+ } else if (consumersToStop.contains(chain)) {
+ STOPPED
+ } else {
+ currentConsumerStatusMap.get(chain)
+ }
+ )
+ (newConsumerStatusMap, "")
+ }
+ }
+ }
+ }
+
+ // Takes the currentValidatorSet and puts it as the newest set of the voting history
+ pure def enterCurValSetIntoBlock(chainState: ChainState): ChainState = {
+ chainState.with(
+ "votingPowerHistory", chainState.votingPowerHistory.prepend(
+ chainState.currentValidatorSet
+ )
+ )
+ }
+
+ // Advances the timestamp in the chainState by timeAdvancement
+ pure def advanceTime(chainState: ChainState, timeAdvancement: Timestamp): ChainState = {
+ chainState.with(
+ "lastTimestamp", chainState.lastTimestamp + timeAdvancement
+ )
+ }
+
+ // common logic to update the chain state, used by both provider and consumers.
+ pure def endAndBeginBlockShared(chainState: ChainState, timeAdvancement: Timestamp): ChainState = {
+ chainState.enterCurValSetIntoBlock().advanceTime(timeAdvancement)
+ }
+
// returns the providerState with the following modifications:
// * sends VSCPackets to all running consumers
// * increments the runningVscId
@@ -283,40 +437,6 @@ module ccv_logic {
)
}
- // Ends a block on the provider. This means that the current validator set is committed on chain,
- // packets are queued, and the next block is started.
- pure def endAndBeginBlockForProvider(
- currentState: ProtocolState,
- chain: Chain,
- // by how much the timestamp should be advanced,
- // i.e. the timestamp for the next block is oldTimestamp + timeAdvancement
- timeAdvancement: Timestamp): Result = {
- // commit the current running validator set on chain
- val currentProviderState = currentState.providerState
- val newChainState = currentProviderState.chainState.with(
- "votingPowerHistory", currentProviderState.chainState.votingPowerHistory.prepend(
- currentProviderState.chainState.currentValidatorSet
- )
- ).with(
- // advance the time
- "lastTimestamp", currentProviderState.chainState.lastTimestamp + timeAdvancement
- )
- val newProviderState = currentProviderState.with(
- "chainState", newChainState
- )
- val providerStateAfterSending =
- if (currentProviderState.providerValidatorSetChangedInThisBlock and getRunningConsumers(currentState.providerState).size() > 0) {
- newProviderState.sendVscPackets()
- } else {
- newProviderState
- }
- Err("not implemented")
- }
-
- // ===================
- // UTILITY FUNCTIONS
- // ===================
-
// receives a given packet (sent by the provider) on the consumer. The arguments are the consumer chain that is receiving the packet, and the packet itself.
// To receive a packet, modify the running validator set (not the one entered into the block yet,
// but the candidate that would be put into the block if it ended now)
From 7b489feefb1a14c660743ec25702f792dce7789f Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Tue, 26 Sep 2023 08:55:40 +0200
Subject: [PATCH 20/35] Start adding state machine module
---
tests/difference/core/quint_model/ccv.qnt | 857 ++++++------------
.../core/quint_model/extraSpells.qnt | 18 +
tests/difference/core/quint_model/time.qnt | 13 +
3 files changed, 296 insertions(+), 592 deletions(-)
create mode 100644 tests/difference/core/quint_model/time.qnt
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index 586c132ac6..43c8f9098d 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -1,29 +1,12 @@
-module ccv_logic {
- import extraSpells.* from "./extraSpells"
-
- // Things that are not modelled:
- // * Reward distribution
- // * Starting/Stopping chains during execution
- // * Slashes
-
- // Things that explicitly are modelled:
- // * Validator set changes are propagated from provider to consumers
- // * VSC packets mature
+module CCVTypes {
+ import Time.* from "./Time"
- // We assume that packet receive + ack happen synchronously,
- // i.e. when the packet is delivered, the ack is delivered right afterwards.
- // This is because it is nontrivial in practice to get a relayer to relay a packet, but not its ack.
-
- // ===================
- // TYPE DEFINITIONS
- // ===================
type Node = str
type Chain = str
type Power = int
type VSCId = int
type ValidatorSet = Node -> Power
type Height = int
- type Timestamp = int
// a list of validator sets per blocks, ordered by recency
type VotingPowerHistory = List[ValidatorSet]
@@ -34,7 +17,7 @@ module ccv_logic {
// the new validator set. in the implementation, this would be a list of validator updates
validatorSet: ValidatorSet,
// the time at which the packet was sent. used to check whether packets have timed out.
- sendingTime: Timestamp
+ sendingTime: Time
}
type VSCMaturedPacket =
@@ -42,7 +25,7 @@ module ccv_logic {
// the id of the VSCPacket that matured
id: VSCId,
// the time at which the packet was sent. used to check whether packets have timed out.
- sendingTime: Timestamp
+ sendingTime: Time
}
@@ -59,9 +42,17 @@ module ccv_logic {
currentValidatorSet: ValidatorSet,
// the latest timestamp that was comitted on chain
- lastTimestamp: Timestamp,
+ lastTimestamp: Time,
}
+ // utility function: returns a chain state that is initialized minimally.
+ pure def GetEmptyChainState(): ChainState =
+ {
+ votingPowerHistory: List(),
+ currentValidatorSet: Map(),
+ lastTimestamp: 0,
+ }
+
// Defines the current state of the provider chain. Essentially, all information here is stored by the provider on-chain (or could be derived purely by information that is on-chain).
type ProviderState =
{
@@ -80,7 +71,7 @@ module ccv_logic {
sentVSCPackets: Chain -> Set[VSCPacket],
// a mapping from (chainId, vscId) tuples to the timestamps of sent VSCPackets.
- vscSendTimestamps: (Chain, VSCId) -> Timestamp,
+ vscSendTimestamps: (Chain, VSCId) -> Time,
// stores whether, in this block, the validator set has changed.
// this is needed because the validator set might be considered to have changed, even though
@@ -97,13 +88,27 @@ module ccv_logic {
runningVscId: int,
}
+ // utility function: returns a provider state that is initialized minimally.
+ pure def GetEmptyProviderState(): ProviderState =
+ {
+ chainState: GetEmptyChainState,
+ outstandingPacketsToConsumer: Map(),
+ receivedMaturations: Set(),
+ sentVSCPackets: Map(),
+ vscSendTimestamps: Map(),
+ providerValidatorSetChangedInThisBlock: false,
+ consumerStatus: Map(),
+ runningVscId: 0,
+ }
+
+
// Defines the current state of a consumer chain. This information is accessible to that consumer on-chain.
type ConsumerState = {
// the state that each chain needs to store
chainState: ChainState,
// Stores the maturation times for VSCPackets received by this consumer
- maturationTimes: VSCPacket -> Timestamp,
+ maturationTimes: VSCPacket -> Time,
// Stores the list of packets that have been sent to the provider chain by this consumer
// and have not been received yet.
@@ -112,6 +117,14 @@ module ccv_logic {
outstandingPacketsToProvider: List[VSCMaturedPacket],
}
+ // utility function: returns a consumer state that is initialized minimally.
+ pure def GetEmptyConsumerState(): ConsumerState =
+ {
+ chainState: GetEmptyChainState,
+ maturationTimes: Map(),
+ outstandingPacketsToProvider: List(),
+ }
+
// the state of the protocol consists of the composition of the state of one provider chain with potentially many consumer chains.
type ProtocolState = {
providerState: ProviderState,
@@ -180,11 +193,36 @@ module ccv_logic {
// given as a pure val so that we can switch cases based on
// whether a chain is the provider or not
pure val PROVIDER_CHAIN = "provider"
+}
+
+module CCV {
+ // Implements the core logic of the cross-chain validation protocol.
+
+ // Things that are not modelled:
+ // * Reward distribution
+ // * Starting/Stopping chains during execution
+ // * Slashes
+
+ // Things that explicitly are modelled:
+ // * Validator set changes are propagated from provider to consumers
+ // * VSC packets mature
+
+ // We assume that packet receive + ack happen synchronously,
+ // i.e. when the packet is delivered, the ack is delivered right afterwards.
+ // This is because it is nontrivial in practice to get a relayer to relay a packet, but not its ack.
+
+ import extraSpells.* from "./extraSpells"
+ import Time.* from "./Time"
+ import CCVTypes.*
+
// ===================
// PROTOCOL PARAMETERS
// ===================
+ // the set of all possible consumer chains.
+ const ConsumerChains: Set[Chain]
+
// For each chain, this defines the time between the initiation of an unbonding and its maturation.
const UnbondingPeriodPerChain: Chain -> int
@@ -192,8 +230,8 @@ module ccv_logic {
// corresponding VSCMaturedPacket, without timing out the consumer chain and consequently removing it.
const VscTimeout: int
- // The timeoutTimestamp for sending CCV packets.
- const CcvTimeoutTimestamp: int
+ // The timeoutTimestamp for sent packets. Can differ by chain.
+ const CcvTimeoutPeriod: Chain -> int
// ===================
// PROTOCOL LOGIC contains the meat of the protocol
@@ -239,13 +277,39 @@ module ccv_logic {
}
}
+ // Delivers the next queued VSCPacket from the provider chain to a consumer chain.
+ // Only argument is the consumer chain, to which the packet will be delivered.
+ pure def deliverPacketToConsumer(currentState: ProtocolState, receiver: Chain): Result = {
+ if (not(isCurrentlyConsumer(receiver, currentState.providerState))) {
+ Err("Receiver is not currently a consumer - must have 'running' status!")
+ } else if (length(currentState.providerState.outstandingPacketsToConsumer.get(receiver)) == 0) {
+ Err("No outstanding packets to deliver")
+ } else {
+ val packet = currentState.providerState.outstandingPacketsToConsumer.get(receiver).head()
+ val result = recvPacketOnConsumer(currentState, receiver, packet)
+ val tmpState = result.newState
+ if (result.hasError) {
+ Err(result.error.message)
+ } else {
+ val result2 = removeOutstandingPacketFromProvider(tmpState, receiver)
+ val tmpState2 = result2.newState
+ val err2 = result2.error
+ if (result2.hasError) {
+ Err(err2.message)
+ } else {
+ Ok(tmpState2)
+ }
+ }
+ }
+ }
+
// Ends a block on the provider. This means that the current validator set is committed on chain,
// packets are queued, and the next block is started.
pure def endAndBeginBlockForProvider(
currentState: ProtocolState,
// by how much the timestamp should be advanced,
// i.e. the timestamp for the next block is oldTimestamp + timeAdvancement
- timeAdvancement: Timestamp,
+ timeAdvancement: Time,
// a set of consumers that were unused before, but should be set to running now.
consumersToStart: Set[Chain],
// a set of consumers that were running before, but should be set to stopped now.
@@ -279,7 +343,7 @@ module ccv_logic {
currentState: ProtocolState,
chain: Chain,
// by how much the timestamp of the chain should be advanced for the next block
- timeAdvancement: Timestamp): Result = {
+ timeAdvancement: Time): Result = {
if (currentState.consumerStates.keys().contains(chain)) {
Err("chain is not a consumer")
} else {
@@ -381,14 +445,14 @@ module ccv_logic {
}
// Advances the timestamp in the chainState by timeAdvancement
- pure def advanceTime(chainState: ChainState, timeAdvancement: Timestamp): ChainState = {
+ pure def advanceTime(chainState: ChainState, timeAdvancement: Time): ChainState = {
chainState.with(
"lastTimestamp", chainState.lastTimestamp + timeAdvancement
)
}
// common logic to update the chain state, used by both provider and consumers.
- pure def endAndBeginBlockShared(chainState: ChainState, timeAdvancement: Timestamp): ChainState = {
+ pure def endAndBeginBlockShared(chainState: ChainState, timeAdvancement: Time): ChainState = {
chainState.enterCurValSetIntoBlock().advanceTime(timeAdvancement)
}
@@ -501,6 +565,22 @@ module ccv_logic {
Ok(newState)
}
+ // removes the oldest outstanding packet (to the given consumer) from the provider.
+ // on-chain, this would happen when the packet is acknowledged.
+ // only the oldest packet can be removed, since we model ordered channels.
+ pure def removeOutstandingPacketFromProvider(currentState: ProtocolState, receiver: Chain): Result = {
+ val currentOutstandingPackets = currentState.providerState.outstandingPacketsToConsumer.get(receiver)
+ val newOutstandingPackets = currentOutstandingPackets.tail()
+ val newProviderState = currentState.providerState.with(
+ "outstandingPacketsToConsumer",
+ currentState.providerState.outstandingPacketsToConsumer.set(receiver, newOutstandingPackets)
+ )
+ val newState = currentState.with(
+ "providerState", newProviderState
+ )
+ Ok(newState)
+ }
+
// Updates the given oldValidatorSet by setting the validator to newVotingPower.
// If newVotingPower is zero, the validator is removed.
pure def getUpdatedValidatorSet(oldValidatorSet: ValidatorSet, validator: Node, newVotingPower: int): ValidatorSet =
@@ -535,570 +615,163 @@ module ccv_logic {
chain => providerState.consumerStatus.get(chain) == RUNNING
)
}
-}
-module ccv_tests {
- import ccv_logic.*
-
- // // UTILITY FUNCTIONS & ACTIONS
- // def wasValidatorSetOnProvider(validatorSet: ValidatorSet): bool = {
- // votingPowerHistories.get(ProviderChain).toSet().exists(
- // historicalValSet => historicalValSet == validatorSet
- // )
- // }
-
- // def getCurrentValidatorSet(chain: Chain): ValidatorSet =
- // votingPowerHistories.get(chain).head()
-
- // // returns true if the consumer has timed out and should be dropped
- // def consumerTimedOut(consumer: Chain): bool =
- // any {
- // // either a package from provider to consumer has timed out
- // outstandingPacketsToConsumer.get(consumer).select(
- // packet => packet.timeout <= curChainTimes.get(consumer)
- // ).length() > 0,
- // // or a package from consumer to provider has timed out
- // outstandingPacketsToProvider.get(consumer).select(
- // packet => packet.timeout <= curChainTimes.get(ProviderChain)
- // ).length() > 0,
- // // or the inactivity timeout has passed since a VSCPacket was sent to the consumer, but the
- // // provider has not received a VSCMaturedPacket for it
- // val packetsWithoutResponse = sentVSCPackets.get(consumer).filter( // get packets without response
- // packet =>
- // not(receivedMaturations.exists(
- // maturedPacket => maturedPacket.id == packet.id
- // ))
- // )
- // // among those, get packets where inactivity timeout has passed
- // packetsWithoutResponse.filter(
- // packet =>
- // val sentAt = curChainTimes.get(ProviderChain) - PacketTimeout // compute when the packet was sent
- // val timesOutAt = sentAt + InactivityTimeout // compute when the packet times out
- // timesOutAt <= curChainTimes.get(ProviderChain)
- // ).size() > 0
- // }
-
- // // utility action that leaves all provider state untouched
- // action PROVIDER_NOOP(): bool =
- // all {
- // receivedMaturations' = receivedMaturations,
- // }
-
- // // utility action that leaves all consumer state untouched
- // action CONSUMER_NOOP(): bool =
- // all {
- // maturationTimes' = maturationTimes,
- // }
-
+ // Returns whether the consumer has timed out, and an error message.
+ // If the second return is not equal to "", the first return should be ignored.
+ // If it is equal to "", the first return will be true if the consumer has timed out and should be dropped,
+ // or false otherwise.
+ def consumerTimedOut(currentState: ProtocolState, consumer: Chain): (bool, str) =
+ // check for errors: the consumer is not running
+ if (not(isCurrentlyConsumer(consumer, currentState.providerState))) {
+ (false, "Consumer is not currently a consumer - must have 'running' status!")
+ } else {
+ val providerState = currentState.providerState
+ val consumerState = currentState.consumerStates.get(consumer)
- // // MODEL ACTIONS
-
- // // the power of a validator on the provider chain is changed to the given amount. We do not care how this happens,
- // // e.g. via undelegations, or delegations, ...
- // action doVotingPowerChange(validator: Node, amount: int): bool =
- // // for the provider chain, we need to adjust the voting power history
- // // by adding a new set
- // all {
- // amount >= 0,
- // val newValidatorSet = getCurrentValidatorSet(ProviderChain).getUpdatedValidatorSet(validator, amount)
- // // set the running validator set on the provider chain, but don't update the history yet
- // runningValidatorSet' = runningValidatorSet.set(ProviderChain, newValidatorSet),
- // // no packets are sent yet, these are only sent on endAndBeginBlock
- // RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
- // outstandingPacketsToProvider' = outstandingPacketsToProvider,
- // receivedMaturations' = receivedMaturations,
- // CONSUMER_NOOP,
- // // voting power history is only updated on endAndBeginBlock
- // votingPowerHistories' = votingPowerHistories,
- // // consumer statusses do not change
- // consumerStatus' = consumerStatus,
- // // chain times do not change
- // curChainTimes' = curChainTimes,
- // // the validator set is considered to have changed
- // providerValidatorSetChangedInThisBlock' = true,
- // }
-
- // // deliver the next outstanding packet from the consumer to the provider.
- // // since this model assumes a single provider chain, this just takes a single chain as argument.
- // action recvPacketOnProvider(consumer: Chain): bool = all {
- // // ensure there is a packet to be received
- // outstandingPacketsToProvider.get(consumer).length() > 0,
- // // remove packet from outstanding packets
- // val newPacketQueue = outstandingPacketsToProvider.get(consumer).tail()
- // outstandingPacketsToProvider' = outstandingPacketsToProvider.set(consumer, newPacketQueue),
- // // register the packet as received
- // val maturedPacket = outstandingPacketsToProvider.get(consumer).head()
- // receivedMaturations' = receivedMaturations.add(maturedPacket),
- // CONSUMER_NOOP,
- // RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
- // votingPowerHistories' = votingPowerHistories,
- // // no validator set changes are made
- // runningValidatorSet' = runningValidatorSet,
- // // consumer statusses do not change
- // consumerStatus' = consumerStatus,
- // // chain times do not change
- // curChainTimes' = curChainTimes,
- // // the validator set was not changed by this action (but might have been changed before in this block)
- // providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
- // }
-
- // // deliver the next outstanding packet from the provider to the consumer.
- // // since this model assumes a single provider chain, this just takes a single chain as argument.
- // action recvPacketOnConsumer(consumer: Chain): bool = all {
- // // ensure there is a packet to be received
- // outstandingPacketsToConsumer.get(consumer).length() > 0,
- // // remove packet from outstanding packets
- // val newPacketQueue = outstandingPacketsToConsumer.get(consumer).tail()
- // val newOutstandingPackets = outstandingPacketsToConsumer.set(consumer, newPacketQueue)
- // RegisterNewOutstandingPackets(newOutstandingPackets),
- // val packet = outstandingPacketsToConsumer.get(consumer).head()
- // all {
- // // update the running validator set, but not the history yet,
- // // as that only happens when the next block is started
- // runningValidatorSet' = runningValidatorSet.set(consumer, packet.validatorSet),
- // // add the new packet and store its maturation time
- // val newMaturationTimes = maturationTimes.get(consumer).put(packet, curChainTimes.get(consumer) + UnbondingPeriod.get(consumer))
- // maturationTimes' = maturationTimes.set(consumer, newMaturationTimes)
- // },
- // PROVIDER_NOOP,
- // votingPowerHistories' = votingPowerHistories,
- // outstandingPacketsToProvider' = outstandingPacketsToProvider,
- // // consumer statusses do not change
- // consumerStatus' = consumerStatus,
- // // chain times do not change
- // curChainTimes' = curChainTimes,
- // // the validator set was not changed by this action (but might have been changed before in this block)
- // providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
- // }
-
- // // ends the current block and starts the next block for a given chain.
- // action endAndBeginBlock(chain: Chain): bool = any {
- // all {
- // chain == ProviderChain,
- // endAndBeginBlockForProvider,
- // },
- // all {
- // chain != ProviderChain,
- // endAndBeginBlockForConsumer(chain),
- // }
- // }
-
- // // gets the updated history for the current chain when ending a block, i.e. the
- // // running validator set is added to the history if different from the last one.
- // def getUpdatedHistory(chain: Chain): List[ValidatorSet] =
- // // update voting power history if the validator set changed
- // val newValidatorSet = runningValidatorSet.get(ProviderChain)
- // val oldValidatorSet = votingPowerHistories.get(ProviderChain).head()
- // if (newValidatorSet != oldValidatorSet)
- // votingPowerHistories.get(ProviderChain).prepend(newValidatorSet)
- // else
- // votingPowerHistories.get(ProviderChain)
-
-
- // action endAndBeginBlockForProvider(): bool = all {
- // // update the voting power history
- // votingPowerHistories' = votingPowerHistories.set(ProviderChain, getUpdatedHistory(ProviderChain)),
- // // the running validator set is now for sure the current validator set,
- // // so start with it in the next block
- // runningValidatorSet' = runningValidatorSet,
- // // send VSCPackets to consumers
- // val newOutstandingPackets =
- // // if running validator set is considered to have changed
- // if (providerValidatorSetChangedInThisBlock)
- // // then send a packet to each running consumer
- // outstandingPacketsToConsumer.keys().mapBy(
- // (consumer) =>
- // val packetQueue = outstandingPacketsToConsumer.get(consumer)
- // if (consumerStatus.get(consumer) == RUNNING) {
- // packetQueue.append(
- // {
- // id: packetQueue.length(),
- // validatorSet: runningValidatorSet.get(ProviderChain),
- // timeout: curChainTimes.get(ProviderChain) + PacketTimeout
- // }
- // )
- // } else {
- // packetQueue
- // }
- // )
- // else
- // // otherwise, don't send any packets
- // outstandingPacketsToConsumer
- // RegisterNewOutstandingPackets(newOutstandingPackets),
- // CONSUMER_NOOP,
- // // no packets are sent to the provider
- // outstandingPacketsToProvider' = outstandingPacketsToProvider,
- // // do not receive any maturations
- // receivedMaturations' = receivedMaturations,
- // // consumer statusses do not change
- // consumerStatus' = consumerStatus,
- // // chain times do not change
- // curChainTimes' = curChainTimes,
- // // the validator set was definitely not changed in the new block yet, so set to false
- // providerValidatorSetChangedInThisBlock' = false
- // }
-
- // action endAndBeginBlockForConsumer(consumer: Chain): bool = all {
- // ConsumerChains.contains(consumer),
- // // update the voting power history
- // votingPowerHistories' = votingPowerHistories.set(consumer, getUpdatedHistory(consumer)),
- // // the running validator set is now for sure the current validator set,
- // // so start with it in the next block
- // runningValidatorSet' = runningValidatorSet,
- // // compute mature packets whose maturation time has passed
- // val maturedPackets = maturationTimes.get(consumer).keys().filter(
- // packet =>
- // val maturationTime = maturationTimes.get(consumer).get(packet)
- // maturationTime <= curChainTimes.get(consumer)
- // )
- // all {
- // // remove matured packets from the maturation times
- // maturationTimes' = maturationTimes.set(consumer, maturationTimes.get(consumer).mapRemoveAll(maturedPackets)),
- // // send matured packets
- // outstandingPacketsToProvider' = outstandingPacketsToProvider.set(
- // consumer,
- // // construct VSCMaturedPackets from the matured VSCPackets
- // outstandingPacketsToProvider.get(consumer).concat(
- // maturedPackets.map(packet => {id: packet.id, timeout: 5}).toList()
- // )
- // )
- // },
- // PROVIDER_NOOP,
- // // no packets are sent to consumer or received by it
- // RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
- // // consumer statusses do not change
- // consumerStatus' = consumerStatus,
- // // chain times do not change
- // curChainTimes' = curChainTimes,
- // // the validator set was not changed by this action (but might have been changed before in this block)
- // // also, this is only a new block for a consumer, so the change variable shouldn't be reset
- // providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
- // }
-
- // // advance timestamps for maps nondeterministically
- // action AdvanceTime(): bool =
- // val advanceAmounts = curChainTimes.keys().mapBy(
- // chain =>
- // nondet amount = oneOf(1.to(10))
- // amount
- // )
- // AdvanceTimeByMap(advanceAmounts)
-
- // // the timestamp for each chain is advanced by the given amount
- // action AdvanceTimeByMap(advancementAmount: Chain -> int): bool = all
- // {
- // curChainTimes' = curChainTimes.keys().mapBy(
- // chain =>
- // curChainTimes.get(chain) + advancementAmount.get(chain)
- // ),
- // // all other variables are left untouched
- // votingPowerHistories' = votingPowerHistories,
- // runningValidatorSet' = runningValidatorSet,
- // outstandingPacketsToProvider' = outstandingPacketsToProvider,
- // RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
- // receivedMaturations' = receivedMaturations,
- // maturationTimes' = maturationTimes,
- // // chain times do not change
- // consumerStatus' = consumerStatus,
- // // the validator set was not changed by this action (but might have been changed before in this block)
- // providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
- // }
-
- // // each consumer chain may advance in the order
- // // some events may necessitate a transition, e.g. timeouts.
- // // shouldAdvance gives, for each consumer chain, whether it should advance if possible.
- // // if a chain has to advance, e.g. due to timeouts, or may not advance, the value will have no effect.
- // action AdvanceConsumers(shouldAdvance: Chain -> bool): bool =
- // val newConsumerStatus = consumerStatus.keys().mapBy(
- // chain =>
- // val curStatus = consumerStatus.get(chain)
- // if (curStatus == UNUSED) {
- // if (shouldAdvance.get(chain))
- // {
- // RUNNING
- // } else {
- // UNUSED
- // }
- // }
- // else if (curStatus == RUNNING) {
- // // the chain may transition to stopped.
- // // it is *forced* to stop if a packet timed out,
- // // or if the inactivity timeout has passed
- // if(consumerTimedOut(chain)) {
- // STOPPED
- // } else {
- // if (shouldAdvance.get(chain)) {
- // RUNNING
- // } else {
- // STOPPED
- // }
- // }
- // } else {
- // // stopped chains cannot restart, we assume a new chain would be started in that case
- // STOPPED
- // }
- // )
- // all {
- // consumerStatus' = newConsumerStatus,
- // // all other variables are left untouched
- // votingPowerHistories' = votingPowerHistories,
- // runningValidatorSet' = runningValidatorSet.keys().mapBy(
- // chain =>
- // if (newConsumerStatus.get(chain) == RUNNING and consumerStatus.get(chain) == UNUSED)
- // // consumers that went from unused to running start with the current validator set on the provider
- // {
- // runningValidatorSet.get(ProviderChain)
- // } else {
- // runningValidatorSet.get(chain)
- // }
- // ),
- // outstandingPacketsToProvider' = outstandingPacketsToProvider,
- // RegisterNewOutstandingPackets(outstandingPacketsToConsumer),
- // receivedMaturations' = receivedMaturations,
- // maturationTimes' = maturationTimes,
- // // chain times do not change
- // curChainTimes' = curChainTimes,
- // // the validator set was not changed by this action (but might have been changed before in this block)
- // providerValidatorSetChangedInThisBlock' = providerValidatorSetChangedInThisBlock
- // }
-
- // // Updates the outstandingPacketsToConsumer and sentVSCPackets variables
- // action RegisterNewOutstandingPackets(newOutstandingPackets: Chain -> List[VSCPacket]): bool =
- // all {
- // outstandingPacketsToConsumer' = newOutstandingPackets,
- // StoreSentPackets(newOutstandingPackets),
- // }
-
-
- // // stores the VSCPackets sent in this step in sentVSCPackets
- // action StoreSentPackets(newOutstandingPackets: Chain -> List[VSCPacket]): bool =
- // sentVSCPackets' = sentVSCPackets.keys().mapBy(
- // (chain) =>
- // sentVSCPackets.get(chain).union(newOutstandingPackets.get(chain).toSet())
- // )
+ // has a packe from provider to consumer timed out?
+ val outstandingPacketsToConsumer = providerState.outstandingPacketsToConsumer.get(consumer)
+ val providerToConsumerPacketTimedOut = outstandingPacketsToConsumer.head().sendingTime +
+ CcvTimeoutPeriod.get(PROVIDER_CHAIN) <= consumerState.chainState.lastTimestamp
-
- // // the main step action
- // action step: bool = any {
- // AdvanceTime,
- // nondet node = oneOf(Nodes)
- // nondet amount = oneOf(1.to(10))
- // votingPowerChange(node, amount),
- // recvPacketOnProvider(oneOf(ConsumerChains)),
- // recvPacketOnConsumer(oneOf(ConsumerChains)),
- // nondet chain = oneOf(chains)
- // endAndBeginBlock(chain),
- // val shouldAdvance = ConsumerChains.mapBy(
- // chain =>
- // nondet should = oneOf(Set(true, false))
- // should
- // )
- // AdvanceConsumers(shouldAdvance),
- // }
-
- // pure val nodePowerSet = Nodes.powerset()
-
- // def getArbitraryValidatorSet(): ValidatorSet =
- // nondet numValidators = oneOf(1.to(Nodes.size()))
- // // toList has nondeterministic behaviour, so this gets arbitrary validators
- // nondet validators = oneOf(nodePowerSet.filter(s => s.size() == numValidators))
- // validators.mapBy(
- // validator =>
- // nondet votingPower = oneOf(1.to(10))
- // votingPower
- // )
-
- // // INITIALIZATION
- // action init: bool =
- // all {
- // val validatorSets = chains.mapBy(
- // (chain) =>
- // // provider chain gets an arbitrary validator set, consumer chains have none
- // if (chain == ProviderChain) getArbitraryValidatorSet else Map()
- // )
- // all {
- // votingPowerHistories' = chains.mapBy(
- // (chain) =>
- // List(validatorSets.get(chain))
- // ),
- // runningValidatorSet' = validatorSets,
- // },
- // // each chain starts at time 0
- // curChainTimes' = chains.mapBy(
- // (chain) => 0
- // ),
- // // all consumer chains are unused
- // consumerStatus' = chains.mapBy(chain => UNUSED),
- // // no packets are outstanding
- // outstandingPacketsToProvider' = chains.mapBy(chain => List()),
- // outstandingPacketsToConsumer' = chains.mapBy(chain => List()),
- // // no maturations have been received by provider
- // receivedMaturations' = Set(),
- // // no packets have been sent to consumers
- // sentVSCPackets' = chains.mapBy(chain => Set()),
- // // no packets have been received by consumers, so no maturation times set
- // maturationTimes' = chains.mapBy(chain => Map()),
- // // validator set was not changed yet
- // providerValidatorSetChangedInThisBlock' = false
- // }
-
- // // PROPERTIES
-
- // // Every validator set on any consumer chain MUST either be or
- // // have been a validator set on the provider chain.
- // val ValidatorSetReplication: bool =
- // chains.forall(
- // chain => chain.getCurrentValidatorSet().wasValidatorSetOnProvider()
- // )
-
- // // TESTS
- // run VSCHappyPathTest: bool = {
- // init
- // // trigger a votingPowerChange on the provider chain
- // .then(votingPowerChange("A", 10))
- // // endAndBeginBlock on provider. No consumer chains are running, so no packets are sent
- // .then(endAndBeginBlock(ProviderChain))
- // .then(all {
- // // no packet was sent
- // assert(outstandingPacketsToConsumer.get("chain1").length() == 0),
- // // advance chain1 to running
- // AdvanceConsumers(NoStatusAdvancement.set("chain1", true))
- // })
- // // consumer chain should have current validator set from provider
- // .then(
- // all {
- // // since consumer chain just started, its assumed to have the validator set from provider
- // assert(runningValidatorSet.get("chain1") == runningValidatorSet.get(ProviderChain)),
- // // trigger a votingPowerChange on the provider chain
- // votingPowerChange("B", 10)
- // }
- // )
- // .then(
- // val valSet = runningValidatorSet.get(ProviderChain)
- // endAndBeginBlock(ProviderChain)
- // // now the provider should send a packet on block end
- // .then(all {
- // // a packet was sent
- // assert(outstandingPacketsToConsumer.get("chain1").length() == 1),
- // // deliver the packet to the consumer
- // recvPacketOnConsumer("chain1")
- // })
- // .then(
- // // consumer needs to end a block before it has the new validator set
- // endAndBeginBlock("chain1")
- // )
- // .then(all {
- // // the consumer should have the new validator set
- // assert(runningValidatorSet.get("chain1") == valSet),
- // // put a last action to satisfy the action effect
- // AdvanceConsumers(NoStatusAdvancement)
- // })
- // )
- // }
-
- // // utility: the set of consumers currently running
- // val RunningConsumers: Set[Chain] =
- // ConsumerChains.filter(chain => consumerStatus.get(chain) == RUNNING)
-
- // // MODEL STATE
- // // --SHARED STATE
-
- // // Stores, for each chain, the list of voting powers that corresponded to voting powers
- // // at blocks over its entire existence.
- // // Voting powers should be ordered by recency in descending order.
- // var votingPowerHistories: Chain -> List[ValidatorSet]
-
- // // the current validator set on each chain.
- // // this will be included in the next block, but might not be final yet,
- // // e.g. there may be more modifications in the current block.
- // var runningValidatorSet: Chain -> ValidatorSet
-
- // // the current timestamp for each chain
- // var curChainTimes: Chain -> Timestamp
-
- // // stores, for each chain, its current status -
- // // unused, running, or stopped
- // var consumerStatus: Chain -> str
-
- // // --CHANNELS
- // // Stores, for each consumer chain, the list of packets that have been sent to the provider chain
- // // and have not been received yet.
- // var outstandingPacketsToProvider: Chain -> List[VSCMaturedPacket]
-
- // // Stores, for each consumer chain, the list of packets that have been sent to the consumer chain
- // // and have not been received yet.
- // var outstandingPacketsToConsumer: Chain -> List[VSCPacket]
-
-
- // // --CONSUMER STATE
- // // Stores the maturation times for VSCPackets received by consumers
- // var maturationTimes: Chain -> (VSCPacket -> Timestamp)
-
- // // --PROVIDER STATE
- // // the set of VSCMaturedPackets received by the provider chain
- // var receivedMaturations: Set[VSCMaturedPacket]
-
- // // stores which VSC Packets have been sent to compare with receivedMaturations to detect timeouts due to non-responsiveness
- // var sentVSCPackets: Chain -> Set[VSCPacket]
-
- // // stores whether, in this step, the validator set considered to be changed.
- // // this is needed because the validator set might be considered to have changed, even though
- // // it is still technically identical at our level of abstraction, e.g. a validator power change on the provider
- // // might leave the validator set the same because a delegation and undelegation cancel each other out.
- // var providerValidatorSetChangedInThisBlock: bool
-
- // // utility: a struct summarizing the current state
- // val state =
- // {
- // votingPowerHistories: votingPowerHistories,
- // runningValidatorSet: runningValidatorSet,
- // curChainTimes: curChainTimes,
- // consumerStatus: consumerStatus,
- // outstandingPacketsToProvider: outstandingPacketsToProvider,
- // outstandingPacketsToConsumer: outstandingPacketsToConsumer,
- // maturationTimes: maturationTimes,
- // receivedMaturations: receivedMaturations,
- // sentVSCPackets: sentVSCPackets,
- // }
-
- // // set of identifiers of potential nodes
- // pure val Nodes: Set[Node] =
- // Set("A", "B", "C", "D", "E", "F", "G", "H", "I", "J")
-
- // // the set of consumer chains
- // pure val ConsumerChains: Set[Chain] =
- // Set("chain1", "chain2", "chain3")
-
- // // The singular provider chain.
- // pure val ProviderChain: Chain =
- // "provider"
+ // has a packet from consumer to provider timed out?
+ val outstandingPacketsToProvider = consumerState.outstandingPacketsToProvider
+ val consumerToProviderPacketTimedOut = outstandingPacketsToProvider.head().sendingTime +
+ CcvTimeoutPeriod.get(consumer) <= providerState.chainState.lastTimestamp
+
+
+
+ (providerToConsumerPacketTimedOut, "")
+ }
- // pure val chains = ConsumerChains.union(Set(ProviderChain))
-
- // // length of the unbonding period on each chain
- // pure val UnbondingPeriod: Chain -> int = chains.mapBy(
- // (chain) =>
- // 10
- // )
-
- // // the time until a packet times out
- // pure val PacketTimeout: int =
- // 5
-
- // // the time until a consumer chain is dropped by the provider due to inactivity
- // pure val InactivityTimeout: int =
- // 10
-
- // // utility: a map assigning each chain to 0, used for not advancing timestamps
- // pure val NoTimeAdvancement: Chain -> int = chains.mapBy(
- // (chain) =>
- // 0
- // )
-
-
- // // utility: a map assigning each chain to false, used for not advancing consumer status
- // pure val NoStatusAdvancement: Chain -> bool = chains.mapBy(
- // (chain) =>
- // false
- // )
+ // // either a package from provider to consumer has timed out
+ // currentState..get(consumer).select(
+ // packet => packet.timeout <= curChainTimes.get(consumer)
+ // ).length() > 0,
+ // // or a package from consumer to provider has timed out
+ // outstandingPacketsToProvider.get(consumer).select(
+ // packet => packet.timeout <= curChainTimes.get(ProviderChain)
+ // ).length() > 0,
+ // // or the inactivity timeout has passed since a VSCPacket was sent to the consumer, but the
+ // // provider has not received a VSCMaturedPacket for it
+ // val packetsWithoutResponse = sentVSCPackets.get(consumer).filter( // get packets without response
+ // packet =>
+ // not(receivedMaturations.exists(
+ // maturedPacket => maturedPacket.id == packet.id
+ // ))
+ // )
+ // // among those, get packets where inactivity timeout has passed
+ // packetsWithoutResponse.filter(
+ // packet =>
+ // val sentAt = curChainTimes.get(ProviderChain) - PacketTimeout // compute when the packet was sent
+ // val timesOutAt = sentAt + InactivityTimeout // compute when the packet times out
+ // timesOutAt <= curChainTimes.get(ProviderChain)
+ // ).size() > 0
+
+ // ===================
+ // ASSUMPTIONS ON MODEL PARAMETERS
+ // ===================
+
+ // the unbonding period is positive
+ run UnbondingPeriodPositiveTest =
+ UnbondingPeriodPerChain.keys().forall(chain => UnbondingPeriodPerChain.get(chain) > 0)
+
+ // the VSC timeout is positive
+ run VscTimeoutPositiveTest =
+ VscTimeout > 0
+
+ // the CCV timeout is positive
+ run CcvTimeoutPositiveTest =
+ CcvTimeoutPeriod.keys().forall(chain => CcvTimeoutPeriod.get(chain) > 0)
+
+ // ccv timeout on the provider **must** be larger than unbonding period on each chain
+ run CcvTimeoutLargerThanUnbondingPeriodTest =
+ CcvTimeoutPeriod.get(PROVIDER_CHAIN) > UnbondingPeriodPerChain.Values().Max()
+
+ // the provider chain is not a consumer chain
+ run ProviderNotConsumerTest =
+ not(ConsumerChains.contains(PROVIDER_CHAIN))
+
+ // ccv timeout contains only consumers and provider, no other chains
+ run CcvTimeoutSubsetTest =
+ CcvTimeoutPeriod.keys().forall(chain => ConsumerChains.contains(chain) or chain == PROVIDER_CHAIN)
+
+ // unbonding period contains consumers and provider, no other chains
+ def UnbondingPeriodInv = UnbondingPeriodPerChain.keys() == ConsumerChains.add(PROVIDER_CHAIN)
+
+
+}
+
+// A basic state machine that utilizes the CCV protocol.
+// Still leaves constants unassigned, just defines the state machine logic in general,
+// i.e. regardless of how many chains there are, what the unbonding periods are, etc.
+module CCVStatemachinLogic {
+ import Time.* from "./Time"
+ import CCV as CCV
+ import CCVTypes.*
+
+ var currentState: ProtocolState
+
+ action init: bool = all {
+ val providerState = GetEmptyProviderState
+ val consumerStates = CCV::ConsumerChains.mapBy(chain => GetEmptyConsumerState)
+ currentState' = {
+ providerState: providerState,
+ consumerStates: consumerStates
+ }
+ }
+
+ action votingPowerChanges(validator: Node, amount: int): bool =
+ val result = CCV::votingPowerChange(currentState, validator, amount)
+ all {
+ result.hasError == false,
+ currentState' = result.newState,
+ }
+
+ // The receiver receives the next outstanding VSCPacket from the provider.
+ action DeliverVSCPacket(receiver: Chain): bool =
+ val result = CCV::deliverPacketToConsumer(currentState, receiver)
+ all {
+ result.hasError == false,
+ currentState' = result.newState,
+ }
+
+ action DeliverVSCMaturedPacket(sender: Chain): bool =
+ val result = CCV::deliverPacketToProvider(currentState, sender)
+ all {
+ result.hasError == false,
+ currentState' = result.newState,
+ }
+
+ action EndAndBeginBlockForProvider(
+ timeAdvancement: Time,
+ consumersToStart: Set[Chain],
+ consumersToStop: Set[Chain]): bool =
+ val result = CCV::endAndBeginBlockForProvider(currentState, timeAdvancement, consumersToStart, consumersToStop)
+ all {
+ result.hasError == false,
+ currentState' = result.newState,
+ }
+
+ action EndAndBeginBlockForConsumer(
+ chain: Chain,
+ timeAdvancement: Time): bool =
+ val result = CCV::endAndBeginBlockForConsumer(currentState, chain, timeAdvancement)
+ all {
+ result.hasError == false,
+ currentState' = result.newState,
+ }
+}
+
+module CCVDefaultStateMachine {
+ // A basic state machine that utilizes the CCV protocol.
+ import Time.* from "./Time"
+ import CCVTypes.*
+ import extraSpells.* from "./extraSpells"
+
+ pure val consumerChains = Set("consumer1", "consumer2", "consumer3")
+ pure val chains = consumerChains.add(PROVIDER_CHAIN)
+ pure val unbondingPeriods = chains.mapBy(chain => 2 * Week)
+ pure val ccvTimeouts = chains.mapBy(chain => 3 * Week)
+
+ import CCV(VscTimeout = 5 * Week, CcvTimeoutPeriod = unbondingPeriods, UnbondingPeriodPerChain = unbondingPeriods, ConsumerChains = chains).*
}
diff --git a/tests/difference/core/quint_model/extraSpells.qnt b/tests/difference/core/quint_model/extraSpells.qnt
index 32f565b16a..1d7d219640 100644
--- a/tests/difference/core/quint_model/extraSpells.qnt
+++ b/tests/difference/core/quint_model/extraSpells.qnt
@@ -176,4 +176,22 @@ module extraSpells {
__set.union(Set(elem))
}
+ pure def Values(__map: a -> b): Set[b] = {
+ __map.keys().fold(Set(), (__s, __k) => __s.add(__map.get(__k)))
+ }
+ run ValuesTest =
+ all {
+ assert(Values(Map(1 -> 2, 3 -> 4)) == Set(2, 4)),
+ assert(Values(Map()) == Set())
+ }
+
+ pure def Max(__set: Set[int]): int = {
+ __set.fold(0, (__m, __e) => max(__m, __e))
+ }
+
+ run MaxTest =
+ all {
+ assert(Max(Set(1, 2, 3)) == 3),
+ assert(Max(Set()) == 0)
+ }
}
diff --git a/tests/difference/core/quint_model/time.qnt b/tests/difference/core/quint_model/time.qnt
new file mode 100644
index 0000000000..8497d8e1d3
--- /dev/null
+++ b/tests/difference/core/quint_model/time.qnt
@@ -0,0 +1,13 @@
+// A simple module for time that makes timings more readable,
+// e.g. 5 * Minute.
+// The base unit are seconds, i.e. to get the number of seconds in a time value, just treat it as an int.
+module Time {
+ type Time = int
+
+ pure val Second = 1
+ pure val Minute = 60 * Second
+ pure val Hour = 60 * Minute
+ pure val Day = 24 * Hour
+ pure val Week = 7 * Day
+ pure val Year = 365 * Day
+}
\ No newline at end of file
From b992f8c305057d38482c0dd54006336fbb3d0364 Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Tue, 26 Sep 2023 17:16:22 +0200
Subject: [PATCH 21/35] Save status with crashing effect checker
---
tests/difference/core/quint_model/ccv.qnt | 326 ++++++++++++++--------
1 file changed, 215 insertions(+), 111 deletions(-)
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index 43c8f9098d..e0f5753a79 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -68,10 +68,7 @@ module CCVTypes {
receivedMaturations: Set[VSCMaturedPacket],
// stores which VSC Packets have been sent to compare with receivedMaturations to detect timeouts due to non-responsiveness
- sentVSCPackets: Chain -> Set[VSCPacket],
-
- // a mapping from (chainId, vscId) tuples to the timestamps of sent VSCPackets.
- vscSendTimestamps: (Chain, VSCId) -> Time,
+ sentVSCPackets: Chain -> List[VSCPacket],
// stores whether, in this block, the validator set has changed.
// this is needed because the validator set might be considered to have changed, even though
@@ -95,7 +92,6 @@ module CCVTypes {
outstandingPacketsToConsumer: Map(),
receivedMaturations: Set(),
sentVSCPackets: Map(),
- vscSendTimestamps: Map(),
providerValidatorSetChangedInThisBlock: false,
consumerStatus: Map(),
runningVscId: 0,
@@ -173,7 +169,6 @@ module CCVTypes {
providerValidatorSetChangedInThisBlock: false,
consumerStatus: Map(),
runningVscId: 0,
- vscSendTimestamps: Map(),
},
consumerStates: Map(),
},
@@ -231,7 +226,7 @@ module CCV {
const VscTimeout: int
// The timeoutTimestamp for sent packets. Can differ by chain.
- const CcvTimeoutPeriod: Chain -> int
+ const CcvTimeout: Chain -> int
// ===================
// PROTOCOL LOGIC contains the meat of the protocol
@@ -253,6 +248,8 @@ module CCV {
// Delivers the next queued VSCMaturedPacket from a consumer chain to the provider chain.
// Only argument is the consumer chain, from which the packet will be delivered.
+ // If this packet will time out on the provider on delivery,
+ // the consumer will be dropped.
pure def deliverPacketToProvider(currentState: ProtocolState, sender: Chain): Result = {
if (not(isCurrentlyConsumer(sender, currentState.providerState))) {
Err("Sender is not currently a consumer - must have 'running' status!")
@@ -260,18 +257,44 @@ module CCV {
Err("No outstanding packets to deliver")
} else {
val packet = currentState.consumerStates.get(sender).outstandingPacketsToProvider.head()
- val result = recvPacketOnProvider(currentState, sender, packet)
- val tmpState = result.newState
- if (result.hasError) {
- Err(result.error.message)
+ // if the packet has timed out, drop the consumer. its state doesn't matter anymore
+ val timeout = CcvTimeout.get(sender)
+ if(packet.sendingTime + CcvTimeout.get(sender) > currentState.providerState.chainState.lastTimestamp) {
+ // drop consumer
+ val result = getNewConsumerStatusMap(
+ currentState.providerState.consumerStatus,
+ Set(),
+ Set(sender)
+ )
+
+ val newConsumerStatus = result._1
+ val err = result._2
+ if (err != "") {
+ Err(err)
+ } else {
+ val newProviderState = currentState.providerState.with(
+ "consumerStatus", newConsumerStatus
+ )
+ val newState = currentState.with(
+ "providerState", newProviderState
+ )
+ Ok(newState)
+ }
} else {
- val result2 = removeOutstandingPacketFromConsumer(tmpState, sender)
- val tmpState2 = result2.newState
- val err2 = result2.error
- if (result2.hasError) {
- Err(err2.message)
+ // the packet has not timed out, so receive it on the provider
+ val result = recvPacketOnProvider(currentState, sender, packet)
+ val tmpState = result.newState
+ if (result.hasError) {
+ Err(result.error.message)
} else {
- Ok(tmpState2)
+ val result2 = removeOutstandingPacketFromConsumer(tmpState, sender)
+ val tmpState2 = result2.newState
+ val err2 = result2.error
+ if (result2.hasError) {
+ Err(err2.message)
+ } else {
+ Ok(tmpState2)
+ }
}
}
}
@@ -279,6 +302,8 @@ module CCV {
// Delivers the next queued VSCPacket from the provider chain to a consumer chain.
// Only argument is the consumer chain, to which the packet will be delivered.
+ // If this packet will time out on the consumer on delivery,
+ // the consumer will be dropped.
pure def deliverPacketToConsumer(currentState: ProtocolState, receiver: Chain): Result = {
if (not(isCurrentlyConsumer(receiver, currentState.providerState))) {
Err("Receiver is not currently a consumer - must have 'running' status!")
@@ -286,25 +311,54 @@ module CCV {
Err("No outstanding packets to deliver")
} else {
val packet = currentState.providerState.outstandingPacketsToConsumer.get(receiver).head()
- val result = recvPacketOnConsumer(currentState, receiver, packet)
- val tmpState = result.newState
- if (result.hasError) {
- Err(result.error.message)
+ // check if the consumer timed out
+ if (packet.sendingTime + CcvTimeout.get(PROVIDER_CHAIN) > currentState.consumerStates.get(receiver).chainState.lastTimestamp) {
+ // drop consumer
+ val result = getNewConsumerStatusMap(
+ currentState.providerState.consumerStatus,
+ Set(),
+ Set(receiver)
+ )
+
+ val newConsumerStatus = result._1
+ val err = result._2
+ if (err != "") {
+ Err(err)
+ } else {
+ val newProviderState = currentState.providerState.with(
+ "consumerStatus", newConsumerStatus
+ )
+ val newState = currentState.with(
+ "providerState", newProviderState
+ )
+ // Ok(newState)
+ Err("not implemented")
+ }
} else {
- val result2 = removeOutstandingPacketFromProvider(tmpState, receiver)
- val tmpState2 = result2.newState
- val err2 = result2.error
- if (result2.hasError) {
- Err(err2.message)
+ // the packet has not timed out, so receive it on the consumer
+ val result = recvPacketOnConsumer(currentState, receiver, packet)
+ val tmpState = result.newState
+ if (result.hasError) {
+ Err(result.error.message)
} else {
- Ok(tmpState2)
+ val result2 = removeOutstandingPacketFromProvider(tmpState, receiver)
+ val tmpState2 = result2.newState
+ val err2 = result2.error
+ if (result2.hasError) {
+ Err(err2.message)
+ } else {
+ Ok(tmpState2)
+ }
}
}
}
}
+
+
// Ends a block on the provider. This means that the current validator set is committed on chain,
- // packets are queued, and the next block is started.
+ // packets are queued, and the next block is started. Also, consumers that have passed
+ // the VSCTimeout without responding to a pending vscpacket are dropped.
pure def endAndBeginBlockForProvider(
currentState: ProtocolState,
// by how much the timestamp should be advanced,
@@ -313,29 +367,44 @@ module CCV {
// a set of consumers that were unused before, but should be set to running now.
consumersToStart: Set[Chain],
// a set of consumers that were running before, but should be set to stopped now.
+ // This argument only needs to contain "voluntary" stops -
+ // forced stops, e.g. because a consumer timed out,
+ // will be added automatically.
consumersToStop: Set[Chain]): Result = {
// commit the current running validator set on chain
val currentProviderState = currentState.providerState
val newChainState = currentProviderState.chainState.endAndBeginBlockShared(timeAdvancement)
+ val providerStateAfterTimeAdvancement = currentProviderState.with(
+ "chainState", newChainState
+ )
+
+ // check for VSC timeouts
+ val timedOutConsumers = getRunningConsumers(providerStateAfterTimeAdvancement).filter(
+ consumer =>
+ val res = TimeoutDueToVSCTimeout(currentState, consumer)
+ res._1
+ )
+
// modify the states of the consumers that should be started/stopped
- val res = currentProviderState.consumerStatus.getNewConsumerStatusMap(consumersToStart, consumersToStop)
+ val res = providerStateAfterTimeAdvancement.consumerStatus.getNewConsumerStatusMap(consumersToStart, consumersToStop.union(timedOutConsumers))
val newConsumerStatus = res._1
val err = res._2
if (err != "") {
Err(err)
} else {
- val newProviderState = currentProviderState.with(
- "chainState", newChainState
- ).with(
+ val providerStateAfterConsumerAdvancement = providerStateAfterTimeAdvancement.with(
"consumerStatus", newConsumerStatus
)
val providerStateAfterSending =
if (currentProviderState.providerValidatorSetChangedInThisBlock and getRunningConsumers(currentState.providerState).size() > 0) {
- newProviderState.sendVscPackets()
+ providerStateAfterConsumerAdvancement.sendVscPackets()
} else {
- newProviderState
+ providerStateAfterConsumerAdvancement
}
- Err("not implemented")
+ val newState = currentState.with(
+ "providerState", providerStateAfterSending
+ )
+ Ok(newState)
}
}
@@ -463,42 +532,44 @@ module CCV {
// and only when the running validator set is considered to have changed
// and there is a consumer to send a packet to.
pure def sendVscPackets(providerState: ProviderState): ProviderState = {
+ val newSentPacketsPerConsumer = ConsumerChains.mapBy(
+ (consumer) =>
+ // if validator set changed and the consumer is running, send a packet
+ if (providerState.providerValidatorSetChangedInThisBlock and
+ isCurrentlyConsumer(consumer, providerState)) {
+ List({
+ id: providerState.runningVscId,
+ validatorSet: providerState.chainState.currentValidatorSet,
+ sendingTime: providerState.chainState.lastTimestamp
+ })
+ } else {
+ List()
+ }
+ )
providerState.with(
// send VSCPackets to consumers
- "outstandingPacketsToConsumer",
- // if running validator set is considered to have changed and there is a consumer to send a packet to
- if (providerState.providerValidatorSetChangedInThisBlock
- and getRunningConsumers(providerState).size() > 0) {
- // then send a packet to each running consumer
- providerState.consumerStatus.keys().mapBy(
- // go through all potential consumers
- (consumer) =>
- val packetQueue = providerState.outstandingPacketsToConsumer.get(consumer)
- // if the consumer is running, send a packet
- if (isCurrentlyConsumer(consumer, providerState)) {
- packetQueue.append(
- {
- id: providerState.runningVscId,
- validatorSet: providerState.chainState.currentValidatorSet,
- sendingTime: providerState.chainState.lastTimestamp
- }
- )
- } else {
- // otherwise, leave the queue as-is
- packetQueue
- }
+ "outstandingPacketsToConsumer",
+ ConsumerChains.mapBy(
+ (consumer) =>
+ providerState.outstandingPacketsToConsumer.get(consumer).concat(
+ newSentPacketsPerConsumer.get(consumer)
+ )
+ )
+ ).with(
+ // update the sent VSCPackets
+ "sentVSCPackets",
+ ConsumerChains.mapBy(
+ (consumer) =>
+ providerState.sentVSCPackets.get(consumer).concat(
+ newSentPacketsPerConsumer.get(consumer)
)
- } else {
- // running validator set is not considered to have changed
- // ...so don't send any packets
- providerState.outstandingPacketsToConsumer
- }
+ )
).with(
// the validator set has not changed yet in the new block
"providerValidatorSetChangedInThisBlock", false
).with(
"runningVscId", providerState.runningVscId + 1
- )
+ )
}
// receives a given packet (sent by the provider) on the consumer. The arguments are the consumer chain that is receiving the packet, and the packet itself.
@@ -537,12 +608,17 @@ module CCV {
pure def recvPacketOnProvider(currentState: ProtocolState, sender: Chain, packet: VSCMaturedPacket): Result = {
if (not(isCurrentlyConsumer(sender, currentState.providerState))) {
Err("Sender is not currently a consumer - must have 'running' status!")
+ } else if (currentState.providerState.sentVSCPackets.get(sender).head().id != packet.id) {
+ // the packet is not the oldest sentVSCPacket, something went wrong
+ Err("Received maturation is not for the oldest sentVSCPacket")
} else {
val currentReceivedMaturations = currentState.providerState.receivedMaturations
val newReceivedMaturations = currentReceivedMaturations.add(packet)
val newProviderState = currentState.providerState.with(
"receivedMaturations", newReceivedMaturations
)
+ // prune the sentVSCPacket
+ val newSentVSCPacket = currentState.providerState.sentVSCPackets.get(sender).tail()
val newState = currentState.with(
"providerState", newProviderState
)
@@ -616,11 +692,11 @@ module CCV {
)
}
- // Returns whether the consumer has timed out, and an error message.
+ // Returns whether the consumer has timed out due to the VSCTimeout, and an error message.
// If the second return is not equal to "", the first return should be ignored.
// If it is equal to "", the first return will be true if the consumer has timed out and should be dropped,
// or false otherwise.
- def consumerTimedOut(currentState: ProtocolState, consumer: Chain): (bool, str) =
+ pure def TimeoutDueToVSCTimeout(currentState: ProtocolState, consumer: Chain): (bool, str) =
// check for errors: the consumer is not running
if (not(isCurrentlyConsumer(consumer, currentState.providerState))) {
(false, "Consumer is not currently a consumer - must have 'running' status!")
@@ -628,44 +704,16 @@ module CCV {
val providerState = currentState.providerState
val consumerState = currentState.consumerStates.get(consumer)
- // has a packe from provider to consumer timed out?
- val outstandingPacketsToConsumer = providerState.outstandingPacketsToConsumer.get(consumer)
- val providerToConsumerPacketTimedOut = outstandingPacketsToConsumer.head().sendingTime +
- CcvTimeoutPeriod.get(PROVIDER_CHAIN) <= consumerState.chainState.lastTimestamp
-
- // has a packet from consumer to provider timed out?
- val outstandingPacketsToProvider = consumerState.outstandingPacketsToProvider
- val consumerToProviderPacketTimedOut = outstandingPacketsToProvider.head().sendingTime +
- CcvTimeoutPeriod.get(consumer) <= providerState.chainState.lastTimestamp
-
-
-
- (providerToConsumerPacketTimedOut, "")
+ // has a packet been sent on the provider more than VSCTimeout ago, but we have not received an answer since then?
+ val sentVSCPackets = providerState.sentVSCPackets.get(consumer)
+ val oldestSentVSCPacket = sentVSCPackets.head() // if length is 0, this is undefined, but we check for this before we use it
+ if(sentVSCPackets.length() > 0 and oldestSentVSCPacket.sendingTime + VscTimeout < providerState.chainState.lastTimestamp) {
+ (true, "")
+ } else {
+ // no timeout yet, it has not been VscTimeout since that packet was sent
+ (false, "")
+ }
}
-
- // // either a package from provider to consumer has timed out
- // currentState..get(consumer).select(
- // packet => packet.timeout <= curChainTimes.get(consumer)
- // ).length() > 0,
- // // or a package from consumer to provider has timed out
- // outstandingPacketsToProvider.get(consumer).select(
- // packet => packet.timeout <= curChainTimes.get(ProviderChain)
- // ).length() > 0,
- // // or the inactivity timeout has passed since a VSCPacket was sent to the consumer, but the
- // // provider has not received a VSCMaturedPacket for it
- // val packetsWithoutResponse = sentVSCPackets.get(consumer).filter( // get packets without response
- // packet =>
- // not(receivedMaturations.exists(
- // maturedPacket => maturedPacket.id == packet.id
- // ))
- // )
- // // among those, get packets where inactivity timeout has passed
- // packetsWithoutResponse.filter(
- // packet =>
- // val sentAt = curChainTimes.get(ProviderChain) - PacketTimeout // compute when the packet was sent
- // val timesOutAt = sentAt + InactivityTimeout // compute when the packet times out
- // timesOutAt <= curChainTimes.get(ProviderChain)
- // ).size() > 0
// ===================
// ASSUMPTIONS ON MODEL PARAMETERS
@@ -681,11 +729,11 @@ module CCV {
// the CCV timeout is positive
run CcvTimeoutPositiveTest =
- CcvTimeoutPeriod.keys().forall(chain => CcvTimeoutPeriod.get(chain) > 0)
+ CcvTimeout.keys().forall(chain => CcvTimeout.get(chain) > 0)
// ccv timeout on the provider **must** be larger than unbonding period on each chain
run CcvTimeoutLargerThanUnbondingPeriodTest =
- CcvTimeoutPeriod.get(PROVIDER_CHAIN) > UnbondingPeriodPerChain.Values().Max()
+ CcvTimeout.get(PROVIDER_CHAIN) > UnbondingPeriodPerChain.Values().Max()
// the provider chain is not a consumer chain
run ProviderNotConsumerTest =
@@ -693,7 +741,7 @@ module CCV {
// ccv timeout contains only consumers and provider, no other chains
run CcvTimeoutSubsetTest =
- CcvTimeoutPeriod.keys().forall(chain => ConsumerChains.contains(chain) or chain == PROVIDER_CHAIN)
+ CcvTimeout.keys().forall(chain => ConsumerChains.contains(chain) or chain == PROVIDER_CHAIN)
// unbonding period contains consumers and provider, no other chains
def UnbondingPeriodInv = UnbondingPeriodPerChain.keys() == ConsumerChains.add(PROVIDER_CHAIN)
@@ -711,23 +759,46 @@ module CCVStatemachinLogic {
var currentState: ProtocolState
+ const InitialValidatorSet: ValidatorSet
+
action init: bool = all {
val providerState = GetEmptyProviderState
val consumerStates = CCV::ConsumerChains.mapBy(chain => GetEmptyConsumerState)
+ val providerStateWithConsumers = providerState.with(
+ "consumerStatus",
+ CCV::ConsumerChains.mapBy(chain => UNUSED)
+ ).with(
+ "outstandingPacketsToConsumer",
+ CCV::ConsumerChains.mapBy(chain => List())
+ ).with(
+ "sentVSCPackets",
+ CCV::ConsumerChains.mapBy(chain => List())
+ ).with(
+ // set the validator set to be the initial validator set in the history
+ "chainState", providerState.chainState.with(
+ "votingPowerHistory", List(InitialValidatorSet)
+ )
+ ).with(
+ // set the current validator set
+ "chainState", providerState.chainState.with(
+ "currentValidatorSet", InitialValidatorSet
+ )
+ )
currentState' = {
providerState: providerState,
consumerStates: consumerStates
}
}
- action votingPowerChanges(validator: Node, amount: int): bool =
- val result = CCV::votingPowerChange(currentState, validator, amount)
+ action VotingPowerChange(validator: Node, newVotingPower: int): bool =
+ val result = CCV::votingPowerChange(currentState, validator, newVotingPower)
all {
- result.hasError == false,
- currentState' = result.newState,
- }
+ result.hasError == false,
+ currentState' = result.newState,
+ }
// The receiver receives the next outstanding VSCPacket from the provider.
+ // This will time out the consumer if the packet timeout has passed on the receiver.
action DeliverVSCPacket(receiver: Chain): bool =
val result = CCV::deliverPacketToConsumer(currentState, receiver)
all {
@@ -735,6 +806,8 @@ module CCVStatemachinLogic {
currentState' = result.newState,
}
+ // The provider receives the next outstanding VSCMaturedPacket from the sender.
+ // This will time out the consumer if the packet timeout has passed on the provider.
action DeliverVSCMaturedPacket(sender: Chain): bool =
val result = CCV::deliverPacketToProvider(currentState, sender)
all {
@@ -773,5 +846,36 @@ module CCVDefaultStateMachine {
pure val unbondingPeriods = chains.mapBy(chain => 2 * Week)
pure val ccvTimeouts = chains.mapBy(chain => 3 * Week)
- import CCV(VscTimeout = 5 * Week, CcvTimeoutPeriod = unbondingPeriods, UnbondingPeriodPerChain = unbondingPeriods, ConsumerChains = chains).*
+ pure val nodes = Set("node1", "node2", "node3", "node4", "node5", "node6", "node7", "node8", "node9", "node10")
+ pure val initialValidatorSet = nodes.mapBy(node => 100)
+
+ import CCV(VscTimeout = 5 * Week, CcvTimeout = unbondingPeriods, UnbondingPeriodPerChain = unbondingPeriods, ConsumerChains = chains).*
+ import CCVStatemachinLogic(InitialValidatorSet = initialValidatorSet).*
+
+ run InitTest: bool = {
+ init.then(
+ all {
+ assert(ConsumerChains == chains),
+ assert(currentState.providerState.consumerStatus == Map(
+ "consumer1" -> UNUSED,
+ "consumer2" -> UNUSED,
+ "consumer3" -> UNUSED
+ )),
+ assert(currentState.providerState.outstandingPacketsToConsumer == Map(
+ "consumer1" -> List(),
+ "consumer2" -> List(),
+ "consumer3" -> List()
+ )),
+ assert(currentState.providerState.sentVSCPackets == Map(
+ "consumer1" -> List(),
+ "consumer2" -> List(),
+ "consumer3" -> List()
+ )),
+ assert(currentState.consumerStates.keys() == chains),
+ assert(currentState.providerState.chainState.votingPowerHistory == List(InitialValidatorSet)),
+ assert(currentState.providerState.chainState.currentValidatorSet == InitialValidatorSet),
+ assert(currentState.providerState.chainState.lastTimestamp == 0),
+ VotingPowerChange("node1", 50)
+ })
+ }
}
From baaddb7b1671e0e772512c75be7a391bccd1de48 Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Tue, 26 Sep 2023 18:18:58 +0200
Subject: [PATCH 22/35] Resolve issue by removing undefined field
---
tests/difference/core/quint_model/ccv.qnt | 26 +++++++++++------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index e0f5753a79..84bbc9e7e0 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -754,25 +754,27 @@ module CCV {
// i.e. regardless of how many chains there are, what the unbonding periods are, etc.
module CCVStatemachinLogic {
import Time.* from "./Time"
- import CCV as CCV
+ import CCV.*
import CCVTypes.*
+ import extraSpells.* from "./extraSpells"
+
var currentState: ProtocolState
const InitialValidatorSet: ValidatorSet
action init: bool = all {
val providerState = GetEmptyProviderState
- val consumerStates = CCV::ConsumerChains.mapBy(chain => GetEmptyConsumerState)
+ val consumerStates = ConsumerChains.mapBy(chain => GetEmptyConsumerState)
val providerStateWithConsumers = providerState.with(
"consumerStatus",
- CCV::ConsumerChains.mapBy(chain => UNUSED)
+ ConsumerChains.mapBy(chain => UNUSED)
).with(
"outstandingPacketsToConsumer",
- CCV::ConsumerChains.mapBy(chain => List())
+ ConsumerChains.mapBy(chain => List())
).with(
"sentVSCPackets",
- CCV::ConsumerChains.mapBy(chain => List())
+ ConsumerChains.mapBy(chain => List())
).with(
// set the validator set to be the initial validator set in the history
"chainState", providerState.chainState.with(
@@ -791,7 +793,7 @@ module CCVStatemachinLogic {
}
action VotingPowerChange(validator: Node, newVotingPower: int): bool =
- val result = CCV::votingPowerChange(currentState, validator, newVotingPower)
+ val result = votingPowerChange(currentState, validator, newVotingPower)
all {
result.hasError == false,
currentState' = result.newState,
@@ -800,7 +802,7 @@ module CCVStatemachinLogic {
// The receiver receives the next outstanding VSCPacket from the provider.
// This will time out the consumer if the packet timeout has passed on the receiver.
action DeliverVSCPacket(receiver: Chain): bool =
- val result = CCV::deliverPacketToConsumer(currentState, receiver)
+ val result = deliverPacketToConsumer(currentState, receiver)
all {
result.hasError == false,
currentState' = result.newState,
@@ -809,7 +811,7 @@ module CCVStatemachinLogic {
// The provider receives the next outstanding VSCMaturedPacket from the sender.
// This will time out the consumer if the packet timeout has passed on the provider.
action DeliverVSCMaturedPacket(sender: Chain): bool =
- val result = CCV::deliverPacketToProvider(currentState, sender)
+ val result = deliverPacketToProvider(currentState, sender)
all {
result.hasError == false,
currentState' = result.newState,
@@ -819,7 +821,7 @@ module CCVStatemachinLogic {
timeAdvancement: Time,
consumersToStart: Set[Chain],
consumersToStop: Set[Chain]): bool =
- val result = CCV::endAndBeginBlockForProvider(currentState, timeAdvancement, consumersToStart, consumersToStop)
+ val result = endAndBeginBlockForProvider(currentState, timeAdvancement, consumersToStart, consumersToStop)
all {
result.hasError == false,
currentState' = result.newState,
@@ -828,7 +830,7 @@ module CCVStatemachinLogic {
action EndAndBeginBlockForConsumer(
chain: Chain,
timeAdvancement: Time): bool =
- val result = CCV::endAndBeginBlockForConsumer(currentState, chain, timeAdvancement)
+ val result = endAndBeginBlockForConsumer(currentState, chain, timeAdvancement)
all {
result.hasError == false,
currentState' = result.newState,
@@ -849,13 +851,11 @@ module CCVDefaultStateMachine {
pure val nodes = Set("node1", "node2", "node3", "node4", "node5", "node6", "node7", "node8", "node9", "node10")
pure val initialValidatorSet = nodes.mapBy(node => 100)
- import CCV(VscTimeout = 5 * Week, CcvTimeout = unbondingPeriods, UnbondingPeriodPerChain = unbondingPeriods, ConsumerChains = chains).*
- import CCVStatemachinLogic(InitialValidatorSet = initialValidatorSet).*
+ import CCVStatemachinLogic(InitialValidatorSet = initialValidatorSet, VscTimeout = 5 * Week, CcvTimeout = unbondingPeriods, UnbondingPeriodPerChain = unbondingPeriods, ConsumerChains = chains).*
run InitTest: bool = {
init.then(
all {
- assert(ConsumerChains == chains),
assert(currentState.providerState.consumerStatus == Map(
"consumer1" -> UNUSED,
"consumer2" -> UNUSED,
From 2c1341d6453b9326c9f8dcdd6ee18c07c50dac83 Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Wed, 27 Sep 2023 09:08:12 +0200
Subject: [PATCH 23/35] Remove add
---
tests/difference/core/quint_model/ccv.qnt | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index 84bbc9e7e0..fc180a2b35 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -613,7 +613,7 @@ module CCV {
Err("Received maturation is not for the oldest sentVSCPacket")
} else {
val currentReceivedMaturations = currentState.providerState.receivedMaturations
- val newReceivedMaturations = currentReceivedMaturations.add(packet)
+ val newReceivedMaturations = currentReceivedMaturations.union(Set(packet))
val newProviderState = currentState.providerState.with(
"receivedMaturations", newReceivedMaturations
)
@@ -742,10 +742,6 @@ module CCV {
// ccv timeout contains only consumers and provider, no other chains
run CcvTimeoutSubsetTest =
CcvTimeout.keys().forall(chain => ConsumerChains.contains(chain) or chain == PROVIDER_CHAIN)
-
- // unbonding period contains consumers and provider, no other chains
- def UnbondingPeriodInv = UnbondingPeriodPerChain.keys() == ConsumerChains.add(PROVIDER_CHAIN)
-
}
@@ -844,7 +840,7 @@ module CCVDefaultStateMachine {
import extraSpells.* from "./extraSpells"
pure val consumerChains = Set("consumer1", "consumer2", "consumer3")
- pure val chains = consumerChains.add(PROVIDER_CHAIN)
+ pure val chains = consumerChains.union(Set(PROVIDER_CHAIN))
pure val unbondingPeriods = chains.mapBy(chain => 2 * Week)
pure val ccvTimeouts = chains.mapBy(chain => 3 * Week)
From e09968d94a4e5a84bf144e5f0737e1f529494772 Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Wed, 27 Sep 2023 14:35:55 +0200
Subject: [PATCH 24/35] Fix init
---
tests/difference/core/quint_model/ccv.qnt | 29 ++++++++++-------------
1 file changed, 12 insertions(+), 17 deletions(-)
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index fc180a2b35..32f3ac5a88 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -719,30 +719,28 @@ module CCV {
// ASSUMPTIONS ON MODEL PARAMETERS
// ===================
- // the unbonding period is positive
run UnbondingPeriodPositiveTest =
UnbondingPeriodPerChain.keys().forall(chain => UnbondingPeriodPerChain.get(chain) > 0)
- // the VSC timeout is positive
run VscTimeoutPositiveTest =
VscTimeout > 0
- // the CCV timeout is positive
run CcvTimeoutPositiveTest =
CcvTimeout.keys().forall(chain => CcvTimeout.get(chain) > 0)
- // ccv timeout on the provider **must** be larger than unbonding period on each chain
run CcvTimeoutLargerThanUnbondingPeriodTest =
CcvTimeout.get(PROVIDER_CHAIN) > UnbondingPeriodPerChain.Values().Max()
- // the provider chain is not a consumer chain
- run ProviderNotConsumerTest =
+ run ProviderIsNotAConsumerTest =
not(ConsumerChains.contains(PROVIDER_CHAIN))
- // ccv timeout contains only consumers and provider, no other chains
- run CcvTimeoutSubsetTest =
- CcvTimeout.keys().forall(chain => ConsumerChains.contains(chain) or chain == PROVIDER_CHAIN)
-
+ // ccv timeout contains exactly consumers and provider, no other chains
+ run CcvTimeoutKeysTest =
+ CcvTimeout.keys() == ConsumerChains.union(Set(PROVIDER_CHAIN))
+
+ // unbonding period contains exactly consumers and provider, no other chains
+ run UnbondingPeriodKeysTest =
+ UnbondingPeriodPerChain.keys() == ConsumerChains.union(Set(PROVIDER_CHAIN))
}
// A basic state machine that utilizes the CCV protocol.
@@ -775,15 +773,12 @@ module CCVStatemachinLogic {
// set the validator set to be the initial validator set in the history
"chainState", providerState.chainState.with(
"votingPowerHistory", List(InitialValidatorSet)
- )
- ).with(
- // set the current validator set
- "chainState", providerState.chainState.with(
+ ).with(
"currentValidatorSet", InitialValidatorSet
)
)
currentState' = {
- providerState: providerState,
+ providerState: providerStateWithConsumers,
consumerStates: consumerStates
}
}
@@ -847,7 +842,7 @@ module CCVDefaultStateMachine {
pure val nodes = Set("node1", "node2", "node3", "node4", "node5", "node6", "node7", "node8", "node9", "node10")
pure val initialValidatorSet = nodes.mapBy(node => 100)
- import CCVStatemachinLogic(InitialValidatorSet = initialValidatorSet, VscTimeout = 5 * Week, CcvTimeout = unbondingPeriods, UnbondingPeriodPerChain = unbondingPeriods, ConsumerChains = chains).*
+ import CCVStatemachinLogic(InitialValidatorSet = initialValidatorSet, VscTimeout = 5 * Week, CcvTimeout = unbondingPeriods, UnbondingPeriodPerChain = unbondingPeriods, ConsumerChains = consumerChains).*
run InitTest: bool = {
init.then(
@@ -867,7 +862,7 @@ module CCVDefaultStateMachine {
"consumer2" -> List(),
"consumer3" -> List()
)),
- assert(currentState.consumerStates.keys() == chains),
+ assert(currentState.consumerStates.keys() == consumerChains),
assert(currentState.providerState.chainState.votingPowerHistory == List(InitialValidatorSet)),
assert(currentState.providerState.chainState.currentValidatorSet == InitialValidatorSet),
assert(currentState.providerState.chainState.lastTimestamp == 0),
From 55ab5955d71c4e5ea783cdd1a9a2a9de84cad797 Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Thu, 28 Sep 2023 08:50:07 +0200
Subject: [PATCH 25/35] Snapshot spec with parser crasher
---
tests/difference/core/quint_model/ccv.qnt | 243 +++++++++++++++++-----
1 file changed, 192 insertions(+), 51 deletions(-)
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index 32f3ac5a88..f6b8eb19cc 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -130,6 +130,13 @@ module CCVTypes {
consumerStates: Chain -> ConsumerState
}
+ // gets a protocol state that is initialized minimally.
+ pure def GetEmptyProtocolState(): ProtocolState =
+ {
+ providerState: GetEmptyProviderState,
+ consumerStates: Map(),
+ }
+
type Error = {
message: str
}
@@ -298,7 +305,31 @@ module CCV {
}
}
}
- }
+ }
+
+ // Defines a test state to test the deliverPacketToProvider function against.
+ pure val _DeliverPacketToProvider_TestState =
+ val currentState = GetEmptyProtocolState
+ val sender = "sender"
+ val providerState = currentState.providerState
+ val consumerState = GetEmptyConsumerState
+ // add the consumer to the consumerStates
+ val consumerStates = currentState.consumerStates.set(sender, consumerState)
+ val providerState2 = providerState.with(
+ "consumerStatus", providerState.consumerStatus.set(sender, RUNNING)
+ )
+ val providerState3 = providerState2.with(
+ "outstandingPacketsToConsumer", providerState2.outstandingPacketsToConsumer.set(sender, List({
+ id: 0,
+ validatorSet: Map(),
+ sendingTime: 0
+ }))
+ )
+ currentState.with(
+ "providerState", providerState3
+ ).with(
+ "consumerStates", consumerStates
+ )
// Delivers the next queued VSCPacket from the provider chain to a consumer chain.
// Only argument is the consumer chain, to which the packet will be delivered.
@@ -504,6 +535,7 @@ module CCV {
}
}
+
// Takes the currentValidatorSet and puts it as the newest set of the voting history
pure def enterCurValSetIntoBlock(chainState: ChainState): ChainState = {
chainState.with(
@@ -714,49 +746,28 @@ module CCV {
(false, "")
}
}
+}
- // ===================
- // ASSUMPTIONS ON MODEL PARAMETERS
- // ===================
-
- run UnbondingPeriodPositiveTest =
- UnbondingPeriodPerChain.keys().forall(chain => UnbondingPeriodPerChain.get(chain) > 0)
-
- run VscTimeoutPositiveTest =
- VscTimeout > 0
-
- run CcvTimeoutPositiveTest =
- CcvTimeout.keys().forall(chain => CcvTimeout.get(chain) > 0)
-
- run CcvTimeoutLargerThanUnbondingPeriodTest =
- CcvTimeout.get(PROVIDER_CHAIN) > UnbondingPeriodPerChain.Values().Max()
- run ProviderIsNotAConsumerTest =
- not(ConsumerChains.contains(PROVIDER_CHAIN))
+module CCVDefaultStateMachine {
+ // A basic state machine that utilizes the CCV protocol.
+ import Time.* from "./Time"
+ import CCVTypes.*
+ import extraSpells.* from "./extraSpells"
- // ccv timeout contains exactly consumers and provider, no other chains
- run CcvTimeoutKeysTest =
- CcvTimeout.keys() == ConsumerChains.union(Set(PROVIDER_CHAIN))
+ pure val consumerChains = Set("consumer1", "consumer2", "consumer3")
+ pure val chains = consumerChains.union(Set(PROVIDER_CHAIN))
+ pure val unbondingPeriods = chains.mapBy(chain => 2 * Week)
+ pure val ccvTimeouts = chains.mapBy(chain => 3 * Week)
- // unbonding period contains exactly consumers and provider, no other chains
- run UnbondingPeriodKeysTest =
- UnbondingPeriodPerChain.keys() == ConsumerChains.union(Set(PROVIDER_CHAIN))
-}
+ pure val nodes = Set("node1", "node2", "node3", "node4", "node5", "node6", "node7", "node8", "node9", "node10")
+ pure val InitialValidatorSet = nodes.mapBy(node => 100)
-// A basic state machine that utilizes the CCV protocol.
-// Still leaves constants unassigned, just defines the state machine logic in general,
-// i.e. regardless of how many chains there are, what the unbonding periods are, etc.
-module CCVStatemachinLogic {
- import Time.* from "./Time"
- import CCV.*
- import CCVTypes.*
+ import CCV(VscTimeout = 5 * Week, CcvTimeout = ccvTimeouts, UnbondingPeriodPerChain = unbondingPeriods, ConsumerChains = consumerChains).*
- import extraSpells.* from "./extraSpells"
var currentState: ProtocolState
- const InitialValidatorSet: ValidatorSet
-
action init: bool = all {
val providerState = GetEmptyProviderState
val consumerStates = ConsumerChains.mapBy(chain => GetEmptyConsumerState)
@@ -826,23 +837,141 @@ module CCVStatemachinLogic {
result.hasError == false,
currentState' = result.newState,
}
-}
-module CCVDefaultStateMachine {
- // A basic state machine that utilizes the CCV protocol.
- import Time.* from "./Time"
- import CCVTypes.*
- import extraSpells.* from "./extraSpells"
+ // negative voting powers give an error
+ run VotingPowerNegativeTest =
+ votingPowerChange(
+ GetEmptyProtocolState,
+ "validator",
+ -1
+ ).hasError
+
+ run VotingPowerOkTest =
+ val result = votingPowerChange(
+ GetEmptyProtocolState,
+ "validator",
+ 0
+ )
+ not(result.hasError) and
+ result.newState.providerState.chainState.currentValidatorSet.keys().contains("validator") and
+ result.newState.providerState.chainState.currentValidatorSet.get("validator") == 0
+
+ // make sure that VotingPowerChange ONLY changes the current validator set, not the history
+ run VotingPowerChangeDoesNotChangeHistoryTest =
+ val result = votingPowerChange(
+ GetEmptyProtocolState,
+ "validator",
+ 0
+ )
+ not(result.hasError) and
+ result.newState.providerState.chainState.votingPowerHistory == List()
+
+ run DeliverPacketToProviderHappyPathTest =
+ val result = deliverPacketToProvider(_DeliverPacketToProvider_TestState, "sender")
+ val newProviderState = result.newState.providerState
+ val newConsumerState = result.newState.consumerStates.get("sender")
+ not(result.hasError) and
+ newProviderState.receivedMaturations.size() == 1 and
+ newConsumerState.outstandingPacketsToProvider.length() == 0
+
+ run DeliverPacketToProviderTimeoutTest =
+ // set the timestamp to be after the timeout
+ val testState = _DeliverPacketToProvider_TestState.with(
+ "providerState", _DeliverPacketToProvider_TestState.providerState.with(
+ "chainState", _DeliverPacketToProvider_TestState.providerState.chainState.with(
+ "lastTimestamp", CcvTimeout.get("sender") + 1
+ )
+ )
+ )
+ val result = deliverPacketToProvider(testState, "sender")
+ val newProviderState = result.newState.providerState
+ val newConsumerState = result.newState.consumerStates.get("sender")
+ not(result.hasError) and
+ newProviderState.receivedMaturations.size() == 0 and
+ newConsumerState.outstandingPacketsToProvider.length() == 0 and
+ newProviderState.consumerStatus.get("sender") == STOPPED
+
+ run ConsumerStatusMapHappyPathTest =
+ val currentConsumerStatusMap = Map(
+ "chain1" -> UNUSED,
+ "chain2" -> RUNNING,
+ "chain3" -> STOPPED
+ )
+ val res = getNewConsumerStatusMap(
+ currentConsumerStatusMap,
+ Set("chain1"),
+ Set("chain3")
+ )
+ res._2 == "" and
+ res._1.get("chain1") == RUNNING and
+ res._1.get("chain2") == RUNNING and
+ res._1.get("chain3") == UNUSED
+
+ run ConsumerStatusMapAlreadyRunningTest =
+ val currentConsumerStatusMap = Map(
+ "chain1" -> UNUSED,
+ "chain2" -> RUNNING,
+ "chain3" -> STOPPED
+ )
+ val res = getNewConsumerStatusMap(
+ currentConsumerStatusMap,
+ Set("chain2"),
+ Set("chain3")
+ )
+ res._2 == "Cannot start a consumer that is already running"
- pure val consumerChains = Set("consumer1", "consumer2", "consumer3")
- pure val chains = consumerChains.union(Set(PROVIDER_CHAIN))
- pure val unbondingPeriods = chains.mapBy(chain => 2 * Week)
- pure val ccvTimeouts = chains.mapBy(chain => 3 * Week)
+ run ConsumerStatusMapAlreadyStoppedTest =
+ val currentConsumerStatusMap = Map(
+ "chain1" -> UNUSED,
+ "chain2" -> RUNNING,
+ "chain3" -> STOPPED
+ )
+ val res = getNewConsumerStatusMap(
+ currentConsumerStatusMap,
+ Set("chain1"),
+ Set("chain3")
+ )
+ res._2 == "Cannot stop a consumer that is not running"
- pure val nodes = Set("node1", "node2", "node3", "node4", "node5", "node6", "node7", "node8", "node9", "node10")
- pure val initialValidatorSet = nodes.mapBy(node => 100)
+ run ChainBothInStartAndStopTest =
+ val currentConsumerStatusMap = Map(
+ "chain1" -> UNUSED,
+ "chain2" -> RUNNING,
+ "chain3" -> STOPPED
+ )
+ val res = getNewConsumerStatusMap(
+ currentConsumerStatusMap,
+ Set("chain1"),
+ Set("chain1")
+ )
+ res._2 == "Cannot start and stop a consumer at the same time"
- import CCVStatemachinLogic(InitialValidatorSet = initialValidatorSet, VscTimeout = 5 * Week, CcvTimeout = unbondingPeriods, UnbondingPeriodPerChain = unbondingPeriods, ConsumerChains = consumerChains).*
+ // ===================
+ // ASSUMPTIONS ON MODEL PARAMETERS
+ // ===================
+
+ run UnbondingPeriodPositiveTest =
+ UnbondingPeriodPerChain.keys().forall(chain => UnbondingPeriodPerChain.get(chain) > 0)
+
+ run VscTimeoutPositiveTest =
+ VscTimeout > 0
+
+ run CcvTimeoutPositiveTest =
+ CcvTimeout.keys().forall(chain => CcvTimeout.get(chain) > 0)
+
+ run CcvTimeoutLargerThanUnbondingPeriodTest =
+ CcvTimeout.get(PROVIDER_CHAIN) > UnbondingPeriodPerChain.Values().Max()
+
+ run ProviderIsNotAConsumerTest =
+ not(ConsumerChains.contains(PROVIDER_CHAIN))
+
+ // ccv timeout contains exactly consumers and provider, no other chains
+ run CcvTimeoutKeysTest =
+ CcvTimeout.keys() == ConsumerChains.union(Set(PROVIDER_CHAIN))
+
+ // unbonding period contains exactly consumers and provider, no other chains
+ run UnbondingPeriodKeysTest =
+ UnbondingPeriodPerChain.keys() == ConsumerChains.union(Set(PROVIDER_CHAIN))
run InitTest: bool = {
init.then(
@@ -866,7 +995,19 @@ module CCVDefaultStateMachine {
assert(currentState.providerState.chainState.votingPowerHistory == List(InitialValidatorSet)),
assert(currentState.providerState.chainState.currentValidatorSet == InitialValidatorSet),
assert(currentState.providerState.chainState.lastTimestamp == 0),
- VotingPowerChange("node1", 50)
- })
- }
+ val firstState = currentState // snapshot the first state
+ VotingPowerChange("node1", 50).then(all {
+ // ensure that the only change is that the voting power of node1 is changed
+ assert(currentState == firstState.with(
+ "providerState", firstState.providerState.with(
+ "chainState", firstState.providerState.chainState.with(
+ "currentValidatorSet", firstState.providerState.chainState.currentValidatorSet.put("node1", 50)
+ )
+ )
+ )),
+ currentState' = currentState
+ })
+ }
+ )
+ }
}
From 0897e8ccc3834dfa580bd7b3089d8a5051ab778d Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Thu, 28 Sep 2023 09:54:22 +0200
Subject: [PATCH 26/35] Snapshot model
---
tests/difference/core/quint_model/ccv.qnt | 211 ++++++++++++++--------
1 file changed, 137 insertions(+), 74 deletions(-)
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index f6b8eb19cc..149474e5e6 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -314,12 +314,12 @@ module CCV {
val providerState = currentState.providerState
val consumerState = GetEmptyConsumerState
// add the consumer to the consumerStates
- val consumerStates = currentState.consumerStates.set(sender, consumerState)
+ val consumerStates = currentState.consumerStates.put(sender, consumerState)
val providerState2 = providerState.with(
- "consumerStatus", providerState.consumerStatus.set(sender, RUNNING)
+ "consumerStatus", providerState.consumerStatus.put(sender, RUNNING)
)
val providerState3 = providerState2.with(
- "outstandingPacketsToConsumer", providerState2.outstandingPacketsToConsumer.set(sender, List({
+ "outstandingPacketsToConsumer", providerState2.outstandingPacketsToConsumer.put(sender, List({
id: 0,
validatorSet: Map(),
sendingTime: 0
@@ -746,6 +746,33 @@ module CCV {
(false, "")
}
}
+
+ // ===================
+ // ASSUMPTIONS ON MODEL PARAMETERS
+ // ===================
+
+ run UnbondingPeriodPositiveTest =
+ UnbondingPeriodPerChain.keys().forall(chain => UnbondingPeriodPerChain.get(chain) > 0)
+
+ run VscTimeoutPositiveTest =
+ VscTimeout > 0
+
+ run CcvTimeoutPositiveTest =
+ CcvTimeout.keys().forall(chain => CcvTimeout.get(chain) > 0)
+
+ run CcvTimeoutLargerThanUnbondingPeriodTest =
+ CcvTimeout.get(PROVIDER_CHAIN) > UnbondingPeriodPerChain.Values().Max()
+
+ run ProviderIsNotAConsumerTest =
+ not(ConsumerChains.contains(PROVIDER_CHAIN))
+
+ // ccv timeout contains exactly consumers and provider, no other chains
+ run CcvTimeoutKeysTest =
+ CcvTimeout.keys() == ConsumerChains.union(Set(PROVIDER_CHAIN))
+
+ // unbonding period contains exactly consumers and provider, no other chains
+ run UnbondingPeriodKeysTest =
+ UnbondingPeriodPerChain.keys() == ConsumerChains.union(Set(PROVIDER_CHAIN))
}
@@ -838,26 +865,102 @@ module CCVDefaultStateMachine {
currentState' = result.newState,
}
+ run InitTest: bool = {
+ init.then(
+ all {
+ assert(currentState.providerState.consumerStatus == Map(
+ "consumer1" -> UNUSED,
+ "consumer2" -> UNUSED,
+ "consumer3" -> UNUSED
+ )),
+ assert(currentState.providerState.outstandingPacketsToConsumer == Map(
+ "consumer1" -> List(),
+ "consumer2" -> List(),
+ "consumer3" -> List()
+ )),
+ assert(currentState.providerState.sentVSCPackets == Map(
+ "consumer1" -> List(),
+ "consumer2" -> List(),
+ "consumer3" -> List()
+ )),
+ assert(currentState.consumerStates.keys() == consumerChains),
+ assert(currentState.providerState.chainState.votingPowerHistory == List(InitialValidatorSet)),
+ assert(currentState.providerState.chainState.currentValidatorSet == InitialValidatorSet),
+ assert(currentState.providerState.chainState.lastTimestamp == 0),
+ val firstState = currentState // snapshot the first state
+ VotingPowerChange("node1", 50).then(all {
+ // ensure that the only change is that the voting power of node1 is changed
+ assert(currentState == firstState.with(
+ "providerState", firstState.providerState.with(
+ "chainState", firstState.providerState.chainState.with(
+ "currentValidatorSet", firstState.providerState.chainState.currentValidatorSet.put("node1", 50)
+ )
+ )
+ )),
+ currentState' = currentState
+ })
+ }
+ )
+ }
+
+}
+
+// contains test logic for the stateless functions in the CCV module
+module CCVLogicTest {
+ import CCVTypes.*
+ import Time.* from "./Time"
+ import extraSpells.* from "./extraSpells"
+
+ pure val consumerChains = Set("sender", "receiver")
+ pure val chains = consumerChains.union(Set(PROVIDER_CHAIN))
+ pure val unbondingPeriods = chains.mapBy(chain => 2 * Week)
+ pure val ccvTimeouts = chains.mapBy(chain => 3 * Week)
+
+ import CCV(VscTimeout = 5 * Week, CcvTimeout = ccvTimeouts, UnbondingPeriodPerChain = unbondingPeriods, ConsumerChains = consumerChains).*
+
// negative voting powers give an error
run VotingPowerNegativeTest =
+ {
votingPowerChange(
GetEmptyProtocolState,
"validator",
-1
).hasError
+ }
run VotingPowerOkTest =
+ {
val result = votingPowerChange(
GetEmptyProtocolState,
"validator",
- 0
+ 5
)
not(result.hasError) and
result.newState.providerState.chainState.currentValidatorSet.keys().contains("validator") and
- result.newState.providerState.chainState.currentValidatorSet.get("validator") == 0
+ result.newState.providerState.chainState.currentValidatorSet.get("validator") == 5
+ }
+
+ // validators that get zero voting power are removed
+ run VotingPowerZeroTest =
+ {
+ val tmpResult = votingPowerChange(
+ GetEmptyProtocolState,
+ "validator",
+ 5
+ )
+ val finalResult = votingPowerChange(
+ tmpResult.newState,
+ "validator",
+ 0
+ )
+ not(finalResult.hasError) and
+ not(finalResult.newState.providerState.chainState.currentValidatorSet.keys().contains("validator"))
+ }
+
// make sure that VotingPowerChange ONLY changes the current validator set, not the history
run VotingPowerChangeDoesNotChangeHistoryTest =
+ {
val result = votingPowerChange(
GetEmptyProtocolState,
"validator",
@@ -865,16 +968,32 @@ module CCVDefaultStateMachine {
)
not(result.hasError) and
result.newState.providerState.chainState.votingPowerHistory == List()
+ }
run DeliverPacketToProviderHappyPathTest =
- val result = deliverPacketToProvider(_DeliverPacketToProvider_TestState, "sender")
+ {
+ // add a packet on the consumer
+ val testState = _DeliverPacketToProvider_TestState.with(
+ "consumerStates", _DeliverPacketToProvider_TestState.consumerStates.put(
+ "sender", _DeliverPacketToProvider_TestState.consumerStates.get("sender").with(
+ "outstandingPacketsToProvider", List({
+ id: 0,
+ sendingTime: 0
+ })
+ )
+ )
+ )
+
+ val result = deliverPacketToProvider(testState, "sender")
val newProviderState = result.newState.providerState
val newConsumerState = result.newState.consumerStates.get("sender")
not(result.hasError) and
newProviderState.receivedMaturations.size() == 1 and
newConsumerState.outstandingPacketsToProvider.length() == 0
+ }
run DeliverPacketToProviderTimeoutTest =
+ {
// set the timestamp to be after the timeout
val testState = _DeliverPacketToProvider_TestState.with(
"providerState", _DeliverPacketToProvider_TestState.providerState.with(
@@ -890,8 +1009,10 @@ module CCVDefaultStateMachine {
newProviderState.receivedMaturations.size() == 0 and
newConsumerState.outstandingPacketsToProvider.length() == 0 and
newProviderState.consumerStatus.get("sender") == STOPPED
+ }
run ConsumerStatusMapHappyPathTest =
+ {
val currentConsumerStatusMap = Map(
"chain1" -> UNUSED,
"chain2" -> RUNNING,
@@ -900,14 +1021,16 @@ module CCVDefaultStateMachine {
val res = getNewConsumerStatusMap(
currentConsumerStatusMap,
Set("chain1"),
- Set("chain3")
+ Set("chain2")
)
res._2 == "" and
res._1.get("chain1") == RUNNING and
- res._1.get("chain2") == RUNNING and
- res._1.get("chain3") == UNUSED
+ res._1.get("chain2") == STOPPED and
+ res._1.get("chain3") == STOPPED
+ }
run ConsumerStatusMapAlreadyRunningTest =
+ {
val currentConsumerStatusMap = Map(
"chain1" -> UNUSED,
"chain2" -> RUNNING,
@@ -919,8 +1042,10 @@ module CCVDefaultStateMachine {
Set("chain3")
)
res._2 == "Cannot start a consumer that is already running"
+ }
run ConsumerStatusMapAlreadyStoppedTest =
+ {
val currentConsumerStatusMap = Map(
"chain1" -> UNUSED,
"chain2" -> RUNNING,
@@ -932,8 +1057,10 @@ module CCVDefaultStateMachine {
Set("chain3")
)
res._2 == "Cannot stop a consumer that is not running"
+ }
run ChainBothInStartAndStopTest =
+ {
val currentConsumerStatusMap = Map(
"chain1" -> UNUSED,
"chain2" -> RUNNING,
@@ -945,69 +1072,5 @@ module CCVDefaultStateMachine {
Set("chain1")
)
res._2 == "Cannot start and stop a consumer at the same time"
-
- // ===================
- // ASSUMPTIONS ON MODEL PARAMETERS
- // ===================
-
- run UnbondingPeriodPositiveTest =
- UnbondingPeriodPerChain.keys().forall(chain => UnbondingPeriodPerChain.get(chain) > 0)
-
- run VscTimeoutPositiveTest =
- VscTimeout > 0
-
- run CcvTimeoutPositiveTest =
- CcvTimeout.keys().forall(chain => CcvTimeout.get(chain) > 0)
-
- run CcvTimeoutLargerThanUnbondingPeriodTest =
- CcvTimeout.get(PROVIDER_CHAIN) > UnbondingPeriodPerChain.Values().Max()
-
- run ProviderIsNotAConsumerTest =
- not(ConsumerChains.contains(PROVIDER_CHAIN))
-
- // ccv timeout contains exactly consumers and provider, no other chains
- run CcvTimeoutKeysTest =
- CcvTimeout.keys() == ConsumerChains.union(Set(PROVIDER_CHAIN))
-
- // unbonding period contains exactly consumers and provider, no other chains
- run UnbondingPeriodKeysTest =
- UnbondingPeriodPerChain.keys() == ConsumerChains.union(Set(PROVIDER_CHAIN))
-
- run InitTest: bool = {
- init.then(
- all {
- assert(currentState.providerState.consumerStatus == Map(
- "consumer1" -> UNUSED,
- "consumer2" -> UNUSED,
- "consumer3" -> UNUSED
- )),
- assert(currentState.providerState.outstandingPacketsToConsumer == Map(
- "consumer1" -> List(),
- "consumer2" -> List(),
- "consumer3" -> List()
- )),
- assert(currentState.providerState.sentVSCPackets == Map(
- "consumer1" -> List(),
- "consumer2" -> List(),
- "consumer3" -> List()
- )),
- assert(currentState.consumerStates.keys() == consumerChains),
- assert(currentState.providerState.chainState.votingPowerHistory == List(InitialValidatorSet)),
- assert(currentState.providerState.chainState.currentValidatorSet == InitialValidatorSet),
- assert(currentState.providerState.chainState.lastTimestamp == 0),
- val firstState = currentState // snapshot the first state
- VotingPowerChange("node1", 50).then(all {
- // ensure that the only change is that the voting power of node1 is changed
- assert(currentState == firstState.with(
- "providerState", firstState.providerState.with(
- "chainState", firstState.providerState.chainState.with(
- "currentValidatorSet", firstState.providerState.chainState.currentValidatorSet.put("node1", 50)
- )
- )
- )),
- currentState' = currentState
- })
- }
- )
- }
+ }
}
From d227aeec0d6ebd2727ef47a4a5796f929033737e Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Thu, 28 Sep 2023 14:39:52 +0200
Subject: [PATCH 27/35] Start debugging tests
---
tests/difference/core/quint_model/ccv.qnt | 125 +++++++++++++++-------
1 file changed, 85 insertions(+), 40 deletions(-)
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index 149474e5e6..0956e4e9bc 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -257,16 +257,19 @@ module CCV {
// Only argument is the consumer chain, from which the packet will be delivered.
// If this packet will time out on the provider on delivery,
// the consumer will be dropped.
- pure def deliverPacketToProvider(currentState: ProtocolState, sender: Chain): Result = {
+ // The first return is the result of the operation, the second result is a boolean
+ // that indicates whether the consumer timed out or not.
+ // If the result has an error, the second return should be ignored.
+ pure def deliverPacketToProvider(currentState: ProtocolState, sender: Chain): (Result, bool) = {
if (not(isCurrentlyConsumer(sender, currentState.providerState))) {
- Err("Sender is not currently a consumer - must have 'running' status!")
+ (Err("Sender is not currently a consumer - must have 'running' status!"), false)
} else if (length(currentState.consumerStates.get(sender).outstandingPacketsToProvider) == 0) {
- Err("No outstanding packets to deliver")
+ (Err("No outstanding packets to deliver"), false)
} else {
val packet = currentState.consumerStates.get(sender).outstandingPacketsToProvider.head()
// if the packet has timed out, drop the consumer. its state doesn't matter anymore
val timeout = CcvTimeout.get(sender)
- if(packet.sendingTime + CcvTimeout.get(sender) > currentState.providerState.chainState.lastTimestamp) {
+ if(packet.sendingTime + CcvTimeout.get(sender) < currentState.providerState.chainState.lastTimestamp) {
// drop consumer
val result = getNewConsumerStatusMap(
currentState.providerState.consumerStatus,
@@ -277,7 +280,7 @@ module CCV {
val newConsumerStatus = result._1
val err = result._2
if (err != "") {
- Err(err)
+ (Err(err), false)
} else {
val newProviderState = currentState.providerState.with(
"consumerStatus", newConsumerStatus
@@ -285,22 +288,22 @@ module CCV {
val newState = currentState.with(
"providerState", newProviderState
)
- Ok(newState)
+ (Ok(newState), true) // true because the packet timed out
}
} else {
// the packet has not timed out, so receive it on the provider
val result = recvPacketOnProvider(currentState, sender, packet)
val tmpState = result.newState
if (result.hasError) {
- Err(result.error.message)
+ (Err(result.error.message), false)
} else {
val result2 = removeOutstandingPacketFromConsumer(tmpState, sender)
val tmpState2 = result2.newState
val err2 = result2.error
if (result2.hasError) {
- Err(err2.message)
+ (Err(err2.message), false)
} else {
- Ok(tmpState2)
+ (Ok(tmpState2), false) // false because the packet did not time out
}
}
}
@@ -335,15 +338,18 @@ module CCV {
// Only argument is the consumer chain, to which the packet will be delivered.
// If this packet will time out on the consumer on delivery,
// the consumer will be dropped.
- pure def deliverPacketToConsumer(currentState: ProtocolState, receiver: Chain): Result = {
+ // The first return is the result of the operation, the second result is a boolean
+ // that indicates whether the consumer timed out or not.
+ // If the result has an error, the second return should be ignored.
+ pure def deliverPacketToConsumer(currentState: ProtocolState, receiver: Chain): (Result, bool) = {
if (not(isCurrentlyConsumer(receiver, currentState.providerState))) {
- Err("Receiver is not currently a consumer - must have 'running' status!")
+ (Err("Receiver is not currently a consumer - must have 'running' status!"), false)
} else if (length(currentState.providerState.outstandingPacketsToConsumer.get(receiver)) == 0) {
- Err("No outstanding packets to deliver")
+ (Err("No outstanding packets to deliver"), false)
} else {
val packet = currentState.providerState.outstandingPacketsToConsumer.get(receiver).head()
// check if the consumer timed out
- if (packet.sendingTime + CcvTimeout.get(PROVIDER_CHAIN) > currentState.consumerStates.get(receiver).chainState.lastTimestamp) {
+ if (packet.sendingTime + CcvTimeout.get(PROVIDER_CHAIN) < currentState.consumerStates.get(receiver).chainState.lastTimestamp) {
// drop consumer
val result = getNewConsumerStatusMap(
currentState.providerState.consumerStatus,
@@ -354,7 +360,7 @@ module CCV {
val newConsumerStatus = result._1
val err = result._2
if (err != "") {
- Err(err)
+ (Err(err), false)
} else {
val newProviderState = currentState.providerState.with(
"consumerStatus", newConsumerStatus
@@ -362,23 +368,22 @@ module CCV {
val newState = currentState.with(
"providerState", newProviderState
)
- // Ok(newState)
- Err("not implemented")
+ (Ok(newState), true) // true because the packet timed out
}
} else {
// the packet has not timed out, so receive it on the consumer
val result = recvPacketOnConsumer(currentState, receiver, packet)
val tmpState = result.newState
if (result.hasError) {
- Err(result.error.message)
+ (Err(result.error.message), false)
} else {
val result2 = removeOutstandingPacketFromProvider(tmpState, receiver)
val tmpState2 = result2.newState
val err2 = result2.error
if (result2.hasError) {
- Err(err2.message)
+ (Err(err2.message), false)
} else {
- Ok(tmpState2)
+ (Ok(tmpState2), false) // false because the packet did not time out
}
}
}
@@ -831,7 +836,8 @@ module CCVDefaultStateMachine {
// The receiver receives the next outstanding VSCPacket from the provider.
// This will time out the consumer if the packet timeout has passed on the receiver.
action DeliverVSCPacket(receiver: Chain): bool =
- val result = deliverPacketToConsumer(currentState, receiver)
+ val resultAndTimeout = deliverPacketToConsumer(currentState, receiver)
+ val result = resultAndTimeout._1
all {
result.hasError == false,
currentState' = result.newState,
@@ -840,7 +846,8 @@ module CCVDefaultStateMachine {
// The provider receives the next outstanding VSCMaturedPacket from the sender.
// This will time out the consumer if the packet timeout has passed on the provider.
action DeliverVSCMaturedPacket(sender: Chain): bool =
- val result = deliverPacketToProvider(currentState, sender)
+ val resultAndTimeout = deliverPacketToProvider(currentState, sender)
+ val result = resultAndTimeout._1
all {
result.hasError == false,
currentState' = result.newState,
@@ -970,21 +977,44 @@ module CCVLogicTest {
result.newState.providerState.chainState.votingPowerHistory == List()
}
- run DeliverPacketToProviderHappyPathTest =
- {
- // add a packet on the consumer
- val testState = _DeliverPacketToProvider_TestState.with(
- "consumerStates", _DeliverPacketToProvider_TestState.consumerStates.put(
- "sender", _DeliverPacketToProvider_TestState.consumerStates.get("sender").with(
- "outstandingPacketsToProvider", List({
- id: 0,
- sendingTime: 0
- })
- )
+ // add a packet on the consumer
+ pure val DeliverPacketToProviderHappyPathTest_testState = _DeliverPacketToProvider_TestState.with(
+ "consumerStates", _DeliverPacketToProvider_TestState.consumerStates.put(
+ "sender", _DeliverPacketToProvider_TestState.consumerStates.get("sender").with(
+ "outstandingPacketsToProvider", List({
+ id: 0,
+ sendingTime: 0
+ })
+ )
+ )
+ ).with(
+ // put an entry into sentVSCPacket on the provider that corresponds to the packet we put on the consumer
+ "providerState", _DeliverPacketToProvider_TestState.providerState.with(
+ "sentVSCPackets", _DeliverPacketToProvider_TestState.providerState.sentVSCPackets.put(
+ "sender", List({
+ id: 0,
+ validatorSet: _DeliverPacketToProvider_TestState.providerState.chainState.currentValidatorSet,
+ sendingTime: 0
+ })
)
)
+ )
+
+ pure val DeliverPacketToProviderHappyPathTest_resultAndTimeout = deliverPacketToProvider(DeliverPacketToProviderHappyPathTest_testState, "sender")
+
+ // test is split to be easier to pinpoint which assertion failed
+ run DidNotTimeOut_DeliverPacketToProviderHappyPathTest =
+ {
+ val result = DeliverPacketToProviderHappyPathTest_resultAndTimeout._1
+ val timeout = DeliverPacketToProviderHappyPathTest_resultAndTimeout._2
+ not(result.hasError) and
+ not(timeout)
+ }
- val result = deliverPacketToProvider(testState, "sender")
+ run StateModificationOK_DeliverPacketToProviderHappyPathTest =
+ {
+ val result = DeliverPacketToProviderHappyPathTest_resultAndTimeout._1
+ val timedOut = DeliverPacketToProviderHappyPathTest_resultAndTimeout._2
val newProviderState = result.newState.providerState
val newConsumerState = result.newState.consumerStates.get("sender")
not(result.hasError) and
@@ -992,17 +1022,32 @@ module CCVLogicTest {
newConsumerState.outstandingPacketsToProvider.length() == 0
}
- run DeliverPacketToProviderTimeoutTest =
- {
- // set the timestamp to be after the timeout
- val testState = _DeliverPacketToProvider_TestState.with(
- "providerState", _DeliverPacketToProvider_TestState.providerState.with(
- "chainState", _DeliverPacketToProvider_TestState.providerState.chainState.with(
+ // add a packet on the consumer
+ pure val DeliverPacketToProviderTimeoutTest_testState = DeliverPacketToProviderHappyPathTest_testState.with(
+ "providerState", DeliverPacketToProviderHappyPathTest_testState.providerState.with(
+ "chainState", DeliverPacketToProviderHappyPathTest_testState.providerState.chainState.with(
+ // set the timestamp to be after the timeout
"lastTimestamp", CcvTimeout.get("sender") + 1
)
)
)
- val result = deliverPacketToProvider(testState, "sender")
+
+ pure val DeliverPacketToProviderTimeoutTest_resultAndTimeout = deliverPacketToProvider(DeliverPacketToProviderTimeoutTest_testState, "sender")
+
+ run DidTimeOut_DeliverPacketToProviderTimeoutTest =
+ {
+ val result = DeliverPacketToProviderTimeoutTest_resultAndTimeout._1
+ val timedOut = DeliverPacketToProviderTimeoutTest_resultAndTimeout._2
+ val newProviderState = result.newState.providerState
+ val newConsumerState = result.newState.consumerStates.get("sender")
+ not(result.hasError) and
+ timedOut
+ }
+
+ run StateModificationOK_DeliverPacketToProviderTimeoutTest =
+ {
+ val result = DeliverPacketToProviderTimeoutTest_resultAndTimeout._1
+ val timedOut = DeliverPacketToProviderTimeoutTest_resultAndTimeout._2
val newProviderState = result.newState.providerState
val newConsumerState = result.newState.consumerStates.get("sender")
not(result.hasError) and
From 09b1b8780a40c3912e32ddb14c77a0a5fbb2079c Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Thu, 28 Sep 2023 18:23:47 +0200
Subject: [PATCH 28/35] Finish test for quint model
---
tests/difference/core/quint_model/ccv.qnt | 114 ++++++++++++++++++----
1 file changed, 96 insertions(+), 18 deletions(-)
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index 0956e4e9bc..65a3418e4e 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -248,7 +248,14 @@ module CCV {
} else {
pure val currentValidatorSet = currentState.providerState.chainState.currentValidatorSet
pure val newValidatorSet = getUpdatedValidatorSet(currentValidatorSet, validator, amount)
- pure val newState = setProviderValidatorSet(currentState, newValidatorSet)
+ // set the validator set changed flag
+ val newProviderState = currentState.providerState.with(
+ "providerValidatorSetChangedInThisBlock", true
+ )
+ pure val tmpState = currentState.with(
+ "providerState", newProviderState
+ )
+ pure val newState = setProviderValidatorSet(tmpState, newValidatorSet)
Ok(newState)
}
}
@@ -432,7 +439,9 @@ module CCV {
"consumerStatus", newConsumerStatus
)
val providerStateAfterSending =
- if (currentProviderState.providerValidatorSetChangedInThisBlock and getRunningConsumers(currentState.providerState).size() > 0) {
+ if (currentProviderState.providerValidatorSetChangedInThisBlock and
+ // important: check this on the provider state after the consumer advancement, not on the current state.
+ getRunningConsumers(providerStateAfterConsumerAdvancement).size() > 0) {
providerStateAfterConsumerAdvancement.sendVscPackets()
} else {
providerStateAfterConsumerAdvancement
@@ -449,7 +458,7 @@ module CCV {
chain: Chain,
// by how much the timestamp of the chain should be advanced for the next block
timeAdvancement: Time): Result = {
- if (currentState.consumerStates.keys().contains(chain)) {
+ if (not(currentState.consumerStates.keys().contains(chain))) {
Err("chain is not a consumer")
} else {
// if the chain is not a consumer, return an error
@@ -872,7 +881,15 @@ module CCVDefaultStateMachine {
currentState' = result.newState,
}
- run InitTest: bool = {
+ // Test a simple happy path where:
+ // * the consumer chain is set to running
+ // * a validator set change happens
+ // * a block is ended on the provider, i.e. a packet is sent to the consumer
+ // * the consumer receives the packet
+ // * the chains wait until the unbonding period is over
+ // * the consumer sends a VSCMaturedPacket to the provider
+ // * the provider receives the VSCMaturedPacket
+ run HappyPathTest: bool = {
init.then(
all {
assert(currentState.providerState.consumerStatus == Map(
@@ -894,21 +911,64 @@ module CCVDefaultStateMachine {
assert(currentState.providerState.chainState.votingPowerHistory == List(InitialValidatorSet)),
assert(currentState.providerState.chainState.currentValidatorSet == InitialValidatorSet),
assert(currentState.providerState.chainState.lastTimestamp == 0),
- val firstState = currentState // snapshot the first state
- VotingPowerChange("node1", 50).then(all {
- // ensure that the only change is that the voting power of node1 is changed
- assert(currentState == firstState.with(
- "providerState", firstState.providerState.with(
- "chainState", firstState.providerState.chainState.with(
- "currentValidatorSet", firstState.providerState.chainState.currentValidatorSet.put("node1", 50)
- )
- )
- )),
- currentState' = currentState
- })
- }
+ VotingPowerChange("node1", 50)
+ })
+ .then(
+ all {
+ // the validator set has changed
+ assert(currentState.providerState.chainState.currentValidatorSet == InitialValidatorSet.put("node1", 50)),
+ // start consumer1
+ EndAndBeginBlockForProvider(1 * Second, Set("consumer1"), Set())
+ })
+ .then(
+ all {
+ // consumer1 was started
+ assert(currentState.providerState.consumerStatus.get("consumer1") == RUNNING),
+ // a packet was sent to consumer1
+ assert(currentState.providerState.outstandingPacketsToConsumer.get("consumer1").length() == 1),
+ // the validator set on the provider was entered into the history
+ assert(currentState.providerState.chainState.votingPowerHistory == List(InitialValidatorSet.put("node1", 50), InitialValidatorSet)),
+ // deliver the packet
+ DeliverVSCPacket("consumer1")
+ }
+ )
+ .then(
+ all {
+ // make sure the packet was removed from the provider
+ assert(currentState.providerState.outstandingPacketsToConsumer.get("consumer1").length() == 0),
+ // ensure the maturation time was entered on the consumer
+ assert(currentState.consumerStates.get("consumer1").maturationTimes.keys().size() == 1),
+ // the validator set was put as the current validator set
+ assert(currentState.consumerStates.get("consumer1").chainState.currentValidatorSet == InitialValidatorSet.put("node1", 50)),
+ // advance time on provider until the unbonding period is over
+ EndAndBeginBlockForProvider(UnbondingPeriodPerChain.get("consumer1"), Set(), Set()),
+ }
+ )
+ .then(
+ // advance time on consumer until the unbonding period is over
+ EndAndBeginBlockForConsumer("consumer1", UnbondingPeriodPerChain.get("consumer1"))
+ )
+ .then(
+ all {
+ // the packet has matured, so it was sent by the consumer
+ assert(currentState.consumerStates.get("consumer1").outstandingPacketsToProvider.length() == 1),
+ // it was removed from the maturationTimes
+ assert(currentState.consumerStates.get("consumer1").maturationTimes.keys().size() == 0),
+ // receive the packet on the provider
+ DeliverVSCMaturedPacket("consumer1")
+ }
+ )
+ .then(
+ all {
+ // the packet was received on the provider
+ assert(currentState.providerState.receivedMaturations.size() == 1),
+ // the packet was removed from the consumer
+ assert(currentState.consumerStates.get("consumer1").outstandingPacketsToProvider.length() == 0),
+ currentState' = currentState // just so this still has an effect
+ }
)
}
+
}
@@ -964,6 +1024,25 @@ module CCVLogicTest {
not(finalResult.newState.providerState.chainState.currentValidatorSet.keys().contains("validator"))
}
+ run VotingPowerSetsFlagTest =
+ {
+ // change voting power
+ val tmpResult = votingPowerChange(
+ GetEmptyProtocolState,
+ "validator",
+ 5
+ )
+ // but then set it back to the initial value
+ val finalResult = votingPowerChange(
+ tmpResult.newState,
+ "validator",
+ 0
+ )
+ // still should set the flag
+ not(finalResult.hasError) and
+ finalResult.newState.providerState.providerValidatorSetChangedInThisBlock
+ }
+
// make sure that VotingPowerChange ONLY changes the current validator set, not the history
run VotingPowerChangeDoesNotChangeHistoryTest =
@@ -1052,7 +1131,6 @@ module CCVLogicTest {
val newConsumerState = result.newState.consumerStates.get("sender")
not(result.hasError) and
newProviderState.receivedMaturations.size() == 0 and
- newConsumerState.outstandingPacketsToProvider.length() == 0 and
newProviderState.consumerStatus.get("sender") == STOPPED
}
From 96c101f710ea92d0f97c90f22ff6cdab5ef01cd2 Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Fri, 29 Sep 2023 10:37:48 +0200
Subject: [PATCH 29/35] Add README and improve folder structure
---
tests/difference/core/quint_model/README.md | 20 +
tests/difference/core/quint_model/ccv.qnt | 415 +-----------------
.../core/quint_model/ccv_statemachine.qnt | 220 ++++++++++
.../difference/core/quint_model/ccv_test.qnt | 225 ++++++++++
.../{ => libraries}/extraSpells.qnt | 0
.../core/quint_model/{ => libraries}/time.qnt | 0
6 files changed, 468 insertions(+), 412 deletions(-)
create mode 100644 tests/difference/core/quint_model/README.md
create mode 100644 tests/difference/core/quint_model/ccv_statemachine.qnt
create mode 100644 tests/difference/core/quint_model/ccv_test.qnt
rename tests/difference/core/quint_model/{ => libraries}/extraSpells.qnt (100%)
rename tests/difference/core/quint_model/{ => libraries}/time.qnt (100%)
diff --git a/tests/difference/core/quint_model/README.md b/tests/difference/core/quint_model/README.md
new file mode 100644
index 0000000000..30de96350f
--- /dev/null
+++ b/tests/difference/core/quint_model/README.md
@@ -0,0 +1,20 @@
+This folder contains a Quint model for the core logic of Cross-Chain Validation (CCV).
+
+### File structure
+The files are as follows:
+- ccv.qnt: Contains the type definitions, data structures, functional logic for CCV.
+The core of the protocol.
+- ccv_statemachine.qnt: Contains the state machine layer for CCV. Very roughly speaking, this could be seen as "e2e tests".
+Also contains invariants.
+- ccv_test.qnt: Contains unit tests for the functional layer of CCV.
+- libararies/*: Libraries that don't belong to CCV, but are used by it.
+
+### Model details
+
+To see the data structures used in the model, see the ProtocolState type in ccv.qnt.
+
+The "public" endpoints of the model are those functions that take as an input the protocol state, and return a Result.
+Other functions are for utility.
+
+The parameters of the protocol are defined as consts in ccv.qnt.
+
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index 65a3418e4e..fbb9b08fed 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -1,5 +1,5 @@
module CCVTypes {
- import Time.* from "./Time"
+ import Time.* from "./libraries/Time"
type Node = str
type Chain = str
@@ -213,8 +213,8 @@ module CCV {
// i.e. when the packet is delivered, the ack is delivered right afterwards.
// This is because it is nontrivial in practice to get a relayer to relay a packet, but not its ack.
- import extraSpells.* from "./extraSpells"
- import Time.* from "./Time"
+ import Time.* from "./libraries/Time"
+ import extraSpells.* from "./libraries/extraSpells"
import CCVTypes.*
@@ -788,412 +788,3 @@ module CCV {
run UnbondingPeriodKeysTest =
UnbondingPeriodPerChain.keys() == ConsumerChains.union(Set(PROVIDER_CHAIN))
}
-
-
-module CCVDefaultStateMachine {
- // A basic state machine that utilizes the CCV protocol.
- import Time.* from "./Time"
- import CCVTypes.*
- import extraSpells.* from "./extraSpells"
-
- pure val consumerChains = Set("consumer1", "consumer2", "consumer3")
- pure val chains = consumerChains.union(Set(PROVIDER_CHAIN))
- pure val unbondingPeriods = chains.mapBy(chain => 2 * Week)
- pure val ccvTimeouts = chains.mapBy(chain => 3 * Week)
-
- pure val nodes = Set("node1", "node2", "node3", "node4", "node5", "node6", "node7", "node8", "node9", "node10")
- pure val InitialValidatorSet = nodes.mapBy(node => 100)
-
- import CCV(VscTimeout = 5 * Week, CcvTimeout = ccvTimeouts, UnbondingPeriodPerChain = unbondingPeriods, ConsumerChains = consumerChains).*
-
-
- var currentState: ProtocolState
-
- action init: bool = all {
- val providerState = GetEmptyProviderState
- val consumerStates = ConsumerChains.mapBy(chain => GetEmptyConsumerState)
- val providerStateWithConsumers = providerState.with(
- "consumerStatus",
- ConsumerChains.mapBy(chain => UNUSED)
- ).with(
- "outstandingPacketsToConsumer",
- ConsumerChains.mapBy(chain => List())
- ).with(
- "sentVSCPackets",
- ConsumerChains.mapBy(chain => List())
- ).with(
- // set the validator set to be the initial validator set in the history
- "chainState", providerState.chainState.with(
- "votingPowerHistory", List(InitialValidatorSet)
- ).with(
- "currentValidatorSet", InitialValidatorSet
- )
- )
- currentState' = {
- providerState: providerStateWithConsumers,
- consumerStates: consumerStates
- }
- }
-
- action VotingPowerChange(validator: Node, newVotingPower: int): bool =
- val result = votingPowerChange(currentState, validator, newVotingPower)
- all {
- result.hasError == false,
- currentState' = result.newState,
- }
-
- // The receiver receives the next outstanding VSCPacket from the provider.
- // This will time out the consumer if the packet timeout has passed on the receiver.
- action DeliverVSCPacket(receiver: Chain): bool =
- val resultAndTimeout = deliverPacketToConsumer(currentState, receiver)
- val result = resultAndTimeout._1
- all {
- result.hasError == false,
- currentState' = result.newState,
- }
-
- // The provider receives the next outstanding VSCMaturedPacket from the sender.
- // This will time out the consumer if the packet timeout has passed on the provider.
- action DeliverVSCMaturedPacket(sender: Chain): bool =
- val resultAndTimeout = deliverPacketToProvider(currentState, sender)
- val result = resultAndTimeout._1
- all {
- result.hasError == false,
- currentState' = result.newState,
- }
-
- action EndAndBeginBlockForProvider(
- timeAdvancement: Time,
- consumersToStart: Set[Chain],
- consumersToStop: Set[Chain]): bool =
- val result = endAndBeginBlockForProvider(currentState, timeAdvancement, consumersToStart, consumersToStop)
- all {
- result.hasError == false,
- currentState' = result.newState,
- }
-
- action EndAndBeginBlockForConsumer(
- chain: Chain,
- timeAdvancement: Time): bool =
- val result = endAndBeginBlockForConsumer(currentState, chain, timeAdvancement)
- all {
- result.hasError == false,
- currentState' = result.newState,
- }
-
- // Test a simple happy path where:
- // * the consumer chain is set to running
- // * a validator set change happens
- // * a block is ended on the provider, i.e. a packet is sent to the consumer
- // * the consumer receives the packet
- // * the chains wait until the unbonding period is over
- // * the consumer sends a VSCMaturedPacket to the provider
- // * the provider receives the VSCMaturedPacket
- run HappyPathTest: bool = {
- init.then(
- all {
- assert(currentState.providerState.consumerStatus == Map(
- "consumer1" -> UNUSED,
- "consumer2" -> UNUSED,
- "consumer3" -> UNUSED
- )),
- assert(currentState.providerState.outstandingPacketsToConsumer == Map(
- "consumer1" -> List(),
- "consumer2" -> List(),
- "consumer3" -> List()
- )),
- assert(currentState.providerState.sentVSCPackets == Map(
- "consumer1" -> List(),
- "consumer2" -> List(),
- "consumer3" -> List()
- )),
- assert(currentState.consumerStates.keys() == consumerChains),
- assert(currentState.providerState.chainState.votingPowerHistory == List(InitialValidatorSet)),
- assert(currentState.providerState.chainState.currentValidatorSet == InitialValidatorSet),
- assert(currentState.providerState.chainState.lastTimestamp == 0),
- VotingPowerChange("node1", 50)
- })
- .then(
- all {
- // the validator set has changed
- assert(currentState.providerState.chainState.currentValidatorSet == InitialValidatorSet.put("node1", 50)),
- // start consumer1
- EndAndBeginBlockForProvider(1 * Second, Set("consumer1"), Set())
- })
- .then(
- all {
- // consumer1 was started
- assert(currentState.providerState.consumerStatus.get("consumer1") == RUNNING),
- // a packet was sent to consumer1
- assert(currentState.providerState.outstandingPacketsToConsumer.get("consumer1").length() == 1),
- // the validator set on the provider was entered into the history
- assert(currentState.providerState.chainState.votingPowerHistory == List(InitialValidatorSet.put("node1", 50), InitialValidatorSet)),
- // deliver the packet
- DeliverVSCPacket("consumer1")
- }
- )
- .then(
- all {
- // make sure the packet was removed from the provider
- assert(currentState.providerState.outstandingPacketsToConsumer.get("consumer1").length() == 0),
- // ensure the maturation time was entered on the consumer
- assert(currentState.consumerStates.get("consumer1").maturationTimes.keys().size() == 1),
- // the validator set was put as the current validator set
- assert(currentState.consumerStates.get("consumer1").chainState.currentValidatorSet == InitialValidatorSet.put("node1", 50)),
- // advance time on provider until the unbonding period is over
- EndAndBeginBlockForProvider(UnbondingPeriodPerChain.get("consumer1"), Set(), Set()),
- }
- )
- .then(
- // advance time on consumer until the unbonding period is over
- EndAndBeginBlockForConsumer("consumer1", UnbondingPeriodPerChain.get("consumer1"))
- )
- .then(
- all {
- // the packet has matured, so it was sent by the consumer
- assert(currentState.consumerStates.get("consumer1").outstandingPacketsToProvider.length() == 1),
- // it was removed from the maturationTimes
- assert(currentState.consumerStates.get("consumer1").maturationTimes.keys().size() == 0),
- // receive the packet on the provider
- DeliverVSCMaturedPacket("consumer1")
- }
- )
- .then(
- all {
- // the packet was received on the provider
- assert(currentState.providerState.receivedMaturations.size() == 1),
- // the packet was removed from the consumer
- assert(currentState.consumerStates.get("consumer1").outstandingPacketsToProvider.length() == 0),
- currentState' = currentState // just so this still has an effect
- }
- )
- }
-
-
-}
-
-// contains test logic for the stateless functions in the CCV module
-module CCVLogicTest {
- import CCVTypes.*
- import Time.* from "./Time"
- import extraSpells.* from "./extraSpells"
-
- pure val consumerChains = Set("sender", "receiver")
- pure val chains = consumerChains.union(Set(PROVIDER_CHAIN))
- pure val unbondingPeriods = chains.mapBy(chain => 2 * Week)
- pure val ccvTimeouts = chains.mapBy(chain => 3 * Week)
-
- import CCV(VscTimeout = 5 * Week, CcvTimeout = ccvTimeouts, UnbondingPeriodPerChain = unbondingPeriods, ConsumerChains = consumerChains).*
-
- // negative voting powers give an error
- run VotingPowerNegativeTest =
- {
- votingPowerChange(
- GetEmptyProtocolState,
- "validator",
- -1
- ).hasError
- }
-
- run VotingPowerOkTest =
- {
- val result = votingPowerChange(
- GetEmptyProtocolState,
- "validator",
- 5
- )
- not(result.hasError) and
- result.newState.providerState.chainState.currentValidatorSet.keys().contains("validator") and
- result.newState.providerState.chainState.currentValidatorSet.get("validator") == 5
- }
-
- // validators that get zero voting power are removed
- run VotingPowerZeroTest =
- {
- val tmpResult = votingPowerChange(
- GetEmptyProtocolState,
- "validator",
- 5
- )
- val finalResult = votingPowerChange(
- tmpResult.newState,
- "validator",
- 0
- )
- not(finalResult.hasError) and
- not(finalResult.newState.providerState.chainState.currentValidatorSet.keys().contains("validator"))
- }
-
- run VotingPowerSetsFlagTest =
- {
- // change voting power
- val tmpResult = votingPowerChange(
- GetEmptyProtocolState,
- "validator",
- 5
- )
- // but then set it back to the initial value
- val finalResult = votingPowerChange(
- tmpResult.newState,
- "validator",
- 0
- )
- // still should set the flag
- not(finalResult.hasError) and
- finalResult.newState.providerState.providerValidatorSetChangedInThisBlock
- }
-
-
- // make sure that VotingPowerChange ONLY changes the current validator set, not the history
- run VotingPowerChangeDoesNotChangeHistoryTest =
- {
- val result = votingPowerChange(
- GetEmptyProtocolState,
- "validator",
- 0
- )
- not(result.hasError) and
- result.newState.providerState.chainState.votingPowerHistory == List()
- }
-
- // add a packet on the consumer
- pure val DeliverPacketToProviderHappyPathTest_testState = _DeliverPacketToProvider_TestState.with(
- "consumerStates", _DeliverPacketToProvider_TestState.consumerStates.put(
- "sender", _DeliverPacketToProvider_TestState.consumerStates.get("sender").with(
- "outstandingPacketsToProvider", List({
- id: 0,
- sendingTime: 0
- })
- )
- )
- ).with(
- // put an entry into sentVSCPacket on the provider that corresponds to the packet we put on the consumer
- "providerState", _DeliverPacketToProvider_TestState.providerState.with(
- "sentVSCPackets", _DeliverPacketToProvider_TestState.providerState.sentVSCPackets.put(
- "sender", List({
- id: 0,
- validatorSet: _DeliverPacketToProvider_TestState.providerState.chainState.currentValidatorSet,
- sendingTime: 0
- })
- )
- )
- )
-
- pure val DeliverPacketToProviderHappyPathTest_resultAndTimeout = deliverPacketToProvider(DeliverPacketToProviderHappyPathTest_testState, "sender")
-
- // test is split to be easier to pinpoint which assertion failed
- run DidNotTimeOut_DeliverPacketToProviderHappyPathTest =
- {
- val result = DeliverPacketToProviderHappyPathTest_resultAndTimeout._1
- val timeout = DeliverPacketToProviderHappyPathTest_resultAndTimeout._2
- not(result.hasError) and
- not(timeout)
- }
-
- run StateModificationOK_DeliverPacketToProviderHappyPathTest =
- {
- val result = DeliverPacketToProviderHappyPathTest_resultAndTimeout._1
- val timedOut = DeliverPacketToProviderHappyPathTest_resultAndTimeout._2
- val newProviderState = result.newState.providerState
- val newConsumerState = result.newState.consumerStates.get("sender")
- not(result.hasError) and
- newProviderState.receivedMaturations.size() == 1 and
- newConsumerState.outstandingPacketsToProvider.length() == 0
- }
-
- // add a packet on the consumer
- pure val DeliverPacketToProviderTimeoutTest_testState = DeliverPacketToProviderHappyPathTest_testState.with(
- "providerState", DeliverPacketToProviderHappyPathTest_testState.providerState.with(
- "chainState", DeliverPacketToProviderHappyPathTest_testState.providerState.chainState.with(
- // set the timestamp to be after the timeout
- "lastTimestamp", CcvTimeout.get("sender") + 1
- )
- )
- )
-
- pure val DeliverPacketToProviderTimeoutTest_resultAndTimeout = deliverPacketToProvider(DeliverPacketToProviderTimeoutTest_testState, "sender")
-
- run DidTimeOut_DeliverPacketToProviderTimeoutTest =
- {
- val result = DeliverPacketToProviderTimeoutTest_resultAndTimeout._1
- val timedOut = DeliverPacketToProviderTimeoutTest_resultAndTimeout._2
- val newProviderState = result.newState.providerState
- val newConsumerState = result.newState.consumerStates.get("sender")
- not(result.hasError) and
- timedOut
- }
-
- run StateModificationOK_DeliverPacketToProviderTimeoutTest =
- {
- val result = DeliverPacketToProviderTimeoutTest_resultAndTimeout._1
- val timedOut = DeliverPacketToProviderTimeoutTest_resultAndTimeout._2
- val newProviderState = result.newState.providerState
- val newConsumerState = result.newState.consumerStates.get("sender")
- not(result.hasError) and
- newProviderState.receivedMaturations.size() == 0 and
- newProviderState.consumerStatus.get("sender") == STOPPED
- }
-
- run ConsumerStatusMapHappyPathTest =
- {
- val currentConsumerStatusMap = Map(
- "chain1" -> UNUSED,
- "chain2" -> RUNNING,
- "chain3" -> STOPPED
- )
- val res = getNewConsumerStatusMap(
- currentConsumerStatusMap,
- Set("chain1"),
- Set("chain2")
- )
- res._2 == "" and
- res._1.get("chain1") == RUNNING and
- res._1.get("chain2") == STOPPED and
- res._1.get("chain3") == STOPPED
- }
-
- run ConsumerStatusMapAlreadyRunningTest =
- {
- val currentConsumerStatusMap = Map(
- "chain1" -> UNUSED,
- "chain2" -> RUNNING,
- "chain3" -> STOPPED
- )
- val res = getNewConsumerStatusMap(
- currentConsumerStatusMap,
- Set("chain2"),
- Set("chain3")
- )
- res._2 == "Cannot start a consumer that is already running"
- }
-
- run ConsumerStatusMapAlreadyStoppedTest =
- {
- val currentConsumerStatusMap = Map(
- "chain1" -> UNUSED,
- "chain2" -> RUNNING,
- "chain3" -> STOPPED
- )
- val res = getNewConsumerStatusMap(
- currentConsumerStatusMap,
- Set("chain1"),
- Set("chain3")
- )
- res._2 == "Cannot stop a consumer that is not running"
- }
-
- run ChainBothInStartAndStopTest =
- {
- val currentConsumerStatusMap = Map(
- "chain1" -> UNUSED,
- "chain2" -> RUNNING,
- "chain3" -> STOPPED
- )
- val res = getNewConsumerStatusMap(
- currentConsumerStatusMap,
- Set("chain1"),
- Set("chain1")
- )
- res._2 == "Cannot start and stop a consumer at the same time"
- }
-}
diff --git a/tests/difference/core/quint_model/ccv_statemachine.qnt b/tests/difference/core/quint_model/ccv_statemachine.qnt
new file mode 100644
index 0000000000..142db75431
--- /dev/null
+++ b/tests/difference/core/quint_model/ccv_statemachine.qnt
@@ -0,0 +1,220 @@
+module CCVDefaultStateMachine {
+ // A basic state machine that utilizes the CCV protocol.
+ import Time.* from "./libraries/time"
+ import extraSpells.* from "./libraries/extraSpells"
+ import CCVTypes.* from "./ccv"
+
+ pure val consumerChains = Set("consumer1", "consumer2", "consumer3")
+ pure val chains = consumerChains.union(Set(PROVIDER_CHAIN))
+ pure val unbondingPeriods = chains.mapBy(chain => 2 * Week)
+ pure val ccvTimeouts = chains.mapBy(chain => 3 * Week)
+
+ pure val nodes = Set("node1", "node2", "node3", "node4", "node5", "node6", "node7", "node8", "node9", "node10")
+ pure val InitialValidatorSet = nodes.mapBy(node => 100)
+
+ import CCV(VscTimeout = 5 * Week, CcvTimeout = ccvTimeouts, UnbondingPeriodPerChain = unbondingPeriods, ConsumerChains = consumerChains).* from "./ccv"
+
+
+ var currentState: ProtocolState
+
+ action init: bool = all {
+ val providerState = GetEmptyProviderState
+ val consumerStates = ConsumerChains.mapBy(chain => GetEmptyConsumerState)
+ val providerStateWithConsumers = providerState.with(
+ "consumerStatus",
+ ConsumerChains.mapBy(chain => UNUSED)
+ ).with(
+ "outstandingPacketsToConsumer",
+ ConsumerChains.mapBy(chain => List())
+ ).with(
+ "sentVSCPackets",
+ ConsumerChains.mapBy(chain => List())
+ ).with(
+ // set the validator set to be the initial validator set in the history
+ "chainState", providerState.chainState.with(
+ "votingPowerHistory", List(InitialValidatorSet)
+ ).with(
+ "currentValidatorSet", InitialValidatorSet
+ )
+ )
+ currentState' = {
+ providerState: providerStateWithConsumers,
+ consumerStates: consumerStates
+ }
+ }
+
+ action VotingPowerChange(validator: Node, newVotingPower: int): bool =
+ val result = votingPowerChange(currentState, validator, newVotingPower)
+ all {
+ result.hasError == false,
+ currentState' = result.newState,
+ }
+
+ // The receiver receives the next outstanding VSCPacket from the provider.
+ // This will time out the consumer if the packet timeout has passed on the receiver.
+ action DeliverVSCPacket(receiver: Chain): bool =
+ val resultAndTimeout = deliverPacketToConsumer(currentState, receiver)
+ val result = resultAndTimeout._1
+ all {
+ result.hasError == false,
+ currentState' = result.newState,
+ }
+
+ // The provider receives the next outstanding VSCMaturedPacket from the sender.
+ // This will time out the consumer if the packet timeout has passed on the provider.
+ action DeliverVSCMaturedPacket(sender: Chain): bool =
+ val resultAndTimeout = deliverPacketToProvider(currentState, sender)
+ val result = resultAndTimeout._1
+ all {
+ result.hasError == false,
+ currentState' = result.newState,
+ }
+
+ action EndAndBeginBlockForProvider(
+ timeAdvancement: Time,
+ consumersToStart: Set[Chain],
+ consumersToStop: Set[Chain]): bool =
+ val result = endAndBeginBlockForProvider(currentState, timeAdvancement, consumersToStart, consumersToStop)
+ all {
+ result.hasError == false,
+ currentState' = result.newState,
+ }
+
+ action EndAndBeginBlockForConsumer(
+ chain: Chain,
+ timeAdvancement: Time): bool =
+ val result = endAndBeginBlockForConsumer(currentState, chain, timeAdvancement)
+ all {
+ result.hasError == false,
+ currentState' = result.newState,
+ }
+
+ action step = any {
+ nondet node = oneOf(nodes)
+ // very restricted set of voting powers. exact values are not important,
+ // and this keeps the state space smaller.
+ // 0 for getting a validator out of the validator set, and two non-zero values
+ nondet newVotingPower = oneOf(0, 50, 100)
+ VotingPowerChange(node, newVotingPower),
+
+ nondet chain = oneOf(consumerChains)
+ // a few different values for time advancements.
+ // to keep the number of possible steps small, we only have a few different values.
+ // Roughly, 1s for very small advances (like between blocks),
+ // and then longer values for increasingly severe downtime scenarios.
+ // Note that these can still be combined, so in effect we can get all time advancements by any amount of seconds.
+ nondet timeAdvancement = oneOf(1 * Second, 1 * Day, 1 * Week, 1 * Month)
+ EndAndBeginBlockForConsumer(chain, timeAdvancement),
+
+ val runningConsumers = currentState.providerState.consumerStatus.filter((chain, status) => status == RUNNING).keys()
+ val unusedConsumers = currentState.providerState.consumerStatus.filter((chain, status) => status == UNUSED).keys()
+ nondet consumersToStart = oneOf(runningConsumers.powerset())
+ nondet consumersToStop = oneOf(unusedConsumers.powerset())
+ nondet timeAdvancement = oneOf(1 * Second, 1 * Day, 1 * Week, 1 * Month)
+ EndAndBeginBlockForProvider(timeAdvancement, consumersToStart, consumersToStop),
+
+ //
+ // try to send a packet. we could filter by chains that can actually send,
+ // but it's probably not much faster than just trying and failing.
+ nondet sender = oneOf(consumerChains)
+ DeliverVSCMaturedPacket(sender),
+
+ // again, we could filter by chains that can actually receive,
+ // but it's probably not much faster than just trying and failing.
+ nondet recciver = oneOf(consumerChains)
+ DeliverVSCPacket(recciver),
+ }
+
+ // ==================
+ // MANUAL TEST CASES
+ // ==================
+ // Manually written test cases to get confidence in the base operation of the protocol.
+
+ // Test a simple happy path where:
+ // * the consumer chain is set to running
+ // * a validator set change happens
+ // * a block is ended on the provider, i.e. a packet is sent to the consumer
+ // * the consumer receives the packet
+ // * the chains wait until the unbonding period is over
+ // * the consumer sends a VSCMaturedPacket to the provider
+ // * the provider receives the VSCMaturedPacket
+ run HappyPathTest: bool = {
+ init.then(
+ all {
+ assert(currentState.providerState.consumerStatus == Map(
+ "consumer1" -> UNUSED,
+ "consumer2" -> UNUSED,
+ "consumer3" -> UNUSED
+ )),
+ assert(currentState.providerState.outstandingPacketsToConsumer == Map(
+ "consumer1" -> List(),
+ "consumer2" -> List(),
+ "consumer3" -> List()
+ )),
+ assert(currentState.providerState.sentVSCPackets == Map(
+ "consumer1" -> List(),
+ "consumer2" -> List(),
+ "consumer3" -> List()
+ )),
+ assert(currentState.consumerStates.keys() == consumerChains),
+ assert(currentState.providerState.chainState.votingPowerHistory == List(InitialValidatorSet)),
+ assert(currentState.providerState.chainState.currentValidatorSet == InitialValidatorSet),
+ assert(currentState.providerState.chainState.lastTimestamp == 0),
+ VotingPowerChange("node1", 50)
+ })
+ .then(
+ all {
+ // the validator set has changed
+ assert(currentState.providerState.chainState.currentValidatorSet == InitialValidatorSet.put("node1", 50)),
+ // start consumer1
+ EndAndBeginBlockForProvider(1 * Second, Set("consumer1"), Set())
+ })
+ .then(
+ all {
+ // consumer1 was started
+ assert(currentState.providerState.consumerStatus.get("consumer1") == RUNNING),
+ // a packet was sent to consumer1
+ assert(currentState.providerState.outstandingPacketsToConsumer.get("consumer1").length() == 1),
+ // the validator set on the provider was entered into the history
+ assert(currentState.providerState.chainState.votingPowerHistory == List(InitialValidatorSet.put("node1", 50), InitialValidatorSet)),
+ // deliver the packet
+ DeliverVSCPacket("consumer1")
+ }
+ )
+ .then(
+ all {
+ // make sure the packet was removed from the provider
+ assert(currentState.providerState.outstandingPacketsToConsumer.get("consumer1").length() == 0),
+ // ensure the maturation time was entered on the consumer
+ assert(currentState.consumerStates.get("consumer1").maturationTimes.keys().size() == 1),
+ // the validator set was put as the current validator set
+ assert(currentState.consumerStates.get("consumer1").chainState.currentValidatorSet == InitialValidatorSet.put("node1", 50)),
+ // advance time on provider until the unbonding period is over
+ EndAndBeginBlockForProvider(UnbondingPeriodPerChain.get("consumer1"), Set(), Set()),
+ }
+ )
+ .then(
+ // advance time on consumer until the unbonding period is over
+ EndAndBeginBlockForConsumer("consumer1", UnbondingPeriodPerChain.get("consumer1"))
+ )
+ .then(
+ all {
+ // the packet has matured, so it was sent by the consumer
+ assert(currentState.consumerStates.get("consumer1").outstandingPacketsToProvider.length() == 1),
+ // it was removed from the maturationTimes
+ assert(currentState.consumerStates.get("consumer1").maturationTimes.keys().size() == 0),
+ // receive the packet on the provider
+ DeliverVSCMaturedPacket("consumer1")
+ }
+ )
+ .then(
+ all {
+ // the packet was received on the provider
+ assert(currentState.providerState.receivedMaturations.size() == 1),
+ // the packet was removed from the consumer
+ assert(currentState.consumerStates.get("consumer1").outstandingPacketsToProvider.length() == 0),
+ currentState' = currentState // just so this still has an effect
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/tests/difference/core/quint_model/ccv_test.qnt b/tests/difference/core/quint_model/ccv_test.qnt
new file mode 100644
index 0000000000..31c46c0e62
--- /dev/null
+++ b/tests/difference/core/quint_model/ccv_test.qnt
@@ -0,0 +1,225 @@
+// contains test logic for the stateless functions in the CCV module
+module CCVTest {
+ import CCVTypes.* from "./ccv"
+ import Time.* from "./libraries/Time"
+ import extraSpells.* from "./libraries/extraSpells"
+
+ pure val consumerChains = Set("sender", "receiver")
+ pure val chains = consumerChains.union(Set(PROVIDER_CHAIN))
+ pure val unbondingPeriods = chains.mapBy(chain => 2 * Week)
+ pure val ccvTimeouts = chains.mapBy(chain => 3 * Week)
+
+ import CCV(VscTimeout = 5 * Week, CcvTimeout = ccvTimeouts, UnbondingPeriodPerChain = unbondingPeriods, ConsumerChains = consumerChains).* from "./ccv"
+
+ // negative voting powers give an error
+ run VotingPowerNegativeTest =
+ {
+ votingPowerChange(
+ GetEmptyProtocolState,
+ "validator",
+ -1
+ ).hasError
+ }
+
+ run VotingPowerOkTest =
+ {
+ val result = votingPowerChange(
+ GetEmptyProtocolState,
+ "validator",
+ 5
+ )
+ not(result.hasError) and
+ result.newState.providerState.chainState.currentValidatorSet.keys().contains("validator") and
+ result.newState.providerState.chainState.currentValidatorSet.get("validator") == 5
+ }
+
+ // validators that get zero voting power are removed
+ run VotingPowerZeroTest =
+ {
+ val tmpResult = votingPowerChange(
+ GetEmptyProtocolState,
+ "validator",
+ 5
+ )
+ val finalResult = votingPowerChange(
+ tmpResult.newState,
+ "validator",
+ 0
+ )
+ not(finalResult.hasError) and
+ not(finalResult.newState.providerState.chainState.currentValidatorSet.keys().contains("validator"))
+ }
+
+ run VotingPowerSetsFlagTest =
+ {
+ // change voting power
+ val tmpResult = votingPowerChange(
+ GetEmptyProtocolState,
+ "validator",
+ 5
+ )
+ // but then set it back to the initial value
+ val finalResult = votingPowerChange(
+ tmpResult.newState,
+ "validator",
+ 0
+ )
+ // still should set the flag
+ not(finalResult.hasError) and
+ finalResult.newState.providerState.providerValidatorSetChangedInThisBlock
+ }
+
+
+ // make sure that VotingPowerChange ONLY changes the current validator set, not the history
+ run VotingPowerChangeDoesNotChangeHistoryTest =
+ {
+ val result = votingPowerChange(
+ GetEmptyProtocolState,
+ "validator",
+ 0
+ )
+ not(result.hasError) and
+ result.newState.providerState.chainState.votingPowerHistory == List()
+ }
+
+ // add a packet on the consumer
+ pure val DeliverPacketToProviderHappyPathTest_testState = _DeliverPacketToProvider_TestState.with(
+ "consumerStates", _DeliverPacketToProvider_TestState.consumerStates.put(
+ "sender", _DeliverPacketToProvider_TestState.consumerStates.get("sender").with(
+ "outstandingPacketsToProvider", List({
+ id: 0,
+ sendingTime: 0
+ })
+ )
+ )
+ ).with(
+ // put an entry into sentVSCPacket on the provider that corresponds to the packet we put on the consumer
+ "providerState", _DeliverPacketToProvider_TestState.providerState.with(
+ "sentVSCPackets", _DeliverPacketToProvider_TestState.providerState.sentVSCPackets.put(
+ "sender", List({
+ id: 0,
+ validatorSet: _DeliverPacketToProvider_TestState.providerState.chainState.currentValidatorSet,
+ sendingTime: 0
+ })
+ )
+ )
+ )
+
+ pure val DeliverPacketToProviderHappyPathTest_resultAndTimeout = deliverPacketToProvider(DeliverPacketToProviderHappyPathTest_testState, "sender")
+
+ // test is split to be easier to pinpoint which assertion failed
+ run DidNotTimeOut_DeliverPacketToProviderHappyPathTest =
+ {
+ val result = DeliverPacketToProviderHappyPathTest_resultAndTimeout._1
+ val timeout = DeliverPacketToProviderHappyPathTest_resultAndTimeout._2
+ not(result.hasError) and
+ not(timeout)
+ }
+
+ run StateModificationOK_DeliverPacketToProviderHappyPathTest =
+ {
+ val result = DeliverPacketToProviderHappyPathTest_resultAndTimeout._1
+ val timedOut = DeliverPacketToProviderHappyPathTest_resultAndTimeout._2
+ val newProviderState = result.newState.providerState
+ val newConsumerState = result.newState.consumerStates.get("sender")
+ not(result.hasError) and
+ newProviderState.receivedMaturations.size() == 1 and
+ newConsumerState.outstandingPacketsToProvider.length() == 0
+ }
+
+ // add a packet on the consumer
+ pure val DeliverPacketToProviderTimeoutTest_testState = DeliverPacketToProviderHappyPathTest_testState.with(
+ "providerState", DeliverPacketToProviderHappyPathTest_testState.providerState.with(
+ "chainState", DeliverPacketToProviderHappyPathTest_testState.providerState.chainState.with(
+ // set the timestamp to be after the timeout
+ "lastTimestamp", CcvTimeout.get("sender") + 1
+ )
+ )
+ )
+
+ pure val DeliverPacketToProviderTimeoutTest_resultAndTimeout = deliverPacketToProvider(DeliverPacketToProviderTimeoutTest_testState, "sender")
+
+ run DidTimeOut_DeliverPacketToProviderTimeoutTest =
+ {
+ val result = DeliverPacketToProviderTimeoutTest_resultAndTimeout._1
+ val timedOut = DeliverPacketToProviderTimeoutTest_resultAndTimeout._2
+ val newProviderState = result.newState.providerState
+ val newConsumerState = result.newState.consumerStates.get("sender")
+ not(result.hasError) and
+ timedOut
+ }
+
+ run StateModificationOK_DeliverPacketToProviderTimeoutTest =
+ {
+ val result = DeliverPacketToProviderTimeoutTest_resultAndTimeout._1
+ val timedOut = DeliverPacketToProviderTimeoutTest_resultAndTimeout._2
+ val newProviderState = result.newState.providerState
+ val newConsumerState = result.newState.consumerStates.get("sender")
+ not(result.hasError) and
+ newProviderState.receivedMaturations.size() == 0 and
+ newProviderState.consumerStatus.get("sender") == STOPPED
+ }
+
+ run ConsumerStatusMapHappyPathTest =
+ {
+ val currentConsumerStatusMap = Map(
+ "chain1" -> UNUSED,
+ "chain2" -> RUNNING,
+ "chain3" -> STOPPED
+ )
+ val res = getNewConsumerStatusMap(
+ currentConsumerStatusMap,
+ Set("chain1"),
+ Set("chain2")
+ )
+ res._2 == "" and
+ res._1.get("chain1") == RUNNING and
+ res._1.get("chain2") == STOPPED and
+ res._1.get("chain3") == STOPPED
+ }
+
+ run ConsumerStatusMapAlreadyRunningTest =
+ {
+ val currentConsumerStatusMap = Map(
+ "chain1" -> UNUSED,
+ "chain2" -> RUNNING,
+ "chain3" -> STOPPED
+ )
+ val res = getNewConsumerStatusMap(
+ currentConsumerStatusMap,
+ Set("chain2"),
+ Set("chain3")
+ )
+ res._2 == "Cannot start a consumer that is already running"
+ }
+
+ run ConsumerStatusMapAlreadyStoppedTest =
+ {
+ val currentConsumerStatusMap = Map(
+ "chain1" -> UNUSED,
+ "chain2" -> RUNNING,
+ "chain3" -> STOPPED
+ )
+ val res = getNewConsumerStatusMap(
+ currentConsumerStatusMap,
+ Set("chain1"),
+ Set("chain3")
+ )
+ res._2 == "Cannot stop a consumer that is not running"
+ }
+
+ run ChainBothInStartAndStopTest =
+ {
+ val currentConsumerStatusMap = Map(
+ "chain1" -> UNUSED,
+ "chain2" -> RUNNING,
+ "chain3" -> STOPPED
+ )
+ val res = getNewConsumerStatusMap(
+ currentConsumerStatusMap,
+ Set("chain1"),
+ Set("chain1")
+ )
+ res._2 == "Cannot start and stop a consumer at the same time"
+ }
+}
\ No newline at end of file
diff --git a/tests/difference/core/quint_model/extraSpells.qnt b/tests/difference/core/quint_model/libraries/extraSpells.qnt
similarity index 100%
rename from tests/difference/core/quint_model/extraSpells.qnt
rename to tests/difference/core/quint_model/libraries/extraSpells.qnt
diff --git a/tests/difference/core/quint_model/time.qnt b/tests/difference/core/quint_model/libraries/time.qnt
similarity index 100%
rename from tests/difference/core/quint_model/time.qnt
rename to tests/difference/core/quint_model/libraries/time.qnt
From aa2989e40e6fb4578adb5e6b94752c36670f3cdc Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Fri, 29 Sep 2023 10:54:08 +0200
Subject: [PATCH 30/35] Fix import
---
.../core/quint_model/ccv_statemachine.qnt | 22 +++++++++++++------
1 file changed, 15 insertions(+), 7 deletions(-)
diff --git a/tests/difference/core/quint_model/ccv_statemachine.qnt b/tests/difference/core/quint_model/ccv_statemachine.qnt
index 142db75431..2b4e857078 100644
--- a/tests/difference/core/quint_model/ccv_statemachine.qnt
+++ b/tests/difference/core/quint_model/ccv_statemachine.qnt
@@ -1,8 +1,8 @@
module CCVDefaultStateMachine {
// A basic state machine that utilizes the CCV protocol.
- import Time.* from "./libraries/time"
- import extraSpells.* from "./libraries/extraSpells"
import CCVTypes.* from "./ccv"
+ import Time.* from "./libraries/Time"
+ import extraSpells.* from "./libraries/extraSpells"
pure val consumerChains = Set("consumer1", "consumer2", "consumer3")
pure val chains = consumerChains.union(Set(PROVIDER_CHAIN))
@@ -89,12 +89,19 @@ module CCVDefaultStateMachine {
currentState' = result.newState,
}
+ // a few different values for time advancements.
+ // to keep the number of possible steps small, we only have a few different values.
+ // Roughly, 1s for very small advances (like between blocks),
+ // and then longer values for increasingly severe downtime scenarios.
+ // Note that these can still be combined, so in effect we can get all time advancements by any amount of seconds.
+ pure val timeAdvancements = Set(1 * Second, 1 * Day, 1 * Week, 4 * Week)
+
action step = any {
nondet node = oneOf(nodes)
// very restricted set of voting powers. exact values are not important,
// and this keeps the state space smaller.
// 0 for getting a validator out of the validator set, and two non-zero values
- nondet newVotingPower = oneOf(0, 50, 100)
+ nondet newVotingPower = oneOf(Set(0, 50, 100))
VotingPowerChange(node, newVotingPower),
nondet chain = oneOf(consumerChains)
@@ -103,14 +110,15 @@ module CCVDefaultStateMachine {
// Roughly, 1s for very small advances (like between blocks),
// and then longer values for increasingly severe downtime scenarios.
// Note that these can still be combined, so in effect we can get all time advancements by any amount of seconds.
- nondet timeAdvancement = oneOf(1 * Second, 1 * Day, 1 * Week, 1 * Month)
+ nondet timeAdvancement = oneOf(timeAdvancements)
EndAndBeginBlockForConsumer(chain, timeAdvancement),
- val runningConsumers = currentState.providerState.consumerStatus.filter((chain, status) => status == RUNNING).keys()
- val unusedConsumers = currentState.providerState.consumerStatus.filter((chain, status) => status == UNUSED).keys()
+ val consumerStatus = currentState.providerState.consumerStatus
+ val runningConsumers = consumerStatus.keys().filter(chain => consumerStatus.get(chain) == RUNNING)
+ val unusedConsumers = consumerStatus.keys().filter(chain => consumerStatus.get(chain) == UNUSED)
nondet consumersToStart = oneOf(runningConsumers.powerset())
nondet consumersToStop = oneOf(unusedConsumers.powerset())
- nondet timeAdvancement = oneOf(1 * Second, 1 * Day, 1 * Week, 1 * Month)
+ nondet timeAdvancement = oneOf(timeAdvancements)
EndAndBeginBlockForProvider(timeAdvancement, consumersToStart, consumersToStop),
//
From c291303159ce3c28cf00484e34252e07fd94bffc Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Fri, 29 Sep 2023 12:51:31 +0200
Subject: [PATCH 31/35] Add some invariants
---
tests/difference/core/quint_model/ccv.qnt | 7 ++
.../core/quint_model/ccv_statemachine.qnt | 104 +++++++++++++++++-
2 files changed, 107 insertions(+), 4 deletions(-)
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index fbb9b08fed..3f6a8e1982 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -738,6 +738,13 @@ module CCV {
)
}
+ // Returns the set of all consumer chains that currently have the status UNUSED.
+ pure def getUnusedConsumers(providerState: ProviderState): Set[Chain] = {
+ providerState.consumerStatus.keys().filter(
+ chain => providerState.consumerStatus.get(chain) == UNUSED
+ )
+ }
+
// Returns whether the consumer has timed out due to the VSCTimeout, and an error message.
// If the second return is not equal to "", the first return should be ignored.
// If it is equal to "", the first return will be true if the consumer has timed out and should be dropped,
diff --git a/tests/difference/core/quint_model/ccv_statemachine.qnt b/tests/difference/core/quint_model/ccv_statemachine.qnt
index 2b4e857078..024731e446 100644
--- a/tests/difference/core/quint_model/ccv_statemachine.qnt
+++ b/tests/difference/core/quint_model/ccv_statemachine.qnt
@@ -16,6 +16,14 @@ module CCVDefaultStateMachine {
var currentState: ProtocolState
+
+ // bookkeeping
+ var lastAction: str
+
+ // some utility stateful vals to make invariants easier to define
+ val providerValidatorHistory = currentState.providerState.chainState.votingPowerHistory
+ val runningConsumers = getRunningConsumers(currentState.providerState)
+ val unusedConsumers = getUnusedConsumers(currentState.providerState)
action init: bool = all {
val providerState = GetEmptyProviderState
@@ -40,7 +48,8 @@ module CCVDefaultStateMachine {
currentState' = {
providerState: providerStateWithConsumers,
consumerStates: consumerStates
- }
+ },
+ lastAction' = "init"
}
action VotingPowerChange(validator: Node, newVotingPower: int): bool =
@@ -48,6 +57,7 @@ module CCVDefaultStateMachine {
all {
result.hasError == false,
currentState' = result.newState,
+ lastAction' = "VotingPowerChange"
}
// The receiver receives the next outstanding VSCPacket from the provider.
@@ -58,6 +68,7 @@ module CCVDefaultStateMachine {
all {
result.hasError == false,
currentState' = result.newState,
+ lastAction' = "DeliverVSCPacket"
}
// The provider receives the next outstanding VSCMaturedPacket from the sender.
@@ -68,6 +79,7 @@ module CCVDefaultStateMachine {
all {
result.hasError == false,
currentState' = result.newState,
+ lastAction' = "DeliverVSCMaturedPacket"
}
action EndAndBeginBlockForProvider(
@@ -78,6 +90,7 @@ module CCVDefaultStateMachine {
all {
result.hasError == false,
currentState' = result.newState,
+ lastAction' = "EndAndBeginBlockForProvider"
}
action EndAndBeginBlockForConsumer(
@@ -87,6 +100,7 @@ module CCVDefaultStateMachine {
all {
result.hasError == false,
currentState' = result.newState,
+ lastAction' = "EndAndBeginBlockForConsumer"
}
// a few different values for time advancements.
@@ -114,8 +128,6 @@ module CCVDefaultStateMachine {
EndAndBeginBlockForConsumer(chain, timeAdvancement),
val consumerStatus = currentState.providerState.consumerStatus
- val runningConsumers = consumerStatus.keys().filter(chain => consumerStatus.get(chain) == RUNNING)
- val unusedConsumers = consumerStatus.keys().filter(chain => consumerStatus.get(chain) == UNUSED)
nondet consumersToStart = oneOf(runningConsumers.powerset())
nondet consumersToStop = oneOf(unusedConsumers.powerset())
nondet timeAdvancement = oneOf(timeAdvancements)
@@ -133,6 +145,89 @@ module CCVDefaultStateMachine {
DeliverVSCPacket(recciver),
}
+ // ==================
+ // INVARIANT CHECKS
+ // ==================
+
+
+ // Every validator set on any consumer chain MUST either be or have been
+ // a validator set on the provider chain.
+ val ValidatorSetHasExistedInv =
+ ConsumerChains.forall(chain =>
+ currentState.consumerStates.get(chain).chainState.votingPowerHistory.toSet().forall(
+ validatorSet => providerValidatorHistory.toSet().contains(validatorSet)
+ )
+ )
+
+ // Any update in the power of a validator on the provider
+ // MUST be present in a ValidatorSetChangePacket that is sent to all registered consumer chains
+ val ValidatorUpdatesArePropagated =
+ // when the provider has just entered a validator set into a block...
+ if (lastAction == "EndAndBeginBlockForProvider") {
+ val providerValSetInCurBlock = providerValidatorHistory.head()
+ // ... for each consumer that is running then ...
+ runningConsumers.forall(
+ // ...the validator set is in a sent packet
+ consumer => currentState.providerState.sentVSCPackets.get(consumer).toSet().exists(
+ packet => packet.validatorSet == providerValSetInCurBlock
+ )
+ )
+ } else {
+ true
+ }
+
+
+// \* Invariants from https://github.com/cosmos/interchain-security/blob/main/docs/quality_assurance.md
+
+// (*
+// 6.02 - Any update in the power of a validator val on the provider, as a result of
+// - (increase) Delegate() / Redelegate() to val
+// - (increase) val joining the provider validator set
+// - (decrease) Undelegate() / Redelegate() from val
+// - (decrease) Slash(val)
+// - (decrease) val leaving the provider validator set
+// MUST be present in a ValidatorSetChangePacket that is sent to all registered consumer chains
+// *)
+// Inv602 ==
+// \A packet \in DOMAIN votingPowerHist:
+// \A c \in LiveConsumers:
+// LET packetsToC == PacketOrder(c) IN
+// \E i \in DOMAIN packetsToC:
+// packetsToC[i] = packet
+
+// (*
+// 6.03 - Every consumer chain receives the same sequence of
+// ValidatorSetChangePackets in the same order.
+
+// Note: consider only prefixes on received packets (ccvChannelsResolved)
+// *)
+// Inv603 ==
+// \A c1,c2 \in LiveConsumers:
+// \A i \in (DOMAIN ccvChannelsResolved[c1] \intersect DOMAIN ccvChannelsResolved[c2]):
+// ccvChannelsResolved[c1][i] = ccvChannelsResolved[c2][i]
+
+// (*
+// 7.01 - For every ValidatorSetChangePacket received by a consumer chain at
+// time t, a MaturedVSCPacket is sent back to the provider in the first block
+// with a timestamp >= t + UnbondingPeriod
+
+// Modification: not necessarily _first_ block with that timestamp,
+// since we don't model height _and_ time. Also, only true for ACTIVE chains.
+// *)
+// Inv701 ==
+// boundedDrift => MaturedBeforeTimeout
+
+// (*
+// 7.02 - If an unbonding operation resulted in a ValidatorSetChangePacket sent
+// to all registered consumer chains, then it cannot complete before receiving
+// matching MaturedVSCPackets from these consumer chains
+// (unless some of these consumer chains are removed)
+
+// We can define change completion, but we don't model it. Best approximation:
+// *)
+// Inv702 ==
+// boundedDrift => EventuallyMatureOnProvider
+
// ==================
// MANUAL TEST CASES
// ==================
@@ -221,7 +316,8 @@ module CCVDefaultStateMachine {
assert(currentState.providerState.receivedMaturations.size() == 1),
// the packet was removed from the consumer
assert(currentState.consumerStates.get("consumer1").outstandingPacketsToProvider.length() == 0),
- currentState' = currentState // just so this still has an effect
+ currentState' = currentState, // just so this still has an effect
+ lastAction' = "HappyPathTest"
}
)
}
From 94a0acb96b39b80f75df8082f2a0bb6dc1acd999 Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Fri, 29 Sep 2023 13:25:25 +0200
Subject: [PATCH 32/35] Refactor Consumer advancement
---
tests/difference/core/quint_model/README.md | 4 +
tests/difference/core/quint_model/ccv.qnt | 115 ++++++++++++------
.../difference/core/quint_model/ccv_test.qnt | 10 +-
3 files changed, 87 insertions(+), 42 deletions(-)
diff --git a/tests/difference/core/quint_model/README.md b/tests/difference/core/quint_model/README.md
index 30de96350f..24e620bf45 100644
--- a/tests/difference/core/quint_model/README.md
+++ b/tests/difference/core/quint_model/README.md
@@ -18,3 +18,7 @@ Other functions are for utility.
The parameters of the protocol are defined as consts in ccv.qnt.
+### Invariants
+
+The invariants I am checking are in ccv_statemachine.qnt, and are as follows:
+- ValidatorUpdatesArePropagated: When a validator power update is comitted on chain, a packet containing that change in voting power is sent to all running consumers. Check via `quint run --invariant ValidatorUpdatesArePropagated ccv_statemachine.qnt --main CCVDefaultStateMachine`
\ No newline at end of file
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index 3f6a8e1982..09abd1c0ab 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -278,9 +278,8 @@ module CCV {
val timeout = CcvTimeout.get(sender)
if(packet.sendingTime + CcvTimeout.get(sender) < currentState.providerState.chainState.lastTimestamp) {
// drop consumer
- val result = getNewConsumerStatusMap(
+ val result = stopConsumers(
currentState.providerState.consumerStatus,
- Set(),
Set(sender)
)
@@ -358,9 +357,8 @@ module CCV {
// check if the consumer timed out
if (packet.sendingTime + CcvTimeout.get(PROVIDER_CHAIN) < currentState.consumerStates.get(receiver).chainState.lastTimestamp) {
// drop consumer
- val result = getNewConsumerStatusMap(
+ val result = stopConsumers(
currentState.providerState.consumerStatus,
- Set(),
Set(receiver)
)
@@ -428,16 +426,19 @@ module CCV {
res._1
)
- // modify the states of the consumers that should be started/stopped
- val res = providerStateAfterTimeAdvancement.consumerStatus.getNewConsumerStatusMap(consumersToStart, consumersToStop.union(timedOutConsumers))
+ // start/stop chains
+ val res = providerStateAfterTimeAdvancement.consumerStatus.StartStopConsumers(
+ consumersToStart,
+ consumersToStop
+ )
val newConsumerStatus = res._1
val err = res._2
+ val providerStateAfterConsumerAdvancement = providerStateAfterTimeAdvancement.with(
+ "consumerStatus", newConsumerStatus
+ )
if (err != "") {
Err(err)
} else {
- val providerStateAfterConsumerAdvancement = providerStateAfterTimeAdvancement.with(
- "consumerStatus", newConsumerStatus
- )
val providerStateAfterSending =
if (currentProviderState.providerValidatorSetChangedInThisBlock and
// important: check this on the provider state after the consumer advancement, not on the current state.
@@ -511,43 +512,83 @@ module CCV {
)
}
- // Returns the new ConsumerStatusMap according to the consumers to start/stop.
+ // Returns the new ConsumerStatusMap according to the consumers to stop.
// The second return is an error string: If it is not equal to "",
// it contains an error message, and the first return should be ignored.
- pure def getNewConsumerStatusMap(
+ pure def stopConsumers(
currentConsumerStatusMap: Chain -> str,
- consumersToStart: Set[Chain],
consumersToStop: Set[Chain]): (Chain -> str, str) = {
- val runningConsumers = getRunningConsumersFromMap(currentConsumerStatusMap)
- val stoppedConsumers = getStoppedConsumersFromMap(currentConsumerStatusMap)
+ val runningConsumers = currentConsumerStatusMap.keys().filter(
+ chain => currentConsumerStatusMap.get(chain) == RUNNING
+ )
// if a consumer is both started and stopped, this is an error
- if (consumersToStart.intersect(consumersToStop).size() > 0) {
- (currentConsumerStatusMap, "Cannot start and stop a consumer at the same time")
- } else {
- // if a consumer is started, it must be unused
- if (consumersToStart.intersect(runningConsumers).size() > 0) {
- (currentConsumerStatusMap, "Cannot start a consumer that is already running")
+ // if a consumer is stopped, it must be running
+ if (consumersToStop.exclude(runningConsumers).size() > 0) {
+ (currentConsumerStatusMap, "Cannot stop a consumer that is not running")
} else {
- // if a consumer is stopped, it must be running
- if (consumersToStop.intersect(stoppedConsumers).size() > 0) {
- (currentConsumerStatusMap, "Cannot stop a consumer that is not running")
- } else {
- // if a consumer is started, it must be unused
- val newConsumerStatusMap = currentConsumerStatusMap.keys().mapBy(
- (chain) =>
- if (consumersToStart.contains(chain)) {
- RUNNING
- } else if (consumersToStop.contains(chain)) {
- STOPPED
- } else {
- currentConsumerStatusMap.get(chain)
- }
- )
- (newConsumerStatusMap, "")
- }
+ // if a consumer is started, it must be unused
+ val newConsumerStatusMap = currentConsumerStatusMap.keys().mapBy(
+ (chain) =>
+ if (consumersToStop.contains(chain)) {
+ STOPPED
+ } else {
+ currentConsumerStatusMap.get(chain)
+ }
+ )
+ (newConsumerStatusMap, "")
+ }
+ }
+
+ // Returns the new ConsumerStatusMap according to the consumers to start.
+ // The second return is an error string: If it is not equal to "",
+ // it contains an error message, and the first return should be ignored.
+ pure def startConsumers(
+ currentConsumerStatusMap: Chain -> str,
+ consumersToStart: Set[Chain]): (Chain -> str, str) = {
+ val unusedConsumers = currentConsumerStatusMap.keys().filter(
+ chain => currentConsumerStatusMap.get(chain) == UNUSED
+ )
+ // if a consumer is both started and stopped, this is an error
+ // if a consumer is stopped, it must be running
+ if (consumersToStart.exclude(unusedConsumers).size() > 0) {
+ (currentConsumerStatusMap, "cannot start a consumer that is not unused")
+ } else {
+ // if a consumer is started, it must be unused
+ val newConsumerStatusMap = currentConsumerStatusMap.keys().mapBy(
+ (chain) =>
+ if (consumersToStart.contains(chain)) {
+ RUNNING
+ } else {
+ currentConsumerStatusMap.get(chain)
+ }
+ )
+ (newConsumerStatusMap, "")
}
}
+
+ pure def StartStopConsumers(
+ currentConsumerStatusMap: Chain -> str,
+ consumersToStart: Set[Chain],
+ consumersToStop: Set[Chain]
+ ): (Chain -> str, str) = {
+ // check if any consumer is both started and stopped
+ if (consumersToStart.intersect(consumersToStop).size() > 0) {
+ (currentConsumerStatusMap, "Cannot start and stop a consumer at the same time")
+ } else {
+ val res1 = currentConsumerStatusMap.startConsumers(consumersToStart)
+ val newConsumerStatus = res1._1
+ val err1 = res1._2
+ val res2 = newConsumerStatus.stopConsumers(consumersToStop)
+ val err2 = res2._2
+ if (err1 != "") {
+ (currentConsumerStatusMap, err1)
+ } else if (err2 != "") {
+ (currentConsumerStatusMap, err2)
+ } else {
+ (res2._1, "")
+ }
}
+ }
// Takes the currentValidatorSet and puts it as the newest set of the voting history
diff --git a/tests/difference/core/quint_model/ccv_test.qnt b/tests/difference/core/quint_model/ccv_test.qnt
index 31c46c0e62..d84d8f0ea4 100644
--- a/tests/difference/core/quint_model/ccv_test.qnt
+++ b/tests/difference/core/quint_model/ccv_test.qnt
@@ -167,7 +167,7 @@ module CCVTest {
"chain2" -> RUNNING,
"chain3" -> STOPPED
)
- val res = getNewConsumerStatusMap(
+ val res = StartStopConsumers(
currentConsumerStatusMap,
Set("chain1"),
Set("chain2")
@@ -185,12 +185,12 @@ module CCVTest {
"chain2" -> RUNNING,
"chain3" -> STOPPED
)
- val res = getNewConsumerStatusMap(
+ val res = StartStopConsumers(
currentConsumerStatusMap,
Set("chain2"),
Set("chain3")
)
- res._2 == "Cannot start a consumer that is already running"
+ res._2 == "cannot start a consumer that is not unused"
}
run ConsumerStatusMapAlreadyStoppedTest =
@@ -200,7 +200,7 @@ module CCVTest {
"chain2" -> RUNNING,
"chain3" -> STOPPED
)
- val res = getNewConsumerStatusMap(
+ val res = StartStopConsumers(
currentConsumerStatusMap,
Set("chain1"),
Set("chain3")
@@ -215,7 +215,7 @@ module CCVTest {
"chain2" -> RUNNING,
"chain3" -> STOPPED
)
- val res = getNewConsumerStatusMap(
+ val res = StartStopConsumers(
currentConsumerStatusMap,
Set("chain1"),
Set("chain1")
From 8818531faf9c27c55756053863b86e0311edff32 Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Fri, 29 Sep 2023 15:12:05 +0200
Subject: [PATCH 33/35] Snapshot error
---
tests/difference/core/quint_model/README.md | 3 +-
tests/difference/core/quint_model/ccv.qnt | 8 +++
.../core/quint_model/ccv_statemachine.qnt | 44 ++++++++++-------
.../quint_model/libraries/extraSpells.qnt | 49 +++++++++++++++++++
4 files changed, 86 insertions(+), 18 deletions(-)
diff --git a/tests/difference/core/quint_model/README.md b/tests/difference/core/quint_model/README.md
index 24e620bf45..910cbe7232 100644
--- a/tests/difference/core/quint_model/README.md
+++ b/tests/difference/core/quint_model/README.md
@@ -21,4 +21,5 @@ The parameters of the protocol are defined as consts in ccv.qnt.
### Invariants
The invariants I am checking are in ccv_statemachine.qnt, and are as follows:
-- ValidatorUpdatesArePropagated: When a validator power update is comitted on chain, a packet containing that change in voting power is sent to all running consumers. Check via `quint run --invariant ValidatorUpdatesArePropagated ccv_statemachine.qnt --main CCVDefaultStateMachine`
\ No newline at end of file
+- ValidatorUpdatesArePropagated: When a validator power update is comitted on chain, a packet containing that change in voting power is sent to all running consumers. Check via `quint run --invariant ValidatorUpdatesArePropagated ccv_statemachine.qnt --main CCVDefaultStateMachine`
+-
\ No newline at end of file
diff --git a/tests/difference/core/quint_model/ccv.qnt b/tests/difference/core/quint_model/ccv.qnt
index 09abd1c0ab..72a0e0d933 100644
--- a/tests/difference/core/quint_model/ccv.qnt
+++ b/tests/difference/core/quint_model/ccv.qnt
@@ -106,6 +106,10 @@ module CCVTypes {
// Stores the maturation times for VSCPackets received by this consumer
maturationTimes: VSCPacket -> Time,
+ // stores the received vscpackets in descending order of recency,
+ // i.e. newest first.
+ receivedVSCPackets: List[VSCPacket],
+
// Stores the list of packets that have been sent to the provider chain by this consumer
// and have not been received yet.
// ordered by recency, so the head is the oldest packet.
@@ -119,6 +123,7 @@ module CCVTypes {
chainState: GetEmptyChainState,
maturationTimes: Map(),
outstandingPacketsToProvider: List(),
+ receivedVSCPackets: List(),
}
// the state of the protocol consists of the composition of the state of one provider chain with potentially many consumer chains.
@@ -681,6 +686,9 @@ module CCV {
packet,
currentConsumerState.chainState.lastTimestamp + UnbondingPeriodPerChain.get(receiver)
)
+ ).with(
+ "receivedVSCPackets",
+ currentConsumerState.receivedVSCPackets.prepend(packet)
)
val newConsumerStates = currentState.consumerStates.set(receiver, newConsumerState)
val newState = currentState.with(
diff --git a/tests/difference/core/quint_model/ccv_statemachine.qnt b/tests/difference/core/quint_model/ccv_statemachine.qnt
index 024731e446..8f3b918f22 100644
--- a/tests/difference/core/quint_model/ccv_statemachine.qnt
+++ b/tests/difference/core/quint_model/ccv_statemachine.qnt
@@ -153,7 +153,7 @@ module CCVDefaultStateMachine {
// Every validator set on any consumer chain MUST either be or have been
// a validator set on the provider chain.
val ValidatorSetHasExistedInv =
- ConsumerChains.forall(chain =>
+ runningConsumers.forall(chain =>
currentState.consumerStates.get(chain).chainState.votingPowerHistory.toSet().forall(
validatorSet => providerValidatorHistory.toSet().contains(validatorSet)
)
@@ -176,24 +176,34 @@ module CCVDefaultStateMachine {
true
}
+ // Every consumer chain receives the same sequence of
+ // ValidatorSetChangePackets in the same order.
+ // NOTE: since not all consumer chains are running all the time,
+ // we need a slightly weaker invariant:
+ // For consumer chains c1, c2, if both c1 and c2 received a packet p1 sent at t1 and a packet p2 sent at t2,
+ // then both have received ALL packets that were sent between t1 and t2.
+ val SameVSCPacketsInv =
+ runningConsumers.forall(
+ consumer1 => runningConsumers.forall(
+ consumer2 => {
+ val packets1 = currentState.consumerStates.get(consumer1).receivedVSCPackets
+ val packets2 = currentState.consumerStates.get(consumer2).receivedVSCPackets
+ val commonPackets = packets1.toSet().intersect(packets2.toSet())
+ if (commonPackets.size() == 0) {
+ true // they don't share any packets, so nothing to check
+ } else {
+ // get oldest common packet
+ val oldestCommonPacket = commonPackets.MinBy(packet => packet.sendingTime, packets1.head())
+ false
+ }
+ }
+ )
+ )
-// \* Invariants from https://github.com/cosmos/interchain-security/blob/main/docs/quality_assurance.md
+
-// (*
-// 6.02 - Any update in the power of a validator val on the provider, as a result of
-// - (increase) Delegate() / Redelegate() to val
-// - (increase) val joining the provider validator set
-// - (decrease) Undelegate() / Redelegate() from val
-// - (decrease) Slash(val)
-// - (decrease) val leaving the provider validator set
-// MUST be present in a ValidatorSetChangePacket that is sent to all registered consumer chains
-// *)
-// Inv602 ==
-// \A packet \in DOMAIN votingPowerHist:
-// \A c \in LiveConsumers:
-// LET packetsToC == PacketOrder(c) IN
-// \E i \in DOMAIN packetsToC:
-// packetsToC[i] = packet
+
+// \* Invariants from https://github.com/cosmos/interchain-security/blob/main/docs/quality_assurance.md
// (*
// 6.03 - Every consumer chain receives the same sequence of
diff --git a/tests/difference/core/quint_model/libraries/extraSpells.qnt b/tests/difference/core/quint_model/libraries/extraSpells.qnt
index 1d7d219640..54b5feca09 100644
--- a/tests/difference/core/quint_model/libraries/extraSpells.qnt
+++ b/tests/difference/core/quint_model/libraries/extraSpells.qnt
@@ -194,4 +194,53 @@ module extraSpells {
assert(Max(Set(1, 2, 3)) == 3),
assert(Max(Set()) == 0)
}
+
+ pure def HasSubsequence(__this: List[a], __other: List[a]): bool = {
+ if(__other.length() > __this.length()) {
+ false
+ } else {
+ 0.to(__this.length() - __other.length()) // go through all possible starting points of __other in __this
+ .exists(
+ __start => __this.slice(__start, __start + __other.length()) == __other
+ )
+ }
+ }
+
+ run HasSubsequenceTest =
+ all {
+ assert(List(1, 2, 3, 4).HasSubsequence(List(1, 2))),
+ assert(List(1, 2, 3, 4).HasSubsequence(List(2, 3))),
+ assert(List(1, 2, 3, 4).HasSubsequence(List(3, 4))),
+ assert(List(1, 2, 3, 4).HasSubsequence(List(1, 2, 3))),
+ assert(not(List(1, 2, 3, 4).HasSubsequence(List(1, 3)))),
+ assert(not(List().HasSubsequence(List(1)))),
+ assert(List().HasSubsequence(List())),
+ assert(List(1).HasSubsequence(List())),
+ assert(List(1).HasSubsequence(List(1))),
+ }
+
+ // Returns the maximum element of a set, according to a given function.
+ // __i should be part of the set if it is nonempty. If the set is empty, __i will be returned.
+ // If two elements are equally large, an arbitrary one will be returned.
+ pure def MaxBy(__set: Set[a], __f: a => int, __i: a): a = {
+ __set.fold(
+ __i,
+ (__m, __e) => if(__f(__m) > __f(__e)) {__m } else {__e}
+ )
+ }
+
+ run MaxByTest =
+ all {
+ assert(MaxBy(Set(1, 2, 3), __x => __x, 0) == 3),
+ assert(MaxBy(Set(1, 2, 3), __x => -__x, 0) == 1),
+ assert(MaxBy(Set(), __x => __x, 0) == 0),
+ }
+
+ // Like MaxBy, but returns the minimum element.
+ pure def MinBy(__set: Set[a], __f: a => int, __i: a): a = {
+ __set.fold(
+ __i,
+ (__m, __e) => if(__f(__m) < __f(__e)) {__m } else {__e}
+ )
+ }
}
From 633f8bb3ff005b26eb1f22df52112a5c0a28e48a Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Fri, 29 Sep 2023 15:33:35 +0200
Subject: [PATCH 34/35] Make time module upper case
---
.../core/quint_model/ccv_statemachine.qnt | 28 ++++++++-----------
.../libraries/{time.qnt => Time.qnt} | 0
2 files changed, 12 insertions(+), 16 deletions(-)
rename tests/difference/core/quint_model/libraries/{time.qnt => Time.qnt} (100%)
diff --git a/tests/difference/core/quint_model/ccv_statemachine.qnt b/tests/difference/core/quint_model/ccv_statemachine.qnt
index 8f3b918f22..b8ad1a82f3 100644
--- a/tests/difference/core/quint_model/ccv_statemachine.qnt
+++ b/tests/difference/core/quint_model/ccv_statemachine.qnt
@@ -192,30 +192,26 @@ module CCVDefaultStateMachine {
if (commonPackets.size() == 0) {
true // they don't share any packets, so nothing to check
} else {
- // get oldest common packet
- val oldestCommonPacket = commonPackets.MinBy(packet => packet.sendingTime, packets1.head())
- false
+ val oldestCommonPacket = packets1.head()
+ val newestCommonPacket = packets1[packets1.length() - 1]
+ // get all packets sent between the oldest and newest common packet
+ val packetsBetween1 = packets1.select(
+ packet => packet.sendingTime >= oldestCommonPacket.sendingTime and packet.sendingTime <= newestCommonPacket.sendingTime
+ )
+ val packetsBetween2 = packets2.select(
+ packet => packet.sendingTime >= oldestCommonPacket.sendingTime and packet.sendingTime <= newestCommonPacket.sendingTime
+ )
+ // these should be the same on both chains
+ packetsBetween1 == packetsBetween2
}
}
)
)
-
-
+ // val MaturedBeforeTimeoutInv =
// \* Invariants from https://github.com/cosmos/interchain-security/blob/main/docs/quality_assurance.md
-// (*
-// 6.03 - Every consumer chain receives the same sequence of
-// ValidatorSetChangePackets in the same order.
-
-// Note: consider only prefixes on received packets (ccvChannelsResolved)
-// *)
-// Inv603 ==
-// \A c1,c2 \in LiveConsumers:
-// \A i \in (DOMAIN ccvChannelsResolved[c1] \intersect DOMAIN ccvChannelsResolved[c2]):
-// ccvChannelsResolved[c1][i] = ccvChannelsResolved[c2][i]
-
// (*
// 7.01 - For every ValidatorSetChangePacket received by a consumer chain at
// time t, a MaturedVSCPacket is sent back to the provider in the first block
diff --git a/tests/difference/core/quint_model/libraries/time.qnt b/tests/difference/core/quint_model/libraries/Time.qnt
similarity index 100%
rename from tests/difference/core/quint_model/libraries/time.qnt
rename to tests/difference/core/quint_model/libraries/Time.qnt
From f28d074ce70ebc9c37c08d34388f8ffbb9abcc59 Mon Sep 17 00:00:00 2001
From: Philip Offtermatt
Date: Fri, 29 Sep 2023 16:05:32 +0200
Subject: [PATCH 35/35] Add invariants
---
tests/difference/core/quint_model/README.md | 18 ++++++++--
.../core/quint_model/ccv_statemachine.qnt | 36 ++++++++++++++++++-
2 files changed, 50 insertions(+), 4 deletions(-)
diff --git a/tests/difference/core/quint_model/README.md b/tests/difference/core/quint_model/README.md
index 910cbe7232..6d1ad8d2be 100644
--- a/tests/difference/core/quint_model/README.md
+++ b/tests/difference/core/quint_model/README.md
@@ -20,6 +20,18 @@ The parameters of the protocol are defined as consts in ccv.qnt.
### Invariants
-The invariants I am checking are in ccv_statemachine.qnt, and are as follows:
-- ValidatorUpdatesArePropagated: When a validator power update is comitted on chain, a packet containing that change in voting power is sent to all running consumers. Check via `quint run --invariant ValidatorUpdatesArePropagated ccv_statemachine.qnt --main CCVDefaultStateMachine`
--
\ No newline at end of file
+The invariants I am checking are in ccv_statemachine.qnt.
+Check a single invariant by running
+`quint run --invariant INVARIANT_NAME ccv_statemachine.qnt --main CCVDefaultStateMachine`,
+or all invariants by running this command:
+
+Invariants are as follows:
+- ValidatorUpdatesArePropagated: When a validator power update is comitted on chain, a packet containing that change in voting power is sent to all running consumers.
+- ValidatorSetHasExistedInv: Every validator set on consumer chains is/was a validator set on the provider.
+- SameVSCPacketsInv: Ensure that consumer chains receive the same VSCPackets in the same order.
+Because of nuances with starting/stopping consumers, this invariant is not as simple as it sounds. In detail:
+For consumer chains c1, c2, if both c1 and c2 received a packet p1 sent at t1 and a packet p2 sent at t2,
+then both have received ALL packets that were sent between t1 and t2 in the same order.
+- MatureOnTimeInv: For every ValidatorSetChangePacket received by a consumer chain at
+time t, a MaturedVSCPacket is sent back to the provider in the first block
+with a timestamp >= t + UnbondingPeriod on that consumer.
diff --git a/tests/difference/core/quint_model/ccv_statemachine.qnt b/tests/difference/core/quint_model/ccv_statemachine.qnt
index b8ad1a82f3..4a6ac2e558 100644
--- a/tests/difference/core/quint_model/ccv_statemachine.qnt
+++ b/tests/difference/core/quint_model/ccv_statemachine.qnt
@@ -208,7 +208,41 @@ module CCVDefaultStateMachine {
)
)
- // val MaturedBeforeTimeoutInv =
+ // For every ValidatorSetChangePacket received by a consumer chain at
+ // time t, a MaturedVSCPacket is sent back to the provider in the first block
+ // with a timestamp >= t + UnbondingPeriod
+ // NOTE: because we remove the maturationTimes entry when we send the packets,
+ // it suffices to check that there is never an entry in maturationTimes
+ // that is older than the current time minus the unbonding period.
+ val MatureOnTimeInv =
+ runningConsumers.forall(
+ consumer => {
+ val maturationTimes = currentState.consumerStates.get(consumer).maturationTimes
+ maturationTimes.keys().forall(
+ packet => packet.sendingTime + UnbondingPeriodPerChain.get(consumer) <= currentState.providerState.chainState.lastTimestamp
+ )
+ }
+ )
+
+ // If we send a VSCPacket, this is eventually responded to by all consumers
+ // that were running at the time the packet was sent (and are still running).
+ // Since we remove sentVSCPackets when we receive responses for them,
+ // we just check that if a sentVSCPacket has been sent more than
+ // VSCTimeout ago, the consumer must have been dropped.
+ // In practice, when this is true, a pending unbonding can mature.
+ val EventuallyMatureOnProviderInv =
+ runningConsumers.forall(
+ consumer => {
+ val sentPackets = currentState.providerState.sentVSCPackets.get(consumer).toSet()
+ sentPackets.forall(
+ packet =>
+ // consumer still has time to respond
+ currentState.providerState.chainState.lastTimestamp <= packet.sendingTime + VscTimeout or
+ // consumer was dropped due to inactivity
+ currentState.providerState.consumerStatus.get(consumer) == STOPPED
+ )
+ }
+ )
// \* Invariants from https://github.com/cosmos/interchain-security/blob/main/docs/quality_assurance.md