Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: MBT: Add partial set security to model #1614

Closed
wants to merge 54 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
214f6e5
Start implementing key assignment in Quint
p-offtermatt Jan 9, 2024
79e8044
Update model
p-offtermatt Jan 9, 2024
e52e0ce
Update model
p-offtermatt Jan 9, 2024
ed0901e
Update model
p-offtermatt Jan 9, 2024
c4423ff
Add key assignment to functional layer
p-offtermatt Jan 10, 2024
e9a7699
Start writing test for key assignment
p-offtermatt Jan 11, 2024
05feedb
Continue adding key assignment to model
p-offtermatt Jan 11, 2024
dba46b5
Fix some behaviours in key assignment logic
p-offtermatt Jan 12, 2024
ba6e684
Pull key assignment into its own module
p-offtermatt Jan 15, 2024
c9ae38c
Split key assignment into extra module
p-offtermatt Jan 15, 2024
b9914a2
Start updating invariants for key assignment
p-offtermatt Jan 16, 2024
7b23574
Update key assignment spec
p-offtermatt Jan 16, 2024
9c21742
Merge key assignment into main model
p-offtermatt Jan 16, 2024
6a0eb29
Update spec, fix bug
p-offtermatt Jan 17, 2024
db78b88
Fix map building for key assignment
p-offtermatt Jan 17, 2024
2869fac
Fix key assignment invariants
p-offtermatt Jan 17, 2024
1f368de
Update driver to include key assignment
p-offtermatt Jan 18, 2024
ff0489e
Fix key assignment in bounded steps
p-offtermatt Jan 18, 2024
35e5d6c
Finish key assignment integration in driver
p-offtermatt Jan 19, 2024
147569d
Update clients every block
p-offtermatt Jan 19, 2024
6c110f7
Add key assignment to invariant check
p-offtermatt Jan 19, 2024
7cf2855
Use key assignment in its step
p-offtermatt Jan 19, 2024
bb722a4
Fix merge
p-offtermatt Jan 19, 2024
36a7041
Revert changes to extraSpells
p-offtermatt Jan 19, 2024
36b5d92
Add check to avoid empty set of running consumers
p-offtermatt Jan 22, 2024
a90e79d
Update tests/mbt/model/ccv.qnt
p-offtermatt Jan 24, 2024
dc62877
Update tests/mbt/model/ccv.qnt
p-offtermatt Jan 24, 2024
ec67531
Start adapting spec to pss
p-offtermatt Jan 24, 2024
33ace67
Continue modelling partial set security
p-offtermatt Jan 25, 2024
e7c220d
Incorporate review comments
p-offtermatt Jan 25, 2024
7377eec
Refactor keyAssignmentReplacement
p-offtermatt Jan 26, 2024
b7aca7c
Start refactoring a few things
p-offtermatt Jan 26, 2024
b36bf35
Refactor, take into account comments
p-offtermatt Jan 26, 2024
36867d5
Refactor utility functions to type module
p-offtermatt Jan 26, 2024
5aec102
Minor refactor
p-offtermatt Jan 26, 2024
10c2a43
Merge branch 'ph/mbt-keyassignment' into ph/mbt-pss
p-offtermatt Jan 26, 2024
f60d715
Refactor for PSS
p-offtermatt Jan 26, 2024
69baecc
Merge branch 'main' into ph/mbt-pss
p-offtermatt Jan 29, 2024
be01ecb
Merge branch 'main' into ph/mbt-pss
p-offtermatt Jan 29, 2024
fcf275f
Fix merge
p-offtermatt Jan 29, 2024
a4a17c8
Refactor start/stop consumers
p-offtermatt Jan 29, 2024
12c752b
Start integrating partial set security
p-offtermatt Jan 31, 2024
43c1053
Refine PSS model
p-offtermatt Feb 5, 2024
37d43be
Refine invariant
p-offtermatt Feb 5, 2024
89420f9
Add sanity checks
p-offtermatt Feb 5, 2024
f064ec2
Add PSS to README
p-offtermatt Feb 5, 2024
21852a1
Add model analysis make target
p-offtermatt Feb 5, 2024
7f57098
Update tests/mbt/model/libraries/extraSpells.qnt
p-offtermatt Feb 6, 2024
f91b909
Add another test for sortedListInsert
p-offtermatt Feb 6, 2024
9da2959
Merge branch 'ph/mbt-pss' of https://github.com/cosmos/interchain-sec…
p-offtermatt Feb 6, 2024
ad1b555
Unify comments for __cmp
p-offtermatt Feb 6, 2024
8d65f07
Reformulate top N comment
p-offtermatt Feb 6, 2024
cf3cb18
Revert map order, better naming for possible values
p-offtermatt Feb 6, 2024
5f0fb0e
Rename e -> d
p-offtermatt Feb 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ test-trace:
verify-models:
quint test tests/mbt/model/ccv_test.qnt;\
quint test tests/mbt/model/ccv_model.qnt;\
quint test tests/mbt/model/ccv_pss_test.qnt;\
quint run --invariant "all{ValidatorUpdatesArePropagatedInv,ValidatorSetHasExistedInv,SameVscPacketsInv,MatureOnTimeInv,EventuallyMatureOnProviderInv}" tests/mbt/model/ccv_model.qnt --max-steps 200 --max-samples 200;\
quint run --invariant "all{ValidatorUpdatesArePropagatedKeyAssignmentInv,ValidatorSetHasExistedKeyAssignmentInv,SameVscPacketsKeyAssignmentInv,MatureOnTimeInv,EventuallyMatureOnProviderInv,KeyAssignmentRulesInv}" tests/mbt/model/ccv_model.qnt --step stepKeyAssignment --max-steps 200 --max-samples 200

