github.com/aakash4dev/cometbft@v0.38.2/spec/light-client/verification/Lightclient_002_draft.tla (about) 1 -------------------------- MODULE Lightclient_002_draft ---------------------------- 2 (** 3 * A state-machine specification of the lite client, following the English spec: 4 * 5 * https://github.com/informalsystems/tendermint-rs/blob/master/docs/spec/lightclient/verification.md 6 *) 7 8 EXTENDS Integers, FiniteSets 9 10 \* the parameters of Light Client 11 CONSTANTS 12 TRUSTED_HEIGHT, 13 (* an index of the block header that the light client trusts by social consensus *) 14 TARGET_HEIGHT, 15 (* an index of the block header that the light client tries to verify *) 16 TRUSTING_PERIOD, 17 (* the period within which the validators are trusted *) 18 IS_PRIMARY_CORRECT 19 (* is primary correct? *) 20 21 VARIABLES (* see TypeOK below for the variable types *) 22 state, (* the current state of the light client *) 23 nextHeight, (* the next height to explore by the light client *) 24 nprobes (* the lite client iteration, or the number of block tests *) 25 26 (* the light store *) 27 VARIABLES 28 fetchedLightBlocks, (* a function from heights to LightBlocks *) 29 lightBlockStatus, (* a function from heights to block statuses *) 30 latestVerified (* the latest verified block *) 31 32 (* the variables of the lite client *) 33 lcvars == <<state, nextHeight, fetchedLightBlocks, lightBlockStatus, latestVerified>> 34 35 (* the light client previous state components, used for monitoring *) 36 VARIABLES 37 prevVerified, 38 prevCurrent, 39 prevNow, 40 prevVerdict 41 42 InitMonitor(verified, current, now, verdict) == 43 /\ prevVerified = verified 44 /\ prevCurrent = current 45 /\ prevNow = now 46 /\ prevVerdict = verdict 47 48 NextMonitor(verified, current, now, verdict) == 49 /\ prevVerified' = verified 50 /\ prevCurrent' = current 51 /\ prevNow' = now 52 /\ prevVerdict' = verdict 53 54 55 (******************* Blockchain instance ***********************************) 56 57 \* the parameters that are propagated into Blockchain 58 CONSTANTS 59 AllNodes 60 (* a set of all nodes that can act as validators (correct and faulty) *) 61 62 \* the state variables of Blockchain, see Blockchain.tla for the details 63 VARIABLES now, blockchain, Faulty 64 65 \* All the variables of Blockchain. For some reason, BC!vars does not work 66 bcvars == <<now, blockchain, Faulty>> 67 68 (* Create an instance of Blockchain. 69 We could write EXTENDS Blockchain, but then all the constants and state variables 70 would be hidden inside the Blockchain module. 71 *) 72 ULTIMATE_HEIGHT == TARGET_HEIGHT + 1 73 74 BC == INSTANCE Blockchain_002_draft WITH 75 now <- now, blockchain <- blockchain, Faulty <- Faulty 76 77 (************************** Lite client ************************************) 78 79 (* the heights on which the light client is working *) 80 HEIGHTS == TRUSTED_HEIGHT..TARGET_HEIGHT 81 82 (* the control states of the lite client *) 83 States == { "working", "finishedSuccess", "finishedFailure" } 84 85 (** 86 Check the precondition of ValidAndVerified. 87 88 [LCV-FUNC-VALID.1::TLA-PRE.1] 89 *) 90 ValidAndVerifiedPre(trusted, untrusted) == 91 LET thdr == trusted.header 92 uhdr == untrusted.header 93 IN 94 /\ BC!InTrustingPeriod(thdr) 95 /\ thdr.height < uhdr.height 96 \* the trusted block has been created earlier (no drift here) 97 /\ thdr.time < uhdr.time 98 \* the untrusted block is not from the future 99 /\ uhdr.time < now 100 /\ untrusted.Commits \subseteq uhdr.VS 101 /\ LET TP == Cardinality(uhdr.VS) 102 SP == Cardinality(untrusted.Commits) 103 IN 104 3 * SP > 2 * TP 105 /\ thdr.height + 1 = uhdr.height => thdr.NextVS = uhdr.VS 106 (* As we do not have explicit hashes we ignore these three checks of the English spec: 107 108 1. "trusted.Commit is a commit is for the header trusted.Header, 109 i.e. it contains the correct hash of the header". 110 2. untrusted.Validators = hash(untrusted.Header.Validators) 111 3. untrusted.NextValidators = hash(untrusted.Header.NextValidators) 112 *) 113 114 (** 115 * Check that the commits in an untrusted block form 1/3 of the next validators 116 * in a trusted header. 117 *) 118 SignedByOneThirdOfTrusted(trusted, untrusted) == 119 LET TP == Cardinality(trusted.header.NextVS) 120 SP == Cardinality(untrusted.Commits \intersect trusted.header.NextVS) 121 IN 122 3 * SP > TP 123 124 (** 125 Check, whether an untrusted block is valid and verifiable w.r.t. a trusted header. 126 127 [LCV-FUNC-VALID.1::TLA.1] 128 *) 129 ValidAndVerified(trusted, untrusted) == 130 IF ~ValidAndVerifiedPre(trusted, untrusted) 131 THEN "INVALID" 132 ELSE IF ~BC!InTrustingPeriod(untrusted.header) 133 (* We leave the following test for the documentation purposes. 134 The implementation should do this test, as signature verification may be slow. 135 In the TLA+ specification, ValidAndVerified happens in no time. 136 *) 137 THEN "FAILED_TRUSTING_PERIOD" 138 ELSE IF untrusted.header.height = trusted.header.height + 1 139 \/ SignedByOneThirdOfTrusted(trusted, untrusted) 140 THEN "SUCCESS" 141 ELSE "NOT_ENOUGH_TRUST" 142 143 (* 144 Initial states of the light client. 145 Initially, only the trusted light block is present. 146 *) 147 LCInit == 148 /\ state = "working" 149 /\ nextHeight = TARGET_HEIGHT 150 /\ nprobes = 0 \* no tests have been done so far 151 /\ LET trustedBlock == blockchain[TRUSTED_HEIGHT] 152 trustedLightBlock == [header |-> trustedBlock, Commits |-> AllNodes] 153 IN 154 \* initially, fetchedLightBlocks is a function of one element, i.e., TRUSTED_HEIGHT 155 /\ fetchedLightBlocks = [h \in {TRUSTED_HEIGHT} |-> trustedLightBlock] 156 \* initially, lightBlockStatus is a function of one element, i.e., TRUSTED_HEIGHT 157 /\ lightBlockStatus = [h \in {TRUSTED_HEIGHT} |-> "StateVerified"] 158 \* the latest verified block the the trusted block 159 /\ latestVerified = trustedLightBlock 160 /\ InitMonitor(trustedLightBlock, trustedLightBlock, now, "SUCCESS") 161 162 \* block should contain a copy of the block from the reference chain, with a matching commit 163 CopyLightBlockFromChain(block, height) == 164 LET ref == blockchain[height] 165 lastCommit == 166 IF height < ULTIMATE_HEIGHT 167 THEN blockchain[height + 1].lastCommit 168 \* for the ultimate block, which we never use, as ULTIMATE_HEIGHT = TARGET_HEIGHT + 1 169 ELSE blockchain[height].VS 170 IN 171 block = [header |-> ref, Commits |-> lastCommit] 172 173 \* Either the primary is correct and the block comes from the reference chain, 174 \* or the block is produced by a faulty primary. 175 \* 176 \* [LCV-FUNC-FETCH.1::TLA.1] 177 FetchLightBlockInto(block, height) == 178 IF IS_PRIMARY_CORRECT 179 THEN CopyLightBlockFromChain(block, height) 180 ELSE BC!IsLightBlockAllowedByDigitalSignatures(height, block) 181 182 \* add a block into the light store 183 \* 184 \* [LCV-FUNC-UPDATE.1::TLA.1] 185 LightStoreUpdateBlocks(lightBlocks, block) == 186 LET ht == block.header.height IN 187 [h \in DOMAIN lightBlocks \union {ht} |-> 188 IF h = ht THEN block ELSE lightBlocks[h]] 189 190 \* update the state of a light block 191 \* 192 \* [LCV-FUNC-UPDATE.1::TLA.1] 193 LightStoreUpdateStates(statuses, ht, blockState) == 194 [h \in DOMAIN statuses \union {ht} |-> 195 IF h = ht THEN blockState ELSE statuses[h]] 196 197 \* Check, whether newHeight is a possible next height for the light client. 198 \* 199 \* [LCV-FUNC-SCHEDULE.1::TLA.1] 200 CanScheduleTo(newHeight, pLatestVerified, pNextHeight, pTargetHeight) == 201 LET ht == pLatestVerified.header.height IN 202 \/ /\ ht = pNextHeight 203 /\ ht < pTargetHeight 204 /\ pNextHeight < newHeight 205 /\ newHeight <= pTargetHeight 206 \/ /\ ht < pNextHeight 207 /\ ht < pTargetHeight 208 /\ ht < newHeight 209 /\ newHeight < pNextHeight 210 \/ /\ ht = pTargetHeight 211 /\ newHeight = pTargetHeight 212 213 \* The loop of VerifyToTarget. 214 \* 215 \* [LCV-FUNC-MAIN.1::TLA-LOOP.1] 216 VerifyToTargetLoop == 217 \* the loop condition is true 218 /\ latestVerified.header.height < TARGET_HEIGHT 219 \* pick a light block, which will be constrained later 220 /\ \E current \in BC!LightBlocks: 221 \* Get next LightBlock for verification 222 /\ IF nextHeight \in DOMAIN fetchedLightBlocks 223 THEN \* copy the block from the light store 224 /\ current = fetchedLightBlocks[nextHeight] 225 /\ UNCHANGED fetchedLightBlocks 226 ELSE \* retrieve a light block and save it in the light store 227 /\ FetchLightBlockInto(current, nextHeight) 228 /\ fetchedLightBlocks' = LightStoreUpdateBlocks(fetchedLightBlocks, current) 229 \* Record that one more probe has been done (for complexity and model checking) 230 /\ nprobes' = nprobes + 1 231 \* Verify the current block 232 /\ LET verdict == ValidAndVerified(latestVerified, current) IN 233 NextMonitor(latestVerified, current, now, verdict) /\ 234 \* Decide whether/how to continue 235 CASE verdict = "SUCCESS" -> 236 /\ lightBlockStatus' = LightStoreUpdateStates(lightBlockStatus, nextHeight, "StateVerified") 237 /\ latestVerified' = current 238 /\ state' = 239 IF latestVerified'.header.height < TARGET_HEIGHT 240 THEN "working" 241 ELSE "finishedSuccess" 242 /\ \E newHeight \in HEIGHTS: 243 /\ CanScheduleTo(newHeight, current, nextHeight, TARGET_HEIGHT) 244 /\ nextHeight' = newHeight 245 246 [] verdict = "NOT_ENOUGH_TRUST" -> 247 (* 248 do nothing: the light block current passed validation, but the validator 249 set is too different to verify it. We keep the state of 250 current at StateUnverified. For a later iteration, Schedule 251 might decide to try verification of that light block again. 252 *) 253 /\ lightBlockStatus' = LightStoreUpdateStates(lightBlockStatus, nextHeight, "StateUnverified") 254 /\ \E newHeight \in HEIGHTS: 255 /\ CanScheduleTo(newHeight, latestVerified, nextHeight, TARGET_HEIGHT) 256 /\ nextHeight' = newHeight 257 /\ UNCHANGED <<latestVerified, state>> 258 259 [] OTHER -> 260 \* verdict is some error code 261 /\ lightBlockStatus' = LightStoreUpdateStates(lightBlockStatus, nextHeight, "StateFailed") 262 /\ state' = "finishedFailure" 263 /\ UNCHANGED <<latestVerified, nextHeight>> 264 265 \* The terminating condition of VerifyToTarget. 266 \* 267 \* [LCV-FUNC-MAIN.1::TLA-LOOPCOND.1] 268 VerifyToTargetDone == 269 /\ latestVerified.header.height >= TARGET_HEIGHT 270 /\ state' = "finishedSuccess" 271 /\ UNCHANGED <<nextHeight, nprobes, fetchedLightBlocks, lightBlockStatus, latestVerified>> 272 /\ UNCHANGED <<prevVerified, prevCurrent, prevNow, prevVerdict>> 273 274 (********************* Lite client + Blockchain *******************) 275 Init == 276 \* the blockchain is initialized immediately to the ULTIMATE_HEIGHT 277 /\ BC!InitToHeight 278 \* the light client starts 279 /\ LCInit 280 281 (* 282 The system step is very simple. 283 The light client is either executing VerifyToTarget, or it has terminated. 284 (In the latter case, a model checker reports a deadlock.) 285 Simultaneously, the global clock may advance. 286 *) 287 Next == 288 /\ state = "working" 289 /\ VerifyToTargetLoop \/ VerifyToTargetDone 290 /\ BC!AdvanceTime \* the global clock is advanced by zero or more time units 291 292 (************************* Types ******************************************) 293 TypeOK == 294 /\ state \in States 295 /\ nextHeight \in HEIGHTS 296 /\ latestVerified \in BC!LightBlocks 297 /\ \E HS \in SUBSET HEIGHTS: 298 /\ fetchedLightBlocks \in [HS -> BC!LightBlocks] 299 /\ lightBlockStatus 300 \in [HS -> {"StateVerified", "StateUnverified", "StateFailed"}] 301 302 (************************* Properties ******************************************) 303 304 (* The properties to check *) 305 \* this invariant candidate is false 306 NeverFinish == 307 state = "working" 308 309 \* this invariant candidate is false 310 NeverFinishNegative == 311 state /= "finishedFailure" 312 313 \* This invariant holds true, when the primary is correct. 314 \* This invariant candidate is false when the primary is faulty. 315 NeverFinishNegativeWhenTrusted == 316 (*(minTrustedHeight <= TRUSTED_HEIGHT)*) 317 BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) 318 => state /= "finishedFailure" 319 320 \* this invariant candidate is false 321 NeverFinishPositive == 322 state /= "finishedSuccess" 323 324 (** 325 Correctness states that all the obtained headers are exactly like in the blockchain. 326 327 It is always the case that every verified header in LightStore was generated by 328 an instance of Tendermint consensus. 329 330 [LCV-DIST-SAFE.1::CORRECTNESS-INV.1] 331 *) 332 CorrectnessInv == 333 \A h \in DOMAIN fetchedLightBlocks: 334 lightBlockStatus[h] = "StateVerified" => 335 fetchedLightBlocks[h].header = blockchain[h] 336 337 (** 338 Check that the sequence of the headers in storedLightBlocks satisfies ValidAndVerified = "SUCCESS" pairwise 339 This property is easily violated, whenever a header cannot be trusted anymore. 340 *) 341 StoredHeadersAreVerifiedInv == 342 state = "finishedSuccess" 343 => 344 \A lh, rh \in DOMAIN fetchedLightBlocks: \* for every pair of different stored headers 345 \/ lh >= rh 346 \* either there is a header between them 347 \/ \E mh \in DOMAIN fetchedLightBlocks: 348 lh < mh /\ mh < rh 349 \* or we can verify the right one using the left one 350 \/ "SUCCESS" = ValidAndVerified(fetchedLightBlocks[lh], fetchedLightBlocks[rh]) 351 352 \* An improved version of StoredHeadersAreSound, assuming that a header may be not trusted. 353 \* This invariant candidate is also violated, 354 \* as there may be some unverified blocks left in the middle. 355 StoredHeadersAreVerifiedOrNotTrustedInv == 356 state = "finishedSuccess" 357 => 358 \A lh, rh \in DOMAIN fetchedLightBlocks: \* for every pair of different stored headers 359 \/ lh >= rh 360 \* either there is a header between them 361 \/ \E mh \in DOMAIN fetchedLightBlocks: 362 lh < mh /\ mh < rh 363 \* or we can verify the right one using the left one 364 \/ "SUCCESS" = ValidAndVerified(fetchedLightBlocks[lh], fetchedLightBlocks[rh]) 365 \* or the left header is outside the trusting period, so no guarantees 366 \/ ~BC!InTrustingPeriod(fetchedLightBlocks[lh].header) 367 368 (** 369 * An improved version of StoredHeadersAreSoundOrNotTrusted, 370 * checking the property only for the verified headers. 371 * This invariant holds true. 372 *) 373 ProofOfChainOfTrustInv == 374 state = "finishedSuccess" 375 => 376 \A lh, rh \in DOMAIN fetchedLightBlocks: 377 \* for every pair of stored headers that have been verified 378 \/ lh >= rh 379 \/ lightBlockStatus[lh] = "StateUnverified" 380 \/ lightBlockStatus[rh] = "StateUnverified" 381 \* either there is a header between them 382 \/ \E mh \in DOMAIN fetchedLightBlocks: 383 lh < mh /\ mh < rh /\ lightBlockStatus[mh] = "StateVerified" 384 \* or the left header is outside the trusting period, so no guarantees 385 \/ ~(BC!InTrustingPeriod(fetchedLightBlocks[lh].header)) 386 \* or we can verify the right one using the left one 387 \/ "SUCCESS" = ValidAndVerified(fetchedLightBlocks[lh], fetchedLightBlocks[rh]) 388 389 (** 390 * When the light client terminates, there are no failed blocks. (Otherwise, someone lied to us.) 391 *) 392 NoFailedBlocksOnSuccessInv == 393 state = "finishedSuccess" => 394 \A h \in DOMAIN fetchedLightBlocks: 395 lightBlockStatus[h] /= "StateFailed" 396 397 \* This property states that whenever the light client finishes with a positive outcome, 398 \* the trusted header is still within the trusting period. 399 \* We expect this property to be violated. And Apalache shows us a counterexample. 400 PositiveBeforeTrustedHeaderExpires == 401 (state = "finishedSuccess") => BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) 402 403 \* If the primary is correct and the initial trusted block has not expired, 404 \* then whenever the algorithm terminates, it reports "success" 405 CorrectPrimaryAndTimeliness == 406 (BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) 407 /\ state /= "working" /\ IS_PRIMARY_CORRECT) => 408 state = "finishedSuccess" 409 410 (** 411 If the primary is correct and there is a trusted block that has not expired, 412 then whenever the algorithm terminates, it reports "success". 413 414 [LCV-DIST-LIVE.1::SUCCESS-CORR-PRIMARY-CHAIN-OF-TRUST.1] 415 *) 416 SuccessOnCorrectPrimaryAndChainOfTrust == 417 (\E h \in DOMAIN fetchedLightBlocks: 418 lightBlockStatus[h] = "StateVerified" /\ BC!InTrustingPeriod(blockchain[h]) 419 /\ state /= "working" /\ IS_PRIMARY_CORRECT) => 420 state = "finishedSuccess" 421 422 \* Lite Client Completeness: If header h was correctly generated by an instance 423 \* of Tendermint consensus (and its age is less than the trusting period), 424 \* then the lite client should eventually set trust(h) to true. 425 \* 426 \* Note that Completeness assumes that the lite client communicates with a correct full node. 427 \* 428 \* We decompose completeness into Termination (liveness) and Precision (safety). 429 \* Once again, Precision is an inverse version of the safety property in Completeness, 430 \* as A => B is logically equivalent to ~B => ~A. 431 PrecisionInv == 432 (state = "finishedFailure") 433 => \/ ~BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) \* outside of the trusting period 434 \/ \E h \in DOMAIN fetchedLightBlocks: 435 LET lightBlock == fetchedLightBlocks[h] IN 436 \* the full node lied to the lite client about the block header 437 \/ lightBlock.header /= blockchain[h] 438 \* the full node lied to the lite client about the commits 439 \/ lightBlock.Commits /= lightBlock.header.VS 440 441 \* the old invariant that was found to be buggy by TLC 442 PrecisionBuggyInv == 443 (state = "finishedFailure") 444 => \/ ~BC!InTrustingPeriod(blockchain[TRUSTED_HEIGHT]) \* outside of the trusting period 445 \/ \E h \in DOMAIN fetchedLightBlocks: 446 LET lightBlock == fetchedLightBlocks[h] IN 447 \* the full node lied to the lite client about the block header 448 lightBlock.header /= blockchain[h] 449 450 \* the worst complexity 451 Complexity == 452 LET N == TARGET_HEIGHT - TRUSTED_HEIGHT + 1 IN 453 state /= "working" => 454 (2 * nprobes <= N * (N - 1)) 455 456 (* 457 We omit termination, as the algorithm deadlocks in the end. 458 So termination can be demonstrated by finding a deadlock. 459 Of course, one has to analyze the deadlocked state and see that 460 the algorithm has indeed terminated there. 461 *) 462 ============================================================================= 463 \* Modification History 464 \* Last modified Fri Jun 26 12:08:28 CEST 2020 by igor 465 \* Created Wed Oct 02 16:39:42 CEST 2019 by igor