Add light client detector spec in TLA+pull/7804/head
@ -0,0 +1,10 @@ | |||
no;filename;tool;timeout;init;inv;next;args | |||
1;LCD_MC3_3_faulty.tla;apalache;1h;;CommonHeightOnEvidenceInv;;--length=10 | |||
2;LCD_MC3_3_faulty.tla;apalache;1h;;AccuracyInv;;--length=10 | |||
3;LCD_MC3_3_faulty.tla;apalache;1h;;PrecisionInvLocal;;--length=10 | |||
4;LCD_MC3_4_faulty.tla;apalache;1h;;CommonHeightOnEvidenceInv;;--length=10 | |||
5;LCD_MC3_4_faulty.tla;apalache;1h;;AccuracyInv;;--length=10 | |||
6;LCD_MC3_4_faulty.tla;apalache;1h;;PrecisionInvLocal;;--length=10 | |||
7;LCD_MC4_4_faulty.tla;apalache;1h;;CommonHeightOnEvidenceInv;;--length=10 | |||
8;LCD_MC4_4_faulty.tla;apalache;1h;;AccuracyInv;;--length=10 | |||
9;LCD_MC4_4_faulty.tla;apalache;1h;;PrecisionInvLocal;;--length=10 |
@ -0,0 +1,4 @@ | |||
no;filename;tool;timeout;init;inv;next;args | |||
1;LCD_MC3_3_faulty.tla;apalache;1h;;PrecisionInvGrayZone;;--length=10 | |||
2;LCD_MC3_4_faulty.tla;apalache;1h;;PrecisionInvGrayZone;;--length=10 | |||
3;LCD_MC4_4_faulty.tla;apalache;1h;;PrecisionInvGrayZone;;--length=10 |
@ -0,0 +1,164 @@ | |||
------------------------ MODULE Blockchain_003_draft ----------------------------- | |||
(* | |||
This is a high-level specification of Tendermint blockchain | |||
that is designed specifically for the light client. | |||
Validators have the voting power of one. If you like to model various | |||
voting powers, introduce multiple copies of the same validator | |||
(do not forget to give them unique names though). | |||
*) | |||
EXTENDS Integers, FiniteSets | |||
Min(a, b) == IF a < b THEN a ELSE b | |||
CONSTANT | |||
AllNodes, | |||
(* a set of all nodes that can act as validators (correct and faulty) *) | |||
ULTIMATE_HEIGHT, | |||
(* a maximal height that can be ever reached (modelling artifact) *) | |||
TRUSTING_PERIOD | |||
(* the period within which the validators are trusted *) | |||
Heights == 1..ULTIMATE_HEIGHT (* possible heights *) | |||
(* A commit is just a set of nodes who have committed the block *) | |||
Commits == SUBSET AllNodes | |||
(* The set of all block headers that can be on the blockchain. | |||
This is a simplified version of the Block data structure in the actual implementation. *) | |||
BlockHeaders == [ | |||
height: Heights, | |||
\* the block height | |||
time: Int, | |||
\* the block timestamp in some integer units | |||
lastCommit: Commits, | |||
\* the nodes who have voted on the previous block, the set itself instead of a hash | |||
(* in the implementation, only the hashes of V and NextV are stored in a block, | |||
as V and NextV are stored in the application state *) | |||
VS: SUBSET AllNodes, | |||
\* the validators of this bloc. We store the validators instead of the hash. | |||
NextVS: SUBSET AllNodes | |||
\* the validators of the next block. We store the next validators instead of the hash. | |||
] | |||
(* A signed header is just a header together with a set of commits *) | |||
LightBlocks == [header: BlockHeaders, Commits: Commits] | |||
VARIABLES | |||
refClock, | |||
(* the current global time in integer units as perceived by the reference chain *) | |||
blockchain, | |||
(* A sequence of BlockHeaders, which gives us a bird view of the blockchain. *) | |||
Faulty | |||
(* A set of faulty nodes, which can act as validators. We assume that the set | |||
of faulty processes is non-decreasing. If a process has recovered, it should | |||
connect using a different id. *) | |||
(* all variables, to be used with UNCHANGED *) | |||
vars == <<refClock, blockchain, Faulty>> | |||
(* The set of all correct nodes in a state *) | |||
Corr == AllNodes \ Faulty | |||
(* APALACHE annotations *) | |||
a <: b == a \* type annotation | |||
NT == STRING | |||
NodeSet(S) == S <: {NT} | |||
EmptyNodeSet == NodeSet({}) | |||
BT == [height |-> Int, time |-> Int, lastCommit |-> {NT}, VS |-> {NT}, NextVS |-> {NT}] | |||
LBT == [header |-> BT, Commits |-> {NT}] | |||
(* end of APALACHE annotations *) | |||
(****************************** BLOCKCHAIN ************************************) | |||
(* the header is still within the trusting period *) | |||
InTrustingPeriod(header) == | |||
refClock < header.time + TRUSTING_PERIOD | |||
(* | |||
Given a function pVotingPower \in D -> Powers for some D \subseteq AllNodes | |||
and pNodes \subseteq D, test whether the set pNodes \subseteq AllNodes has | |||
more than 2/3 of voting power among the nodes in D. | |||
*) | |||
TwoThirds(pVS, pNodes) == | |||
LET TP == Cardinality(pVS) | |||
SP == Cardinality(pVS \intersect pNodes) | |||
IN | |||
3 * SP > 2 * TP \* when thinking in real numbers, not integers: SP > 2.0 / 3.0 * TP | |||
(* | |||
Given a set of FaultyNodes, test whether the voting power of the correct nodes in D | |||
is more than 2/3 of the voting power of the faulty nodes in D. | |||
Parameters: | |||
- pFaultyNodes is a set of nodes that are considered faulty | |||
- pVS is a set of all validators, maybe including Faulty, intersecting with it, etc. | |||
- pMaxFaultRatio is a pair <<a, b>> that limits the ratio a / b of the faulty | |||
validators from above (exclusive) | |||
*) | |||
FaultyValidatorsFewerThan(pFaultyNodes, pVS, maxRatio) == | |||
LET FN == pFaultyNodes \intersect pVS \* faulty nodes in pNodes | |||
CN == pVS \ pFaultyNodes \* correct nodes in pNodes | |||
CP == Cardinality(CN) \* power of the correct nodes | |||
FP == Cardinality(FN) \* power of the faulty nodes | |||
IN | |||
\* CP + FP = TP is the total voting power | |||
LET TP == CP + FP IN | |||
FP * maxRatio[2] < TP * maxRatio[1] | |||
(* Can a block be produced by a correct peer, or an authenticated Byzantine peer *) | |||
IsLightBlockAllowedByDigitalSignatures(ht, block) == | |||
\/ block.header = blockchain[ht] \* signed by correct and faulty (maybe) | |||
\/ /\ block.Commits \subseteq Faulty | |||
/\ block.header.height = ht | |||
/\ block.header.time >= 0 \* signed only by faulty | |||
(* | |||
Initialize the blockchain to the ultimate height right in the initial states. | |||
We pick the faulty validators statically, but that should not affect the light client. | |||
Parameters: | |||
- pMaxFaultyRatioExclusive is a pair <<a, b>> that bound the number of | |||
faulty validators in each block by the ratio a / b (exclusive) | |||
*) | |||
InitToHeight(pMaxFaultyRatioExclusive) == | |||
/\ Faulty \in SUBSET AllNodes \* some nodes may fail | |||
\* pick the validator sets and last commits | |||
/\ \E vs, lastCommit \in [Heights -> SUBSET AllNodes]: | |||
\E timestamp \in [Heights -> Int]: | |||
\* refClock is at least as early as the timestamp in the last block | |||
/\ \E tm \in Int: refClock = tm /\ tm >= timestamp[ULTIMATE_HEIGHT] | |||
\* the genesis starts on day 1 | |||
/\ timestamp[1] = 1 | |||
/\ vs[1] = AllNodes | |||
/\ lastCommit[1] = EmptyNodeSet | |||
/\ \A h \in Heights \ {1}: | |||
/\ lastCommit[h] \subseteq vs[h - 1] \* the non-validators cannot commit | |||
/\ TwoThirds(vs[h - 1], lastCommit[h]) \* the commit has >2/3 of validator votes | |||
\* the faulty validators have the power below the threshold | |||
/\ FaultyValidatorsFewerThan(Faulty, vs[h], pMaxFaultyRatioExclusive) | |||
/\ timestamp[h] > timestamp[h - 1] \* the time grows monotonically | |||
/\ timestamp[h] < timestamp[h - 1] + TRUSTING_PERIOD \* but not too fast | |||
\* form the block chain out of validator sets and commits (this makes apalache faster) | |||
/\ blockchain = [h \in Heights |-> | |||
[height |-> h, | |||
time |-> timestamp[h], | |||
VS |-> vs[h], | |||
NextVS |-> IF h < ULTIMATE_HEIGHT THEN vs[h + 1] ELSE AllNodes, | |||
lastCommit |-> lastCommit[h]] | |||
] \****** | |||
(********************* BLOCKCHAIN ACTIONS ********************************) | |||
(* | |||
Advance the clock by zero or more time units. | |||
*) | |||
AdvanceTime == | |||
/\ \E tm \in Int: tm >= refClock /\ refClock' = tm | |||
/\ UNCHANGED <<blockchain, Faulty>> | |||
============================================================================= | |||
\* Modification History | |||
\* Last modified Wed Jun 10 14:10:54 CEST 2020 by igor | |||
\* Created Fri Oct 11 15:45:11 CEST 2019 by igor |
@ -0,0 +1,27 @@ | |||
------------------------- MODULE LCD_MC3_3_faulty --------------------------- | |||
AllNodes == {"n1", "n2", "n3"} | |||
TRUSTED_HEIGHT == 1 | |||
TARGET_HEIGHT == 3 | |||
TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) | |||
CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting | |||
REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting | |||
IS_PRIMARY_CORRECT == FALSE | |||
IS_SECONDARY_CORRECT == TRUE | |||
FAULTY_RATIO == <<2, 3>> \* < 1 / 3 faulty validators | |||
VARIABLES | |||
blockchain, (* the reference blockchain *) | |||
localClock, (* current time in the light client *) | |||
refClock, (* current time in the reference blockchain *) | |||
Faulty, (* the set of faulty validators *) | |||
state, (* the state of the light client detector *) | |||
fetchedLightBlocks1, (* a function from heights to LightBlocks *) | |||
fetchedLightBlocks2, (* a function from heights to LightBlocks *) | |||
fetchedLightBlocks1b, (* a function from heights to LightBlocks *) | |||
commonHeight, (* the height that is trusted in CreateEvidenceForPeer *) | |||
nextHeightToTry, (* the index in CreateEvidenceForPeer *) | |||
evidences | |||
INSTANCE LCDetector_003_draft | |||
============================================================================ |
@ -0,0 +1,27 @@ | |||
------------------------- MODULE LCD_MC3_4_faulty --------------------------- | |||
AllNodes == {"n1", "n2", "n3"} | |||
TRUSTED_HEIGHT == 1 | |||
TARGET_HEIGHT == 4 | |||
TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) | |||
CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting | |||
REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting | |||
IS_PRIMARY_CORRECT == FALSE | |||
IS_SECONDARY_CORRECT == TRUE | |||
FAULTY_RATIO == <<2, 3>> \* < 1 / 3 faulty validators | |||
VARIABLES | |||
blockchain, (* the reference blockchain *) | |||
localClock, (* current time in the light client *) | |||
refClock, (* current time in the reference blockchain *) | |||
Faulty, (* the set of faulty validators *) | |||
state, (* the state of the light client detector *) | |||
fetchedLightBlocks1, (* a function from heights to LightBlocks *) | |||
fetchedLightBlocks2, (* a function from heights to LightBlocks *) | |||
fetchedLightBlocks1b, (* a function from heights to LightBlocks *) | |||
commonHeight, (* the height that is trusted in CreateEvidenceForPeer *) | |||
nextHeightToTry, (* the index in CreateEvidenceForPeer *) | |||
evidences | |||
INSTANCE LCDetector_003_draft | |||
============================================================================ |
@ -0,0 +1,27 @@ | |||
------------------------- MODULE LCD_MC4_4_faulty --------------------------- | |||
AllNodes == {"n1", "n2", "n3", "n4"} | |||
TRUSTED_HEIGHT == 1 | |||
TARGET_HEIGHT == 4 | |||
TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) | |||
CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting | |||
REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting | |||
IS_PRIMARY_CORRECT == FALSE | |||
IS_SECONDARY_CORRECT == TRUE | |||
FAULTY_RATIO == <<2, 3>> \* < 2 / 3 faulty validators | |||
VARIABLES | |||
blockchain, (* the reference blockchain *) | |||
localClock, (* current time in the light client *) | |||
refClock, (* current time in the reference blockchain *) | |||
Faulty, (* the set of faulty validators *) | |||
state, (* the state of the light client detector *) | |||
fetchedLightBlocks1, (* a function from heights to LightBlocks *) | |||
fetchedLightBlocks2, (* a function from heights to LightBlocks *) | |||
fetchedLightBlocks1b, (* a function from heights to LightBlocks *) | |||
commonHeight, (* the height that is trusted in CreateEvidenceForPeer *) | |||
nextHeightToTry, (* the index in CreateEvidenceForPeer *) | |||
evidences | |||
INSTANCE LCDetector_003_draft | |||
============================================================================ |
@ -0,0 +1,27 @@ | |||
------------------------- MODULE LCD_MC5_5_faulty --------------------------- | |||
AllNodes == {"n1", "n2", "n3", "n4", "n5"} | |||
TRUSTED_HEIGHT == 1 | |||
TARGET_HEIGHT == 5 | |||
TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) | |||
CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting | |||
REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting | |||
IS_PRIMARY_CORRECT == FALSE | |||
IS_SECONDARY_CORRECT == TRUE | |||
FAULTY_RATIO == <<2, 3>> \* < 1 / 3 faulty validators | |||
VARIABLES | |||
blockchain, (* the reference blockchain *) | |||
localClock, (* current time in the light client *) | |||
refClock, (* current time in the reference blockchain *) | |||
Faulty, (* the set of faulty validators *) | |||
state, (* the state of the light client detector *) | |||
fetchedLightBlocks1, (* a function from heights to LightBlocks *) | |||
fetchedLightBlocks2, (* a function from heights to LightBlocks *) | |||
fetchedLightBlocks1b, (* a function from heights to LightBlocks *) | |||
commonHeight, (* the height that is trusted in CreateEvidenceForPeer *) | |||
nextHeightToTry, (* the index in CreateEvidenceForPeer *) | |||
evidences | |||
INSTANCE LCDetector_003_draft | |||
============================================================================ |
@ -0,0 +1,373 @@ | |||
-------------------------- MODULE LCDetector_003_draft ----------------------------- | |||
(** | |||
* This is a specification of the light client detector module. | |||
* It follows the English specification: | |||
* | |||
* https://github.com/tendermint/spec/blob/master/rust-spec/lightclient/detection/detection_003_reviewed.md | |||
* | |||
* The assumptions made in this specification: | |||
* | |||
* - light client connects to one primary and one secondary peer | |||
* | |||
* - the light client has its own local clock that can drift from the reference clock | |||
* within the envelope [refClock - CLOCK_DRIFT, refClock + CLOCK_DRIFT]. | |||
* The local clock may increase as well as decrease in the the envelope | |||
* (similar to clock synchronization). | |||
* | |||
* - the ratio of the faulty validators is set as the parameter. | |||
* | |||
* Igor Konnov, Josef Widder, 2020 | |||
*) | |||
EXTENDS Integers | |||
\* the parameters of Light Client | |||
CONSTANTS | |||
AllNodes, | |||
(* a set of all nodes that can act as validators (correct and faulty) *) | |||
TRUSTED_HEIGHT, | |||
(* an index of the block header that the light client trusts by social consensus *) | |||
TARGET_HEIGHT, | |||
(* an index of the block header that the light client tries to verify *) | |||
TRUSTING_PERIOD, | |||
(* the period within which the validators are trusted *) | |||
CLOCK_DRIFT, | |||
(* the assumed precision of the clock *) | |||
REAL_CLOCK_DRIFT, | |||
(* the actual clock drift, which under normal circumstances should not | |||
be larger than CLOCK_DRIFT (otherwise, there will be a bug) *) | |||
FAULTY_RATIO, | |||
(* a pair <<a, b>> that limits that ratio of faulty validator in the blockchain | |||
from above (exclusive). Tendermint security model prescribes 1 / 3. *) | |||
IS_PRIMARY_CORRECT, | |||
IS_SECONDARY_CORRECT | |||
VARIABLES | |||
blockchain, (* the reference blockchain *) | |||
localClock, (* the local clock of the light client *) | |||
refClock, (* the reference clock in the reference blockchain *) | |||
Faulty, (* the set of faulty validators *) | |||
state, (* the state of the light client detector *) | |||
fetchedLightBlocks1, (* a function from heights to LightBlocks *) | |||
fetchedLightBlocks2, (* a function from heights to LightBlocks *) | |||
fetchedLightBlocks1b, (* a function from heights to LightBlocks *) | |||
commonHeight, (* the height that is trusted in CreateEvidenceForPeer *) | |||
nextHeightToTry, (* the index in CreateEvidenceForPeer *) | |||
evidences (* a set of evidences *) | |||
vars == <<state, blockchain, localClock, refClock, Faulty, | |||
fetchedLightBlocks1, fetchedLightBlocks2, fetchedLightBlocks1b, | |||
commonHeight, nextHeightToTry, evidences >> | |||
\* (old) type annotations in Apalache | |||
a <: b == a | |||
\* instantiate a reference chain | |||
ULTIMATE_HEIGHT == TARGET_HEIGHT + 1 | |||
BC == INSTANCE Blockchain_003_draft | |||
WITH ULTIMATE_HEIGHT <- (TARGET_HEIGHT + 1) | |||
\* use the light client API | |||
LC == INSTANCE LCVerificationApi_003_draft | |||
\* evidence type | |||
ET == [peer |-> STRING, conflictingBlock |-> BC!LBT, commonHeight |-> Int] | |||
\* is the algorithm in the terminating state | |||
IsTerminated == | |||
state \in { <<"NoEvidence", "PRIMARY">>, | |||
<<"NoEvidence", "SECONDARY">>, | |||
<<"FaultyPeer", "PRIMARY">>, | |||
<<"FaultyPeer", "SECONDARY">>, | |||
<<"FoundEvidence", "PRIMARY">> } | |||
(********************************* Initialization ******************************) | |||
\* initialization for the light blocks data structure | |||
InitLightBlocks(lb, Heights) == | |||
\* BC!LightBlocks is an infinite set, as time is not restricted. | |||
\* Hence, we initialize the light blocks by picking the sets inside. | |||
\E vs, nextVS, lastCommit, commit \in [Heights -> SUBSET AllNodes]: | |||
\* although [Heights -> Int] is an infinite set, | |||
\* Apalache needs just one instance of this set, so it does not complain. | |||
\E timestamp \in [Heights -> Int]: | |||
LET hdr(h) == | |||
[height |-> h, | |||
time |-> timestamp[h], | |||
VS |-> vs[h], | |||
NextVS |-> nextVS[h], | |||
lastCommit |-> lastCommit[h]] | |||
IN | |||
LET lightHdr(h) == | |||
[header |-> hdr(h), Commits |-> commit[h]] | |||
IN | |||
lb = [ h \in Heights |-> lightHdr(h) ] | |||
\* initialize the detector algorithm | |||
Init == | |||
\* initialize the blockchain to TARGET_HEIGHT + 1 | |||
/\ BC!InitToHeight(FAULTY_RATIO) | |||
/\ \E tm \in Int: | |||
tm >= 0 /\ LC!IsLocalClockWithinDrift(tm, refClock) /\ localClock = tm | |||
\* start with the secondary looking for evidence | |||
/\ state = <<"Init", "SECONDARY">> /\ commonHeight = 0 /\ nextHeightToTry = 0 | |||
/\ evidences = {} <: {ET} | |||
\* Precompute a possible result of light client verification for the primary. | |||
\* It is the input to the detection algorithm. | |||
/\ \E Heights1 \in SUBSET(TRUSTED_HEIGHT..TARGET_HEIGHT): | |||
/\ TRUSTED_HEIGHT \in Heights1 | |||
/\ TARGET_HEIGHT \in Heights1 | |||
/\ InitLightBlocks(fetchedLightBlocks1, Heights1) | |||
\* As we have a non-deterministic scheduler, for every trace that has | |||
\* an unverified block, there is a filtered trace that only has verified | |||
\* blocks. This is a deep observation. | |||
/\ LET status == [h \in Heights1 |-> "StateVerified"] IN | |||
LC!VerifyToTargetPost(blockchain, IS_PRIMARY_CORRECT, | |||
fetchedLightBlocks1, status, | |||
TRUSTED_HEIGHT, TARGET_HEIGHT, "finishedSuccess") | |||
\* initialize the other data structures to the default values | |||
/\ LET trustedBlock == blockchain[TRUSTED_HEIGHT] | |||
trustedLightBlock == [header |-> trustedBlock, Commits |-> AllNodes] | |||
IN | |||
/\ fetchedLightBlocks2 = [h \in {TRUSTED_HEIGHT} |-> trustedLightBlock] | |||
/\ fetchedLightBlocks1b = [h \in {TRUSTED_HEIGHT} |-> trustedLightBlock] | |||
(********************************* Transitions ******************************) | |||
\* a block should contain a copy of the block from the reference chain, | |||
\* with a matching commit | |||
CopyLightBlockFromChain(block, height) == | |||
LET ref == blockchain[height] | |||
lastCommit == | |||
IF height < ULTIMATE_HEIGHT | |||
THEN blockchain[height + 1].lastCommit | |||
\* for the ultimate block, which we never use, | |||
\* as ULTIMATE_HEIGHT = TARGET_HEIGHT + 1 | |||
ELSE blockchain[height].VS | |||
IN | |||
block = [header |-> ref, Commits |-> lastCommit] | |||
\* Either the primary is correct and the block comes from the reference chain, | |||
\* or the block is produced by a faulty primary. | |||
\* | |||
\* [LCV-FUNC-FETCH.1::TLA.1] | |||
FetchLightBlockInto(isPeerCorrect, block, height) == | |||
IF isPeerCorrect | |||
THEN CopyLightBlockFromChain(block, height) | |||
ELSE BC!IsLightBlockAllowedByDigitalSignatures(height, block) | |||
(** | |||
* Pick the next height, for which there is a block. | |||
*) | |||
PickNextHeight(fetchedBlocks, height) == | |||
LET largerHeights == { h \in DOMAIN fetchedBlocks: h > height } IN | |||
IF largerHeights = ({} <: {Int}) | |||
THEN -1 | |||
ELSE CHOOSE h \in largerHeights: | |||
\A h2 \in largerHeights: h <= h2 | |||
(** | |||
* Check, whether the target header matches at the secondary and primary. | |||
*) | |||
CompareLast == | |||
/\ state = <<"Init", "SECONDARY">> | |||
\* fetch a block from the secondary: | |||
\* non-deterministically pick a block that matches the constraints | |||
/\ \E latest \in BC!LightBlocks: | |||
\* for the moment, we ignore the possibility of a timeout when fetching a block | |||
/\ FetchLightBlockInto(IS_SECONDARY_CORRECT, latest, TARGET_HEIGHT) | |||
/\ IF latest.header = fetchedLightBlocks1[TARGET_HEIGHT].header | |||
THEN \* if the headers match, CreateEvidence is not called | |||
/\ state' = <<"NoEvidence", "SECONDARY">> | |||
\* save the retrieved block for further analysis | |||
/\ fetchedLightBlocks2' = | |||
[h \in (DOMAIN fetchedLightBlocks2) \union {TARGET_HEIGHT} |-> | |||
IF h = TARGET_HEIGHT THEN latest ELSE fetchedLightBlocks2[h]] | |||
/\ UNCHANGED <<commonHeight, nextHeightToTry>> | |||
ELSE \* prepare the parameters for CreateEvidence | |||
/\ commonHeight' = TRUSTED_HEIGHT | |||
/\ nextHeightToTry' = PickNextHeight(fetchedLightBlocks1, TRUSTED_HEIGHT) | |||
/\ state' = IF nextHeightToTry' >= 0 | |||
THEN <<"CreateEvidence", "SECONDARY">> | |||
ELSE <<"FaultyPeer", "SECONDARY">> | |||
/\ UNCHANGED fetchedLightBlocks2 | |||
/\ UNCHANGED <<blockchain, Faulty, | |||
fetchedLightBlocks1, fetchedLightBlocks1b, evidences>> | |||
\* the actual loop in CreateEvidence | |||
CreateEvidence(peer, isPeerCorrect, refBlocks, targetBlocks) == | |||
/\ state = <<"CreateEvidence", peer>> | |||
\* precompute a possible result of light client verification for the secondary | |||
\* we have to introduce HeightRange, because Apalache can only handle a..b | |||
\* for constant a and b | |||
/\ LET HeightRange == { h \in TRUSTED_HEIGHT..TARGET_HEIGHT: | |||
commonHeight <= h /\ h <= nextHeightToTry } IN | |||
\E HeightsRange \in SUBSET(HeightRange): | |||
/\ commonHeight \in HeightsRange /\ nextHeightToTry \in HeightsRange | |||
/\ InitLightBlocks(targetBlocks, HeightsRange) | |||
\* As we have a non-deterministic scheduler, for every trace that has | |||
\* an unverified block, there is a filtered trace that only has verified | |||
\* blocks. This is a deep observation. | |||
/\ \E result \in {"finishedSuccess", "finishedFailure"}: | |||
LET targetStatus == [h \in HeightsRange |-> "StateVerified"] IN | |||
\* call VerifyToTarget for (commonHeight, nextHeightToTry). | |||
/\ LC!VerifyToTargetPost(blockchain, isPeerCorrect, | |||
targetBlocks, targetStatus, | |||
commonHeight, nextHeightToTry, result) | |||
\* case 1: the peer has failed (or the trusting period has expired) | |||
/\ \/ /\ result /= "finishedSuccess" | |||
/\ state' = <<"FaultyPeer", peer>> | |||
/\ UNCHANGED <<commonHeight, nextHeightToTry, evidences>> | |||
\* case 2: success | |||
\/ /\ result = "finishedSuccess" | |||
/\ LET block1 == refBlocks[nextHeightToTry] IN | |||
LET block2 == targetBlocks[nextHeightToTry] IN | |||
IF block1.header /= block2.header | |||
THEN \* the target blocks do not match | |||
/\ state' = <<"FoundEvidence", peer>> | |||
/\ evidences' = evidences \union | |||
{[peer |-> peer, | |||
conflictingBlock |-> block1, | |||
commonHeight |-> commonHeight]} | |||
/\ UNCHANGED <<commonHeight, nextHeightToTry>> | |||
ELSE \* the target blocks match | |||
/\ nextHeightToTry' = PickNextHeight(refBlocks, nextHeightToTry) | |||
/\ commonHeight' = nextHeightToTry | |||
/\ state' = IF nextHeightToTry' >= 0 | |||
THEN state | |||
ELSE <<"NoEvidence", peer>> | |||
/\ UNCHANGED evidences | |||
SwitchToPrimary == | |||
/\ state = <<"FoundEvidence", "SECONDARY">> | |||
/\ nextHeightToTry' = PickNextHeight(fetchedLightBlocks2, commonHeight) | |||
/\ state' = <<"CreateEvidence", "PRIMARY">> | |||
/\ UNCHANGED <<blockchain, refClock, Faulty, localClock, | |||
fetchedLightBlocks1, fetchedLightBlocks2, fetchedLightBlocks1b, | |||
commonHeight, evidences >> | |||
CreateEvidenceForSecondary == | |||
/\ CreateEvidence("SECONDARY", IS_SECONDARY_CORRECT, | |||
fetchedLightBlocks1, fetchedLightBlocks2') | |||
/\ UNCHANGED <<blockchain, refClock, Faulty, localClock, | |||
fetchedLightBlocks1, fetchedLightBlocks1b>> | |||
CreateEvidenceForPrimary == | |||
/\ CreateEvidence("PRIMARY", IS_PRIMARY_CORRECT, | |||
fetchedLightBlocks2, | |||
fetchedLightBlocks1b') | |||
/\ UNCHANGED <<blockchain, Faulty, | |||
fetchedLightBlocks1, fetchedLightBlocks2>> | |||
(* | |||
The local and global clocks can be updated. They can also drift from each other. | |||
Note that the local clock can actually go backwards in time. | |||
However, it still stays in the drift envelope | |||
of [refClock - REAL_CLOCK_DRIFT, refClock + REAL_CLOCK_DRIFT]. | |||
*) | |||
AdvanceClocks == | |||
/\ \E tm \in Int: | |||
tm >= refClock /\ refClock' = tm | |||
/\ \E tm \in Int: | |||
/\ tm >= localClock | |||
/\ LC!IsLocalClockWithinDrift(tm, refClock') | |||
/\ localClock' = tm | |||
(** | |||
Execute AttackDetector for one secondary. | |||
[LCD-FUNC-DETECTOR.2::LOOP.1] | |||
*) | |||
Next == | |||
/\ AdvanceClocks | |||
/\ \/ CompareLast | |||
\/ CreateEvidenceForSecondary | |||
\/ SwitchToPrimary | |||
\/ CreateEvidenceForPrimary | |||
\* simple invariants to see the progress of the detector | |||
NeverNoEvidence == state[1] /= "NoEvidence" | |||
NeverFoundEvidence == state[1] /= "FoundEvidence" | |||
NeverFaultyPeer == state[1] /= "FaultyPeer" | |||
NeverCreateEvidence == state[1] /= "CreateEvidence" | |||
NeverFoundEvidencePrimary == state /= <<"FoundEvidence", "PRIMARY">> | |||
NeverReachTargetHeight == nextHeightToTry < TARGET_HEIGHT | |||
EvidenceWhenFaultyInv == | |||
(state[1] = "FoundEvidence") => (~IS_PRIMARY_CORRECT \/ ~IS_SECONDARY_CORRECT) | |||
NoEvidenceForCorrectInv == | |||
IS_PRIMARY_CORRECT /\ IS_SECONDARY_CORRECT => evidences = {} <: {ET} | |||
(** | |||
* If we find an evidence by peer A, peer B has ineded given us a corrupted | |||
* header following the common height. Also, we have a verification trace by peer A. | |||
*) | |||
CommonHeightOnEvidenceInv == | |||
\A e \in evidences: | |||
LET conflicting == e.conflictingBlock IN | |||
LET conflictingHeader == conflicting.header IN | |||
\* the evidence by suspectingPeer can be verified by suspectingPeer in one step | |||
LET SoundEvidence(suspectingPeer, peerBlocks) == | |||
\/ e.peer /= suspectingPeer | |||
\* the conflicting block from another peer verifies against the common height | |||
\/ /\ "SUCCESS" = | |||
LC!ValidAndVerifiedUntimed(peerBlocks[e.commonHeight], conflicting) | |||
\* and the headers of the same height by the two peers do not match | |||
/\ peerBlocks[conflictingHeader.height].header /= conflictingHeader | |||
IN | |||
/\ SoundEvidence("PRIMARY", fetchedLightBlocks1b) | |||
/\ SoundEvidence("SECONDARY", fetchedLightBlocks2) | |||
(** | |||
* If the light client does not find an evidence, | |||
* then there is no attack on the light client. | |||
*) | |||
AccuracyInv == | |||
(LC!InTrustingPeriodLocal(fetchedLightBlocks1[TARGET_HEIGHT].header) | |||
/\ state = <<"NoEvidence", "SECONDARY">>) | |||
=> | |||
(fetchedLightBlocks1[TARGET_HEIGHT].header = blockchain[TARGET_HEIGHT] | |||
/\ fetchedLightBlocks2[TARGET_HEIGHT].header = blockchain[TARGET_HEIGHT]) | |||
(** | |||
* The primary reports a corrupted block at the target height. If the secondary is | |||
* correct and the algorithm has terminated, we should get the evidence. | |||
* This property is violated due to clock drift. VerifyToTarget may fail with | |||
* the correct secondary within the trusting period (due to clock drift, locally | |||
* we think that we are outside of the trusting period). | |||
*) | |||
PrecisionInvGrayZone == | |||
(/\ fetchedLightBlocks1[TARGET_HEIGHT].header /= blockchain[TARGET_HEIGHT] | |||
/\ BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) | |||
/\ IS_SECONDARY_CORRECT | |||
/\ IsTerminated) | |||
=> | |||
evidences /= {} <: {ET} | |||
(** | |||
* The primary reports a corrupted block at the target height. If the secondary is | |||
* correct and the algorithm has terminated, we should get the evidence. | |||
* This invariant does not fail, as we are using the local clock to check the trusting | |||
* period. | |||
*) | |||
PrecisionInvLocal == | |||
(/\ fetchedLightBlocks1[TARGET_HEIGHT].header /= blockchain[TARGET_HEIGHT] | |||
/\ LC!InTrustingPeriodLocalSurely(blockchain[TRUSTED_HEIGHT]) | |||
/\ IS_SECONDARY_CORRECT | |||
/\ IsTerminated) | |||
=> | |||
evidences /= {} <: {ET} | |||
==================================================================================== |
@ -0,0 +1,192 @@ | |||
-------------------- MODULE LCVerificationApi_003_draft -------------------------- | |||
(** | |||
* The common interface of the light client verification and detection. | |||
*) | |||
EXTENDS Integers, FiniteSets | |||
\* the parameters of Light Client | |||
CONSTANTS | |||
TRUSTING_PERIOD, | |||
(* the period within which the validators are trusted *) | |||
CLOCK_DRIFT, | |||
(* the assumed precision of the clock *) | |||
REAL_CLOCK_DRIFT, | |||
(* the actual clock drift, which under normal circumstances should not | |||
be larger than CLOCK_DRIFT (otherwise, there will be a bug) *) | |||
FAULTY_RATIO | |||
(* a pair <<a, b>> that limits that ratio of faulty validator in the blockchain | |||
from above (exclusive). Tendermint security model prescribes 1 / 3. *) | |||
VARIABLES | |||
localClock (* current time as measured by the light client *) | |||
(* the header is still within the trusting period *) | |||
InTrustingPeriodLocal(header) == | |||
\* note that the assumption about the drift reduces the period of trust | |||
localClock < header.time + TRUSTING_PERIOD - CLOCK_DRIFT | |||
(* the header is still within the trusting period, even if the clock can go backwards *) | |||
InTrustingPeriodLocalSurely(header) == | |||
\* note that the assumption about the drift reduces the period of trust | |||
localClock < header.time + TRUSTING_PERIOD - 2 * CLOCK_DRIFT | |||
(* ensure that the local clock does not drift far away from the global clock *) | |||
IsLocalClockWithinDrift(local, global) == | |||
/\ global - REAL_CLOCK_DRIFT <= local | |||
/\ local <= global + REAL_CLOCK_DRIFT | |||
(** | |||
* Check that the commits in an untrusted block form 1/3 of the next validators | |||
* in a trusted header. | |||
*) | |||
SignedByOneThirdOfTrusted(trusted, untrusted) == | |||
LET TP == Cardinality(trusted.header.NextVS) | |||
SP == Cardinality(untrusted.Commits \intersect trusted.header.NextVS) | |||
IN | |||
3 * SP > TP | |||
(** | |||
The first part of the precondition of ValidAndVerified, which does not take | |||
the current time into account. | |||
[LCV-FUNC-VALID.1::TLA-PRE-UNTIMED.1] | |||
*) | |||
ValidAndVerifiedPreUntimed(trusted, untrusted) == | |||
LET thdr == trusted.header | |||
uhdr == untrusted.header | |||
IN | |||
/\ thdr.height < uhdr.height | |||
\* the trusted block has been created earlier | |||
/\ thdr.time < uhdr.time | |||
/\ untrusted.Commits \subseteq uhdr.VS | |||
/\ LET TP == Cardinality(uhdr.VS) | |||
SP == Cardinality(untrusted.Commits) | |||
IN | |||
3 * SP > 2 * TP | |||
/\ thdr.height + 1 = uhdr.height => thdr.NextVS = uhdr.VS | |||
(* As we do not have explicit hashes we ignore these three checks of the English spec: | |||
1. "trusted.Commit is a commit is for the header trusted.Header, | |||
i.e. it contains the correct hash of the header". | |||
2. untrusted.Validators = hash(untrusted.Header.Validators) | |||
3. untrusted.NextValidators = hash(untrusted.Header.NextValidators) | |||
*) | |||
(** | |||
Check the precondition of ValidAndVerified, including the time checks. | |||
[LCV-FUNC-VALID.1::TLA-PRE.1] | |||
*) | |||
ValidAndVerifiedPre(trusted, untrusted, checkFuture) == | |||
LET thdr == trusted.header | |||
uhdr == untrusted.header | |||
IN | |||
/\ InTrustingPeriodLocal(thdr) | |||
\* The untrusted block is not from the future (modulo clock drift). | |||
\* Do the check, if it is required. | |||
/\ checkFuture => uhdr.time < localClock + CLOCK_DRIFT | |||
/\ ValidAndVerifiedPreUntimed(trusted, untrusted) | |||
(** | |||
Check, whether an untrusted block is valid and verifiable w.r.t. a trusted header. | |||
This test does take current time into account, but only looks at the block structure. | |||
[LCV-FUNC-VALID.1::TLA-UNTIMED.1] | |||
*) | |||
ValidAndVerifiedUntimed(trusted, untrusted) == | |||
IF ~ValidAndVerifiedPreUntimed(trusted, untrusted) | |||
THEN "INVALID" | |||
ELSE IF untrusted.header.height = trusted.header.height + 1 | |||
\/ SignedByOneThirdOfTrusted(trusted, untrusted) | |||
THEN "SUCCESS" | |||
ELSE "NOT_ENOUGH_TRUST" | |||
(** | |||
Check, whether an untrusted block is valid and verifiable w.r.t. a trusted header. | |||
[LCV-FUNC-VALID.1::TLA.1] | |||
*) | |||
ValidAndVerified(trusted, untrusted, checkFuture) == | |||
IF ~ValidAndVerifiedPre(trusted, untrusted, checkFuture) | |||
THEN "INVALID" | |||
ELSE IF ~InTrustingPeriodLocal(untrusted.header) | |||
(* We leave the following test for the documentation purposes. | |||
The implementation should do this test, as signature verification may be slow. | |||
In the TLA+ specification, ValidAndVerified happens in no time. | |||
*) | |||
THEN "FAILED_TRUSTING_PERIOD" | |||
ELSE IF untrusted.header.height = trusted.header.height + 1 | |||
\/ SignedByOneThirdOfTrusted(trusted, untrusted) | |||
THEN "SUCCESS" | |||
ELSE "NOT_ENOUGH_TRUST" | |||
(** | |||
The invariant of the light store that is not related to the blockchain | |||
*) | |||
LightStoreInv(fetchedLightBlocks, lightBlockStatus) == | |||
\A lh, rh \in DOMAIN fetchedLightBlocks: | |||
\* for every pair of stored headers that have been verified | |||
\/ lh >= rh | |||
\/ lightBlockStatus[lh] /= "StateVerified" | |||
\/ lightBlockStatus[rh] /= "StateVerified" | |||
\* either there is a header between them | |||
\/ \E mh \in DOMAIN fetchedLightBlocks: | |||
lh < mh /\ mh < rh /\ lightBlockStatus[mh] = "StateVerified" | |||
\* or the left header is outside the trusting period, so no guarantees | |||
\/ LET lhdr == fetchedLightBlocks[lh] | |||
rhdr == fetchedLightBlocks[rh] | |||
IN | |||
\* we can verify the right one using the left one | |||
"SUCCESS" = ValidAndVerifiedUntimed(lhdr, rhdr) | |||
(** | |||
Correctness states that all the obtained headers are exactly like in the blockchain. | |||
It is always the case that every verified header in LightStore was generated by | |||
an instance of Tendermint consensus. | |||
[LCV-DIST-SAFE.1::CORRECTNESS-INV.1] | |||
*) | |||
CorrectnessInv(blockchain, fetchedLightBlocks, lightBlockStatus) == | |||
\A h \in DOMAIN fetchedLightBlocks: | |||
lightBlockStatus[h] = "StateVerified" => | |||
fetchedLightBlocks[h].header = blockchain[h] | |||
(** | |||
* When the light client terminates, there are no failed blocks. | |||
* (Otherwise, someone lied to us.) | |||
*) | |||
NoFailedBlocksOnSuccessInv(fetchedLightBlocks, lightBlockStatus) == | |||
\A h \in DOMAIN fetchedLightBlocks: | |||
lightBlockStatus[h] /= "StateFailed" | |||
(** | |||
The expected post-condition of VerifyToTarget. | |||
*) | |||
VerifyToTargetPost(blockchain, isPeerCorrect, | |||
fetchedLightBlocks, lightBlockStatus, | |||
trustedHeight, targetHeight, finalState) == | |||
LET trustedHeader == fetchedLightBlocks[trustedHeight].header IN | |||
\* The light client is not lying us on the trusted block. | |||
\* It is straightforward to detect. | |||
/\ lightBlockStatus[trustedHeight] = "StateVerified" | |||
/\ trustedHeight \in DOMAIN fetchedLightBlocks | |||
/\ trustedHeader = blockchain[trustedHeight] | |||
\* the invariants we have found in the light client verification | |||
\* there is a problem with trusting period | |||
/\ isPeerCorrect | |||
=> CorrectnessInv(blockchain, fetchedLightBlocks, lightBlockStatus) | |||
\* a correct peer should fail the light client, | |||
\* if the trusted block is in the trusting period | |||
/\ isPeerCorrect /\ InTrustingPeriodLocalSurely(trustedHeader) | |||
=> finalState = "finishedSuccess" | |||
/\ finalState = "finishedSuccess" => | |||
/\ lightBlockStatus[targetHeight] = "StateVerified" | |||
/\ targetHeight \in DOMAIN fetchedLightBlocks | |||
/\ NoFailedBlocksOnSuccessInv(fetchedLightBlocks, lightBlockStatus) | |||
/\ LightStoreInv(fetchedLightBlocks, lightBlockStatus) | |||
================================================================================== |
@ -0,0 +1,55 @@ | |||
no;filename;tool;timeout;init;inv;next;args | |||
1;MC4_3_correct.tla;apalache;1h;;TargetHeightOnSuccessInv;;--length=5 | |||
2;MC4_3_correct.tla;apalache;1h;;StoredHeadersAreVerifiedOrNotTrustedInv;;--length=5 | |||
3;MC4_3_correct.tla;apalache;1h;;CorrectnessInv;;--length=5 | |||
4;MC4_3_correct.tla;apalache;1h;;NoTrustOnFaultyBlockInv;;--length=5 | |||
5;MC4_3_correct.tla;apalache;1h;;ProofOfChainOfTrustInv;;--length=5 | |||
6;MC4_3_correct.tla;apalache;1h;;NoFailedBlocksOnSuccessInv;;--length=5 | |||
7;MC4_3_correct.tla;apalache;1h;;Complexity;;--length=5 | |||
8;MC4_3_correct.tla;apalache;1h;;ApiPostInv;;--length=5 | |||
9;MC4_4_correct.tla;apalache;1h;;TargetHeightOnSuccessInv;;--length=7 | |||
10;MC4_4_correct.tla;apalache;1h;;CorrectnessInv;;--length=7 | |||
11;MC4_4_correct.tla;apalache;1h;;NoTrustOnFaultyBlockInv;;--length=7 | |||
12;MC4_4_correct.tla;apalache;1h;;ProofOfChainOfTrustInv;;--length=7 | |||
13;MC4_4_correct.tla;apalache;1h;;NoFailedBlocksOnSuccessInv;;--length=7 | |||
14;MC4_4_correct.tla;apalache;1h;;Complexity;;--length=7 | |||
15;MC4_4_correct.tla;apalache;1h;;ApiPostInv;;--length=7 | |||
16;MC4_5_correct.tla;apalache;1h;;TargetHeightOnSuccessInv;;--length=11 | |||
17;MC4_5_correct.tla;apalache;1h;;CorrectnessInv;;--length=11 | |||
18;MC4_5_correct.tla;apalache;1h;;NoTrustOnFaultyBlockInv;;--length=11 | |||
19;MC4_5_correct.tla;apalache;1h;;ProofOfChainOfTrustInv;;--length=11 | |||
20;MC4_5_correct.tla;apalache;1h;;NoFailedBlocksOnSuccessInv;;--length=11 | |||
21;MC4_5_correct.tla;apalache;1h;;Complexity;;--length=11 | |||
22;MC4_5_correct.tla;apalache;1h;;ApiPostInv;;--length=11 | |||
23;MC5_5_correct.tla;apalache;1h;;TargetHeightOnSuccessInv;;--length=11 | |||
24;MC5_5_correct.tla;apalache;1h;;CorrectnessInv;;--length=11 | |||
25;MC5_5_correct.tla;apalache;1h;;NoTrustOnFaultyBlockInv;;--length=11 | |||
26;MC5_5_correct.tla;apalache;1h;;ProofOfChainOfTrustInv;;--length=11 | |||
27;MC5_5_correct.tla;apalache;1h;;NoFailedBlocksOnSuccessInv;;--length=11 | |||
28;MC5_5_correct.tla;apalache;1h;;Complexity;;--length=11 | |||
29;MC5_5_correct.tla;apalache;1h;;ApiPostInv;;--length=11 | |||
30;MC4_3_faulty.tla;apalache;1h;;TargetHeightOnSuccessInv;;--length=5 | |||
31;MC4_3_faulty.tla;apalache;1h;;StoredHeadersAreVerifiedOrNotTrustedInv;;--length=5 | |||
32;MC4_3_faulty.tla;apalache;1h;;CorrectnessInv;;--length=5 | |||
33;MC4_3_faulty.tla;apalache;1h;;NoTrustOnFaultyBlockInv;;--length=5 | |||
34;MC4_3_faulty.tla;apalache;1h;;ProofOfChainOfTrustInv;;--length=5 | |||
35;MC4_3_faulty.tla;apalache;1h;;NoFailedBlocksOnSuccessInv;;--length=5 | |||
36;MC4_3_faulty.tla;apalache;1h;;Complexity;;--length=5 | |||
37;MC4_3_faulty.tla;apalache;1h;;ApiPostInv;;--length=5 | |||
38;MC4_4_faulty.tla;apalache;1h;;TargetHeightOnSuccessInv;;--length=7 | |||
39;MC4_4_faulty.tla;apalache;1h;;StoredHeadersAreVerifiedOrNotTrustedInv;;--length=7 | |||
40;MC4_4_faulty.tla;apalache;1h;;CorrectnessInv;;--length=7 | |||
41;MC4_4_faulty.tla;apalache;1h;;NoTrustOnFaultyBlockInv;;--length=7 | |||
42;MC4_4_faulty.tla;apalache;1h;;ProofOfChainOfTrustInv;;--length=7 | |||
43;MC4_4_faulty.tla;apalache;1h;;NoFailedBlocksOnSuccessInv;;--length=7 | |||
44;MC4_4_faulty.tla;apalache;1h;;Complexity;;--length=7 | |||
45;MC4_4_faulty.tla;apalache;1h;;ApiPostInv;;--length=7 | |||
46;MC4_5_faulty.tla;apalache;1h;;TargetHeightOnSuccessInv;;--length=11 | |||
47;MC4_5_faulty.tla;apalache;1h;;StoredHeadersAreVerifiedOrNotTrustedInv;;--length=11 | |||
48;MC4_5_faulty.tla;apalache;1h;;CorrectnessInv;;--length=11 | |||
49;MC4_5_faulty.tla;apalache;1h;;NoTrustOnFaultyBlockInv;;--length=11 | |||
50;MC4_5_faulty.tla;apalache;1h;;ProofOfChainOfTrustInv;;--length=11 | |||
51;MC4_5_faulty.tla;apalache;1h;;NoFailedBlocksOnSuccessInv;;--length=11 | |||
52;MC4_5_faulty.tla;apalache;1h;;Complexity;;--length=11 | |||
53;MC4_5_faulty.tla;apalache;1h;;ApiPostInv;;--length=11 | |||
@ -0,0 +1,45 @@ | |||
no;filename;tool;timeout;init;inv;next;args | |||
1;MC4_3_correct.tla;apalache;1h;;StoredHeadersAreVerifiedInv;;--length=5 | |||
2;MC4_3_correct.tla;apalache;1h;;PositiveBeforeTrustedHeaderExpires;;--length=5 | |||
3;MC4_3_correct.tla;apalache;1h;;CorrectPrimaryAndTimeliness;;--length=5 | |||
4;MC4_3_correct.tla;apalache;1h;;PrecisionInv;;--length=5 | |||
5;MC4_3_correct.tla;apalache;1h;;PrecisionBuggyInv;;--length=5 | |||
6;MC4_3_correct.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustGlobal;;--length=5 | |||
7;MC4_3_correct.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustLocal;;--length=5 | |||
8;MC4_4_correct.tla;apalache;1h;;StoredHeadersAreVerifiedInv;;--length=7 | |||
9;MC4_4_correct.tla;apalache;1h;;PositiveBeforeTrustedHeaderExpires;;--length=7 | |||
10;MC4_4_correct.tla;apalache;1h;;CorrectPrimaryAndTimeliness;;--length=7 | |||
11;MC4_4_correct.tla;apalache;1h;;PrecisionInv;;--length=7 | |||
12;MC4_4_correct.tla;apalache;1h;;PrecisionBuggyInv;;--length=7 | |||
13;MC4_4_correct.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustGlobal;;--length=7 | |||
14;MC4_4_correct.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustLocal;;--length=7 | |||
15;MC4_5_correct.tla;apalache;1h;;StoredHeadersAreVerifiedInv;;--length=11 | |||
16;MC4_5_correct.tla;apalache;1h;;PositiveBeforeTrustedHeaderExpires;;--length=11 | |||
17;MC4_5_correct.tla;apalache;1h;;CorrectPrimaryAndTimeliness;;--length=11 | |||
18;MC4_5_correct.tla;apalache;1h;;PrecisionInv;;--length=11 | |||
19;MC4_5_correct.tla;apalache;1h;;PrecisionBuggyInv;;--length=11 | |||
20;MC4_5_correct.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustGlobal;;--length=11 | |||
21;MC4_5_correct.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustLocal;;--length=11 | |||
22;MC4_5_correct.tla;apalache;1h;;StoredHeadersAreVerifiedOrNotTrustedInv;;--length=11 | |||
23;MC4_3_faulty.tla;apalache;1h;;StoredHeadersAreVerifiedInv;;--length=5 | |||
24;MC4_3_faulty.tla;apalache;1h;;PositiveBeforeTrustedHeaderExpires;;--length=5 | |||
25;MC4_3_faulty.tla;apalache;1h;;CorrectPrimaryAndTimeliness;;--length=5 | |||
26;MC4_3_faulty.tla;apalache;1h;;PrecisionInv;;--length=5 | |||
27;MC4_3_faulty.tla;apalache;1h;;PrecisionBuggyInv;;--length=5 | |||
28;MC4_3_faulty.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustGlobal;;--length=5 | |||
29;MC4_3_faulty.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustLocal;;--length=5 | |||
30;MC4_4_faulty.tla;apalache;1h;;StoredHeadersAreVerifiedInv;;--length=7 | |||
31;MC4_4_faulty.tla;apalache;1h;;PositiveBeforeTrustedHeaderExpires;;--length=7 | |||
32;MC4_4_faulty.tla;apalache;1h;;CorrectPrimaryAndTimeliness;;--length=7 | |||
33;MC4_4_faulty.tla;apalache;1h;;PrecisionInv;;--length=7 | |||
34;MC4_4_faulty.tla;apalache;1h;;PrecisionBuggyInv;;--length=7 | |||
35;MC4_4_faulty.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustGlobal;;--length=7 | |||
36;MC4_4_faulty.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustLocal;;--length=7 | |||
37;MC4_5_faulty.tla;apalache;1h;;StoredHeadersAreVerifiedInv;;--length=11 | |||
38;MC4_5_faulty.tla;apalache;1h;;PositiveBeforeTrustedHeaderExpires;;--length=11 | |||
39;MC4_5_faulty.tla;apalache;1h;;CorrectPrimaryAndTimeliness;;--length=11 | |||
40;MC4_5_faulty.tla;apalache;1h;;PrecisionInv;;--length=11 | |||
41;MC4_5_faulty.tla;apalache;1h;;PrecisionBuggyInv;;--length=11 | |||
42;MC4_5_faulty.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustGlobal;;--length=11 | |||
43;MC4_5_faulty.tla;apalache;1h;;SuccessOnCorrectPrimaryAndChainOfTrustLocal;;--length=11 | |||
44;MC4_5_faulty.tla;apalache;1h;;StoredHeadersAreVerifiedOrNotTrustedInv;;--length=11 |
@ -0,0 +1,10 @@ | |||
no;filename;tool;timeout;init;inv;next;args | |||
1;LCD_MC3_3_faulty.tla;apalache;1h;;CommonHeightOnEvidenceInv;;--length=10 | |||
2;LCD_MC3_3_faulty.tla;apalache;1h;;AccuracyInv;;--length=10 | |||
3;LCD_MC3_3_faulty.tla;apalache;1h;;PrecisionInvLocal;;--length=10 | |||
4;LCD_MC3_4_faulty.tla;apalache;1h;;CommonHeightOnEvidenceInv;;--length=10 | |||
5;LCD_MC3_4_faulty.tla;apalache;1h;;AccuracyInv;;--length=10 | |||
6;LCD_MC3_4_faulty.tla;apalache;1h;;PrecisionInvLocal;;--length=10 | |||
7;LCD_MC4_4_faulty.tla;apalache;1h;;CommonHeightOnEvidenceInv;;--length=10 | |||
8;LCD_MC4_4_faulty.tla;apalache;1h;;AccuracyInv;;--length=10 | |||
9;LCD_MC4_4_faulty.tla;apalache;1h;;PrecisionInvLocal;;--length=10 |
@ -0,0 +1,4 @@ | |||
no;filename;tool;timeout;init;inv;next;args | |||
1;LCD_MC3_3_faulty.tla;apalache;1h;;PrecisionInvGrayZone;;--length=10 | |||
2;LCD_MC3_4_faulty.tla;apalache;1h;;PrecisionInvGrayZone;;--length=10 | |||
3;LCD_MC4_4_faulty.tla;apalache;1h;;PrecisionInvGrayZone;;--length=10 |
@ -0,0 +1,192 @@ | |||
-------------------- MODULE LCVerificationApi_003_draft -------------------------- | |||
(** | |||
* The common interface of the light client verification and detection. | |||
*) | |||
EXTENDS Integers, FiniteSets | |||
\* the parameters of Light Client | |||
CONSTANTS | |||
TRUSTING_PERIOD, | |||
(* the period within which the validators are trusted *) | |||
CLOCK_DRIFT, | |||
(* the assumed precision of the clock *) | |||
REAL_CLOCK_DRIFT, | |||
(* the actual clock drift, which under normal circumstances should not | |||
be larger than CLOCK_DRIFT (otherwise, there will be a bug) *) | |||
FAULTY_RATIO | |||
(* a pair <<a, b>> that limits that ratio of faulty validator in the blockchain | |||
from above (exclusive). Tendermint security model prescribes 1 / 3. *) | |||
VARIABLES | |||
localClock (* current time as measured by the light client *) | |||
(* the header is still within the trusting period *) | |||
InTrustingPeriodLocal(header) == | |||
\* note that the assumption about the drift reduces the period of trust | |||
localClock < header.time + TRUSTING_PERIOD - CLOCK_DRIFT | |||
(* the header is still within the trusting period, even if the clock can go backwards *) | |||
InTrustingPeriodLocalSurely(header) == | |||
\* note that the assumption about the drift reduces the period of trust | |||
localClock < header.time + TRUSTING_PERIOD - 2 * CLOCK_DRIFT | |||
(* ensure that the local clock does not drift far away from the global clock *) | |||
IsLocalClockWithinDrift(local, global) == | |||
/\ global - REAL_CLOCK_DRIFT <= local | |||
/\ local <= global + REAL_CLOCK_DRIFT | |||
(** | |||
* Check that the commits in an untrusted block form 1/3 of the next validators | |||
* in a trusted header. | |||
*) | |||
SignedByOneThirdOfTrusted(trusted, untrusted) == | |||
LET TP == Cardinality(trusted.header.NextVS) | |||
SP == Cardinality(untrusted.Commits \intersect trusted.header.NextVS) | |||
IN | |||
3 * SP > TP | |||
(** | |||
The first part of the precondition of ValidAndVerified, which does not take | |||
the current time into account. | |||
[LCV-FUNC-VALID.1::TLA-PRE-UNTIMED.1] | |||
*) | |||
ValidAndVerifiedPreUntimed(trusted, untrusted) == | |||
LET thdr == trusted.header | |||
uhdr == untrusted.header | |||
IN | |||
/\ thdr.height < uhdr.height | |||
\* the trusted block has been created earlier | |||
/\ thdr.time < uhdr.time | |||
/\ untrusted.Commits \subseteq uhdr.VS | |||
/\ LET TP == Cardinality(uhdr.VS) | |||
SP == Cardinality(untrusted.Commits) | |||
IN | |||
3 * SP > 2 * TP | |||
/\ thdr.height + 1 = uhdr.height => thdr.NextVS = uhdr.VS | |||
(* As we do not have explicit hashes we ignore these three checks of the English spec: | |||
1. "trusted.Commit is a commit is for the header trusted.Header, | |||
i.e. it contains the correct hash of the header". | |||
2. untrusted.Validators = hash(untrusted.Header.Validators) | |||
3. untrusted.NextValidators = hash(untrusted.Header.NextValidators) | |||
*) | |||
(** | |||
Check the precondition of ValidAndVerified, including the time checks. | |||
[LCV-FUNC-VALID.1::TLA-PRE.1] | |||
*) | |||
ValidAndVerifiedPre(trusted, untrusted, checkFuture) == | |||
LET thdr == trusted.header | |||
uhdr == untrusted.header | |||
IN | |||
/\ InTrustingPeriodLocal(thdr) | |||
\* The untrusted block is not from the future (modulo clock drift). | |||
\* Do the check, if it is required. | |||
/\ checkFuture => uhdr.time < localClock + CLOCK_DRIFT | |||
/\ ValidAndVerifiedPreUntimed(trusted, untrusted) | |||
(** | |||
Check, whether an untrusted block is valid and verifiable w.r.t. a trusted header. | |||
This test does take current time into account, but only looks at the block structure. | |||
[LCV-FUNC-VALID.1::TLA-UNTIMED.1] | |||
*) | |||
ValidAndVerifiedUntimed(trusted, untrusted) == | |||
IF ~ValidAndVerifiedPreUntimed(trusted, untrusted) | |||
THEN "INVALID" | |||
ELSE IF untrusted.header.height = trusted.header.height + 1 | |||
\/ SignedByOneThirdOfTrusted(trusted, untrusted) | |||
THEN "SUCCESS" | |||
ELSE "NOT_ENOUGH_TRUST" | |||
(** | |||
Check, whether an untrusted block is valid and verifiable w.r.t. a trusted header. | |||
[LCV-FUNC-VALID.1::TLA.1] | |||
*) | |||
ValidAndVerified(trusted, untrusted, checkFuture) == | |||
IF ~ValidAndVerifiedPre(trusted, untrusted, checkFuture) | |||
THEN "INVALID" | |||
ELSE IF ~InTrustingPeriodLocal(untrusted.header) | |||
(* We leave the following test for the documentation purposes. | |||
The implementation should do this test, as signature verification may be slow. | |||
In the TLA+ specification, ValidAndVerified happens in no time. | |||
*) | |||
THEN "FAILED_TRUSTING_PERIOD" | |||
ELSE IF untrusted.header.height = trusted.header.height + 1 | |||
\/ SignedByOneThirdOfTrusted(trusted, untrusted) | |||
THEN "SUCCESS" | |||
ELSE "NOT_ENOUGH_TRUST" | |||
(** | |||
The invariant of the light store that is not related to the blockchain | |||
*) | |||
LightStoreInv(fetchedLightBlocks, lightBlockStatus) == | |||
\A lh, rh \in DOMAIN fetchedLightBlocks: | |||
\* for every pair of stored headers that have been verified | |||
\/ lh >= rh | |||
\/ lightBlockStatus[lh] /= "StateVerified" | |||
\/ lightBlockStatus[rh] /= "StateVerified" | |||
\* either there is a header between them | |||
\/ \E mh \in DOMAIN fetchedLightBlocks: | |||
lh < mh /\ mh < rh /\ lightBlockStatus[mh] = "StateVerified" | |||
\* or the left header is outside the trusting period, so no guarantees | |||
\/ LET lhdr == fetchedLightBlocks[lh] | |||
rhdr == fetchedLightBlocks[rh] | |||
IN | |||
\* we can verify the right one using the left one | |||
"SUCCESS" = ValidAndVerifiedUntimed(lhdr, rhdr) | |||
(** | |||
Correctness states that all the obtained headers are exactly like in the blockchain. | |||
It is always the case that every verified header in LightStore was generated by | |||
an instance of Tendermint consensus. | |||
[LCV-DIST-SAFE.1::CORRECTNESS-INV.1] | |||
*) | |||
CorrectnessInv(blockchain, fetchedLightBlocks, lightBlockStatus) == | |||
\A h \in DOMAIN fetchedLightBlocks: | |||
lightBlockStatus[h] = "StateVerified" => | |||
fetchedLightBlocks[h].header = blockchain[h] | |||
(** | |||
* When the light client terminates, there are no failed blocks. | |||
* (Otherwise, someone lied to us.) | |||
*) | |||
NoFailedBlocksOnSuccessInv(fetchedLightBlocks, lightBlockStatus) == | |||
\A h \in DOMAIN fetchedLightBlocks: | |||
lightBlockStatus[h] /= "StateFailed" | |||
(** | |||
The expected post-condition of VerifyToTarget. | |||
*) | |||
VerifyToTargetPost(blockchain, isPeerCorrect, | |||
fetchedLightBlocks, lightBlockStatus, | |||
trustedHeight, targetHeight, finalState) == | |||
LET trustedHeader == fetchedLightBlocks[trustedHeight].header IN | |||
\* The light client is not lying us on the trusted block. | |||
\* It is straightforward to detect. | |||
/\ lightBlockStatus[trustedHeight] = "StateVerified" | |||
/\ trustedHeight \in DOMAIN fetchedLightBlocks | |||
/\ trustedHeader = blockchain[trustedHeight] | |||
\* the invariants we have found in the light client verification | |||
\* there is a problem with trusting period | |||
/\ isPeerCorrect | |||
=> CorrectnessInv(blockchain, fetchedLightBlocks, lightBlockStatus) | |||
\* a correct peer should fail the light client, | |||
\* if the trusted block is in the trusting period | |||
/\ isPeerCorrect /\ InTrustingPeriodLocalSurely(trustedHeader) | |||
=> finalState = "finishedSuccess" | |||
/\ finalState = "finishedSuccess" => | |||
/\ lightBlockStatus[targetHeight] = "StateVerified" | |||
/\ targetHeight \in DOMAIN fetchedLightBlocks | |||
/\ NoFailedBlocksOnSuccessInv(fetchedLightBlocks, lightBlockStatus) | |||
/\ LightStoreInv(fetchedLightBlocks, lightBlockStatus) | |||
================================================================================== |
@ -0,0 +1,26 @@ | |||
------------------------- MODULE MC4_4_correct --------------------------- | |||
AllNodes == {"n1", "n2", "n3", "n4"} | |||
TRUSTED_HEIGHT == 1 | |||
TARGET_HEIGHT == 4 | |||
TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) | |||
CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting | |||
REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting | |||
IS_PRIMARY_CORRECT == TRUE | |||
FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators | |||
VARIABLES | |||
state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, | |||
nprobes, | |||
localClock, | |||
refClock, blockchain, Faulty | |||
(* the light client previous state components, used for monitoring *) | |||
VARIABLES | |||
prevVerified, | |||
prevCurrent, | |||
prevLocalClock, | |||
prevVerdict | |||
INSTANCE Lightclient_003_draft | |||
============================================================================ |
@ -0,0 +1,26 @@ | |||
---------------------- MODULE MC4_4_correct_drifted --------------------------- | |||
AllNodes == {"n1", "n2", "n3", "n4"} | |||
TRUSTED_HEIGHT == 1 | |||
TARGET_HEIGHT == 4 | |||
TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) | |||
CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting | |||
REAL_CLOCK_DRIFT == 30 \* how much the local clock is actually drifting | |||
IS_PRIMARY_CORRECT == TRUE | |||
FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators | |||
VARIABLES | |||
state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, | |||
nprobes, | |||
localClock, | |||
refClock, blockchain, Faulty | |||
(* the light client previous state components, used for monitoring *) | |||
VARIABLES | |||
prevVerified, | |||
prevCurrent, | |||
prevLocalClock, | |||
prevVerdict | |||
INSTANCE Lightclient_003_draft | |||
============================================================================== |
@ -0,0 +1,26 @@ | |||
---------------------- MODULE MC4_4_faulty_drifted --------------------------- | |||
AllNodes == {"n1", "n2", "n3", "n4"} | |||
TRUSTED_HEIGHT == 1 | |||
TARGET_HEIGHT == 4 | |||
TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) | |||
CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting | |||
REAL_CLOCK_DRIFT == 30 \* how much the local clock is actually drifting | |||
IS_PRIMARY_CORRECT == FALSE | |||
FAULTY_RATIO == <<1, 3>> \* < 1 / 3 faulty validators | |||
VARIABLES | |||
state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, | |||
nprobes, | |||
localClock, | |||
refClock, blockchain, Faulty | |||
(* the light client previous state components, used for monitoring *) | |||
VARIABLES | |||
prevVerified, | |||
prevCurrent, | |||
prevLocalClock, | |||
prevVerdict | |||
INSTANCE Lightclient_003_draft | |||
============================================================================== |
@ -1,22 +1,25 @@ | |||
----------------- MODULE MC5_5_faulty_peers_two_thirds_faulty --------------------- | |||
----------------- MODULE MC5_5_faulty --------------------- | |||
AllNodes == {"n1", "n2", "n3", "n4", "n5"} | |||
TRUSTED_HEIGHT == 1 | |||
TARGET_HEIGHT == 5 | |||
TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) | |||
CLOCK_DRIFT == 10 \* how much we assume the local clock is drifting | |||
REAL_CLOCK_DRIFT == 3 \* how much the local clock is actually drifting | |||
IS_PRIMARY_CORRECT == FALSE | |||
FAULTY_RATIO == <<2, 3>> \* < 1 / 3 faulty validators | |||
VARIABLES | |||
state, nextHeight, nprobes, fetchedLightBlocks, lightBlockStatus, | |||
latestVerified, nprobes, | |||
now, blockchain, Faulty | |||
state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified, | |||
nprobes, | |||
localClock, | |||
refClock, blockchain, Faulty | |||
(* the light client previous state components, used for monitoring *) | |||
VARIABLES | |||
prevVerified, | |||
prevCurrent, | |||
prevNow, | |||
prevLocalClock, | |||
prevVerdict | |||
INSTANCE Lightclient_003_draft | |||