Expand Down
17 changes: 12 additions & 5 deletions tests/mbt/model/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ To run with key assignment, specify the step flag: `--step stepKeyAssignment`.

KeyAssignment also needs some different invariants, see below.

#### Partial Set Security

To run with Partial Set Security, specify the step flag `--step stepBoundedDriftKeyAndPSS`.
This runs both PSS and Key Assignment.
It also requires running with `ccv_boundeddrift.qnt`, see below.

#### ccv_boundeddrift.qnt
This state machine layer is more restricted to generate more interesting traces:
* It never allows consumer chains to drift more than `MaxDrift` time apart from each other.
Expand All @@ -75,10 +81,7 @@ traces are not very useful for testing.

To run unit tests, run
```
quint test ccv_test.qnt
```
and
```
quint test ccv_test.qnt;
quint test ccv_model.qnt
```

Expand Down Expand Up @@ -131,4 +134,8 @@ The available sanity checks are:
- CanSendVscPackets
- CanSendVscMaturedPackets
- CanAssignConsumerKey (only with `--step stepKeyAssignment`)
- CanHaveConsumerAddresses (only with `--step stepKeyAssignment`)
- CanHaveConsumerAddresses (only with `--step stepKeyAssignment`)
- CanOptIn (only with `--step stepBoundedDriftKeyAndPSS` on `ccv_boundeddrift.qnt`)
- CanOptOut (only with `--step stepBoundedDriftKeyAndPSS` on `ccv_boundeddrift.qnt`)
- CanFailOptOut (only with `--step stepBoundedDriftKeyAndPSS` on `ccv_boundeddrift.qnt`)
- CanHaveOptIn (only with `--step stepBoundedDriftKeyAndPSS` on `ccv_boundeddrift.qnt`)
117 changes: 81 additions & 36 deletions tests/mbt/model/ccv.qnt
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,18 @@ module ccv_types {
consumerAddrsToPrune: Chain -> VscId -> List[ConsumerAddr],

// For every sent VSCPacket, stores the key assignments that were applied to send it.
keyAssignmentsForVSCPackets: VscId -> (Chain -> (Node -> ConsumerAddr))
keyAssignmentsForVSCPackets: VscId -> (Chain -> (Node -> ConsumerAddr)),

// For each consumer chain,
// stores the set of validators that are opted into running the chain.
optedInVals: Chain -> Set[Node],

// for each consumer, stores the top N for that consumer.
// The top N% of the validator set by voting power
// is obliged to run a topN chain.
// If the chain is a pure opt-in chain (where noone is forced to run it),
// this is 0.
topNByConsumer: Chain -> int,
}

// utility function: returns a provider state that is initialized minimally.
Expand All @@ -138,6 +149,8 @@ module ccv_types {
consumerAddrToValidator: Map(),
consumerAddrsToPrune: Map(),
keyAssignmentsForVSCPackets: Map(),
optedInVals: Map(),
topNByConsumer: Map(),
consumersWithAddrAssignmentChangesInThisBlock: Set()
}

Expand Down Expand Up @@ -226,6 +239,33 @@ module ccv_types {
// 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"

// A record that keeps the information needed to add a new consumer.
// In particular, holds:
// the chain name/identifier,
// and the top N factor for the chain.
type ConsumerAdditionMsg = {
chain: Chain,
topN: int
}

// Creates a new ConsumerAdditionMsg with a given top N.
pure def NewTopNConsumer(chain: Chain, topN: int): ConsumerAdditionMsg = {
{
chain: chain,
topN: topN
}
}

// Creates a new ConsumerAdditionMsg with topN = 0.
pure def NewOptInConsumer(chain: Chain): ConsumerAdditionMsg = {
NewTopNConsumer(chain, 0)
}

// Creates a new ConsumerAdditionMsg with top N = 100%.
pure def NewFullConsumer(chain: Chain): ConsumerAdditionMsg = {
NewTopNConsumer(chain, 100)
}
}

