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"} | AllNodes == {"n1", "n2", "n3", "n4", "n5"} | ||||
TRUSTED_HEIGHT == 1 | TRUSTED_HEIGHT == 1 | ||||
TARGET_HEIGHT == 5 | TARGET_HEIGHT == 5 | ||||
TRUSTING_PERIOD == 1400 \* two weeks, one day is 100 time units :-) | 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_PRIMARY_CORRECT == FALSE | ||||
FAULTY_RATIO == <<2, 3>> \* < 1 / 3 faulty validators | FAULTY_RATIO == <<2, 3>> \* < 1 / 3 faulty validators | ||||
VARIABLES | 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 *) | (* the light client previous state components, used for monitoring *) | ||||
VARIABLES | VARIABLES | ||||
prevVerified, | prevVerified, | ||||
prevCurrent, | prevCurrent, | ||||
prevNow, | |||||
prevLocalClock, | |||||
prevVerdict | prevVerdict | ||||
INSTANCE Lightclient_003_draft | INSTANCE Lightclient_003_draft | ||||