|
|
- -------------------------- MODULE Tinychain ----------------------------------
- (* A very abstract model of Tendermint blockchain. Its only purpose is to highlight
- the relation between validator sets, next validator sets, and last commits.
- *)
-
- EXTENDS Integers
-
- \* type annotation
- a <: b == a
-
- \* the type of validator sets, e.g., STRING
- VST == STRING
-
- \* LastCommit type.
- \* It contains:
- \* 1) the flag of whether the block id equals to the hash of the previous block, and
- \* 2) the set of the validators who have committed the block.
- \* In the implementation, blockId is the hash of the previous block, which cannot be forged.
- \* We abstract block id into whether it equals to the hash of the previous block or not.
- LCT == [blockIdEqRef |-> BOOLEAN, committers |-> VST]
-
- \* Block type.
- \* A block contains its height, validator set, next validator set, and last commit.
- \* Moreover, it contains the flag that tells us whether the block is equal to the one
- \* on the reference chain (this is an abstraction of hash).
- BT == [height |-> Int, hashEqRef |-> BOOLEAN, wellFormed |-> BOOLEAN,
- VS |-> VST, NextVS |-> VST, lastCommit |-> LCT]
-
- CONSTANTS
- (*
- A set of abstract values, each value representing a set of validators.
- For the purposes of this specification, they can be any values,
- e.g., "s1", "s2", etc.
- *)
- VALIDATOR_SETS,
- (* a nil validator set that is outside of VALIDATOR_SETS *)
- NIL_VS,
- (* The maximal height, up to which the blockchain may grow *)
- MAX_HEIGHT
-
- Heights == 1..MAX_HEIGHT
-
- \* the set of all potential commits
- Commits == [blockIdEqRef: BOOLEAN, committers: VALIDATOR_SETS]
-
- \* the set of all potential blocks, not necessarily coming from the blockchain
- Blocks ==
- [height: Heights, hashEqRef: BOOLEAN, wellFormed: BOOLEAN,
- VS: VALIDATOR_SETS, NextVS: VALIDATOR_SETS, lastCommit: Commits]
-
- \* Does the chain contain a sound sequence of blocks that could be produced by
- \* a 2/3 of faulty validators. This operator can be used to initialise the chain!
- \* Since we are abstracting validator sets with VALIDATOR_SETS, which are
- \* 2/3 quorums, we just compare committers to those sets. In a more detailed
- \* specification, one would write the \subseteq operator instead of equality.
- IsCorrectChain(chain) ==
- \* restrict the structure of the blocks, to decrease the TLC search space
- LET OkCommits == [blockIdEqRef: {TRUE}, committers: VALIDATOR_SETS]
- OkBlocks == [height: Heights, hashEqRef: {TRUE}, wellFormed: {TRUE},
- VS: VALIDATOR_SETS, NextVS: VALIDATOR_SETS, lastCommit: OkCommits]
- IN
- /\ chain \in [1..MAX_HEIGHT -> OkBlocks]
- /\ \A h \in 1..MAX_HEIGHT:
- LET b == chain[h] IN
- /\ b.height = h \* the height is correct
- /\ h > 1 =>
- LET p == chain[h - 1] IN
- /\ b.VS = p.NextVS \* the validators propagate from the previous block
- /\ b.lastCommit.committers = p.VS \* and they are the committers
-
-
- \* The basic properties of blocks on the blockchain:
- \* They should pass the validity check and they may verify the next block.
-
- \* Does the block pass the consistency check against the next validators of the previous block
- IsMatchingValidators(block, nextVS) ==
- \* simply check that the validator set is propagated correctly.
- \* (the implementation tests hashes and the application state)
- block.VS = nextVS
-
- \* Does the block verify the commit (of the next block)
- PossibleCommit(block, commit) ==
- \* the commits are signed by the block validators
- /\ commit.committers = block.VS
- \* The block id in the commit matches the block hash (abstract comparison).
- \* (The implementation has extensive tests for that.)
- \* this is an abstraction of: commit.blockId = hash(block)
- \*
- \* These are possible scenarios on the concrete hashes:
- \*
- \* scenario 1: commit.blockId = 10 /\ hash(block) = 10 /\ hash(ref) = 10
- \* scenario 2: commit.blockId = 20 /\ hash(block) = 20 /\ block.VS /= ref.VS
- \* scenario 3: commit.blockId = 50 /\ hash(block) = 100
- \* scenario 4: commit.blockId = 10 /\ hash(block) = 100
- \* scenario 5: commit.blockId = 100 /\ hash(block) = 10
- /\ commit.blockIdEqRef = block.hashEqRef
- \* the following test would be cheating, as we do not have access to the
- \* reference chain:
- \* /\ commit.blockIdEqRef
-
- \* Basic invariants
-
- \* every block has the validator set that is chosen by its predecessor
- ValidBlockInv(chain) ==
- \A h \in 2..MAX_HEIGHT:
- IsMatchingValidators(chain[h], chain[h - 1].NextVS)
-
- \* last commit of every block is signed by the validators of the predecessor
- VerifiedBlockInv(chain) ==
- \A h \in 2..MAX_HEIGHT:
- PossibleCommit(chain[h - 1], chain[h].lastCommit)
-
- ==================================================================================
|