module ccv {
Expand All @@ -246,6 +286,7 @@ module ccv {
import Time.* from "./libraries/Time"
import extraSpells.* from "./libraries/extraSpells"
import ccv_types.*
import ccv_pss.* from "./ccv_pss"
import ccv_utils.* from "./ccv_utils"


Expand Down Expand Up @@ -413,41 +454,26 @@ module ccv {
// i.e. the timestamp for the next block is oldTimestamp + timeAdvancement
timeAdvancement: Time,
// a set of consumers that were not consumers before, but should be set to running now.
consumersToStart: Set[Chain],
consumersToStart: Set[ConsumerAdditionMsg],
// 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 = {
val currentProviderState = currentState.providerState
val curValSet = currentProviderState.chainState.currentValidatorSet

// check for vsc timeouts
val timedOutConsumers = getRunningConsumers(currentProviderState).filter(
consumer =>
val res = TimeoutDueToVscTimeout(currentState, consumer, VscTimeout)
res._1
)

// for each consumer chain, apply the key assignment to the current validator set
val currentValSets = ConsumerChains.mapBy(
(consumer) =>
currentProviderState.applyKeyAssignmentToValSet(
consumer,
currentProviderState.chainState.currentValidatorSet
)
)
// store the current validator set with the key assignments applied in the history
val newKeyAssignedValSetHistory = currentValSets.keys().mapBy(
(consumer) =>
currentProviderState.keyAssignedValSetHistory
.getOrElse(consumer, List()) // get the existing history (empty list if no history yet)
.prepend(currentValSets.get(consumer)) // prepend the current validator set with key assignments applied
)

// run the shared core chainState logic
val newChainState = currentProviderState.chainState.endAndBeginBlockShared(timeAdvancement)
val providerStateAfterTimeAdvancement =
{...currentProviderState, chainState: newChainState, keyAssignedValSetHistory: newKeyAssignedValSetHistory}
{...currentProviderState, chainState: newChainState}
val tmpState = currentState.with(
"providerState", providerStateAfterTimeAdvancement
)
Expand All @@ -462,43 +488,62 @@ module ccv {


// start/stop chains
val res = providerStateAfterSending.consumerStatus.StartStopConsumers(
val res = providerStateAfterSending.StartStopConsumers(
consumersToStart,
consumersToStop,
timedOutConsumers
)
val newConsumerStatus = res._1
val providerStateAfterConsumerAdvancement = res._1.with("providerValidatorSetChangedInThisBlock", false)
val err = res._2
val providerStateAfterConsumerAdvancement = providerStateAfterSending.with(
"consumerStatus", newConsumerStatus
).with(
"providerValidatorSetChangedInThisBlock", false
)

val consumerAdditions = consumersToStart.map(consumer => consumer.chain)

// for each running consumer chain, opt in validators that are in the top N
val providerStateAfterPSS = providerStateAfterConsumerAdvancement.endBlockPSS()

if (err != "") {
Err(err)
} else {
// for each consumer we just set to running, set its initial validator set to be the current one on the provider...
val valSet = providerStateAfterConsumerAdvancement.chainState.currentValidatorSet
// for each consumer chain, apply the key assignment to the current validator set
val currentValSets = getRunningConsumers(providerStateAfterPSS).mapBy(
(consumer) =>
providerStateAfterPSS.applyKeyAssignmentToValSet(
consumer,
// get the validator set after partial set security has been applied
GetPSSValidatorSet(providerStateAfterPSS, curValSet, consumer)
)
)

// store the current validator set with the key assignments applied in the history
val newKeyAssignedValSetHistory = currentValSets.keys().mapBy(
(consumer) =>
providerStateAfterPSS.keyAssignedValSetHistory
.getOrElse(consumer, List()) // get the existing history (empty list if no history yet)
.prepend(currentValSets.get(consumer)) // prepend the current validator set with key assignments applied
)

val providerStateAfterStoringValSets = providerStateAfterPSS.with(
"keyAssignedValSetHistory", newKeyAssignedValSetHistory
)

val newConsumerStateMap =
tmpState.consumerStates.keys().mapBy(
(consumer) =>
if (consumersToStart.contains(consumer)) {
// ...modified by the key assignments for the consumer
val consValSet = applyKeyAssignmentToValSet(providerStateAfterConsumerAdvancement, consumer, valSet)
if (consumerAdditions.contains(consumer)) {
val currentConsumerState: ConsumerState = tmpState.consumerStates.get(consumer)
// correctly set the state for the new consumer
val newConsumerState: ConsumerState = currentConsumerState.with(
"chainState", currentConsumerState.chainState.with(
"currentValidatorSet", consValSet
"currentValidatorSet", currentValSets.get(consumer)
).with(
"votingPowerHistory",
List(consValSet)
List(currentValSets.get(consumer))
).with(
"lastTimestamp",
providerStateAfterConsumerAdvancement.chainState.lastTimestamp
providerStateAfterStoringValSets.chainState.lastTimestamp
).with(
"runningTimestamp",
providerStateAfterConsumerAdvancement.chainState.runningTimestamp
providerStateAfterStoringValSets.chainState.runningTimestamp
)
)
newConsumerState
Expand All @@ -507,7 +552,7 @@ module ccv {
}
)
val newState = tmpState.with(
"providerState", providerStateAfterConsumerAdvancement
"providerState", providerStateAfterStoringValSets
).with(
"consumerStates", newConsumerStateMap
)
Expand Down
51 changes: 39 additions & 12 deletions tests/mbt/model/ccv_boundeddrift.qnt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module ccv_boundeddrift {
import ccv from "ccv"
import Time.* from "./libraries/Time"
import extraSpells.* from "./libraries/extraSpells"
import ccv_pss_model.* from "ccv_pss_model"


// The boundeddrift module has its own step function.
Expand Down Expand Up @@ -60,39 +61,65 @@ module ccv_boundeddrift {
stepCommon, // allow actions that do not influence time

// advance a block for a consumer
all {
runningConsumers.size() > 0, // ensure there is a running consumer, otherwise this action does not make sense
nondet chain = runningConsumers.oneOf()
val maxAdv = findMaxTimeAdvancement(GetChainState(chain), GetOtherChainStates(chain), maxDrift)
val possibleAdvancements = timeAdvancements.filter(t => t <= maxAdv)
all {
possibleAdvancements.size() > 0, // ensure there is a possible advancement, otherwise this action does not make sense
nondet timeAdvancement = possibleAdvancements.oneOf()
EndAndBeginBlockForConsumer(chain, timeAdvancement),
}
},
stepBoundedDriftConsumer,

// advance a block for the provider
stepBoundedDriftProvider
}

action stepBoundedDriftProvider: bool = {
stepBoundedDriftProvider_helper(allFullConsumers)
}

action stepBoundedDriftProviderPSS: bool = {
stepBoundedDriftProvider_helper(variousPossibleTopN)
}

// As an argument, takes a function that, when invoked, gives a top N value to use for a new consumer chain.
action stepBoundedDriftProvider_helper(topNOracle: Set[int]): bool = {
val maxAdv = findMaxTimeAdvancement(GetChainState(Ccvt::PROVIDER_CHAIN), GetOtherChainStates(Ccvt::PROVIDER_CHAIN), maxDrift)
val possibleAdvancements = timeAdvancements.filter(t => t <= maxAdv)
all {
possibleAdvancements.size() > 0, // ensure there is a possible advancement, otherwise this action does not make sense
// advance a block for the provider
val consumerStatus = currentState.providerState.consumerStatus
nondet consumersToStart = oneOf(nonConsumers.powerset())
nondet topN = oneOf(topNOracle)
nondet consumerAdditions = consumersToStart.map(c => Ccvt::NewTopNConsumer(c, topN))
// make it so we stop consumers only with small likelihood:
nondet stopConsumersRand = oneOf(1.to(100))
nondet consumersToStop = if (stopConsumersRand <= consumerStopChance) oneOf(runningConsumers.powerset()) else Set()
nondet timeAdvancement = oneOf(possibleAdvancements)
EndAndBeginBlockForProvider(timeAdvancement, consumersToStart, consumersToStop),
EndAndBeginBlockForProvider(timeAdvancement, consumerAdditions, consumersToStop),
}
}

action stepBoundedDriftConsumer = all {
runningConsumers.size() > 0, // ensure there is a running consumer, otherwise this action does not make sense
nondet chain = runningConsumers.oneOf()
val maxAdv = findMaxTimeAdvancement(GetChainState(chain), GetOtherChainStates(chain), maxDrift)
val possibleAdvancements = timeAdvancements.filter(t => t <= maxAdv)
all {
possibleAdvancements.size() > 0, // ensure there is a possible advancement, otherwise this action does not make sense
nondet timeAdvancement = possibleAdvancements.oneOf()
EndAndBeginBlockForConsumer(chain, timeAdvancement),
}
}

action stepBoundedDriftKeyAssignment = any {
stepBoundedDrift,
nondetKeyAssignment,
}

action stepBoundedDriftKeyAndPSS = any {
stepCommon,
stepBoundedDriftProviderPSS,
stepBoundedDriftConsumer,
nondetKeyAssignment,
StepOptIn,
StepOptOut,
}

// INVARIANT
// The maxDrift between chains is never exceeded.
// This *should* be ensured by the step function.
Expand Down
Loading
Loading