Skip to content

Commit d3c5f47

Browse files
authored
Merge pull request #1023 from informalsystems/igor/tendermint-imports
Rearranging modules in the Tendermint spec
2 parents 4ee5c50 + 2d139a0 commit d3c5f47

File tree

7 files changed

+152
-150
lines changed

7 files changed

+152
-150
lines changed

examples/.scripts/run-example.sh

+6-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ result () {
1515

1616
# Skip tests for parameterized modules
1717
if [[ "$cmd" == "test" && (
18+
"$file" == "cosmos/tendermint/Tendermint.qnt" ||
19+
"$file" == "cosmos/tendermint/TendermintTest.qnt" ||
1820
"$file" == "cosmos/lightclient/Blockchain.qnt" ||
1921
"$file" == "cosmos/lightclient/LCVerificationApi.qnt" ) ]] ; then
2022
printf "N/A[^parameterized]"; return
@@ -24,6 +26,8 @@ result () {
2426
"$file" == "cosmos/lightclient/Blockchain.qnt" ||
2527
"$file" == "cosmos/lightclient/LCVerificationApi.qnt" ||
2628
"$file" == "cosmos/lightclient/typedefs.qnt" ||
29+
"$file" == "cosmos/tendermint/Tendermint.qnt" ||
30+
"$file" == "cosmos/tendermint/TendermintTest.qnt" ||
2731
"$file" =~ ^spells/ ||
2832
"$file" == "solidity/SimpleAuction/SimpleAuction.qnt" ||
2933
"$file" == "cosmos/ics20/base.qnt" ) ]] ; then
@@ -51,8 +55,6 @@ result () {
5155
elif [[ "$file" == "cosmos/ics23/ics23.qnt" && "$cmd" == "verify" ]] ; then
5256
printf "<sup>https://github.com/informalsystems/quint/issues/693,</sup>"
5357
printf "<sup>https://github.com/informalsystems/quint/pull/975</sup>"
54-
elif [[ "$file" == "cosmos/tendermint/TendermintAcc005.qnt" && ( "$cmd" == "test" || "$cmd" == "verify" ) ]] ; then
55-
printf "<sup>https://github.com/informalsystems/quint/pull/1023</sup>"
5658
elif [[ "$file" == "cosmwasm/zero-to-hero/vote.qnt" && "$cmd" == "verify" ]] ; then
5759
printf "<sup>https://github.com/informalsystems/quint/issues/693</sup>"
5860
elif [[ "$file" == "language-features/option.qnt" && "$cmd" == "verify" ]] ; then
@@ -109,6 +111,8 @@ get_verify_args () {
109111
"$file" == "classic/distributed/ReadersWriters/ReadersWriters.qnt" ||
110112
"$file" == "cosmos/lightclient/Lightclient.qnt" ]] ; then
111113
args="--init=Init --step=Next"
114+
elif [[ "$file" == "cosmos/tendermint/TendermintModels.qnt" ]] ; then
115+
args="--init=n4_f1::Init --step=n4_f1::Next --invariant=n4_f1::Agreement"
112116
fi
113117
echo "${args}"
114118
}

examples/README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ listed without any additional command line arguments.
6464
| [cosmos/lightclient/LCVerificationApi.qnt](./cosmos/lightclient/LCVerificationApi.qnt) | :white_check_mark: | :white_check_mark: | N/A[^parameterized] | N/A[^nostatemachine] |
6565
| [cosmos/lightclient/Lightclient.qnt](./cosmos/lightclient/Lightclient.qnt) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
6666
| [cosmos/lightclient/typedefs.qnt](./cosmos/lightclient/typedefs.qnt) | :white_check_mark: | :white_check_mark: | :white_check_mark: | N/A[^nostatemachine] |
67-
| [cosmos/tendermint/TendermintAcc005.qnt](./cosmos/tendermint/TendermintAcc005.qnt) | :white_check_mark: | :white_check_mark: | :x:<sup>https://github.com/informalsystems/quint/pull/1023</sup> | :x:<sup>https://github.com/informalsystems/quint/pull/1023</sup> |
67+
| [cosmos/tendermint/Tendermint.qnt](./cosmos/tendermint/Tendermint.qnt) | :white_check_mark: | :white_check_mark: | N/A[^parameterized] | N/A[^nostatemachine] |
68+
| [cosmos/tendermint/TendermintModels.qnt](./cosmos/tendermint/TendermintModels.qnt) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
69+
| [cosmos/tendermint/TendermintTest.qnt](./cosmos/tendermint/TendermintTest.qnt) | :white_check_mark: | :white_check_mark: | N/A[^parameterized] | N/A[^nostatemachine] |
6870
| [cosmwasm/zero-to-hero/vote.qnt](./cosmwasm/zero-to-hero/vote.qnt) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x:<sup>https://github.com/informalsystems/quint/issues/693</sup> |
6971
| [language-features/booleans.qnt](./language-features/booleans.qnt) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
7072
| [language-features/counters.qnt](./language-features/counters.qnt) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |

