wunderspec 0.136.1__tar.gz → 0.136.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {wunderspec-0.136.1 → wunderspec-0.136.4}/CHANGELOG.md +6 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/PKG-INFO +6 -35
- {wunderspec-0.136.1 → wunderspec-0.136.4}/README.md +5 -34
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/tendermint_single.py +100 -134
- wunderspec-0.136.4/examples/tendermint_single_indinv.py +807 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/pyproject.toml +1 -1
- {wunderspec-0.136.1 → wunderspec-0.136.4}/tests/README.md +8 -8
- {wunderspec-0.136.1 → wunderspec-0.136.4}/uv.lock +1 -1
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/__init__.py +1 -1
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/apalache.py +3 -2
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/api.py +72 -2
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/tlc.py +3 -2
- wunderspec-0.136.1/examples/tendermint_single_indinv.py +0 -516
- {wunderspec-0.136.1 → wunderspec-0.136.4}/.devcontainer/Dockerfile +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/.devcontainer/devcontainer.json +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/.devcontainer/post-create.sh +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/.flake8 +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/.github/workflows/build.yml +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/.github/workflows/convert-to-tla.yml +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/.github/workflows/lint.yml +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/.github/workflows/publish.yml +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/.github/workflows/run-examples.yml +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/.gitignore +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/CONTRIBUTING.md +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/LICENSE +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/README.md +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-avatar-github-1024.png +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-avatar-github-256.png +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-avatar-github-512.png +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-icon-circle-dark-1024.png +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-icon-circle-dark-512.png +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-icon-circle-transparent-1024.png +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-lockup-horizontal-dark-1600.png +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-lockup-horizontal-dark-800.png +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-mark-transparent-1024.png +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-readme-header-dark-1200x480.png +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-readme-header-dark-1600x640.png +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-social-preview-1280x640.png +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/png/wunderspec-wordmark-transparent-900.png +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/svg/wunderspec-avatar-github.svg +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/svg/wunderspec-icon-circle-dark.svg +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/svg/wunderspec-icon-circle-transparent.svg +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/svg/wunderspec-lockup-horizontal-dark.svg +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/svg/wunderspec-lockup-horizontal-transparent.svg +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/svg/wunderspec-mark-transparent.svg +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/svg/wunderspec-readme-header-dark.svg +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/svg/wunderspec-wordmark-transparent.svg +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/assets/design/svg/wunderspec_flower.svg +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/basedpyrightconfig.json +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/docs/references/from-quint-llms.md +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/docs/references/from-tla-llms.md +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/docs/user-references/booleans.md +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/docs/user-references/cheatsheet.html +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/docs/user-references/comprehensions.md +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/docs/user-references/decorators.md +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/docs/user-references/enums.md +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/docs/user-references/flow.md +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/docs/user-references/integers.md +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/docs/user-references/lists.md +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/docs/user-references/maps.md +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/docs/user-references/records.md +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/docs/user-references/sets.md +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/docs/user-references/state-machine.md +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/docs/user-references/strings.md +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/docs/user-references/temporal.md +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/docs/user-references/tuples.md +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/docs/user-references/unions.md +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/docs/user-references/wunderspec-five-minutes.md +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/docs/user-stories/bobs_log.md +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/docs/user-stories/bobs_log.png +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/docs/user-stories/img/bob.png +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/bags.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/bakery.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/bakery_walk.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/ben_or.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/channel.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/dekker.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/epfd.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/etcdraft.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/examples.yaml +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/fifo.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/fpaxos.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/kv_store.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/lamport_mutex.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/ledger.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/minimmit.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/payment.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/producer_consumer.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/readers_writers.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/readers_writers_walk.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/simple_ponzi.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/simple_ponzi_machine.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/simple_ponzi_machine_walk.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/simple_wal1.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/simple_wal2.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/sliding_puzzles.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/sloppy_counter.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/test_simple_ponzi_machine.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/tiny_workers.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/examples/two_phase.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/pyrightconfig.json +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/scripts/__init__.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/scripts/_spec_utils.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/scripts/convert_examples_to_tla.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/scripts/run_examples.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/__main__.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/_edition.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/ast/__init__.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/ast/action_ast.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/ast/ast.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/ast/list_ast.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/ast/map_ast.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/ast/record_ast.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/ast/serialization.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/ast/set_ast.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/ast/sorts.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/ast/temporal_ast.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/ast/terms.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/ast/tuple_ast.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/ast/union_ast.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/cli.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/dev_debug.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/doc_format.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/errors.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/exec/__init__.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/exec/action_exec.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/exec/action_profile.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/exec/context.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/exec/scheduler.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/explain.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/expr.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/flow.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/fuzzer.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/interpreter.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/interpreter_sampling.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/interpreter_value.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/itf_trace.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/lang.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/linter.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/machine.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/model_checker.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/permutation.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/petnames.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/pretty.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/py.typed +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/quint_convert.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/random_walk.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/source_tracking.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/submachine.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/sym_context.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/tla.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/tlc_trace.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/trace_output.py +0 -0
- {wunderspec-0.136.1 → wunderspec-0.136.4}/wunderspec/uniq_names.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wunderspec
|
|
3
|
-
Version: 0.136.
|
|
3
|
+
Version: 0.136.4
|
|
4
4
|
Summary: Protocol specifications as Python code
|
|
5
5
|
Author: Igor Konnov, Thomas Pani
|
|
6
6
|
License-File: LICENSE
|
|
@@ -103,45 +103,16 @@ commands are not included in this package.
|
|
|
103
103
|
|
|
104
104
|
## 4. Release Provenance
|
|
105
105
|
|
|
106
|
-
- Release tag: `v0.136.
|
|
107
|
-
- Source commit: `
|
|
106
|
+
- Release tag: `v0.136.4`
|
|
107
|
+
- Source commit: `7beedd2736b068f1c371f59fd8ed619a1d9e75a5`
|
|
108
108
|
|
|
109
109
|
See [tests/README.md][] for the development test log captured at release time.
|
|
110
110
|
|
|
111
111
|
## 5. Latest Release Notes
|
|
112
112
|
|
|
113
|
-
Changes since public release v0.
|
|
114
|
-
|
|
115
|
-
- Add
|
|
116
|
-
TLA+ operator for the selected `@coverage` function as `--view=<Oper>` for
|
|
117
|
-
`check` and `simulate`.
|
|
118
|
-
- Allow `wunderspec with-apalache --max-steps 0` to check initial states.
|
|
119
|
-
- Write Apalache output to an `apalache.log` file while it runs, report the log
|
|
120
|
-
path, and add `--verbose` to stream the log to the terminal immediately.
|
|
121
|
-
- Require `default=...` for collection-form `Max` and `Min`, e.g.
|
|
122
|
-
`Max(s, default=0)`. The default is the reduce seed, so empty collections no
|
|
123
|
-
longer need a separate size guard or `CHOOSE`-based seed.
|
|
124
|
-
- Render collection-form `Max` and `Min` reducers as TLAPS-friendly `CHOOSE`
|
|
125
|
-
expressions in generated TLA+ while keeping reducer-based interpreter
|
|
126
|
-
evaluation.
|
|
127
|
-
- Add TLA+ labels to `wunderspec convert` output for `@invariant` and
|
|
128
|
-
`@example` operators, including nested predicate calls by default. Use
|
|
129
|
-
`@invariant(inline=True)` or `@example(inline=True)` to inline nested
|
|
130
|
-
predicate calls instead.
|
|
131
|
-
- Preserve definition docstrings when converting Wunderspec to TLA+ by emitting
|
|
132
|
-
them as `\*` comments immediately before the generated operator definitions.
|
|
133
|
-
- Allow generator-form `Forall`, `Exists`, `SetIf`, `Set`, and `Map` calls to
|
|
134
|
-
pass `name=` for readable TLA+ binder names while preserving internal unique
|
|
135
|
-
binder identities.
|
|
136
|
-
- Short-circuit interpreter evaluation of `Implies(False, rhs)`, so the right
|
|
137
|
-
side is not evaluated when the antecedent is false.
|
|
138
|
-
- Add `Max` and `Min` builtins. They take either several integers
|
|
139
|
-
(`Max(a, b, c)`) or a single set or list of integers (`Max(s)`). Both are
|
|
140
|
-
syntactic sugar — the multi-argument form expands to nested `Ite` and the
|
|
141
|
-
collection form folds with `reduce` — so they work in the interpreter, TLC,
|
|
142
|
-
and Apalache. `Max(s)`/`Min(s)` over an empty collection are undefined and
|
|
143
|
-
raise, mirroring `CHOOSE` over an empty set; guard with
|
|
144
|
-
`Ite(s.size == 0, default, Max(s))` when you need a fallback.
|
|
113
|
+
Changes since public release v0.136.1.
|
|
114
|
+
|
|
115
|
+
- Add an inductive invariant in `examples/tendermint_single_indinv.py`.
|
|
145
116
|
|
|
146
117
|
## 6. License
|
|
147
118
|
|
|
@@ -87,45 +87,16 @@ commands are not included in this package.
|
|
|
87
87
|
|
|
88
88
|
## 4. Release Provenance
|
|
89
89
|
|
|
90
|
-
- Release tag: `v0.136.
|
|
91
|
-
- Source commit: `
|
|
90
|
+
- Release tag: `v0.136.4`
|
|
91
|
+
- Source commit: `7beedd2736b068f1c371f59fd8ed619a1d9e75a5`
|
|
92
92
|
|
|
93
93
|
See [tests/README.md][] for the development test log captured at release time.
|
|
94
94
|
|
|
95
95
|
## 5. Latest Release Notes
|
|
96
96
|
|
|
97
|
-
Changes since public release v0.
|
|
98
|
-
|
|
99
|
-
- Add
|
|
100
|
-
TLA+ operator for the selected `@coverage` function as `--view=<Oper>` for
|
|
101
|
-
`check` and `simulate`.
|
|
102
|
-
- Allow `wunderspec with-apalache --max-steps 0` to check initial states.
|
|
103
|
-
- Write Apalache output to an `apalache.log` file while it runs, report the log
|
|
104
|
-
path, and add `--verbose` to stream the log to the terminal immediately.
|
|
105
|
-
- Require `default=...` for collection-form `Max` and `Min`, e.g.
|
|
106
|
-
`Max(s, default=0)`. The default is the reduce seed, so empty collections no
|
|
107
|
-
longer need a separate size guard or `CHOOSE`-based seed.
|
|
108
|
-
- Render collection-form `Max` and `Min` reducers as TLAPS-friendly `CHOOSE`
|
|
109
|
-
expressions in generated TLA+ while keeping reducer-based interpreter
|
|
110
|
-
evaluation.
|
|
111
|
-
- Add TLA+ labels to `wunderspec convert` output for `@invariant` and
|
|
112
|
-
`@example` operators, including nested predicate calls by default. Use
|
|
113
|
-
`@invariant(inline=True)` or `@example(inline=True)` to inline nested
|
|
114
|
-
predicate calls instead.
|
|
115
|
-
- Preserve definition docstrings when converting Wunderspec to TLA+ by emitting
|
|
116
|
-
them as `\*` comments immediately before the generated operator definitions.
|
|
117
|
-
- Allow generator-form `Forall`, `Exists`, `SetIf`, `Set`, and `Map` calls to
|
|
118
|
-
pass `name=` for readable TLA+ binder names while preserving internal unique
|
|
119
|
-
binder identities.
|
|
120
|
-
- Short-circuit interpreter evaluation of `Implies(False, rhs)`, so the right
|
|
121
|
-
side is not evaluated when the antecedent is false.
|
|
122
|
-
- Add `Max` and `Min` builtins. They take either several integers
|
|
123
|
-
(`Max(a, b, c)`) or a single set or list of integers (`Max(s)`). Both are
|
|
124
|
-
syntactic sugar — the multi-argument form expands to nested `Ite` and the
|
|
125
|
-
collection form folds with `reduce` — so they work in the interpreter, TLC,
|
|
126
|
-
and Apalache. `Max(s)`/`Min(s)` over an empty collection are undefined and
|
|
127
|
-
raise, mirroring `CHOOSE` over an empty set; guard with
|
|
128
|
-
`Ite(s.size == 0, default, Max(s))` when you need a fallback.
|
|
97
|
+
Changes since public release v0.136.1.
|
|
98
|
+
|
|
99
|
+
- Add an inductive invariant in `examples/tendermint_single_indinv.py`.
|
|
129
100
|
|
|
130
101
|
## 6. License
|
|
131
102
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Single-height Tendermint consensus protocol in Wunderspec.
|
|
3
3
|
|
|
4
|
+
The original Tendermint consensus appears in: https://arxiv.org/abs/1807.04938.
|
|
5
|
+
|
|
4
6
|
The first version was translated by Codex GPT 5.5 from TLA+ specification:
|
|
7
|
+
|
|
5
8
|
https://github.com/cometbft/cometbft/blob/main/spec/light-client/accountability/TendermintAcc_004_draft.tla
|
|
6
9
|
|
|
7
10
|
We made additional specification extensions:
|
|
@@ -11,6 +14,9 @@ We made additional specification extensions:
|
|
|
11
14
|
- Add `faulty_step` to model the messages by the faulty replicas
|
|
12
15
|
- Add `correct_step` to model the behavior by the correct replicas only
|
|
13
16
|
|
|
17
|
+
As in the original specification, timeouts are in general omitted, except when
|
|
18
|
+
they might affect safety.
|
|
19
|
+
|
|
14
20
|
Igor Konnov, 2026
|
|
15
21
|
"""
|
|
16
22
|
|
|
@@ -29,7 +35,6 @@ from wunderspec import (
|
|
|
29
35
|
Implies,
|
|
30
36
|
Interval,
|
|
31
37
|
Map,
|
|
32
|
-
Or,
|
|
33
38
|
Param,
|
|
34
39
|
Set,
|
|
35
40
|
SetIf,
|
|
@@ -153,9 +158,6 @@ class TendermintAccState(MachineStateBase):
|
|
|
153
158
|
msgs_propose: StateVar[dict[Round, set[ProposalMsg]]]
|
|
154
159
|
msgs_prevote: StateVar[dict[Round, set[VoteMsg]]]
|
|
155
160
|
msgs_precommit: StateVar[dict[Round, set[VoteMsg]]]
|
|
156
|
-
evidence_propose: StateVar[set[ProposalMsg]]
|
|
157
|
-
evidence_prevote: StateVar[set[VoteMsg]]
|
|
158
|
-
evidence_precommit: StateVar[set[VoteMsg]]
|
|
159
161
|
last_action: StateVar[str]
|
|
160
162
|
|
|
161
163
|
|
|
@@ -251,7 +253,10 @@ def all_faulty_precommits(s: TendermintAccState) -> Expr:
|
|
|
251
253
|
|
|
252
254
|
@action(init=True)
|
|
253
255
|
def init(c: Context[TendermintAccState]):
|
|
254
|
-
"""
|
|
256
|
+
"""
|
|
257
|
+
Algorithm 1, lines 1-9, adapted to one height.
|
|
258
|
+
This initializer starts with empty message logs for faulty processes.
|
|
259
|
+
"""
|
|
255
260
|
s = c.state
|
|
256
261
|
s.round = Map(Val(0) for _ in s.Corr)
|
|
257
262
|
s.step = Map(Val(Step.PROPOSE) for _ in s.Corr)
|
|
@@ -260,18 +265,18 @@ def init(c: Context[TendermintAccState]):
|
|
|
260
265
|
s.locked_round = Map(Val(NIL_ROUND) for _ in s.Corr)
|
|
261
266
|
s.valid_value = Map(Val(NIL_VALUE) for _ in s.Corr)
|
|
262
267
|
s.valid_round = Map(Val(NIL_ROUND) for _ in s.Corr)
|
|
263
|
-
s.msgs_propose = Map(Set(ProposalMsg) for
|
|
264
|
-
s.msgs_prevote = Map(Set(VoteMsg) for
|
|
265
|
-
s.msgs_precommit = Map(Set(VoteMsg) for
|
|
266
|
-
s.evidence_propose = Set(ProposalMsg)
|
|
267
|
-
s.evidence_prevote = Set(VoteMsg)
|
|
268
|
-
s.evidence_precommit = Set(VoteMsg)
|
|
268
|
+
s.msgs_propose = Map(Set(ProposalMsg) for _ in rounds(s))
|
|
269
|
+
s.msgs_prevote = Map(Set(VoteMsg) for _ in rounds(s))
|
|
270
|
+
s.msgs_precommit = Map(Set(VoteMsg) for _ in rounds(s))
|
|
269
271
|
s.last_action = Val(ACTION_INIT)
|
|
270
272
|
|
|
271
273
|
|
|
272
274
|
@action(init=True)
|
|
273
275
|
def init_with_faults(c: Context[TendermintAccState]):
|
|
274
|
-
"""
|
|
276
|
+
"""
|
|
277
|
+
Algorithm 1, lines 1-9, adapted to one height.
|
|
278
|
+
Extension: the initial message logs may contain arbitrary faulty messages.
|
|
279
|
+
"""
|
|
275
280
|
s = c.state
|
|
276
281
|
with (
|
|
277
282
|
c.one_of(AllSubsets(all_faulty_proposals(s)), "faulty_proposals") as fprop,
|
|
@@ -294,9 +299,6 @@ def init_with_faults(c: Context[TendermintAccState]):
|
|
|
294
299
|
s.msgs_precommit = Map(
|
|
295
300
|
fcommit.filter(lambda m: m.round == rnd) for rnd in rounds(s)
|
|
296
301
|
)
|
|
297
|
-
s.evidence_propose = Set(ProposalMsg)
|
|
298
|
-
s.evidence_prevote = Set(VoteMsg)
|
|
299
|
-
s.evidence_precommit = Set(VoteMsg)
|
|
300
302
|
s.last_action = Val(ACTION_INIT)
|
|
301
303
|
|
|
302
304
|
|
|
@@ -333,7 +335,12 @@ def broadcast_precommit(
|
|
|
333
335
|
|
|
334
336
|
@action(inline=True)
|
|
335
337
|
def start_round(c: Context[TendermintAccState], p: Expr, rnd: Expr):
|
|
336
|
-
"""
|
|
338
|
+
"""
|
|
339
|
+
10: upon start, enter StartRound(0)
|
|
340
|
+
11: StartRound(round)
|
|
341
|
+
12: round_p <- round
|
|
342
|
+
13: step_p <- propose
|
|
343
|
+
"""
|
|
337
344
|
s = c.state
|
|
338
345
|
c.assume(s.step[p] != Step.DECIDED)
|
|
339
346
|
s.round[p] = rnd
|
|
@@ -342,7 +349,16 @@ def start_round(c: Context[TendermintAccState], p: Expr, rnd: Expr):
|
|
|
342
349
|
|
|
343
350
|
@action(inline=False)
|
|
344
351
|
def insert_proposal(c: Context[TendermintAccState], p: Expr):
|
|
345
|
-
"""
|
|
352
|
+
"""
|
|
353
|
+
14: if proposer(h_p, round_p) = p then
|
|
354
|
+
15: if validValue_p != nil then
|
|
355
|
+
16: proposal <- validValue_p
|
|
356
|
+
17: else:
|
|
357
|
+
18: proposal <- getValue()
|
|
358
|
+
19: broadcast <PROPOSAL, h_p, round_p, proposal, validRound_p>
|
|
359
|
+
20: else:
|
|
360
|
+
21: schedule OnTimeoutPropose(h_p, round_p) ...
|
|
361
|
+
"""
|
|
346
362
|
s = c.state
|
|
347
363
|
rnd = c.cache(s.round[p])
|
|
348
364
|
c.assume(p == s.Proposer[rnd])
|
|
@@ -356,14 +372,20 @@ def insert_proposal(c: Context[TendermintAccState], p: Expr):
|
|
|
356
372
|
|
|
357
373
|
@action(inline=False)
|
|
358
374
|
def upon_proposal_in_propose(c: Context[TendermintAccState], p: Expr):
|
|
359
|
-
"""
|
|
375
|
+
"""
|
|
376
|
+
22: upon proposal (v, -1) from proposer while step_p = propose
|
|
377
|
+
23: if valid(v) and (lockedRound_p = -1 or lockedValue_p = v) then
|
|
378
|
+
24: broadcast PREVOTE for v
|
|
379
|
+
25: else
|
|
380
|
+
26: broadcast PREVOTE for nil
|
|
381
|
+
27: step_p <- prevote
|
|
382
|
+
"""
|
|
360
383
|
s = c.state
|
|
361
384
|
rnd = c.cache(s.round[p])
|
|
362
385
|
c.assume(s.step[p] == Step.PROPOSE)
|
|
363
386
|
with c.one_of(values(s), "v") as v:
|
|
364
387
|
msg = mk_proposal(s.Proposer[rnd], rnd, v, Val(NIL_ROUND))
|
|
365
388
|
c.assume(s.msgs_propose[rnd].contains(msg))
|
|
366
|
-
s.evidence_propose |= Set(msg)
|
|
367
389
|
vote_id = v.if_(
|
|
368
390
|
is_valid(s, v)
|
|
369
391
|
& ((s.locked_round[p] == NIL_ROUND) | (s.locked_value[p] == v))
|
|
@@ -375,7 +397,15 @@ def upon_proposal_in_propose(c: Context[TendermintAccState], p: Expr):
|
|
|
375
397
|
|
|
376
398
|
@action(inline=False)
|
|
377
399
|
def upon_proposal_in_propose_and_prevote(c: Context[TendermintAccState], p: Expr):
|
|
378
|
-
"""
|
|
400
|
+
"""
|
|
401
|
+
28: upon proposal (v, vr) from proposer and 2f+1 prevotes for v in vr
|
|
402
|
+
while step_p = propose and 0 <= vr < round_p
|
|
403
|
+
29: if valid(v) and (lockedRound_p <= vr or lockedValue_p = v) then
|
|
404
|
+
30: broadcast PREVOTE for v
|
|
405
|
+
31: else
|
|
406
|
+
32: broadcast PREVOTE for nil
|
|
407
|
+
33: step_p <- prevote
|
|
408
|
+
"""
|
|
379
409
|
s = c.state
|
|
380
410
|
rnd = c.cache(s.round[p])
|
|
381
411
|
c.assume(s.step[p] == Step.PROPOSE)
|
|
@@ -386,8 +416,6 @@ def upon_proposal_in_propose_and_prevote(c: Context[TendermintAccState], p: Expr
|
|
|
386
416
|
prevotes = c.cache(s.msgs_prevote[vr].filter(lambda m: m.id == v))
|
|
387
417
|
c.assume(s.msgs_propose[rnd].contains(msg))
|
|
388
418
|
c.assume(prevotes.size >= threshold2(s))
|
|
389
|
-
s.evidence_propose |= Set(msg)
|
|
390
|
-
s.evidence_prevote |= prevotes
|
|
391
419
|
vote_id = v.if_(
|
|
392
420
|
is_valid(s, v) & ((s.locked_round[p] <= vr) | (s.locked_value[p] == v))
|
|
393
421
|
).else_(NIL_VALUE)
|
|
@@ -398,13 +426,18 @@ def upon_proposal_in_propose_and_prevote(c: Context[TendermintAccState], p: Expr
|
|
|
398
426
|
|
|
399
427
|
@action(inline=False)
|
|
400
428
|
def upon_quorum_of_prevotes_any(c: Context[TendermintAccState], p: Expr):
|
|
401
|
-
"""
|
|
429
|
+
"""
|
|
430
|
+
34: upon 2f+1 current-round prevotes while step_p = prevote
|
|
431
|
+
35: schedule OnTimeoutPrevote(h_p, round_p)
|
|
432
|
+
|
|
433
|
+
This safety model does not store timers. The transition represents the
|
|
434
|
+
timeout firing and immediately takes the nil-precommit path.
|
|
435
|
+
"""
|
|
402
436
|
s = c.state
|
|
403
437
|
rnd = c.cache(s.round[p])
|
|
404
438
|
c.assume(s.step[p] == Step.PREVOTE)
|
|
405
439
|
with c.one_of(AllSubsets(s.msgs_prevote[rnd]), "my_evidence") as ev:
|
|
406
440
|
c.assume(senders(s, ev).size >= threshold2(s))
|
|
407
|
-
s.evidence_prevote |= ev
|
|
408
441
|
broadcast_precommit(c, p, rnd, Val(NIL_VALUE))
|
|
409
442
|
s.step[p] = Val(Step.PRECOMMIT)
|
|
410
443
|
s.last_action = Val(ACTION_UPON_QUORUM_OF_PREVOTES_ANY)
|
|
@@ -414,7 +447,17 @@ def upon_quorum_of_prevotes_any(c: Context[TendermintAccState], p: Expr):
|
|
|
414
447
|
def upon_proposal_in_prevote_or_commit_and_prevote(
|
|
415
448
|
c: Context[TendermintAccState], p: Expr
|
|
416
449
|
):
|
|
417
|
-
"""
|
|
450
|
+
"""
|
|
451
|
+
36: upon proposal (v, *) and 2f+1 current-round prevotes for v
|
|
452
|
+
while valid(v) and step_p >= prevote
|
|
453
|
+
37: if step_p = prevote then
|
|
454
|
+
38: lockedValue_p <- v
|
|
455
|
+
39: lockedRound_p <- round_p
|
|
456
|
+
40: broadcast PRECOMMIT for v
|
|
457
|
+
41: step_p <- precommit
|
|
458
|
+
42: validValue_p <- v
|
|
459
|
+
43: validRound_p <- round_p
|
|
460
|
+
"""
|
|
418
461
|
s = c.state
|
|
419
462
|
rnd = c.cache(s.round[p])
|
|
420
463
|
c.assume((s.step[p] == Step.PREVOTE) | (s.step[p] == Step.PRECOMMIT))
|
|
@@ -423,8 +466,6 @@ def upon_proposal_in_prevote_or_commit_and_prevote(
|
|
|
423
466
|
prevotes = c.cache(s.msgs_prevote[rnd].filter(lambda m: m.id == v))
|
|
424
467
|
c.assume(s.msgs_propose[rnd].contains(msg))
|
|
425
468
|
c.assume(prevotes.size >= threshold2(s))
|
|
426
|
-
s.evidence_propose |= Set(msg)
|
|
427
|
-
s.evidence_prevote |= prevotes
|
|
428
469
|
prevote_step, precommit_step = c.split(s.step[p] == Step.PREVOTE)
|
|
429
470
|
with prevote_step:
|
|
430
471
|
s.locked_value[p] = v
|
|
@@ -440,20 +481,34 @@ def upon_proposal_in_prevote_or_commit_and_prevote(
|
|
|
440
481
|
|
|
441
482
|
@action(inline=False)
|
|
442
483
|
def upon_quorum_of_precommits_any(c: Context[TendermintAccState], p: Expr):
|
|
443
|
-
"""
|
|
484
|
+
"""
|
|
485
|
+
47: upon 2f+1 current-round precommits
|
|
486
|
+
48: schedule OnTimeoutPrecommit(h_p, round_p)
|
|
487
|
+
|
|
488
|
+
This safety model does not store timers. The transition represents the
|
|
489
|
+
timeout firing and immediately advances to the next modeled round.
|
|
490
|
+
"""
|
|
444
491
|
s = c.state
|
|
445
492
|
rnd = c.cache(s.round[p])
|
|
446
493
|
with c.one_of(AllSubsets(s.msgs_precommit[rnd]), "my_evidence") as ev:
|
|
447
494
|
c.assume(senders(s, ev).size >= threshold2(s))
|
|
448
495
|
c.assume(rounds(s).contains(rnd + 1))
|
|
449
|
-
s.evidence_precommit |= ev
|
|
450
496
|
start_round(c, p, rnd + 1)
|
|
451
497
|
s.last_action = Val(ACTION_UPON_QUORUM_OF_PRECOMMITS_ANY)
|
|
452
498
|
|
|
453
499
|
|
|
454
500
|
@action(inline=False)
|
|
455
501
|
def upon_proposal_in_precommit_no_decision(c: Context[TendermintAccState], p: Expr):
|
|
456
|
-
"""
|
|
502
|
+
"""
|
|
503
|
+
49: upon proposal (v, *) and 2f+1 precommits for v while undecided
|
|
504
|
+
50: if valid(v) then
|
|
505
|
+
51: decision_p[h_p] <- v
|
|
506
|
+
52: h_p <- h_p + 1
|
|
507
|
+
53: reset locks, valid value, valid round, and message log
|
|
508
|
+
54: StartRound(0)
|
|
509
|
+
|
|
510
|
+
This one-height model records the decision and moves the process to DECIDED.
|
|
511
|
+
"""
|
|
457
512
|
s = c.state
|
|
458
513
|
c.assume(s.decision[p] == NIL_VALUE)
|
|
459
514
|
with (
|
|
@@ -465,8 +520,6 @@ def upon_proposal_in_precommit_no_decision(c: Context[TendermintAccState], p: Ex
|
|
|
465
520
|
precommits = c.cache(s.msgs_precommit[rnd].filter(lambda m: m.id == v))
|
|
466
521
|
c.assume(s.msgs_propose[rnd].contains(msg))
|
|
467
522
|
c.assume(precommits.size >= threshold2(s))
|
|
468
|
-
s.evidence_precommit |= precommits
|
|
469
|
-
s.evidence_propose |= Set(msg)
|
|
470
523
|
s.decision[p] = v
|
|
471
524
|
s.step[p] = Val(Step.DECIDED)
|
|
472
525
|
s.last_action = Val(ACTION_UPON_PROPOSAL_IN_PRECOMMIT_NO_DECISION)
|
|
@@ -474,7 +527,12 @@ def upon_proposal_in_precommit_no_decision(c: Context[TendermintAccState], p: Ex
|
|
|
474
527
|
|
|
475
528
|
@action(inline=False)
|
|
476
529
|
def on_timeout_propose(c: Context[TendermintAccState], p: Expr):
|
|
477
|
-
"""
|
|
530
|
+
"""
|
|
531
|
+
57: OnTimeoutPropose(height, round)
|
|
532
|
+
58: if height = h_p and round = round_p and step_p = propose then
|
|
533
|
+
59: broadcast PREVOTE for nil
|
|
534
|
+
60: step_p <- prevote
|
|
535
|
+
"""
|
|
478
536
|
s = c.state
|
|
479
537
|
rnd = c.cache(s.round[p])
|
|
480
538
|
c.assume(s.step[p] == Step.PROPOSE)
|
|
@@ -486,13 +544,16 @@ def on_timeout_propose(c: Context[TendermintAccState], p: Expr):
|
|
|
486
544
|
|
|
487
545
|
@action(inline=False)
|
|
488
546
|
def on_quorum_of_nil_prevotes(c: Context[TendermintAccState], p: Expr):
|
|
489
|
-
"""
|
|
547
|
+
"""
|
|
548
|
+
44: upon 2f+1 current-round PREVOTE nil messages while step_p = prevote
|
|
549
|
+
45: broadcast PRECOMMIT for nil
|
|
550
|
+
46: step_p <- precommit
|
|
551
|
+
"""
|
|
490
552
|
s = c.state
|
|
491
553
|
rnd = c.cache(s.round[p])
|
|
492
554
|
c.assume(s.step[p] == Step.PREVOTE)
|
|
493
555
|
prevotes = c.cache(s.msgs_prevote[rnd].filter(lambda m: m.id == NIL_VALUE))
|
|
494
556
|
c.assume(prevotes.size >= threshold2(s))
|
|
495
|
-
s.evidence_prevote |= prevotes
|
|
496
557
|
broadcast_precommit(c, p, rnd, Val(NIL_VALUE))
|
|
497
558
|
s.step[p] = Val(Step.PRECOMMIT)
|
|
498
559
|
s.last_action = Val(ACTION_ON_QUORUM_OF_NIL_PREVOTES)
|
|
@@ -500,7 +561,10 @@ def on_quorum_of_nil_prevotes(c: Context[TendermintAccState], p: Expr):
|
|
|
500
561
|
|
|
501
562
|
@action(inline=False)
|
|
502
563
|
def on_round_catchup(c: Context[TendermintAccState], p: Expr):
|
|
503
|
-
"""
|
|
564
|
+
"""
|
|
565
|
+
55: upon f+1 messages from a higher round
|
|
566
|
+
56: StartRound(round)
|
|
567
|
+
"""
|
|
504
568
|
s = c.state
|
|
505
569
|
with (
|
|
506
570
|
c.one_of(rounds(s), "rnd") as rnd,
|
|
@@ -513,9 +577,6 @@ def on_round_catchup(c: Context[TendermintAccState], p: Expr):
|
|
|
513
577
|
senders(s, ev_propose) | senders(s, ev_prevote) | senders(s, ev_precommit)
|
|
514
578
|
)
|
|
515
579
|
c.assume(faster.size >= threshold1(s))
|
|
516
|
-
s.evidence_propose |= ev_propose
|
|
517
|
-
s.evidence_prevote |= ev_prevote
|
|
518
|
-
s.evidence_precommit |= ev_precommit
|
|
519
580
|
start_round(c, p, rnd)
|
|
520
581
|
s.last_action = Val(ACTION_ON_ROUND_CATCHUP)
|
|
521
582
|
|
|
@@ -642,38 +703,6 @@ def equivocation_in(msgs: Expr, p: Expr) -> Expr:
|
|
|
642
703
|
)
|
|
643
704
|
|
|
644
705
|
|
|
645
|
-
def equivocation_by(s: TendermintAccState, p: Expr) -> Expr:
|
|
646
|
-
"""Whether process p equivocates in any collected evidence set."""
|
|
647
|
-
return Or(
|
|
648
|
-
equivocation_in(s.evidence_propose, p),
|
|
649
|
-
equivocation_in(s.evidence_prevote, p),
|
|
650
|
-
equivocation_in(s.evidence_precommit, p),
|
|
651
|
-
)
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
def amnesia_by(s: TendermintAccState, p: Expr) -> Expr:
|
|
655
|
-
"""Whether process p shows amnesia across conflicting locks and votes."""
|
|
656
|
-
return Exists(
|
|
657
|
-
And(
|
|
658
|
-
r1 < r2,
|
|
659
|
-
v1 != v2,
|
|
660
|
-
s.evidence_precommit.contains(mk_precommit(p, r1, v1)),
|
|
661
|
-
s.evidence_prevote.contains(mk_prevote(p, r2, v2)),
|
|
662
|
-
Forall(
|
|
663
|
-
s.evidence_prevote.filter(
|
|
664
|
-
lambda m: (m.round == rnd) & (m.id == v2)
|
|
665
|
-
).size
|
|
666
|
-
< threshold2(s)
|
|
667
|
-
for rnd in rounds(s).filter(lambda r: (r1 <= r) & (r < r2))
|
|
668
|
-
),
|
|
669
|
-
)
|
|
670
|
-
for r1 in rounds(s)
|
|
671
|
-
for r2 in rounds(s)
|
|
672
|
-
for v1 in s.ValidValues
|
|
673
|
-
for v2 in s.ValidValues
|
|
674
|
-
)
|
|
675
|
-
|
|
676
|
-
|
|
677
706
|
@invariant
|
|
678
707
|
def assumptions_hold(s: TendermintAccState) -> BoolExpr:
|
|
679
708
|
"""Model assumptions needed for the bounded accountability instance."""
|
|
@@ -714,18 +743,6 @@ def type_ok(s: TendermintAccState) -> BoolExpr:
|
|
|
714
743
|
)
|
|
715
744
|
for rnd in rounds(s)
|
|
716
745
|
),
|
|
717
|
-
Forall(
|
|
718
|
-
proposal_ok(s, m, m.round) & rounds(s).contains(m.round)
|
|
719
|
-
for m in s.evidence_propose
|
|
720
|
-
),
|
|
721
|
-
Forall(
|
|
722
|
-
vote_ok(s, m, m.round, VoteKind.PREVOTE) & rounds(s).contains(m.round)
|
|
723
|
-
for m in s.evidence_prevote
|
|
724
|
-
),
|
|
725
|
-
Forall(
|
|
726
|
-
vote_ok(s, m, m.round, VoteKind.PRECOMMIT) & rounds(s).contains(m.round)
|
|
727
|
-
for m in s.evidence_precommit
|
|
728
|
-
),
|
|
729
746
|
action_names().contains(s.last_action),
|
|
730
747
|
)
|
|
731
748
|
|
|
@@ -745,30 +762,6 @@ def validity(s: TendermintAccState) -> BoolExpr:
|
|
|
745
762
|
)
|
|
746
763
|
|
|
747
764
|
|
|
748
|
-
@invariant
|
|
749
|
-
def accountability(s: TendermintAccState) -> BoolExpr:
|
|
750
|
-
"""Either agreement holds or enough faulty processes expose evidence."""
|
|
751
|
-
return agreement_expr(s) | Exists(
|
|
752
|
-
And(
|
|
753
|
-
detectable.size >= threshold1(s),
|
|
754
|
-
Forall(equivocation_by(s, p) | amnesia_by(s, p) for p in detectable),
|
|
755
|
-
)
|
|
756
|
-
for detectable in AllSubsets(s.Faulty)
|
|
757
|
-
) # type: ignore
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
@invariant
|
|
761
|
-
def no_amnesia(s: TendermintAccState) -> BoolExpr:
|
|
762
|
-
"""False invariant used to produce traces with amnesic faulty behavior."""
|
|
763
|
-
return Forall(~amnesia_by(s, p) for p in s.Faulty)
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
@invariant
|
|
767
|
-
def no_equivocation(s: TendermintAccState) -> BoolExpr:
|
|
768
|
-
"""False invariant used to produce traces with equivocation evidence."""
|
|
769
|
-
return Forall(~equivocation_by(s, p) for p in s.Faulty)
|
|
770
|
-
|
|
771
|
-
|
|
772
765
|
@example
|
|
773
766
|
def no_agreement(s: TendermintAccState) -> BoolExpr:
|
|
774
767
|
"""Example of violating agreement, e.g. for N=4,T=1,F=2."""
|
|
@@ -782,30 +775,6 @@ def no_agreement(s: TendermintAccState) -> BoolExpr:
|
|
|
782
775
|
)
|
|
783
776
|
|
|
784
777
|
|
|
785
|
-
@example
|
|
786
|
-
def agreement_or_amnesia(s: TendermintAccState) -> BoolExpr:
|
|
787
|
-
"""Counterexample should show disagreement not explained by amnesia alone."""
|
|
788
|
-
return agreement_expr(s) | Forall(amnesia_by(s, p) for p in s.Faulty) # type: ignore
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
@example
|
|
792
|
-
def show_me_amnesia_without_equivocation(s: TendermintAccState) -> BoolExpr:
|
|
793
|
-
"""Example search for amnesia by a faulty process without equivocation."""
|
|
794
|
-
return Implies(
|
|
795
|
-
~agreement_expr(s) & Exists(~equivocation_by(s, p) for p in s.Faulty),
|
|
796
|
-
Forall(~amnesia_by(s, p) for p in s.Faulty),
|
|
797
|
-
)
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
@invariant
|
|
801
|
-
def amnesia_implies_equivocation(s: TendermintAccState) -> BoolExpr:
|
|
802
|
-
"""False invariant: amnesia need not imply equivocation."""
|
|
803
|
-
return Implies(
|
|
804
|
-
Exists(amnesia_by(s, p) for p in s.Faulty),
|
|
805
|
-
Exists(equivocation_by(s, q) for q in s.Faulty),
|
|
806
|
-
)
|
|
807
|
-
|
|
808
|
-
|
|
809
778
|
@example
|
|
810
779
|
def never_undecided_in_max_round(s: TendermintAccState) -> BoolExpr:
|
|
811
780
|
"""Example where all correct processes reach MaxRound but not all decide."""
|
|
@@ -841,9 +810,6 @@ def state_cov(s: TendermintAccState) -> Expr:
|
|
|
841
810
|
s.msgs_propose,
|
|
842
811
|
s.msgs_prevote,
|
|
843
812
|
s.msgs_precommit,
|
|
844
|
-
s.evidence_propose,
|
|
845
|
-
s.evidence_prevote,
|
|
846
|
-
s.evidence_precommit,
|
|
847
813
|
)
|
|
848
814
|
|
|
849
815
|
|