From 647af0011f8d3c3ac9101d4b34a6da33b8b6fc3b Mon Sep 17 00:00:00 2001 From: kirdatatjana Date: Thu, 7 Nov 2024 18:04:21 +0100 Subject: [PATCH 01/11] Added quint example for Algorithm 15 --- .../ConsensusAlgorithm/ConsensusAlg.qnt | 255 ++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt diff --git a/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt b/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt new file mode 100644 index 000000000..5ec88b227 --- /dev/null +++ b/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt @@ -0,0 +1,255 @@ +// -*- mode: Bluespec; -*- + + /************************************************************************************************ + (* Quint Specification for Algorithm 15: Consensus Algorithm in the Presence of Crash Failures *) + (* This specification is derived from book "Distributed Computing: Fundamentals, Simulations, *) + (* and Advanced Topics" (Second Edition) by Hagit Attiya and Jennifer Welch, specifically from *) + (* Chapter 5, page 93. *) + (* http://lib.ysu.am/disciplines_bk/c95d04e111f3e28ae4cc589bfda1e18b.pdf *) + ************************************************************************************************/ +module ConsensusAlg { + + const N : int + const F : int + const actualFaults : int + + type Proc = int + type Value = int + type Round = int + type Message = { sender: Proc, values: Set[Value] } + + type Decision = + | None + | Some(Value) + + type LocalState = { + V: Set[Value], + k: Round, + y: Decision, + S: Set[Set[Value]], + x: Value + } + + // + // Local functions + // + + def getFirst(s: Set[int]): int = s.fold(0, (_, v) => v) + + def minValue(values: Set[int]): int = { val initial = getFirst(values) values.fold(initial, (min, v) => if (v < min) v else min) } + + pure def compute(s: LocalState): LocalState = { + + val newV = s.V.union(flatten(s.S)) + val newK = s.k + 1 + val newY = if (newK == F + 1) Some(minValue(newV)) else s.y + + { + V: newV, + k: newK, + y: newY, + S: Set(), + x: s.x + } + } + + // + // State machine + // + + val Procs: Set[int] = 1.to(N - 1) + + var round: Round + var correctProcsMessages: Set[Message] + var crashedProcsMessages: Set[Message] + var procState: int -> LocalState + var crashed: Set[int] + var newlyCrashed: Set[int] + + // + // Invariants + // + + def agreement = Procs.exclude(crashed).forall(p => + Procs.exclude(crashed).forall(q => + ( procState.get(p).y != None and procState.get(q).y != None) implies + procState.get(p).y == procState.get(q).y)) + + /// If all processes have the same initial value v, then this must be the only decision value + def validity = + val allXValues = Procs.map(p => procState.get(p).x) + if (allXValues.size() == 1) + allXValues.forall(v => + Procs.exclude(crashed).forall(p => + match procState.get(p).y { + | Some(y) => y == v + | None => true + })) + else + true + + // + // Steps + // + + action init = all { + nondet initialValues = Procs.setOfMaps(Set(1, 2, 3)).oneOf() + procState' = Procs.mapBy(i => { + V: Set(initialValues.get(i)), + k: 1, + y: None, + S: Set(), + x: initialValues.get(i) + }), + round' = 1, + correctProcsMessages' = Set(), + crashed' = Set(), + newlyCrashed' = Set(), + crashedProcsMessages' = Set() + } + + action initializeProcsStateWithDistinctValues = all { + procState' = Procs.mapBy(i => { + V: Set(i), + k: 1, + y: None, + S: Set(), + x: i + }), + round' = 1, + correctProcsMessages' = Set(), + crashed' = Set(), + newlyCrashed' = Set(), + crashedProcsMessages' = Set() + } + + action sendMessages = all { + if (round <= F + 1) + correctProcsMessages' = Procs.exclude(crashed).exclude(newlyCrashed).map(p => { + sender: p, + values: procState.get(p).V + }) + else + correctProcsMessages' = correctProcsMessages, + if(newlyCrashed.size() > 0){ + crashedProcsMessages' = newlyCrashed.map(p => { + sender: p, + values: procState.get(p).V + }) + } else{ + crashedProcsMessages' = crashedProcsMessages + }, + round' = round, + procState' = procState, + crashed' = crashed, + newlyCrashed' = newlyCrashed + } + + action crashProcess(p) = all { + newlyCrashed' = Set(p), + crashed' = crashed, + round' = round, + procState' = procState, + correctProcsMessages' = correctProcsMessages, + crashedProcsMessages' = crashedProcsMessages + } + + action randCrash = all { + if (actualFaults - crashed.size() > 0) { + nondet newCrashCount = oneOf(1.to(actualFaults - crashed.size())) + newlyCrashed' = Procs.exclude(crashed).powerset().filter(s => s.size() == newCrashCount).oneOf() + } else { + newlyCrashed' = newlyCrashed + }, + crashed' = crashed, + round' = round, + procState' = procState, + correctProcsMessages' = correctProcsMessages, + crashedProcsMessages' = crashedProcsMessages + } + + action receiveMessages = all { + round' = round, + correctProcsMessages' = Set(), + crashedProcsMessages' = Set(), + val newCorrectValues: Set[Set[Value]] = correctProcsMessages.map(m => m.values) + if (crashedProcsMessages.size() == 0){ + procState' = procState.keys().mapBy(p => {... procState.get(p), S:newCorrectValues}) + } + else{ + val newCrashedProcsValues: Set[Set[Value]] = crashedProcsMessages.map(m => m.values) + nondet crashedMessagesRecived = Procs.setOfMaps(newCrashedProcsValues).union(Set()).oneOf()// for each process we pick from which newly crashed they receive a message + procState' = procState.keys().mapBy(p => { ... procState.get(p), S: newCorrectValues.union(Set(crashedMessagesRecived.get(p))) }) + }, + crashed' = crashed, + newlyCrashed' = newlyCrashed, + } + + action computeAction = all { + correctProcsMessages' = Set(), + procState' = procState.keys().mapBy(p => compute(procState.get(p))), + round' = round + 1, + crashed' = crashed.union(newlyCrashed), + newlyCrashed' = Set(), + crashedProcsMessages' =Set() + } + + /// the set s of correct processes don't receive the messages from newlycrashed + action receiveMessage(s) = all { + round' = round, + correctProcsMessages' = Set(), + crashedProcsMessages' = Set(), + val newCorrectValues: Set[Set[Value]] = correctProcsMessages.map(m => m.values) + val newCrashedProcsValues: Set[Set[Value]] = crashedProcsMessages.map(m => m.values) + procState' = procState.keys().mapBy(p => + { ...procState.get(p), + S: if (s.contains(p)) + newCorrectValues + else + newCorrectValues.union(newCrashedProcsValues) + } + ), + crashed' = crashed, + newlyCrashed' = newlyCrashed, + } + + action step = any{ + randCrash.then(sendMessages).then(receiveMessages).then(computeAction) + } + + /// we crash process p, and the set s does not receive p's messages + action stepHidePsMessagesFromS(p,s) = any{ + crashProcess(p).then(sendMessages).then(receiveMessage(s)).then(computeAction) + } + +} + +module properValues { + //quint run --main=properValues ConsensusAlg.qnt + import ConsensusAlg(N = 6, F = 1, actualFaults = 1 ).* + + run consensusRunTest = + init + .then((F + 1).reps(_ => step)) + .expect(agreement) + .expect(validity) +} + + +module badValues { + //quint run ConsensusAlg.qnt --main badValues --invariant agreement --max-steps 5 + //quint test --main=badValues ConsensusAlg.qnt + import ConsensusAlg(N = 6, F = 1, actualFaults = 2 ).* + + run consensusRunTest = + init + .then((F + 1).reps(_ => step)) + .expect(validity) + + run consensusDisagreementTest = + initializeProcsStateWithDistinctValues + .then(stepHidePsMessagesFromS(1, Set(2))) + .then(stepHidePsMessagesFromS(3, Set(4))) + .expect(not(agreement)) + +} From 66da17154f660b5890637225556f2c7e1021580a Mon Sep 17 00:00:00 2001 From: kirdatatjana Date: Thu, 7 Nov 2024 18:09:53 +0100 Subject: [PATCH 02/11] Added initial implementation of Algorithm 18 --- .../KSetAgreementConsensus.qnt | 264 ++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt diff --git a/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt b/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt new file mode 100644 index 000000000..65ac6f322 --- /dev/null +++ b/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt @@ -0,0 +1,264 @@ +// -*- mode: Bluespec; -*- + + /****************************************************************************************************** + (* Quint Specification for Algorithm 18: K-set Consensus Algorithm in the Presence of Crash Failures *) + (* This specification is derived from book "Distributed Computing: Fundamentals, Simulations, and *) + (* Advanced Topics" (Second Edition) by Hagit Attiya and Jennifer Welch, specifically from Chapter 5, *) + (* page 120. *) + (* http://lib.ysu.am/disciplines_bk/c95d04e111f3e28ae4cc589bfda1e18b.pdf *) + *******************************************************************************************************/ +module KSetAgreementConsensus { + + const N : int + const F : int + const actualFaults : int + const K : int + + type Proc = int + type Value = int + type Round = int + type Message = { sender: Proc, values: Set[Value] } + + type Decision = + | None + | Some(Value) + + type LocalState = { + V: Set[Value], + r: Round, + y: Decision, + S: Set[Set[Value]], + x: Value + } + + // + // Local functions + // + + def getFirst(s: Set[int]): int = s.fold(0, (_, v) => v) + + def minValue(values: Set[int]): int = { val initial = getFirst(values) values.fold(initial, (min, v) => if (v < min) v else min) } + + pure def compute(s: LocalState): LocalState = { + + val newV = s.V.union(flatten(s.S)) + val newK = s.r + 1 + val newY = if (newK == F/K + 1) Some(minValue(newV)) else s.y + + { + V: newV, + r: newK, + y: newY, + S: Set(), + x: s.x + } + } + + // + // State machine + // + + val Procs: Set[int] = 1.to(N - 1) + + var round: Round + var correctProcsMessages: Set[Message] + var crashedProcsMessages: Set[Message] + var procState: int -> LocalState + var crashed: Set[int] + var newlyCrashed: Set[int] + + // + // Invariants + // + + def agreement = Procs.exclude(crashed).forall(p => + Procs.exclude(crashed).forall(q => + ( procState.get(p).y != None and procState.get(q).y != None) implies + procState.get(p).y == procState.get(q).y)) + + def kSetAgreement = { + // Get all decided values (excluding None) and ensure they are unique + val decidedValues = Procs.exclude(crashed).map(p => procState.get(p).y).filter(v => v != None) + + // Check that number of unique decided values is at most K + decidedValues.size() <= K + } + /// If all processes have the same initial value v, then this must be the only decision value + def validity = + val allXValues = Procs.map(p => procState.get(p).x) + if (allXValues.size() == 1) + allXValues.forall(v => + Procs.exclude(crashed).forall(p => + match procState.get(p).y { + | Some(y) => y == v + | None => true + })) + else + true + + // + // Steps + // + + action init = all { + nondet initialValues = Procs.setOfMaps(Set(1, 2, 3)).oneOf() + procState' = Procs.mapBy(i => { + V: Set(initialValues.get(i)), + r: 1, + y: None, + S: Set(), + x: initialValues.get(i) + }), + round' = 1, + correctProcsMessages' = Set(), + crashed' = Set(), + newlyCrashed' = Set(), + crashedProcsMessages' = Set() + } + + action initializeProcsStateWithDistinctValues = all { + procState' = Procs.mapBy(i => { + V: Set(i), + r: 1, + y: None, + S: Set(), + x: i + }), + round' = 1, + correctProcsMessages' = Set(), + crashed' = Set(), + newlyCrashed' = Set(), + crashedProcsMessages' = Set() + } + + action sendMessages = all { + if (round <= F/K + 1) + correctProcsMessages' = Procs.exclude(crashed).exclude(newlyCrashed).map(p => { + sender: p, + values: procState.get(p).V + }) + else + correctProcsMessages' = correctProcsMessages, + if(newlyCrashed.size() > 0){ + crashedProcsMessages' = newlyCrashed.map(p => { + sender: p, + values: procState.get(p).V + }) + } else{ + crashedProcsMessages' = crashedProcsMessages + }, + round' = round, + procState' = procState, + crashed' = crashed, + newlyCrashed' = newlyCrashed + } + + action crashProcess(p) = all { + newlyCrashed' = Set(p), + crashed' = crashed, + round' = round, + procState' = procState, + correctProcsMessages' = correctProcsMessages, + crashedProcsMessages' = crashedProcsMessages + } + + action randCrash = all { + if (actualFaults - crashed.size() > 0) { + nondet newCrashCount = oneOf(1.to(actualFaults - crashed.size())) + newlyCrashed' = Procs.exclude(crashed).powerset().filter(s => s.size() == newCrashCount).oneOf() + } else { + newlyCrashed' = newlyCrashed + }, + crashed' = crashed, + round' = round, + procState' = procState, + correctProcsMessages' = correctProcsMessages, + crashedProcsMessages' = crashedProcsMessages + } + + action receiveMessages = all { + round' = round, + correctProcsMessages' = Set(), + crashedProcsMessages' = Set(), + val newCorrectValues: Set[Set[Value]] = correctProcsMessages.map(m => m.values) + if (crashedProcsMessages.size() == 0){ + procState' = procState.keys().mapBy(p => {... procState.get(p), S:newCorrectValues}) + } + else{ + val newCrashedProcsValues: Set[Set[Value]] = crashedProcsMessages.map(m => m.values) + nondet crashedMessagesRecived = Procs.setOfMaps(newCrashedProcsValues).union(Set()).oneOf()// for each process we pick from which newly crashed they receive a message + procState' = procState.keys().mapBy(p => { ... procState.get(p), S: newCorrectValues.union(Set(crashedMessagesRecived.get(p))) }) + }, + crashed' = crashed, + newlyCrashed' = newlyCrashed, + } + + action computeAction = all { + correctProcsMessages' = Set(), + procState' = procState.keys().mapBy(p => compute(procState.get(p))), + round' = round + 1, + crashed' = crashed.union(newlyCrashed), + newlyCrashed' = Set(), + crashedProcsMessages' =Set() + } + + /// the set s of correct processes don't receive the messages from newlycrashed + action receiveMessage(s) = all { + round' = round, + correctProcsMessages' = Set(), + crashedProcsMessages' = Set(), + val newCorrectValues: Set[Set[Value]] = correctProcsMessages.map(m => m.values) + val newCrashedProcsValues: Set[Set[Value]] = crashedProcsMessages.map(m => m.values) + procState' = procState.keys().mapBy(p => + { ...procState.get(p), + S: if (s.contains(p)) + newCorrectValues + else + newCorrectValues.union(newCrashedProcsValues) + } + ), + crashed' = crashed, + newlyCrashed' = newlyCrashed, + } + + action step = any{ + randCrash.then(sendMessages).then(receiveMessages).then(computeAction) + } + + /// we crash process p, and the set s does not receive p's messages + action stepHidePsMessagesFromS(p,s) = any{ + crashProcess(p).then(sendMessages).then(receiveMessage(s)).then(computeAction) + } + +} + +module properValues { + //quint run --main=properValues KSetAgreementConsensus.qnt + import KSetAgreementConsensus(N = 6, F = 4, actualFaults = 2, K = 2 ).* + + run consensusRunTest = + init + .then((F/K + 1).reps(_ => step)) + .expect(kSetAgreement) + .expect(validity) +} + + +module badValues { + //quint run KSetAgreementConsensus.qnt --main badValues --invariant kSetAgreement --max-steps 5 + //quint test --main=badValues KSetAgreementConsensus.qnt + import KSetAgreementConsensus(N = 8, F = 4, actualFaults = 5, K = 2 ).* + + run consensusRunTest = + init + .then((F/K + 1).reps(_ => step)) + .expect(validity) + + run consensusDisagreementTest = + initializeProcsStateWithDistinctValues + .then(stepHidePsMessagesFromS(1, Set(2,4))) + .then(stepHidePsMessagesFromS(3, Set(4))) + .then(stepHidePsMessagesFromS(5, Set(6))) + .expect(not(kSetAgreement)) + +} From 848cbaa869046f4ae9297860af92c153eed31481 Mon Sep 17 00:00:00 2001 From: Tatjana Date: Wed, 13 Nov 2024 23:48:49 +0100 Subject: [PATCH 03/11] Added receiveMessagesWithHiding --- .../KSetAgreementConsensus.qnt | 74 ++++++++++++++++--- 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt b/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt index 65ac6f322..1c06d4968 100644 --- a/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt +++ b/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt @@ -31,6 +31,11 @@ module KSetAgreementConsensus { x: Value } + type HiddenProcs = { + hiddenProcs: Set[Proc], // processes whose messages are hidden + targetProc: Proc // process from which messages are hidden + } + // // Local functions // @@ -176,6 +181,16 @@ module KSetAgreementConsensus { crashedProcsMessages' = crashedProcsMessages } + action crashProcessesFromConfig(hidingConfigs) = all { + // Collect all processes that need to be crashed from all hiding configurations + newlyCrashed' = flatten(hidingConfigs.map(config => config.hiddenProcs)), + crashed' = crashed, + round' = round, + procState' = procState, + correctProcsMessages' = correctProcsMessages, + crashedProcsMessages' = crashedProcsMessages + } + action receiveMessages = all { round' = round, correctProcsMessages' = Set(), @@ -221,20 +236,53 @@ module KSetAgreementConsensus { newlyCrashed' = newlyCrashed, } + action receiveMessagesWithHiding(hidingConfigs) = all { + round' = round, + correctProcsMessages' = Set(), + crashedProcsMessages' = Set(), + val newCorrectValues: Set[Set[Value]] = correctProcsMessages.map(m => m.values) + val newCrashedProcsValues: Set[Set[Value]] = crashedProcsMessages.map(m => m.values) + procState' = procState.keys().mapBy(p => { + // Find if this process is a target in any hiding config + val configForThisProc = hidingConfigs.filter(config => config.targetProc == p) + + val processedValues = + if (configForThisProc.size() > 0) { + // Get all processes that should be hidden from this process + val hiddenFromThis = flatten(configForThisProc.map(config => config.hiddenProcs)) + + // Filter out messages from hidden processes + val allowedCrashedMessages = crashedProcsMessages + .filter(m => not(hiddenFromThis.contains(m.sender))) + .map(m => m.values) + + newCorrectValues.union(allowedCrashedMessages) + } else { + // If process is not in hiding configs, it receives all messages + newCorrectValues.union(newCrashedProcsValues) + } + + { ...procState.get(p), S: processedValues } + }), + crashed' = crashed, + newlyCrashed' = newlyCrashed, + } + action step = any{ randCrash.then(sendMessages).then(receiveMessages).then(computeAction) } - /// we crash process p, and the set s does not receive p's messages - action stepHidePsMessagesFromS(p,s) = any{ - crashProcess(p).then(sendMessages).then(receiveMessage(s)).then(computeAction) - } + action stepWithMultipleHiding(hidingConfigs) = + crashProcessesFromConfig(hidingConfigs) + .then(sendMessages) + .then(receiveMessagesWithHiding(hidingConfigs)) + .then(computeAction) } module properValues { //quint run --main=properValues KSetAgreementConsensus.qnt - import KSetAgreementConsensus(N = 6, F = 4, actualFaults = 2, K = 2 ).* + import KSetAgreementConsensus(N = 8, F = 3, actualFaults = 3, K = 2).* run consensusRunTest = init @@ -247,18 +295,26 @@ module properValues { module badValues { //quint run KSetAgreementConsensus.qnt --main badValues --invariant kSetAgreement --max-steps 5 //quint test --main=badValues KSetAgreementConsensus.qnt - import KSetAgreementConsensus(N = 8, F = 4, actualFaults = 5, K = 2 ).* + import KSetAgreementConsensus(N = 8, F = 3, actualFaults = 4, K = 2).* run consensusRunTest = init .then((F/K + 1).reps(_ => step)) .expect(validity) + // Test scenario where processes decide on different values: + // - Process 6 doesn't receive from 1,2 => decides 3 + // - Process 5 doesn't receive from 1 => decides 2 + // - Process 8 doesn't receive from 3 => decides 1 + // - Process 7 doesn't receive from 1,2,3,4 => decides 5 run consensusDisagreementTest = initializeProcsStateWithDistinctValues - .then(stepHidePsMessagesFromS(1, Set(2,4))) - .then(stepHidePsMessagesFromS(3, Set(4))) - .then(stepHidePsMessagesFromS(5, Set(6))) + .then((F/K + 1).reps(_ => stepWithMultipleHiding(Set( + { hiddenProcs: Set(1, 2), targetProc: 6 }, + { hiddenProcs: Set(1), targetProc: 5 }, + { hiddenProcs: Set(3), targetProc: 8 }, + { hiddenProcs: Set(1, 2, 3, 4), targetProc: 7 } + )))) .expect(not(kSetAgreement)) } From f26a82625e1869c9fd0ff1c75bf3cb124d2398ac Mon Sep 17 00:00:00 2001 From: bugarela Date: Thu, 14 Nov 2024 09:25:20 -0300 Subject: [PATCH 04/11] Refactoring from group discussion --- .../ConsensusAlgorithm/ConsensusAlg.qnt | 102 ++++--- .../KSetAgreementConsensus.qnt | 268 ++---------------- 2 files changed, 90 insertions(+), 280 deletions(-) diff --git a/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt b/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt index 5ec88b227..a2ed7ebe2 100644 --- a/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt +++ b/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt @@ -12,6 +12,7 @@ module ConsensusAlg { const N : int const F : int const actualFaults : int + const MAX_ROUNDS : int type Proc = int type Value = int @@ -24,12 +25,14 @@ module ConsensusAlg { type LocalState = { V: Set[Value], - k: Round, + r: Round, y: Decision, S: Set[Set[Value]], x: Value } + type Stage = Starting | Sending | Receiving | Computing + // // Local functions // @@ -41,12 +44,12 @@ module ConsensusAlg { pure def compute(s: LocalState): LocalState = { val newV = s.V.union(flatten(s.S)) - val newK = s.k + 1 - val newY = if (newK == F + 1) Some(minValue(newV)) else s.y + val newR = s.r + 1 + val newY = if (newR == MAX_ROUNDS) Some(minValue(newV)) else s.y { V: newV, - k: newK, + r: newR, y: newY, S: Set(), x: s.x @@ -65,6 +68,7 @@ module ConsensusAlg { var procState: int -> LocalState var crashed: Set[int] var newlyCrashed: Set[int] + var stage: Stage // // Invariants @@ -96,7 +100,7 @@ module ConsensusAlg { nondet initialValues = Procs.setOfMaps(Set(1, 2, 3)).oneOf() procState' = Procs.mapBy(i => { V: Set(initialValues.get(i)), - k: 1, + r: 1, y: None, S: Set(), x: initialValues.get(i) @@ -105,13 +109,14 @@ module ConsensusAlg { correctProcsMessages' = Set(), crashed' = Set(), newlyCrashed' = Set(), - crashedProcsMessages' = Set() + crashedProcsMessages' = Set(), + stage' = Starting, } action initializeProcsStateWithDistinctValues = all { procState' = Procs.mapBy(i => { V: Set(i), - k: 1, + r: 1, y: None, S: Set(), x: i @@ -120,30 +125,28 @@ module ConsensusAlg { correctProcsMessages' = Set(), crashed' = Set(), newlyCrashed' = Set(), - crashedProcsMessages' = Set() + crashedProcsMessages' = Set(), } action sendMessages = all { - if (round <= F + 1) - correctProcsMessages' = Procs.exclude(crashed).exclude(newlyCrashed).map(p => { - sender: p, - values: procState.get(p).V - }) - else - correctProcsMessages' = correctProcsMessages, - if(newlyCrashed.size() > 0){ - crashedProcsMessages' = newlyCrashed.map(p => { - sender: p, - values: procState.get(p).V - }) - } else{ - crashedProcsMessages' = crashedProcsMessages - }, + correctProcsMessages' = Procs.exclude(crashed).exclude(newlyCrashed).map(p => { + sender: p, + values: procState.get(p).V + }), + crashedProcsMessages' = + if (newlyCrashed.size() > 0){ + newlyCrashed.map(p => { + sender: p, + values: procState.get(p).V + }) + } else{ + crashedProcsMessages + }, round' = round, procState' = procState, crashed' = crashed, - newlyCrashed' = newlyCrashed - } + newlyCrashed' = newlyCrashed, + } action crashProcess(p) = all { newlyCrashed' = Set(p), @@ -157,7 +160,8 @@ module ConsensusAlg { action randCrash = all { if (actualFaults - crashed.size() > 0) { nondet newCrashCount = oneOf(1.to(actualFaults - crashed.size())) - newlyCrashed' = Procs.exclude(crashed).powerset().filter(s => s.size() == newCrashCount).oneOf() + nondet newlyCrashedProcesses = Procs.exclude(crashed).powerset().filter(s => s.size() == newCrashCount).oneOf() + newlyCrashed' = newlyCrashedProcesses } else { newlyCrashed' = newlyCrashed }, @@ -191,7 +195,7 @@ module ConsensusAlg { round' = round + 1, crashed' = crashed.union(newlyCrashed), newlyCrashed' = Set(), - crashedProcsMessages' =Set() + crashedProcsMessages' = Set() } /// the set s of correct processes don't receive the messages from newlycrashed @@ -213,20 +217,33 @@ module ConsensusAlg { newlyCrashed' = newlyCrashed, } - action step = any{ - randCrash.then(sendMessages).then(receiveMessages).then(computeAction) - } - - /// we crash process p, and the set s does not receive p's messages - action stepHidePsMessagesFromS(p,s) = any{ - crashProcess(p).then(sendMessages).then(receiveMessage(s)).then(computeAction) + action stuttering = all { + round' = round, + correctProcsMessages' = correctProcsMessages, + crashedProcsMessages' = crashedProcsMessages, + procState' = procState, + crashed' = crashed, + newlyCrashed' = newlyCrashed, + stage' = stage, + } + + action step = + if (round > MAX_ROUNDS) { + stuttering + } else { + match stage { + | Starting => all { randCrash, stage' = Sending } + | Sending => all { sendMessages, stage' = Receiving } + | Receiving => all { receiveMessages, stage' = Computing } + | Computing => all { computeAction, stage' = Starting } + } } } module properValues { //quint run --main=properValues ConsensusAlg.qnt - import ConsensusAlg(N = 6, F = 1, actualFaults = 1 ).* + import ConsensusAlg(N = 6, F = 1, actualFaults = 1, MAX_ROUNDS = 2).* run consensusRunTest = init @@ -239,14 +256,19 @@ module properValues { module badValues { //quint run ConsensusAlg.qnt --main badValues --invariant agreement --max-steps 5 //quint test --main=badValues ConsensusAlg.qnt - import ConsensusAlg(N = 6, F = 1, actualFaults = 2 ).* + import ConsensusAlg(N = 6, F = 1, actualFaults = 2, MAX_ROUNDS = 2).* - run consensusRunTest = + run consensusRunTest = init .then((F + 1).reps(_ => step)) - .expect(validity) - - run consensusDisagreementTest = + .expect(validity) + + /// we crash process p, and the set s does not receive p's messages + run stepHidePsMessagesFromS(p,s) = any { + crashProcess(p).then(sendMessages).then(receiveMessage(s)).then(computeAction) + } + + run consensusDisagreementTest = initializeProcsStateWithDistinctValues .then(stepHidePsMessagesFromS(1, Set(2))) .then(stepHidePsMessagesFromS(3, Set(4))) diff --git a/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt b/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt index 1c06d4968..275575ba6 100644 --- a/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt +++ b/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt @@ -8,178 +8,40 @@ (* http://lib.ysu.am/disciplines_bk/c95d04e111f3e28ae4cc589bfda1e18b.pdf *) *******************************************************************************************************/ module KSetAgreementConsensus { + import ConsensusAlg.* from "ConsensusAlg" + export ConsensusAlg.* - const N : int - const F : int - const actualFaults : int const K : int - type Proc = int - type Value = int - type Round = int - type Message = { sender: Proc, values: Set[Value] } - - type Decision = - | None - | Some(Value) - - type LocalState = { - V: Set[Value], - r: Round, - y: Decision, - S: Set[Set[Value]], - x: Value - } - - type HiddenProcs = { - hiddenProcs: Set[Proc], // processes whose messages are hidden - targetProc: Proc // process from which messages are hidden - } - - // - // Local functions - // - - def getFirst(s: Set[int]): int = s.fold(0, (_, v) => v) - - def minValue(values: Set[int]): int = { val initial = getFirst(values) values.fold(initial, (min, v) => if (v < min) v else min) } - - pure def compute(s: LocalState): LocalState = { - - val newV = s.V.union(flatten(s.S)) - val newK = s.r + 1 - val newY = if (newK == F/K + 1) Some(minValue(newV)) else s.y - - { - V: newV, - r: newK, - y: newY, - S: Set(), - x: s.x - } - } - - // - // State machine - // - - val Procs: Set[int] = 1.to(N - 1) - - var round: Round - var correctProcsMessages: Set[Message] - var crashedProcsMessages: Set[Message] - var procState: int -> LocalState - var crashed: Set[int] - var newlyCrashed: Set[int] - - // - // Invariants - // - - def agreement = Procs.exclude(crashed).forall(p => - Procs.exclude(crashed).forall(q => - ( procState.get(p).y != None and procState.get(q).y != None) implies - procState.get(p).y == procState.get(q).y)) - def kSetAgreement = { // Get all decided values (excluding None) and ensure they are unique val decidedValues = Procs.exclude(crashed).map(p => procState.get(p).y).filter(v => v != None) - + // Check that number of unique decided values is at most K decidedValues.size() <= K } - /// If all processes have the same initial value v, then this must be the only decision value - def validity = - val allXValues = Procs.map(p => procState.get(p).x) - if (allXValues.size() == 1) - allXValues.forall(v => - Procs.exclude(crashed).forall(p => - match procState.get(p).y { - | Some(y) => y == v - | None => true - })) - else - true - - // - // Steps - // +} - action init = all { - nondet initialValues = Procs.setOfMaps(Set(1, 2, 3)).oneOf() - procState' = Procs.mapBy(i => { - V: Set(initialValues.get(i)), - r: 1, - y: None, - S: Set(), - x: initialValues.get(i) - }), - round' = 1, - correctProcsMessages' = Set(), - crashed' = Set(), - newlyCrashed' = Set(), - crashedProcsMessages' = Set() - } +module KSetProperValues { + //quint run --main=KSetProperValues KSetAgreementConsensus.qnt + import KSetAgreementConsensus(N = 8, F = 3, actualFaults = 3, K = 2, MAX_ROUNDS = (3/2 + 1)).* - action initializeProcsStateWithDistinctValues = all { - procState' = Procs.mapBy(i => { - V: Set(i), - r: 1, - y: None, - S: Set(), - x: i - }), - round' = 1, - correctProcsMessages' = Set(), - crashed' = Set(), - newlyCrashed' = Set(), - crashedProcsMessages' = Set() - } + run consensusRunTest = + init + .then((F/K + 1).reps(_ => step)) + .expect(kSetAgreement) + .expect(validity) +} - action sendMessages = all { - if (round <= F/K + 1) - correctProcsMessages' = Procs.exclude(crashed).exclude(newlyCrashed).map(p => { - sender: p, - values: procState.get(p).V - }) - else - correctProcsMessages' = correctProcsMessages, - if(newlyCrashed.size() > 0){ - crashedProcsMessages' = newlyCrashed.map(p => { - sender: p, - values: procState.get(p).V - }) - } else{ - crashedProcsMessages' = crashedProcsMessages - }, - round' = round, - procState' = procState, - crashed' = crashed, - newlyCrashed' = newlyCrashed - } +module KSetBadValues { + //quint run KSetAgreementConsensus.qnt --main KSetBadValues --invariant kSetAgreement --max-steps 5 + //quint test --main=KSetBadValues KSetAgreementConsensus.qnt + import KSetAgreementConsensus(N = 8, F = 3, actualFaults = 4, K = 2, MAX_ROUNDS = (3/2 + 1)).* - action crashProcess(p) = all { - newlyCrashed' = Set(p), - crashed' = crashed, - round' = round, - procState' = procState, - correctProcsMessages' = correctProcsMessages, - crashedProcsMessages' = crashedProcsMessages - } - - action randCrash = all { - if (actualFaults - crashed.size() > 0) { - nondet newCrashCount = oneOf(1.to(actualFaults - crashed.size())) - newlyCrashed' = Procs.exclude(crashed).powerset().filter(s => s.size() == newCrashCount).oneOf() - } else { - newlyCrashed' = newlyCrashed - }, - crashed' = crashed, - round' = round, - procState' = procState, - correctProcsMessages' = correctProcsMessages, - crashedProcsMessages' = crashedProcsMessages - } + run consensusRunTest = + init + .then((F/K + 1).reps(_ => step)) + .expect(validity) action crashProcessesFromConfig(hidingConfigs) = all { // Collect all processes that need to be crashed from all hiding configurations @@ -191,51 +53,6 @@ module KSetAgreementConsensus { crashedProcsMessages' = crashedProcsMessages } - action receiveMessages = all { - round' = round, - correctProcsMessages' = Set(), - crashedProcsMessages' = Set(), - val newCorrectValues: Set[Set[Value]] = correctProcsMessages.map(m => m.values) - if (crashedProcsMessages.size() == 0){ - procState' = procState.keys().mapBy(p => {... procState.get(p), S:newCorrectValues}) - } - else{ - val newCrashedProcsValues: Set[Set[Value]] = crashedProcsMessages.map(m => m.values) - nondet crashedMessagesRecived = Procs.setOfMaps(newCrashedProcsValues).union(Set()).oneOf()// for each process we pick from which newly crashed they receive a message - procState' = procState.keys().mapBy(p => { ... procState.get(p), S: newCorrectValues.union(Set(crashedMessagesRecived.get(p))) }) - }, - crashed' = crashed, - newlyCrashed' = newlyCrashed, - } - - action computeAction = all { - correctProcsMessages' = Set(), - procState' = procState.keys().mapBy(p => compute(procState.get(p))), - round' = round + 1, - crashed' = crashed.union(newlyCrashed), - newlyCrashed' = Set(), - crashedProcsMessages' =Set() - } - - /// the set s of correct processes don't receive the messages from newlycrashed - action receiveMessage(s) = all { - round' = round, - correctProcsMessages' = Set(), - crashedProcsMessages' = Set(), - val newCorrectValues: Set[Set[Value]] = correctProcsMessages.map(m => m.values) - val newCrashedProcsValues: Set[Set[Value]] = crashedProcsMessages.map(m => m.values) - procState' = procState.keys().mapBy(p => - { ...procState.get(p), - S: if (s.contains(p)) - newCorrectValues - else - newCorrectValues.union(newCrashedProcsValues) - } - ), - crashed' = crashed, - newlyCrashed' = newlyCrashed, - } - action receiveMessagesWithHiding(hidingConfigs) = all { round' = round, correctProcsMessages' = Set(), @@ -245,69 +62,41 @@ module KSetAgreementConsensus { procState' = procState.keys().mapBy(p => { // Find if this process is a target in any hiding config val configForThisProc = hidingConfigs.filter(config => config.targetProc == p) - - val processedValues = + + val processedValues = if (configForThisProc.size() > 0) { // Get all processes that should be hidden from this process val hiddenFromThis = flatten(configForThisProc.map(config => config.hiddenProcs)) - + // Filter out messages from hidden processes val allowedCrashedMessages = crashedProcsMessages .filter(m => not(hiddenFromThis.contains(m.sender))) .map(m => m.values) - + newCorrectValues.union(allowedCrashedMessages) } else { // If process is not in hiding configs, it receives all messages newCorrectValues.union(newCrashedProcsValues) } - + { ...procState.get(p), S: processedValues } }), crashed' = crashed, newlyCrashed' = newlyCrashed, } - action step = any{ - randCrash.then(sendMessages).then(receiveMessages).then(computeAction) - } - - action stepWithMultipleHiding(hidingConfigs) = + run stepWithMultipleHiding(hidingConfigs) = crashProcessesFromConfig(hidingConfigs) .then(sendMessages) .then(receiveMessagesWithHiding(hidingConfigs)) .then(computeAction) -} - -module properValues { - //quint run --main=properValues KSetAgreementConsensus.qnt - import KSetAgreementConsensus(N = 8, F = 3, actualFaults = 3, K = 2).* - - run consensusRunTest = - init - .then((F/K + 1).reps(_ => step)) - .expect(kSetAgreement) - .expect(validity) -} - - -module badValues { - //quint run KSetAgreementConsensus.qnt --main badValues --invariant kSetAgreement --max-steps 5 - //quint test --main=badValues KSetAgreementConsensus.qnt - import KSetAgreementConsensus(N = 8, F = 3, actualFaults = 4, K = 2).* - - run consensusRunTest = - init - .then((F/K + 1).reps(_ => step)) - .expect(validity) - // Test scenario where processes decide on different values: // - Process 6 doesn't receive from 1,2 => decides 3 // - Process 5 doesn't receive from 1 => decides 2 // - Process 8 doesn't receive from 3 => decides 1 // - Process 7 doesn't receive from 1,2,3,4 => decides 5 - run consensusDisagreementTest = + run consensusDisagreementTest = initializeProcsStateWithDistinctValues .then((F/K + 1).reps(_ => stepWithMultipleHiding(Set( { hiddenProcs: Set(1, 2), targetProc: 6 }, @@ -316,5 +105,4 @@ module badValues { { hiddenProcs: Set(1, 2, 3, 4), targetProc: 7 } )))) .expect(not(kSetAgreement)) - } From 179eb5166f0e4bd3e450dc3092454c4088577cd4 Mon Sep 17 00:00:00 2001 From: Josef Widder Date: Mon, 3 Mar 2025 16:34:45 +0100 Subject: [PATCH 05/11] start fix --- .../classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt | 3 +++ .../ConsensusAlgorithm/KSetAgreementConsensus.qnt | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt b/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt index a2ed7ebe2..fefd7fe98 100644 --- a/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt +++ b/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt @@ -268,6 +268,9 @@ module badValues { crashProcess(p).then(sendMessages).then(receiveMessage(s)).then(computeAction) } + /// If there is at most one fault (F = 1) two rounds should be enough to reach agreement. + /// But in reality we have two faulty processes, 1 and 3, and they have different initial values. + /// Therefore, the agreement invariant is not satisfied. run consensusDisagreementTest = initializeProcsStateWithDistinctValues .then(stepHidePsMessagesFromS(1, Set(2))) diff --git a/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt b/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt index 275575ba6..f47d887ed 100644 --- a/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt +++ b/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt @@ -45,12 +45,16 @@ module KSetBadValues { action crashProcessesFromConfig(hidingConfigs) = all { // Collect all processes that need to be crashed from all hiding configurations - newlyCrashed' = flatten(hidingConfigs.map(config => config.hiddenProcs)), + val nc = flatten(hidingConfigs.map(config => config.hiddenProcs)) + all { + newlyCrashed' = nc, + crashed.intersect(nc).size() == 0, // Ensure no process is crashed twice crashed' = crashed, round' = round, procState' = procState, correctProcsMessages' = correctProcsMessages, crashedProcsMessages' = crashedProcsMessages + } } action receiveMessagesWithHiding(hidingConfigs) = all { From fa22e940e59efb7fbda3d05317e76ac2a91f690c Mon Sep 17 00:00:00 2001 From: Josef Widder Date: Mon, 3 Mar 2025 18:19:03 +0100 Subject: [PATCH 06/11] fix to violated agreement scenarios --- .../ConsensusAlgorithm/ConsensusAlg.qnt | 11 ++++--- .../KSetAgreementConsensus.qnt | 31 +++++++++++++------ 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt b/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt index fefd7fe98..988fbdc2d 100644 --- a/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt +++ b/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt @@ -45,7 +45,7 @@ module ConsensusAlg { val newV = s.V.union(flatten(s.S)) val newR = s.r + 1 - val newY = if (newR == MAX_ROUNDS) Some(minValue(newV)) else s.y + val newY = if (s.r == MAX_ROUNDS) Some(minValue(newV)) else s.y { V: newV, @@ -269,12 +269,13 @@ module badValues { } /// If there is at most one fault (F = 1) two rounds should be enough to reach agreement. - /// But in reality we have two faulty processes, 1 and 3, and they have different initial values. - /// Therefore, the agreement invariant is not satisfied. + /// But in reality we have two faulty processes, 1 and 2. In the first round, process 1 crashes, + /// and process 2 receives messages from process 1. In the second round, process 2 crashes, and + /// some process see the 1 while others don't run consensusDisagreementTest = initializeProcsStateWithDistinctValues - .then(stepHidePsMessagesFromS(1, Set(2))) - .then(stepHidePsMessagesFromS(3, Set(4))) + .then(stepHidePsMessagesFromS(1, Set(3, 4, 5))) + .then(stepHidePsMessagesFromS(2, Set(4, 5))) .expect(not(agreement)) } diff --git a/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt b/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt index f47d887ed..75b999879 100644 --- a/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt +++ b/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt @@ -96,17 +96,30 @@ module KSetBadValues { .then(computeAction) // Test scenario where processes decide on different values: - // - Process 6 doesn't receive from 1,2 => decides 3 - // - Process 5 doesn't receive from 1 => decides 2 - // - Process 8 doesn't receive from 3 => decides 1 - // - Process 7 doesn't receive from 1,2,3,4 => decides 5 + // We need to execute two rounds, but because there are too many faults in each round 2 fail. + // In the first round, processes 1 and 2 crash: + // only process 3 sees process 1's value (the smallest), process 4 sees process 2's value (the second smallest), + // while all others consider 3 to be the smallest value. + // In the second round, processes 3 and 4 crash: + // only process 5 sees all values, including process 3's value (the smallest), + // process 6 sees process 4's value (the second smallest), + // process 7 doesn't see a new value and sticks to 3. + // Thus process 5, 6, and 7 decide on different values, although K=2 and only two different + // values are allowed. run consensusDisagreementTest = initializeProcsStateWithDistinctValues - .then((F/K + 1).reps(_ => stepWithMultipleHiding(Set( - { hiddenProcs: Set(1, 2), targetProc: 6 }, + .then(stepWithMultipleHiding(Set( + { hiddenProcs: Set(1), targetProc: 4 }, { hiddenProcs: Set(1), targetProc: 5 }, - { hiddenProcs: Set(3), targetProc: 8 }, - { hiddenProcs: Set(1, 2, 3, 4), targetProc: 7 } - )))) + { hiddenProcs: Set(1), targetProc: 6 }, + { hiddenProcs: Set(1), targetProc: 7 }, + { hiddenProcs: Set(2), targetProc: 5 }, + { hiddenProcs: Set(2), targetProc: 6 }, + { hiddenProcs: Set(2), targetProc: 7 }, + ))) + .then(stepWithMultipleHiding(Set( + { hiddenProcs: Set(3), targetProc: 6 }, + { hiddenProcs: Set(3, 4), targetProc: 7 }) )) .expect(not(kSetAgreement)) + } From c69953a9fd6afb9900a4ebacc44dea38d9071659 Mon Sep 17 00:00:00 2001 From: bugarela Date: Wed, 5 Mar 2025 17:20:24 -0300 Subject: [PATCH 07/11] Improve formatting and comments --- .../ConsensusAlgorithm/ConsensusAlg.qnt | 165 +++++++++--------- .../KSetAgreementConsensus.qnt | 70 ++++---- 2 files changed, 120 insertions(+), 115 deletions(-) diff --git a/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt b/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt index 988fbdc2d..2aff323fe 100644 --- a/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt +++ b/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt @@ -1,18 +1,20 @@ // -*- mode: Bluespec; -*- - /************************************************************************************************ - (* Quint Specification for Algorithm 15: Consensus Algorithm in the Presence of Crash Failures *) - (* This specification is derived from book "Distributed Computing: Fundamentals, Simulations, *) - (* and Advanced Topics" (Second Edition) by Hagit Attiya and Jennifer Welch, specifically from *) - (* Chapter 5, page 93. *) - (* http://lib.ysu.am/disciplines_bk/c95d04e111f3e28ae4cc589bfda1e18b.pdf *) - ************************************************************************************************/ +/* + * Quint Specification for Algorithm 15: Consensus Algorithm in the Presence of Crash Failures + * This specification is derived from book "Distributed Computing: Fundamentals, Simulations, + * and Advanced Topics" (Second Edition) by Hagit Attiya and Jennifer Welch [1], specifically from + * Chapter 5, page 93. + * + * Tatjana Kirda, Josef Widder and Gabriela Moreira, Informal Systems, 2024-2025 + * + * [1]: http://lib.ysu.am/disciplines_bk/c95d04e111f3e28ae4cc589bfda1e18b.pdf +*/ module ConsensusAlg { - - const N : int - const F : int - const actualFaults : int - const MAX_ROUNDS : int + const N: int + const F: int + const actualFaults: int + const MAX_ROUNDS: int type Proc = int type Value = int @@ -20,15 +22,15 @@ module ConsensusAlg { type Message = { sender: Proc, values: Set[Value] } type Decision = - | None - | Some(Value) + | None + | Some(Value) type LocalState = { - V: Set[Value], - r: Round, - y: Decision, - S: Set[Set[Value]], - x: Value + V: Set[Value], + r: Round, + y: Decision, + S: Set[Set[Value]], + x: Value } type Stage = Starting | Sending | Receiving | Computing @@ -39,21 +41,17 @@ module ConsensusAlg { def getFirst(s: Set[int]): int = s.fold(0, (_, v) => v) - def minValue(values: Set[int]): int = { val initial = getFirst(values) values.fold(initial, (min, v) => if (v < min) v else min) } + def minValue(values: Set[int]): int = { + val initial = getFirst(values) + values.fold(initial, (min, v) => if (v < min) v else min) + } pure def compute(s: LocalState): LocalState = { - val newV = s.V.union(flatten(s.S)) val newR = s.r + 1 val newY = if (s.r == MAX_ROUNDS) Some(minValue(newV)) else s.y - { - V: newV, - r: newR, - y: newY, - S: Set(), - x: s.x - } + { V: newV, r: newR, y: newY, S: Set(), x: s.x } } // @@ -74,26 +72,28 @@ module ConsensusAlg { // Invariants // - def agreement = Procs.exclude(crashed).forall(p => - Procs.exclude(crashed).forall(q => - ( procState.get(p).y != None and procState.get(q).y != None) implies - procState.get(p).y == procState.get(q).y)) + def agreement = Procs.exclude(crashed).forall(p => { + Procs.exclude(crashed).forall(q => { + (procState.get(p).y != None and procState.get(q).y != None) implies + procState.get(p).y == procState.get(q).y + }) + }) /// If all processes have the same initial value v, then this must be the only decision value def validity = val allXValues = Procs.map(p => procState.get(p).x) - if (allXValues.size() == 1) - allXValues.forall(v => - Procs.exclude(crashed).forall(p => + allXValues.size() == 1 implies + allXValues.forall(v => { + Procs.exclude(crashed).forall(p => { match procState.get(p).y { | Some(y) => y == v | None => true - })) - else - true + } + }) + }) // - // Steps + // Actions // action init = all { @@ -134,12 +134,12 @@ module ConsensusAlg { values: procState.get(p).V }), crashedProcsMessages' = - if (newlyCrashed.size() > 0){ + if (newlyCrashed.size() > 0) { newlyCrashed.map(p => { sender: p, values: procState.get(p).V }) - } else{ + } else { crashedProcsMessages }, round' = round, @@ -157,10 +157,13 @@ module ConsensusAlg { crashedProcsMessages' = crashedProcsMessages } - action randCrash = all { + /// Crash some number of processes smaller or equal to `actualFaults` + action crash = all { if (actualFaults - crashed.size() > 0) { nondet newCrashCount = oneOf(1.to(actualFaults - crashed.size())) - nondet newlyCrashedProcesses = Procs.exclude(crashed).powerset().filter(s => s.size() == newCrashCount).oneOf() + nondet newlyCrashedProcesses = Procs.exclude(crashed).powerset().filter(s => { + s.size() == newCrashCount + }).oneOf() newlyCrashed' = newlyCrashedProcesses } else { newlyCrashed' = newlyCrashed @@ -177,72 +180,74 @@ module ConsensusAlg { correctProcsMessages' = Set(), crashedProcsMessages' = Set(), val newCorrectValues: Set[Set[Value]] = correctProcsMessages.map(m => m.values) - if (crashedProcsMessages.size() == 0){ - procState' = procState.keys().mapBy(p => {... procState.get(p), S:newCorrectValues}) - } - else{ + if (crashedProcsMessages.size() == 0) { + procState' = procState.keys().mapBy(p => {... procState.get(p), S: newCorrectValues}) + } else { val newCrashedProcsValues: Set[Set[Value]] = crashedProcsMessages.map(m => m.values) - nondet crashedMessagesRecived = Procs.setOfMaps(newCrashedProcsValues).union(Set()).oneOf()// for each process we pick from which newly crashed they receive a message - procState' = procState.keys().mapBy(p => { ... procState.get(p), S: newCorrectValues.union(Set(crashedMessagesRecived.get(p))) }) + // for each process we pick from which newly crashed they receive a message + nondet crashedMessagesReceived = Procs.setOfMaps(newCrashedProcsValues).union(Set()).oneOf() + procState' = procState.keys().mapBy(p => { + ...procState.get(p), + S: newCorrectValues.union(Set(crashedMessagesReceived.get(p))) + }) }, crashed' = crashed, newlyCrashed' = newlyCrashed, } action computeAction = all { - correctProcsMessages' = Set(), - procState' = procState.keys().mapBy(p => compute(procState.get(p))), - round' = round + 1, - crashed' = crashed.union(newlyCrashed), - newlyCrashed' = Set(), - crashedProcsMessages' = Set() + correctProcsMessages' = Set(), + procState' = procState.keys().mapBy(p => compute(procState.get(p))), + round' = round + 1, + crashed' = crashed.union(newlyCrashed), + newlyCrashed' = Set(), + crashedProcsMessages' = Set() } - /// the set s of correct processes don't receive the messages from newlycrashed + /// the set `s` of correct processes don't receive the messages from `newlyCrashed` action receiveMessage(s) = all { round' = round, correctProcsMessages' = Set(), crashedProcsMessages' = Set(), val newCorrectValues: Set[Set[Value]] = correctProcsMessages.map(m => m.values) val newCrashedProcsValues: Set[Set[Value]] = crashedProcsMessages.map(m => m.values) - procState' = procState.keys().mapBy(p => + procState' = procState.keys().mapBy(p => { { ...procState.get(p), S: if (s.contains(p)) - newCorrectValues + newCorrectValues else - newCorrectValues.union(newCrashedProcsValues) + newCorrectValues.union(newCrashedProcsValues) } - ), + }), crashed' = crashed, newlyCrashed' = newlyCrashed, } action stuttering = all { - round' = round, - correctProcsMessages' = correctProcsMessages, - crashedProcsMessages' = crashedProcsMessages, - procState' = procState, - crashed' = crashed, - newlyCrashed' = newlyCrashed, - stage' = stage, - } + round' = round, + correctProcsMessages' = correctProcsMessages, + crashedProcsMessages' = crashedProcsMessages, + procState' = procState, + crashed' = crashed, + newlyCrashed' = newlyCrashed, + stage' = stage, + } action step = if (round > MAX_ROUNDS) { stuttering } else { match stage { - | Starting => all { randCrash, stage' = Sending } + | Starting => all { crash, stage' = Sending } | Sending => all { sendMessages, stage' = Receiving } | Receiving => all { receiveMessages, stage' = Computing } | Computing => all { computeAction, stage' = Starting } } } - } module properValues { - //quint run --main=properValues ConsensusAlg.qnt + // quint run --main properValues --invariant agreement ConsensusAlg.qnt import ConsensusAlg(N = 6, F = 1, actualFaults = 1, MAX_ROUNDS = 2).* run consensusRunTest = @@ -252,18 +257,17 @@ module properValues { .expect(validity) } - module badValues { - //quint run ConsensusAlg.qnt --main badValues --invariant agreement --max-steps 5 - //quint test --main=badValues ConsensusAlg.qnt + // quint run ConsensusAlg.qnt --main badValues --invariant agreement --max-steps 5 + // quint test --main=badValues ConsensusAlg.qnt import ConsensusAlg(N = 6, F = 1, actualFaults = 2, MAX_ROUNDS = 2).* run consensusRunTest = init - .then((F + 1).reps(_ => step)) - .expect(validity) + .then((F + 1).reps(_ => step)) + .expect(validity) - /// we crash process p, and the set s does not receive p's messages + /// We crash process p, and the set s does not receive p's messages run stepHidePsMessagesFromS(p,s) = any { crashProcess(p).then(sendMessages).then(receiveMessage(s)).then(computeAction) } @@ -274,8 +278,7 @@ module badValues { /// some process see the 1 while others don't run consensusDisagreementTest = initializeProcsStateWithDistinctValues - .then(stepHidePsMessagesFromS(1, Set(3, 4, 5))) - .then(stepHidePsMessagesFromS(2, Set(4, 5))) - .expect(not(agreement)) - + .then(stepHidePsMessagesFromS(1, Set(3, 4, 5))) + .then(stepHidePsMessagesFromS(2, Set(4, 5))) + .expect(not(agreement)) } diff --git a/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt b/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt index 75b999879..ca8efeecb 100644 --- a/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt +++ b/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt @@ -1,17 +1,20 @@ // -*- mode: Bluespec; -*- - /****************************************************************************************************** - (* Quint Specification for Algorithm 18: K-set Consensus Algorithm in the Presence of Crash Failures *) - (* This specification is derived from book "Distributed Computing: Fundamentals, Simulations, and *) - (* Advanced Topics" (Second Edition) by Hagit Attiya and Jennifer Welch, specifically from Chapter 5, *) - (* page 120. *) - (* http://lib.ysu.am/disciplines_bk/c95d04e111f3e28ae4cc589bfda1e18b.pdf *) - *******************************************************************************************************/ +/* + * Quint Specification for Algorithm 18: K-set Consensus Algorithm in the Presence of Crash Failures + * This specification is derived from book "Distributed Computing: Fundamentals, Simulations, and + * Advanced Topics" (Second Edition) by Hagit Attiya and Jennifer Welch [1], specifically from Chapter 5, + * page 120. + * + * Tatjana Kirda, Josef Widder and Gabriela Moreira, Informal Systems, 2024-2025 + * + * [1]: http://lib.ysu.am/disciplines_bk/c95d04e111f3e28ae4cc589bfda1e18b.pdf + */ module KSetAgreementConsensus { import ConsensusAlg.* from "ConsensusAlg" export ConsensusAlg.* - const K : int + const K: int def kSetAgreement = { // Get all decided values (excluding None) and ensure they are unique @@ -23,7 +26,7 @@ module KSetAgreementConsensus { } module KSetProperValues { - //quint run --main=KSetProperValues KSetAgreementConsensus.qnt + // quint run --main KSetProperValues --invariant kSetAgreement KSetAgreementConsensus.qnt import KSetAgreementConsensus(N = 8, F = 3, actualFaults = 3, K = 2, MAX_ROUNDS = (3/2 + 1)).* run consensusRunTest = @@ -34,26 +37,26 @@ module KSetProperValues { } module KSetBadValues { - //quint run KSetAgreementConsensus.qnt --main KSetBadValues --invariant kSetAgreement --max-steps 5 - //quint test --main=KSetBadValues KSetAgreementConsensus.qnt + // quint run KSetAgreementConsensus.qnt --main KSetBadValues --invariant kSetAgreement --max-steps 5 + // quint test --main=KSetBadValues KSetAgreementConsensus.qnt import KSetAgreementConsensus(N = 8, F = 3, actualFaults = 4, K = 2, MAX_ROUNDS = (3/2 + 1)).* run consensusRunTest = init - .then((F/K + 1).reps(_ => step)) - .expect(validity) + .then((F/K + 1).reps(_ => step)) + .expect(validity) action crashProcessesFromConfig(hidingConfigs) = all { // Collect all processes that need to be crashed from all hiding configurations val nc = flatten(hidingConfigs.map(config => config.hiddenProcs)) all { - newlyCrashed' = nc, - crashed.intersect(nc).size() == 0, // Ensure no process is crashed twice - crashed' = crashed, - round' = round, - procState' = procState, - correctProcsMessages' = correctProcsMessages, - crashedProcsMessages' = crashedProcsMessages + newlyCrashed' = nc, + crashed.intersect(nc).size() == 0, // Ensure no process is crashed twice + crashed' = crashed, + round' = round, + procState' = procState, + correctProcsMessages' = correctProcsMessages, + crashedProcsMessages' = crashedProcsMessages } } @@ -108,18 +111,17 @@ module KSetBadValues { // values are allowed. run consensusDisagreementTest = initializeProcsStateWithDistinctValues - .then(stepWithMultipleHiding(Set( - { hiddenProcs: Set(1), targetProc: 4 }, - { hiddenProcs: Set(1), targetProc: 5 }, - { hiddenProcs: Set(1), targetProc: 6 }, - { hiddenProcs: Set(1), targetProc: 7 }, - { hiddenProcs: Set(2), targetProc: 5 }, - { hiddenProcs: Set(2), targetProc: 6 }, - { hiddenProcs: Set(2), targetProc: 7 }, - ))) - .then(stepWithMultipleHiding(Set( - { hiddenProcs: Set(3), targetProc: 6 }, - { hiddenProcs: Set(3, 4), targetProc: 7 }) )) - .expect(not(kSetAgreement)) - + .then(stepWithMultipleHiding(Set( + { hiddenProcs: Set(1), targetProc: 4 }, + { hiddenProcs: Set(1), targetProc: 5 }, + { hiddenProcs: Set(1), targetProc: 6 }, + { hiddenProcs: Set(1), targetProc: 7 }, + { hiddenProcs: Set(2), targetProc: 5 }, + { hiddenProcs: Set(2), targetProc: 6 }, + { hiddenProcs: Set(2), targetProc: 7 }, + ))) + .then(stepWithMultipleHiding(Set( + { hiddenProcs: Set(3), targetProc: 6 }, + { hiddenProcs: Set(3, 4), targetProc: 7 }) )) + .expect(not(kSetAgreement)) } From 6f000d58cada6ede924d18bb3d0a8762d5e059d7 Mon Sep 17 00:00:00 2001 From: bugarela Date: Thu, 6 Mar 2025 09:40:53 -0300 Subject: [PATCH 08/11] Clean up and add more comments --- .../ConsensusAlgorithm/ConsensusAlg.qnt | 123 +++++++++--------- .../KSetAgreementConsensus.qnt | 44 ++++--- 2 files changed, 90 insertions(+), 77 deletions(-) diff --git a/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt b/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt index 2aff323fe..0a722c6f1 100644 --- a/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt +++ b/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt @@ -1,19 +1,19 @@ // -*- mode: Bluespec; -*- /* - * Quint Specification for Algorithm 15: Consensus Algorithm in the Presence of Crash Failures - * This specification is derived from book "Distributed Computing: Fundamentals, Simulations, - * and Advanced Topics" (Second Edition) by Hagit Attiya and Jennifer Welch [1], specifically from + * Quint Specification for Algorithm 15: Consensus Algorithm in the Presence of Crash Failures + * This specification is derived from book "Distributed Computing: Fundamentals, Simulations, + * and Advanced Topics" (Second Edition) by Hagit Attiya and Jennifer Welch [1], specifically from * Chapter 5, page 93. * * Tatjana Kirda, Josef Widder and Gabriela Moreira, Informal Systems, 2024-2025 - * - * [1]: http://lib.ysu.am/disciplines_bk/c95d04e111f3e28ae4cc589bfda1e18b.pdf + * + * [1]: http://lib.ysu.am/disciplines_bk/c95d04e111f3e28ae4cc589bfda1e18b.pdf */ module ConsensusAlg { const N: int const F: int - const actualFaults: int + const ACTUAL_FAULTS: int const MAX_ROUNDS: int type Proc = int @@ -21,15 +21,21 @@ module ConsensusAlg { type Round = int type Message = { sender: Proc, values: Set[Value] } - type Decision = - | None + type Decision = + | None | Some(Value) + /// The state of a process, using the same nomenclature from the paper type LocalState = { + // The set of values received from processes V: Set[Value], + // The current round r: Round, + // The decision value, if any y: Decision, + // The set of messages received - each message is a set of values S: Set[Set[Value]], + // The value of this process x: Value } @@ -37,7 +43,7 @@ module ConsensusAlg { // // Local functions - // + // def getFirst(s: Set[int]): int = s.fold(0, (_, v) => v) @@ -50,7 +56,7 @@ module ConsensusAlg { val newV = s.V.union(flatten(s.S)) val newR = s.r + 1 val newY = if (s.r == MAX_ROUNDS) Some(minValue(newV)) else s.y - + { V: newV, r: newR, y: newY, S: Set(), x: s.x } } @@ -72,25 +78,27 @@ module ConsensusAlg { // Invariants // - def agreement = Procs.exclude(crashed).forall(p => { + val agreement = Procs.exclude(crashed).forall(p => { Procs.exclude(crashed).forall(q => { - (procState.get(p).y != None and procState.get(q).y != None) implies + (procState.get(p).y != None and procState.get(q).y != None) implies procState.get(p).y == procState.get(q).y }) }) /// If all processes have the same initial value v, then this must be the only decision value - def validity = + val validity = { val allXValues = Procs.map(p => procState.get(p).x) + allXValues.size() == 1 implies - allXValues.forall(v => { - Procs.exclude(crashed).forall(p => { - match procState.get(p).y { - | Some(y) => y == v - | None => true - } - }) + val decisionValue = allXValues.getOnlyElement() + + Procs.exclude(crashed).forall(p => { + match procState.get(p).y { + | Some(y) => y == decisionValue + | None => true + } }) + } // // Actions @@ -99,11 +107,14 @@ module ConsensusAlg { action init = all { nondet initialValues = Procs.setOfMaps(Set(1, 2, 3)).oneOf() procState' = Procs.mapBy(i => { - V: Set(initialValues.get(i)), + val initialValue = initialValues.get(i) + { + V: Set(initialValue), r: 1, - y: None, + y: None, S: Set(), - x: initialValues.get(i) + x: initialValue, + } }), round' = 1, correctProcsMessages' = Set(), @@ -115,11 +126,7 @@ module ConsensusAlg { action initializeProcsStateWithDistinctValues = all { procState' = Procs.mapBy(i => { - V: Set(i), - r: 1, - y: None, - S: Set(), - x: i + { V: Set(i), r: 1, y: None, S: Set(), x: i } }), round' = 1, correctProcsMessages' = Set(), @@ -130,15 +137,11 @@ module ConsensusAlg { action sendMessages = all { correctProcsMessages' = Procs.exclude(crashed).exclude(newlyCrashed).map(p => { - sender: p, - values: procState.get(p).V + { sender: p, values: procState.get(p).V } }), crashedProcsMessages' = if (newlyCrashed.size() > 0) { - newlyCrashed.map(p => { - sender: p, - values: procState.get(p).V - }) + newlyCrashed.map(p => { sender: p, values: procState.get(p).V }) } else { crashedProcsMessages }, @@ -156,11 +159,11 @@ module ConsensusAlg { correctProcsMessages' = correctProcsMessages, crashedProcsMessages' = crashedProcsMessages } - - /// Crash some number of processes smaller or equal to `actualFaults` + + /// Crash some number of processes smaller or equal to ACTUAL_FAULTS action crash = all { - if (actualFaults - crashed.size() > 0) { - nondet newCrashCount = oneOf(1.to(actualFaults - crashed.size())) + if (ACTUAL_FAULTS - crashed.size() > 0) { + nondet newCrashCount = oneOf(1.to(ACTUAL_FAULTS - crashed.size())) nondet newlyCrashedProcesses = Procs.exclude(crashed).powerset().filter(s => { s.size() == newCrashCount }).oneOf() @@ -179,6 +182,9 @@ module ConsensusAlg { round' = round, correctProcsMessages' = Set(), crashedProcsMessages' = Set(), + crashed' = crashed, + newlyCrashed' = newlyCrashed, + val newCorrectValues: Set[Set[Value]] = correctProcsMessages.map(m => m.values) if (crashedProcsMessages.size() == 0) { procState' = procState.keys().mapBy(p => {... procState.get(p), S: newCorrectValues}) @@ -186,14 +192,13 @@ module ConsensusAlg { val newCrashedProcsValues: Set[Set[Value]] = crashedProcsMessages.map(m => m.values) // for each process we pick from which newly crashed they receive a message nondet crashedMessagesReceived = Procs.setOfMaps(newCrashedProcsValues).union(Set()).oneOf() + procState' = procState.keys().mapBy(p => { ...procState.get(p), S: newCorrectValues.union(Set(crashedMessagesReceived.get(p))) }) }, - crashed' = crashed, - newlyCrashed' = newlyCrashed, - } + } action computeAction = all { correctProcsMessages' = Set(), @@ -201,7 +206,7 @@ module ConsensusAlg { round' = round + 1, crashed' = crashed.union(newlyCrashed), newlyCrashed' = Set(), - crashedProcsMessages' = Set() + crashedProcsMessages' = Set(), } /// the set `s` of correct processes don't receive the messages from `newlyCrashed` @@ -211,13 +216,13 @@ module ConsensusAlg { crashedProcsMessages' = Set(), val newCorrectValues: Set[Set[Value]] = correctProcsMessages.map(m => m.values) val newCrashedProcsValues: Set[Set[Value]] = crashedProcsMessages.map(m => m.values) - procState' = procState.keys().mapBy(p => { - { ...procState.get(p), - S: if (s.contains(p)) - newCorrectValues - else - newCorrectValues.union(newCrashedProcsValues) - } + procState' = procState.keys().mapBy(p => { + { ...procState.get(p), + S: if (s.contains(p)) + newCorrectValues + else + newCorrectValues.union(newCrashedProcsValues) + } }), crashed' = crashed, newlyCrashed' = newlyCrashed, @@ -248,31 +253,31 @@ module ConsensusAlg { module properValues { // quint run --main properValues --invariant agreement ConsensusAlg.qnt - import ConsensusAlg(N = 6, F = 1, actualFaults = 1, MAX_ROUNDS = 2).* + import ConsensusAlg(N = 6, F = 1, ACTUAL_FAULTS = 1, MAX_ROUNDS = 2).* - run consensusRunTest = + run consensusRunTest = init - .then((F + 1).reps(_ => step)) - .expect(agreement) - .expect(validity) + .then((F + 1).reps(_ => step)) + .expect(agreement) + .expect(validity) } module badValues { - // quint run ConsensusAlg.qnt --main badValues --invariant agreement --max-steps 5 + // quint run ConsensusAlg.qnt --main badValues --invariant agreement --max-steps 5 // quint test --main=badValues ConsensusAlg.qnt - import ConsensusAlg(N = 6, F = 1, actualFaults = 2, MAX_ROUNDS = 2).* + import ConsensusAlg(N = 6, F = 1, ACTUAL_FAULTS = 2, MAX_ROUNDS = 2).* - run consensusRunTest = + run consensusRunTest = init .then((F + 1).reps(_ => step)) .expect(validity) /// We crash process p, and the set s does not receive p's messages - run stepHidePsMessagesFromS(p,s) = any { + run stepHidePsMessagesFromS(p, s) = { crashProcess(p).then(sendMessages).then(receiveMessage(s)).then(computeAction) } - /// If there is at most one fault (F = 1) two rounds should be enough to reach agreement. + /// If there is at most one fault (F = 1) two rounds should be enough to reach agreement. /// But in reality we have two faulty processes, 1 and 2. In the first round, process 1 crashes, /// and process 2 receives messages from process 1. In the second round, process 2 crashes, and /// some process see the 1 while others don't diff --git a/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt b/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt index ca8efeecb..c25c7b633 100644 --- a/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt +++ b/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt @@ -1,14 +1,14 @@ // -*- mode: Bluespec; -*- /* - * Quint Specification for Algorithm 18: K-set Consensus Algorithm in the Presence of Crash Failures - * This specification is derived from book "Distributed Computing: Fundamentals, Simulations, and + * Quint Specification for Algorithm 18: K-set Consensus Algorithm in the Presence of Crash Failures + * This specification is derived from book "Distributed Computing: Fundamentals, Simulations, and * Advanced Topics" (Second Edition) by Hagit Attiya and Jennifer Welch [1], specifically from Chapter 5, - * page 120. + * page 120. * * Tatjana Kirda, Josef Widder and Gabriela Moreira, Informal Systems, 2024-2025 * - * [1]: http://lib.ysu.am/disciplines_bk/c95d04e111f3e28ae4cc589bfda1e18b.pdf + * [1]: http://lib.ysu.am/disciplines_bk/c95d04e111f3e28ae4cc589bfda1e18b.pdf */ module KSetAgreementConsensus { import ConsensusAlg.* from "ConsensusAlg" @@ -16,7 +16,7 @@ module KSetAgreementConsensus { const K: int - def kSetAgreement = { + val kSetAgreement = { // Get all decided values (excluding None) and ensure they are unique val decidedValues = Procs.exclude(crashed).map(p => procState.get(p).y).filter(v => v != None) @@ -27,7 +27,7 @@ module KSetAgreementConsensus { module KSetProperValues { // quint run --main KSetProperValues --invariant kSetAgreement KSetAgreementConsensus.qnt - import KSetAgreementConsensus(N = 8, F = 3, actualFaults = 3, K = 2, MAX_ROUNDS = (3/2 + 1)).* + import KSetAgreementConsensus(N = 8, F = 3, ACTUAL_FAULTS = 3, K = 2, MAX_ROUNDS = (3/2 + 1)).* run consensusRunTest = init @@ -39,13 +39,17 @@ module KSetProperValues { module KSetBadValues { // quint run KSetAgreementConsensus.qnt --main KSetBadValues --invariant kSetAgreement --max-steps 5 // quint test --main=KSetBadValues KSetAgreementConsensus.qnt - import KSetAgreementConsensus(N = 8, F = 3, actualFaults = 4, K = 2, MAX_ROUNDS = (3/2 + 1)).* + import KSetAgreementConsensus(N = 8, F = 3, ACTUAL_FAULTS = 4, K = 2, MAX_ROUNDS = (3/2 + 1)).* run consensusRunTest = init .then((F/K + 1).reps(_ => step)) .expect(validity) + // + // Auxiliary definitions to crash in a specific way + // + action crashProcessesFromConfig(hidingConfigs) = all { // Collect all processes that need to be crashed from all hiding configurations val nc = flatten(hidingConfigs.map(config => config.hiddenProcs)) @@ -64,8 +68,12 @@ module KSetBadValues { round' = round, correctProcsMessages' = Set(), crashedProcsMessages' = Set(), + crashed' = crashed, + newlyCrashed' = newlyCrashed, + val newCorrectValues: Set[Set[Value]] = correctProcsMessages.map(m => m.values) val newCrashedProcsValues: Set[Set[Value]] = crashedProcsMessages.map(m => m.values) + procState' = procState.keys().mapBy(p => { // Find if this process is a target in any hiding config val configForThisProc = hidingConfigs.filter(config => config.targetProc == p) @@ -88,8 +96,6 @@ module KSetBadValues { { ...procState.get(p), S: processedValues } }), - crashed' = crashed, - newlyCrashed' = newlyCrashed, } run stepWithMultipleHiding(hidingConfigs) = @@ -99,18 +105,14 @@ module KSetBadValues { .then(computeAction) // Test scenario where processes decide on different values: + // TODO: I don't understand this next sentence // We need to execute two rounds, but because there are too many faults in each round 2 fail. - // In the first round, processes 1 and 2 crash: - // only process 3 sees process 1's value (the smallest), process 4 sees process 2's value (the second smallest), - // while all others consider 3 to be the smallest value. - // In the second round, processes 3 and 4 crash: - // only process 5 sees all values, including process 3's value (the smallest), - // process 6 sees process 4's value (the second smallest), - // process 7 doesn't see a new value and sticks to 3. - // Thus process 5, 6, and 7 decide on different values, although K=2 and only two different - // values are allowed. run consensusDisagreementTest = initializeProcsStateWithDistinctValues + // In the first round, processes 1 and 2 crash: + // only process 3 sees process 1's value (the smallest), + // process 4 sees process 2's value (the second smallest), + // while all others consider 3 to be the smallest value. .then(stepWithMultipleHiding(Set( { hiddenProcs: Set(1), targetProc: 4 }, { hiddenProcs: Set(1), targetProc: 5 }, @@ -120,8 +122,14 @@ module KSetBadValues { { hiddenProcs: Set(2), targetProc: 6 }, { hiddenProcs: Set(2), targetProc: 7 }, ))) + // In the second round, processes 3 and 4 crash: + // only process 5 sees all values, including process 3's value (the smallest), + // process 6 sees process 4's value (the second smallest), + // process 7 doesn't see a new value and sticks to 3. .then(stepWithMultipleHiding(Set( { hiddenProcs: Set(3), targetProc: 6 }, { hiddenProcs: Set(3, 4), targetProc: 7 }) )) + // Thus process 5, 6, and 7 decide on different values, although K=2 and only two different + // values are allowed. .expect(not(kSetAgreement)) } From 883cc2c579bd11954129d207899790e80fb8c91e Mon Sep 17 00:00:00 2001 From: bugarela Date: Thu, 6 Mar 2025 09:57:17 -0300 Subject: [PATCH 09/11] Update examples dashboard --- examples/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/README.md b/examples/README.md index 0e8c954b0..8a438dcca 100644 --- a/examples/README.md +++ b/examples/README.md @@ -49,6 +49,8 @@ listed without any additional command line arguments. | Example | Syntax (`parse`) | Types (`typecheck`) | Unit tests (`test`) | Apalache (`verify`) | |---------|:----------------:|:-------------------:|:-------------------:|:-------------------:| | [classic/distributed/ClockSync/clockSync3.qnt](./classic/distributed/ClockSync/clockSync3.qnt) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| [classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt](./classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | +| [classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt](./classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | | [classic/distributed/ewd426/ewd426.qnt](./classic/distributed/ewd426/ewd426.qnt) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | [classic/distributed/ewd426/ewd426_3.qnt](./classic/distributed/ewd426/ewd426_3.qnt) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | [classic/distributed/ewd426/ewd426_4.qnt](./classic/distributed/ewd426/ewd426_4.qnt) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | From 35df73a703259890303659e421f79016897993b3 Mon Sep 17 00:00:00 2001 From: bugarela Date: Thu, 6 Mar 2025 14:01:32 -0300 Subject: [PATCH 10/11] Fix union and improve comments --- .../classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt | 2 +- .../ConsensusAlgorithm/KSetAgreementConsensus.qnt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt b/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt index 0a722c6f1..b0aed02f8 100644 --- a/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt +++ b/examples/classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt @@ -191,7 +191,7 @@ module ConsensusAlg { } else { val newCrashedProcsValues: Set[Set[Value]] = crashedProcsMessages.map(m => m.values) // for each process we pick from which newly crashed they receive a message - nondet crashedMessagesReceived = Procs.setOfMaps(newCrashedProcsValues).union(Set()).oneOf() + nondet crashedMessagesReceived = Procs.setOfMaps(newCrashedProcsValues.union(Set(Set()))).oneOf() procState' = procState.keys().mapBy(p => { ...procState.get(p), diff --git a/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt b/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt index c25c7b633..358e1a2f2 100644 --- a/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt +++ b/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt @@ -105,8 +105,8 @@ module KSetBadValues { .then(computeAction) // Test scenario where processes decide on different values: - // TODO: I don't understand this next sentence - // We need to execute two rounds, but because there are too many faults in each round 2 fail. + // For K-Set agreement, we would need at least one round with less than K crashes + // In this scenario, we don't have this, as we have 2 round and in each round 2 processes fail. run consensusDisagreementTest = initializeProcsStateWithDistinctValues // In the first round, processes 1 and 2 crash: @@ -129,7 +129,7 @@ module KSetBadValues { .then(stepWithMultipleHiding(Set( { hiddenProcs: Set(3), targetProc: 6 }, { hiddenProcs: Set(3, 4), targetProc: 7 }) )) - // Thus process 5, 6, and 7 decide on different values, although K=2 and only two different + // Thus processes 5, 6, and 7 decide on different values, although K=2 and only two different // values are allowed. .expect(not(kSetAgreement)) } From b43f30c5d3d8c60aa03d1dfd99c316e70d2b9aa7 Mon Sep 17 00:00:00 2001 From: bugarela Date: Thu, 6 Mar 2025 14:10:34 -0300 Subject: [PATCH 11/11] Update commands on comments and on script --- examples/.scripts/run-example.sh | 4 ++++ .../distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/.scripts/run-example.sh b/examples/.scripts/run-example.sh index b1de4db9b..fc61aedce 100755 --- a/examples/.scripts/run-example.sh +++ b/examples/.scripts/run-example.sh @@ -82,6 +82,10 @@ get_main () { main="--main=two_phase_commit_3" elif [[ "$file" == "classic/distributed/Paxos/Paxos.qnt" ]] ; then main="--main=Paxos_val2_accept3_quorum2" + elif [[ "$file" == "classic/distributed/ConsensusAlgorithm/ConsensusAlg.qnt" ]] ; then + main="--main=badValues" + elif [[ "$file" == "classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt" ]] ; then + main="--main=KSetBadValues" elif [[ "$file" == "classic/sequential/BinSearch/BinSearch.qnt" ]] ; then main="--main=BinSearch10" elif [[ "$file" == "cosmos/ics20/bank.qnt" ]] ; then diff --git a/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt b/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt index 358e1a2f2..42c1b4cc6 100644 --- a/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt +++ b/examples/classic/distributed/ConsensusAlgorithm/KSetAgreementConsensus.qnt @@ -27,6 +27,7 @@ module KSetAgreementConsensus { module KSetProperValues { // quint run --main KSetProperValues --invariant kSetAgreement KSetAgreementConsensus.qnt + // quint test --main KSetProperValues KSetAgreementConsensus.qnt import KSetAgreementConsensus(N = 8, F = 3, ACTUAL_FAULTS = 3, K = 2, MAX_ROUNDS = (3/2 + 1)).* run consensusRunTest = @@ -37,7 +38,7 @@ module KSetProperValues { } module KSetBadValues { - // quint run KSetAgreementConsensus.qnt --main KSetBadValues --invariant kSetAgreement --max-steps 5 + // (should fail) quint run KSetAgreementConsensus.qnt --main KSetBadValues --invariant kSetAgreement --max-steps 5 // quint test --main=KSetBadValues KSetAgreementConsensus.qnt import KSetAgreementConsensus(N = 8, F = 3, ACTUAL_FAULTS = 4, K = 2, MAX_ROUNDS = (3/2 + 1)).*