|
-------------------- 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)
|
|
|
|
|
|
==================================================================================
|