examples/cosmos/tendermint/README.md

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
[TendermintAcc005.qnt][] is a manually translated version of the TLA+
2-
specification [TendermintAcc005.tla][], which is currently located in the
3-
directory
4-
[accoutability](https://github.com/cometbft/cometbft/tree/igor/tendermint-ind-inv/spec/light-client/accountability)
5-
of [CometBFT][].
6-
1+
[Tendermint.qnt][] is a Quint version of the TLA+ specification
2+
[TendermintAcc005.tla][], which is currently located under the
3+
[accountability][] specification of [CometBFT][].
74

85
[TendermintAcc005.qnt]: ./TendermintAcc005.qnt
96
[TendermintAcc005.tla]: ./tla/TendermintAcc005.tla
107
[CometBFT]: https://github.com/cometbft/cometbft
8+
[accountability]: https://github.com/cometbft/cometbft/tree/main/spec/light-client/accountability

examples/cosmos/tendermint/TendermintAcc005.qnt examples/cosmos/tendermint/Tendermint.qnt

+18-138
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// -*- mode: Bluespec; -*-
2-
module TendermintAcc {
2+
module Tendermint {
33
/*
44
A Quint specification of a simplified Tendermint consensus, tuned for
55
fork accountability. The simplifications are as follows:
@@ -320,9 +320,9 @@ module TendermintAcc {
320320
}
321321

322322
// lines 34-35 + lines 61-64 (onTimeoutPrevote)
323-
action UponQuorumOfPrevotesAny(p, Evidence) =
323+
action UponQuorumOfPrevotesAny(p: str, Evidence: Set[{ id: Value_t, round: Round_t, src: Proc_t }]): bool =
324324
// find the unique voters in the evidence
325-
val Voters = Evidence.map(m => m.src)
325+
val Voters: Set[str] = Evidence.map(m => m.src)
326326
// compare the number of the unique voters against the threshold
327327
all {
328328
step.get(p) == "prevote", // line 34 and 61
@@ -380,9 +380,9 @@ module TendermintAcc {
380380
}
381381

382382
// lines 47-48 + 65-67 (onTimeoutPrecommit)
383-
action UponQuorumOfPrecommitsAny(p, Evidence) =
383+
action UponQuorumOfPrecommitsAny(p: str, Evidence: Set[{ id: Value_t, round: Round_t, src: Proc_t }]): bool =
384384
// find the unique committers in the evidence
385-
val Committers = Evidence.map( m => m.src )
385+
val Committers: Set[str] = Evidence.map( m => m.src )
386386
// compare the number of the unique committers against the threshold
387387
all {
388388
size(Committers) >= THRESHOLD2, // line 47
@@ -531,23 +531,22 @@ module TendermintAcc {
531531
}
532532

533533
//**************************** FORK SCENARIOS ***************************
534+
def EquivocationIn(p, S) =
535+
tuples(S, S).exists((m1, m2) =>
536+
and {
537+
m1 != m2,
538+
m1.src == p,
539+
m2.src == p,
540+
m1.round == m2.round,
541+
}
542+
)
534543

535544
// equivocation by a process p
536-
def EquivocationBy(p) =
537-
def EquivocationIn(S) =
538-
tuples(S, S).exists((m1, m2) =>
539-
and {
540-
m1 != m2,
541-
m1.src == p,
542-
m2.src == p,
543-
m1.round == m2.round,
544-
}
545-
)
546-
545+
def EquivocationBy(p: str): bool =
547546
or {
548-
EquivocationIn(evidencePropose),
549-
EquivocationIn(evidencePrevote),
550-
EquivocationIn(evidencePrecommit),
547+
EquivocationIn(p, evidencePropose),
548+
EquivocationIn(p, evidencePrevote),
549+
EquivocationIn(p, evidencePrecommit),
551550
}
552551

553552
// amnesic behavior by a process p
@@ -661,122 +660,3 @@ module TendermintAcc {
661660
AllInMax implies AllDecided
662661
)
663662
}
664-
665-
// Tests that demonstrate typical behavior.
666-
module TendermintTest {
667-
import TendermintAcc.*
668-
export TendermintAcc.*
669-
670-
// Quint will automatically compute the unchanged block in the future
671-
action unchangedAll = all {
672-
step' = step,
673-
fired_action' = fired_action,
674-
round' = round,
675-
validValue' = validValue,
676-
validRound' = validRound,
677-
msgsPrevote' = msgsPrevote,
678-
msgsPropose' = msgsPropose,
679-
msgsPrecommit' = msgsPrecommit,
680-
decision' = decision,
681-
lockedValue' = lockedValue,
682-
lockedRound' = lockedRound,
683-
evidencePropose' = evidencePropose,
684-
evidencePrevote' = evidencePrevote,
685-
evidencePrecommit' = evidencePrecommit,
686-
}
687-
688-
// three correct processes behave and decide on the same value
689-
run decisionTest = {
690-
nondet v = oneOf(ValidValues)
691-
val p1 = Proposer.get(0)
692-
nondet p2 = Corr.exclude(Set(p1)).oneOf()
693-
nondet p3 = Corr.exclude(Set(p1, p2)).oneOf()
694-
Init.then(InsertProposal(p1, v))
695-
.then(UponProposalInPropose(p1, v))
696-
.then(UponProposalInPropose(p2, v))
697-
.then(UponProposalInPropose(p3, v))
698-
.then(UponProposalInPrevoteOrCommitAndPrevote(p1, v, NilRound))
699-
.then(UponProposalInPrevoteOrCommitAndPrevote(p2, v, NilRound))
700-
.then(UponProposalInPrevoteOrCommitAndPrevote(p3, v, NilRound))
701-
.then(UponProposalInPrecommitNoDecision(p1, v, 0, NilRound))
702-
.then(UponProposalInPrecommitNoDecision(p2, v, 0, NilRound))
703-
.then(UponProposalInPrecommitNoDecision(p3, v, 0, NilRound))
704-
.then(all {
705-
assert(decision.get(p1) == v),
706-
assert(decision.get(p2) == v),
707-
assert(decision.get(p3) == v),
708-
unchangedAll,
709-
})
710-
}
711-
712-
// a correct proposer cannot propose twice in the same round
713-
run noProposeTwiceTest = {
714-
val p1 = Proposer.get(0)
715-
Init.then(InsertProposal(p1, "v0"))
716-
.then(InsertProposal(p1, "v1"))
717-
.fail()
718-
}
719-
720-
// a correct proposer proposes but other processes timeout
721-
run timeoutProposeTest = {
722-
val p1 = Proposer.get(0)
723-
nondet p2 = Corr.exclude(Set(p1)).oneOf()
724-
nondet p3 = Corr.exclude(Set(p1, p2)).oneOf()
725-
Init.then(InsertProposal(p1, "v0"))
726-
.then(UponProposalInPropose(p1, "v0"))
727-
.then(OnTimeoutPropose(p2))
728-
.then(OnTimeoutPropose(p3))
729-
.then(
730-
val E = msgsPrevote.get(0).filter(m => m.src.in(Corr))
731-
UponQuorumOfPrevotesAny(p1, E)
732-
.then(UponQuorumOfPrevotesAny(p2, E))
733-
.then(UponQuorumOfPrevotesAny(p3, E))
734-
)
735-
.then(
736-
val E = msgsPrecommit.get(0).filter(m => m.src.in(Corr))
737-
UponQuorumOfPrecommitsAny(p1, E)
738-
.then(UponQuorumOfPrecommitsAny(p2, E))
739-
.then(UponQuorumOfPrecommitsAny(p3, E))
740-
)
741-
.then(all {
742-
// all correct processes switch to the next round
743-
assert(Corr.forall(p => round.get(p) == 1)),
744-
unchangedAll,
745-
})
746-
}
747-
}
748-
749-
module InstanceTests {
750-
import TendermintTest(
751-
Corr = Set("p1", "p2", "p3"),
752-
Faulty = Set("p4"),
753-
N = 4,
754-
T = 1,
755-
ValidValues = Set("v0", "v1"),
756-
InvalidValues = Set("v2"),
757-
MaxRound = 4,
758-
Proposer = Map(0 -> "p1", 1 -> "p2", 2 -> "p3", 3 -> "p4", 4 -> "p1")
759-
) as Tendermint_n4_f1
760-
761-
import TendermintTest(
762-
Corr = Set("p1", "p2", "p3"),
763-
Faulty = Set("p3", "p4"),
764-
N = 4,
765-
T = 1,
766-
ValidValues = Set("v0", "v1"),
767-
InvalidValues = Set("v2"),
768-
MaxRound = 4,
769-
Proposer = Map(0 -> "p1", 1 -> "p2", 2 -> "p3", 3 -> "p4", 4 -> "p1")
770-
) as Tendermint_n4_f2
771-
772-
import TendermintTest(
773-
Corr = Set("p1", "p2", "p3"),
774-
Faulty = Set("p4", "p5"),
775-
N = 5,
776-
T = 1,
777-
ValidValues = Set("v0", "v1"),
778-
InvalidValues = Set("v2"),
779-
MaxRound = 4,
780-
Proposer = Map(0 -> "p1", 1 -> "p2", 2 -> "p3", 3 -> "p4", 4 -> "p1")
781-
) as Tendermint_n5_f2
782-
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/// A few handy small models that are good for debugging and inspection
2+
module TendermintModels {
3+
import TendermintTest(
4+
Corr = Set("p1", "p2", "p3"),
5+
Faulty = Set("p4"),
6+
N = 4,
7+
T = 1,
8+
ValidValues = Set("v0", "v1"),
9+
InvalidValues = Set("v2"),
10+
MaxRound = 4,
11+
Proposer = Map(0 -> "p1", 1 -> "p2", 2 -> "p3", 3 -> "p4", 4 -> "p1")
12+
) as n4_f1 from "./TendermintTest"
13+
14+
import TendermintTest(
15+
Corr = Set("p1", "p2", "p3"),
16+
Faulty = Set("p3", "p4"),
17+
N = 4,
18+
T = 1,
19+
ValidValues = Set("v0", "v1"),
20+
InvalidValues = Set("v2"),
21+
MaxRound = 4,
22+
Proposer = Map(0 -> "p1", 1 -> "p2", 2 -> "p3", 3 -> "p4", 4 -> "p1")
23+
) as n4_f2 from "./TendermintTest"
24+
25+
import TendermintTest(
26+
Corr = Set("p1", "p2", "p3"),
27+
Faulty = Set("p4", "p5"),
28+
N = 5,
29+
T = 1,
30+
ValidValues = Set("v0", "v1"),
31+
InvalidValues = Set("v2"),
32+
MaxRound = 4,
33+
Proposer = Map(0 -> "p1", 1 -> "p2", 2 -> "p3", 3 -> "p4", 4 -> "p1")
34+
) as n5_f2 from "./TendermintTest"
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Tests that demonstrate typical behavior.
2+
module TendermintTest {
3+
import Tendermint.* from "./Tendermint"
4+
export Tendermint.*
5+
6+
// Quint will automatically compute the unchanged block in the future
7+
action unchangedAll = all {
8+
step' = step,
9+
fired_action' = fired_action,
10+
round' = round,
11+
validValue' = validValue,
12+
validRound' = validRound,
13+
msgsPrevote' = msgsPrevote,
14+
msgsPropose' = msgsPropose,
15+
msgsPrecommit' = msgsPrecommit,
16+
decision' = decision,
17+
lockedValue' = lockedValue,
18+
lockedRound' = lockedRound,
19+
evidencePropose' = evidencePropose,
20+
evidencePrevote' = evidencePrevote,
21+
evidencePrecommit' = evidencePrecommit,
22+
}
23+
24+
// three correct processes behave and decide on the same value
25+
run decisionTest = {
26+
nondet v = oneOf(ValidValues)
27+
val p1 = Proposer.get(0)
28+
nondet p2 = Corr.exclude(Set(p1)).oneOf()
29+
nondet p3 = Corr.exclude(Set(p1, p2)).oneOf()
30+
Init.then(InsertProposal(p1, v))
31+
.then(UponProposalInPropose(p1, v))
32+
.then(UponProposalInPropose(p2, v))
33+
.then(UponProposalInPropose(p3, v))
34+
.then(UponProposalInPrevoteOrCommitAndPrevote(p1, v, NilRound))
35+
.then(UponProposalInPrevoteOrCommitAndPrevote(p2, v, NilRound))
36+
.then(UponProposalInPrevoteOrCommitAndPrevote(p3, v, NilRound))
37+
.then(UponProposalInPrecommitNoDecision(p1, v, 0, NilRound))
38+
.then(UponProposalInPrecommitNoDecision(p2, v, 0, NilRound))
39+
.then(UponProposalInPrecommitNoDecision(p3, v, 0, NilRound))
40+
.then(all {
41+
assert(decision.get(p1) == v),
42+
assert(decision.get(p2) == v),
43+
assert(decision.get(p3) == v),
44+
unchangedAll,
45+
})
46+
}
47+
48+
// a correct proposer cannot propose twice in the same round
49+
run noProposeTwiceTest = {
50+
val p1 = Proposer.get(0)
51+
Init.then(InsertProposal(p1, "v0"))
52+
.then(InsertProposal(p1, "v1"))
53+
.fail()
54+
}
55+
56+
// a correct proposer proposes but other processes timeout
57+
run timeoutProposeTest = {
58+
val p1 = Proposer.get(0)
59+
nondet p2 = Corr.exclude(Set(p1)).oneOf()
60+
nondet p3 = Corr.exclude(Set(p1, p2)).oneOf()
61+
Init.then(InsertProposal(p1, "v0"))
62+
.then(UponProposalInPropose(p1, "v0"))
63+
.then(OnTimeoutPropose(p2))
64+
.then(OnTimeoutPropose(p3))
65+
.then(
66+
val E = msgsPrevote.get(0).filter(m => m.src.in(Corr))
67+
UponQuorumOfPrevotesAny(p1, E)
68+
.then(UponQuorumOfPrevotesAny(p2, E))
69+
.then(UponQuorumOfPrevotesAny(p3, E))
70+
)
71+
.then(
72+
val E = msgsPrecommit.get(0).filter(m => m.src.in(Corr))
73+
UponQuorumOfPrecommitsAny(p1, E)
74+
.then(UponQuorumOfPrecommitsAny(p2, E))
75+
.then(UponQuorumOfPrecommitsAny(p3, E))
76+
)
77+
.then(all {
78+
// all correct processes switch to the next round
79+
assert(Corr.forall(p => round.get(p) == 1)),
80+
unchangedAll,
81+
})
82+
}
83+
}

quint/cli-tests.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -70,17 +70,17 @@ Temporarily disabled.
7070
### OK on parse Tendermint
7171

7272
<!-- !test check Tendermint -->
73-
quint parse ../examples/cosmos/tendermint/TendermintAcc005.qnt
73+
quint parse ../examples/cosmos/tendermint/Tendermint.qnt
7474

7575
### OK on typecheck Tendermint
7676

7777
<!-- !test check Tendermint - Types & Effects -->
78-
quint typecheck ../examples/cosmos/tendermint/TendermintAcc005.qnt
78+
quint typecheck ../examples/cosmos/tendermint/Tendermint.qnt
7979

8080
### OK on test Tendermint
8181

8282
<!-- !test check Tendermint - Test -->
83-
quint test --max-samples=100 --main InstanceTests ../examples/cosmos/tendermint/TendermintAcc005.qnt
83+
quint test --max-samples=100 --main TendermintModels ../examples/cosmos/tendermint/TendermintModels.qnt
8484

8585
### OK on parse imports
8686

0 commit comments

Comments
 (0)