You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

606 lines
24 KiB

  1. ------------------------------- MODULE scheduler -------------------------------
  2. (*
  3. A specification of the fast sync scheduler that is introduced in blockchain/v2:
  4. https://github.com/tendermint/tendermint/tree/brapse/blockchain-v2-riri-reactor-2
  5. The model includes:
  6. - a scheduler that maintains the peers and blocks that it receives from the peers, and
  7. - one environment simulating a correct peer
  8. This specification focuses on the events that are received and produced by the scheduler.
  9. Communication between the scheduler and the other fastsync components is not specified.
  10. *)
  11. EXTENDS Integers, FiniteSets
  12. \* the protocol parameters
  13. CONSTANTS
  14. PeerIDs, \* potential peer ids, a set of integers, e.g. 0..2
  15. ultimateHeight, \* the maximum height of the blockchain, an integer, e.g. 3
  16. numRequests \* the maximum number of requests made when scheduling new blocks, e.g. 2
  17. \* a few definitions
  18. None == -1 \* an undefined value
  19. Heights == 0..ultimateHeight \* potential heights
  20. noErr == "errNone"
  21. Errors == {
  22. noErr, "errPeerNotFound", "errDelRemovedPeer", "errAddDuplicatePeer",
  23. "errUpdateRemovedPeer", "errAfterPeerRemove", "errBadPeer", "errBadPeerState",
  24. "errProcessedBlockEv", "finished", "timeout", "errAddRemovedPeer", "errPeerNoBlock", "errBadBlockState"}
  25. PeerStates == {"peerStateUnknown", "peerStateNew", "peerStateReady", "peerStateRemoved"}
  26. BlockStates == {
  27. "blockStateUnknown", "blockStateNew", "blockStatePending",
  28. "blockStateReceived", "blockStateProcessed"}
  29. \* basic stuff
  30. Min(a, b) == IF a < b THEN a ELSE b
  31. Max(a, b) == IF a > b THEN a ELSE b
  32. \* the state of the scheduler:
  33. VARIABLE turn \* who makes a step: the scheduler or the environment
  34. \* the state of the reactor:
  35. VARIABLES inEvent, \* an event from the environment to the scheduler
  36. envRunning \* a Boolean, negation of stopProcessing in the implementation
  37. \* the state of the scheduler:
  38. VARIABLES outEvent, \* an event from the scheduler to the environment
  39. scRunning
  40. \* the block pool:
  41. VARIABLE scheduler
  42. (*
  43. scheduler is a record that contains:
  44. height: Int,
  45. height of the next block to collect
  46. peers: PeerIDs,
  47. the set of peers that have connected in the past, may include removed peers
  48. peerHeights: [PeerIDs -> Heights],
  49. a map to collect the peer heights, >0 if peer in ready state, None (-1) otherwise
  50. peerStates: [PeerIDs -> PeerStates],
  51. a map to record the peer states
  52. blockStates: [Heights -> BlockStates]
  53. a set of heights for which blocks are to be scheduled, pending or received
  54. pendingBlocks: [Heights -> PeerIDs],
  55. a set of heights for which blocks are to be scheduled or pending
  56. receivedBlocks: [Heights -> PeerIDs],
  57. a set of heights for which blocks were received but not yet processed
  58. blocks: Heights,
  59. the full set of blocks requested or downloaded by the scheduler
  60. *)
  61. vars == <<turn, envRunning, inEvent, scRunning, outEvent, scheduler>>
  62. \* for now just keep the height in the block
  63. Blocks == [ height: Heights ]
  64. noEvent == [type |-> "NoEvent"]
  65. InEvents ==
  66. {noEvent} \cup
  67. [type: {"rTrySchedule", "tNoAdvanceExp"}] \cup
  68. [type: {"bcStatusResponse"}, peerID: PeerIDs, height: Heights] \cup
  69. [type: {"bcBlockResponse"}, peerID: PeerIDs, height: Heights, block: Blocks] \cup
  70. [type: {"bcNoBlockResponse"}, peerID: PeerIDs, height: Heights] \cup
  71. [type: {"pcBlockProcessed"}, peerID: PeerIDs, height: Heights] \cup
  72. [type: {"pcBlockVerificationFailure"}, height: Heights, firstPeerID: PeerIDs, secondPeerID: PeerIDs] \cup
  73. [type: {"bcAddNewPeer"}, peerID: PeerIDs] \cup
  74. [type: {"bcRemovePeer"}, peerID: PeerIDs]
  75. \* Output events produced by the scheduler.
  76. \* Note: in v2 the status request is done by the reactor/ environment
  77. OutEvents ==
  78. {noEvent} \cup
  79. [type: {"scPeerError"}, peerID: PeerIDs, error: Errors] \cup
  80. [type: {"scSchedulerFail"}, error: Errors] \cup
  81. [type: {"scBlockRequest"}, peerID: PeerIDs, height: Heights] \cup
  82. [type: {"scBlockReceived"}, peerID: PeerIDs, block: Blocks] \cup
  83. [type: {"scPeersPruned"}, pruned: SUBSET [peerID: PeerIDs]] \cup
  84. [type: {"scFinishedEv"}, error: Errors]
  85. (* ----------------------------------------------------------------------------------------------*)
  86. (* The behavior of the scheduler that keeps track of peers, block requests and responses, etc. *)
  87. (* See scheduler.go *)
  88. (* https://github.com/tendermint/tendermint/blob/v0.33.3/blockchain/v2/scheduler.go *)
  89. (* ----------------------------------------------------------------------------------------------*)
  90. addPeer(sc, peerID) ==
  91. IF peerID \in sc.peers THEN
  92. [err |-> "errAddDuplicatePeer", val |-> sc]
  93. ELSE IF sc.peerStates[peerID] = "peerStateRemoved" THEN
  94. [err |-> "errAddRemovedPeer", val |-> sc]
  95. ELSE
  96. LET newPeers == sc.peers \cup { peerID } IN
  97. LET newPeerHeights == [sc.peerHeights EXCEPT ![peerID] = None] IN
  98. LET newPeerStates == [sc.peerStates EXCEPT ![peerID] = "peerStateNew"] IN
  99. LET newSc == [sc EXCEPT
  100. !.peers = newPeers,
  101. !.peerHeights = newPeerHeights,
  102. !.peerStates = newPeerStates] IN
  103. [err |-> noErr, val |-> newSc]
  104. maxHeight(states, heights) ==
  105. LET activePeers == {p \in DOMAIN states: states[p] = "peerStateReady"} IN
  106. IF activePeers = {} THEN
  107. 0 \* no peers, just return 0
  108. ELSE
  109. CHOOSE max \in { heights[p] : p \in activePeers }:
  110. \A p \in activePeers: heights[p] <= max \* max is the maximum
  111. maxHeightScheduler(sc) ==
  112. maxHeight(sc.peerStates, sc.peerHeights)
  113. removePeer(sc, peerID) ==
  114. IF peerID \notin sc.peers THEN
  115. [err |-> "errPeerNotFound", val |-> sc]
  116. ELSE IF sc.peerStates[peerID] = "peerStateRemoved" THEN
  117. [err |-> "errDelRemovedPeer", val |-> sc]
  118. ELSE
  119. LET newSc == [sc EXCEPT
  120. !.peerHeights[peerID] = None,
  121. !.peerStates[peerID] = "peerStateRemoved",
  122. \* remove all blocks from peerID and block requests to peerID, see scheduler.removePeer
  123. !.blockStates = [h \in Heights |->
  124. IF sc.pendingBlocks[h] = peerID \/ sc.receivedBlocks[h] = peerID THEN "blockStateNew"
  125. ELSE sc.blockStates[h]],
  126. !.pendingBlocks = [h \in Heights |-> IF sc.pendingBlocks[h] = peerID THEN None ELSE sc.pendingBlocks[h]],
  127. !.receivedBlocks = [h \in Heights |-> IF sc.receivedBlocks[h] = peerID THEN None ELSE sc.receivedBlocks[h]]
  128. ] IN
  129. [err |-> noErr, val |-> newSc]
  130. addNewBlocks(sc, newMph) ==
  131. \* add new blocks to be requested (e.g. when overall max peer height has changed)
  132. IF Cardinality(sc.blocks) >= numRequests THEN
  133. [newBlocks |-> sc.blocks, newBlockStates |-> sc.blockStates]
  134. ELSE
  135. LET requestSet == sc.height.. Min(numRequests+sc.height, newMph) IN
  136. LET heightsToRequest == {h \in requestSet: sc.blockStates[h] = "blockStateUnknown"} IN
  137. LET newBlockStates == [
  138. h \in Heights |-> IF h \in heightsToRequest THEN "blockStateNew"
  139. ELSE sc.blockStates[h]] IN
  140. [newBlocks |-> heightsToRequest, newBlockStates |-> newBlockStates]
  141. \* Update the peer height (the peer should have been previously added)
  142. setPeerHeight(sc, peerID, height) ==
  143. IF peerID \notin sc.peers THEN
  144. [err |-> "errPeerNotFound", val |-> sc]
  145. ELSE IF sc.peerStates[peerID] = "peerStateRemoved" THEN
  146. [err |-> "errUpdateRemovedPeer", val |-> sc]
  147. ELSE IF height < sc.peerHeights[peerID] THEN (* The peer is corrupt? Remove the peer. *)
  148. removePeer(sc, peerID)
  149. ELSE
  150. LET newPeerHeights == [sc.peerHeights EXCEPT ![peerID] = height] IN \* set the peer's height
  151. LET newPeerStates == [sc.peerStates EXCEPT ![peerID] = "peerStateReady"] IN \* set the peer's state
  152. LET newMph == maxHeight(newPeerStates, newPeerHeights) IN
  153. LET res == addNewBlocks(sc, newMph) IN
  154. LET newSc == [sc EXCEPT
  155. !.peerHeights = newPeerHeights, !.peerStates = newPeerStates,
  156. !.blocks = res.newBlocks, !.blockStates = res.newBlockStates] IN
  157. [err |-> noErr, val |-> newSc]
  158. nextHeightToSchedule(sc) ==
  159. LET toBeScheduled == {h \in DOMAIN sc.blockStates: sc.blockStates[h] = "blockStateNew"} \cup {ultimateHeight+1} IN
  160. CHOOSE minH \in toBeScheduled: \A h \in toBeScheduled: h >= minH
  161. getStateAtHeight(sc, h) ==
  162. IF h < sc.height THEN
  163. "blockStateProcessed"
  164. ELSE IF h \in DOMAIN sc.blockStates THEN
  165. sc.blockStates[h]
  166. ELSE
  167. "blockStateUnknown"
  168. markPending(sc, peerID, h) ==
  169. IF getStateAtHeight(sc, h) /= "blockStateNew" THEN
  170. [err |-> "errBadBlockState", val |-> sc]
  171. ELSE IF peerID \notin sc.peers \/ sc.peerStates[peerID] /= "peerStateReady" THEN
  172. [err |-> "errBadPeerState", val |-> sc]
  173. ELSE IF h > sc.peerHeights[peerID] THEN
  174. [err |-> "errPeerTooShort", val |-> sc]
  175. ELSE
  176. LET newSc == [sc EXCEPT
  177. !.blockStates = [sc.blockStates EXCEPT ![h] = "blockStatePending"],
  178. !.pendingBlocks = [sc.pendingBlocks EXCEPT ![h] = peerID]] IN
  179. [err |-> noErr, val |-> newSc]
  180. markReceived(sc, peerID, h) ==
  181. IF peerID \notin sc.peers \/ sc.peerStates[peerID] /= "peerStateReady" THEN
  182. [err |-> "errBadPeerState", val |-> sc]
  183. ELSE IF getStateAtHeight(sc, h) /= "blockStatePending" \/ sc.pendingBlocks[h] /= peerID THEN
  184. [err |-> "errBadPeer", val |-> sc]
  185. ELSE
  186. LET newSc == [sc EXCEPT
  187. !.blockStates = [sc.blockStates EXCEPT ![h] = "blockStateReceived"],
  188. !.pendingBlocks = [sc.pendingBlocks EXCEPT ![h] = None],
  189. !.receivedBlocks = [sc.receivedBlocks EXCEPT ![h] = peerID]] IN
  190. [err |-> noErr, val |-> newSc]
  191. markProcessed(sc, h) ==
  192. IF getStateAtHeight(sc, h) /= "blockStateReceived" THEN
  193. [err |-> "errProcessedBlockEv", val |-> sc]
  194. ELSE
  195. LET newSc == [sc EXCEPT
  196. !.blockStates = [sc.blockStates EXCEPT ![h] = "blockStateProcessed"],
  197. !.receivedBlocks = [sc.receivedBlocks EXCEPT ![h] = None],
  198. !.height = sc.height + 1] IN
  199. [err |-> noErr, val |-> newSc]
  200. reachedMaxHeight(sc) ==
  201. IF sc.peers = {} THEN
  202. FALSE
  203. ELSE
  204. LET maxH == maxHeightScheduler(sc) IN
  205. maxH > 0 /\ (sc.height >= maxH)
  206. highPeers(sc, minH) == {p \in sc.peers: sc.peerHeights[p] >= minH}
  207. (* ----------------------------------------------------------------------------------------------*)
  208. (* The behavior of the scheduler state machine *)
  209. (* See scheduler.go *)
  210. (* https://github.com/tendermint/tendermint/tree/brapse/blockchain-v2-riri-reactor-2/scheduler.go*)
  211. (* ----------------------------------------------------------------------------------------------*)
  212. blStateInit(h, start) ==
  213. IF h <= start THEN
  214. "blockStateProcessed"
  215. ELSE "blockStateUnknown"
  216. InitSc ==
  217. /\ scRunning = TRUE
  218. /\ outEvent = noEvent
  219. /\ \E startHeight \in Heights:
  220. scheduler = [
  221. initHeight |-> startHeight,
  222. height |-> startHeight + 1,
  223. peers |-> {},
  224. peerHeights |-> [p \in PeerIDs |-> None],
  225. peerStates |-> [p \in PeerIDs |-> "peerStateUnknown"],
  226. blocks |-> {},
  227. blockStates |-> [h \in Heights |-> blStateInit(h, startHeight)],
  228. pendingBlocks |-> [h \in Heights |-> None],
  229. receivedBlocks |-> [h \in Heights |-> None]
  230. ]
  231. handleAddNewPeer ==
  232. /\ inEvent.type = "bcAddNewPeer"
  233. /\ LET res == addPeer(scheduler, inEvent.peerID) IN
  234. IF res.err /= noErr THEN
  235. /\ outEvent' = [type |-> "scSchedulerFail", error |-> res.err]
  236. /\ UNCHANGED <<scheduler>>
  237. ELSE
  238. /\ scheduler' = res.val
  239. /\ UNCHANGED outEvent
  240. /\ UNCHANGED scRunning
  241. finishSc(event) ==
  242. event.type = "scFinishedEv" /\ event.error = "finished"
  243. handleRemovePeer ==
  244. /\ inEvent.type = "bcRemovePeer"
  245. /\ LET res == removePeer(scheduler, inEvent.peerID) IN
  246. IF res.err /= noErr THEN
  247. /\ outEvent' = [type |-> "scSchedulerFail", error |-> res.err]
  248. /\ UNCHANGED scheduler
  249. ELSE
  250. /\ scheduler' = res.val
  251. /\ IF reachedMaxHeight(scheduler') THEN
  252. outEvent' = [type |-> "scFinishedEv", error |-> "errAfterPeerRemove"]
  253. ELSE
  254. UNCHANGED outEvent
  255. /\ IF finishSc(outEvent') THEN
  256. scRunning' = FALSE
  257. ELSE UNCHANGED scRunning
  258. handleStatusResponse ==
  259. /\ inEvent.type = "bcStatusResponse"
  260. /\ LET res == setPeerHeight(scheduler, inEvent.peerID, inEvent.height) IN
  261. IF res.err /= noErr THEN
  262. /\ outEvent' = [type |-> "scPeerError", peerID |-> inEvent.peerID, error |-> res.err]
  263. /\ UNCHANGED scheduler
  264. ELSE
  265. /\ scheduler' = res.val
  266. /\ UNCHANGED outEvent
  267. /\ UNCHANGED scRunning
  268. handleTrySchedule == \* every 10 ms, but our spec is asynchronous
  269. /\ inEvent.type = "rTrySchedule"
  270. /\ LET minH == nextHeightToSchedule(scheduler) IN
  271. IF minH = ultimateHeight+1 THEN
  272. /\ outEvent' = noEvent
  273. /\ UNCHANGED scheduler
  274. ELSE IF minH = ultimateHeight+1 THEN
  275. /\ outEvent' = noEvent
  276. /\ UNCHANGED scheduler
  277. ELSE
  278. /\ LET hp == highPeers(scheduler, minH) IN
  279. IF hp = {} THEN
  280. /\ outEvent' = noEvent
  281. /\ UNCHANGED scheduler
  282. ELSE \E bestPeerID \in hp:
  283. /\ LET res == markPending(scheduler, bestPeerID, minH) IN
  284. /\ IF res.err /= noErr THEN
  285. outEvent' = [type |-> "scSchedulerFail", error|-> res.err]
  286. ELSE
  287. outEvent' = [type |-> "scBlockRequest", peerID |-> bestPeerID, height |-> minH]
  288. /\ scheduler' = res.val
  289. /\ UNCHANGED scRunning
  290. handleBlockResponse ==
  291. /\ inEvent.type = "bcBlockResponse"
  292. /\ LET res == markReceived(scheduler, inEvent.peerID, inEvent.height) IN
  293. IF res.err /= noErr THEN
  294. LET res1 == removePeer(scheduler, inEvent.peerID) IN
  295. /\ outEvent' = [type |-> "scPeerError", peerID |-> inEvent.peerID, error |-> res.err]
  296. /\ scheduler' = res1.val
  297. ELSE
  298. /\ outEvent' = [type |-> "scBlockReceived", peerID |-> inEvent.peerID, block |-> inEvent.block]
  299. /\ scheduler' = res.val
  300. /\ UNCHANGED scRunning
  301. handleNoBlockResponse ==
  302. /\ inEvent.type = "bcNoBlockResponse"
  303. /\ IF (scheduler.peers = {} \/ scheduler.peerStates[inEvent.peerID] = "peerStateRemoved") THEN
  304. /\ outEvent' = noEvent
  305. /\ UNCHANGED scheduler
  306. ELSE
  307. LET res == removePeer(scheduler, inEvent.peerID) IN
  308. /\ outEvent' = [type |-> "scPeerError", peerID |-> inEvent.peerID, error |-> "errPeerNoBlock"]
  309. /\ scheduler' = res.val
  310. /\ UNCHANGED scRunning
  311. handleBlockProcessed ==
  312. /\ inEvent.type = "pcBlockProcessed"
  313. /\ IF inEvent.height /= scheduler.height THEN
  314. /\ outEvent' = [type |-> "scSchedulerFail", error |-> "errProcessedBlockEv"]
  315. /\ UNCHANGED scheduler
  316. ELSE
  317. LET res == markProcessed(scheduler, inEvent.height) IN
  318. IF res.err /= noErr THEN
  319. /\ outEvent' = [type |-> "scSchedulerFail", error |-> res.err]
  320. /\ UNCHANGED scheduler
  321. ELSE
  322. /\ scheduler' = res.val
  323. /\ IF reachedMaxHeight(scheduler') THEN
  324. outEvent' = [type |-> "scFinishedEv", error |-> "finished"]
  325. ELSE
  326. outEvent' = noEvent
  327. /\ IF finishSc(outEvent') THEN
  328. scRunning' = FALSE
  329. ELSE UNCHANGED scRunning
  330. handleBlockProcessError ==
  331. /\ inEvent.type = "pcBlockVerificationFailure"
  332. /\ IF scheduler.peers = {} THEN
  333. /\ outEvent' = noEvent
  334. /\ UNCHANGED scheduler
  335. ELSE
  336. LET res1 == removePeer(scheduler, inEvent.firstPeerID) IN
  337. LET res2 == removePeer(res1.val, inEvent.secondPeerID) IN
  338. /\ IF reachedMaxHeight(res2.val) THEN
  339. outEvent' = [type |-> "scFinishedEv", error |-> "finished"]
  340. ELSE
  341. outEvent' = noEvent
  342. /\ scheduler' = res2.val
  343. /\ IF finishSc(outEvent') THEN
  344. scRunning' = FALSE
  345. ELSE UNCHANGED scRunning
  346. handleNoAdvanceExp ==
  347. /\ inEvent.type = "tNoAdvanceExp"
  348. /\ outEvent' = [type |-> "scFinishedEv", error |-> "timeout"]
  349. /\ scRunning' = FALSE
  350. /\ UNCHANGED <<scheduler>>
  351. NextSc ==
  352. IF ~scRunning THEN
  353. UNCHANGED <<outEvent, scRunning, scheduler>>
  354. ELSE
  355. \/ handleStatusResponse
  356. \/ handleAddNewPeer
  357. \/ handleRemovePeer
  358. \/ handleTrySchedule
  359. \/ handleBlockResponse
  360. \/ handleNoBlockResponse
  361. \/ handleBlockProcessed
  362. \/ handleBlockProcessError
  363. \/ handleNoAdvanceExp
  364. (* ----------------------------------------------------------------------------------------------*)
  365. (* The behavior of the environment. *)
  366. (* ----------------------------------------------------------------------------------------------*)
  367. InitEnv ==
  368. /\ inEvent = noEvent
  369. /\ envRunning = TRUE
  370. OnGlobalTimeoutTicker ==
  371. /\ inEvent' = [type |-> "tNoAdvanceExp"]
  372. /\ envRunning' = FALSE
  373. OnTrySchedule ==
  374. /\ inEvent' = [type |-> "rTrySchedule"]
  375. /\ UNCHANGED envRunning
  376. OnAddPeerEv ==
  377. /\ inEvent' \in [type: {"bcAddNewPeer"}, peerID: PeerIDs]
  378. /\ UNCHANGED envRunning
  379. OnStatusResponseEv ==
  380. \* any status response can come from the blockchain, pick one non-deterministically
  381. /\ inEvent' \in [type: {"bcStatusResponse"}, peerID: PeerIDs, height: Heights]
  382. /\ UNCHANGED envRunning
  383. OnBlockResponseEv ==
  384. \* any block response can come from the blockchain, pick one non-deterministically
  385. /\ inEvent' \in [type: {"bcBlockResponse"}, peerID: PeerIDs, height: Heights, block: Blocks]
  386. /\ UNCHANGED envRunning
  387. OnNoBlockResponseEv ==
  388. \* any no block response can come from the blockchain, pick one non-deterministically
  389. /\ inEvent' \in [type: {"bcNoBlockResponse"}, peerID: PeerIDs, height: Heights]
  390. /\ UNCHANGED envRunning
  391. OnRemovePeerEv ==
  392. \* although bcRemovePeer admits an arbitrary set, we produce just a singleton
  393. /\ inEvent' \in [type: {"bcRemovePeer"}, peerID: PeerIDs]
  394. /\ UNCHANGED envRunning
  395. OnPcBlockProcessed ==
  396. /\ inEvent' \in [type: {"pcBlockProcessed"}, peerID: PeerIDs, height: Heights]
  397. /\ UNCHANGED envRunning
  398. OnPcBlockVerificationFailure ==
  399. /\ inEvent' \in [type: {"pcBlockVerificationFailure"}, firstPeerID: PeerIDs, secondPeerID: PeerIDs, height: Heights]
  400. /\ UNCHANGED envRunning
  401. \* messages from scheduler
  402. OnScFinishedEv ==
  403. /\ outEvent.type = "scFinishedEv"
  404. /\ envRunning' = FALSE \* stop the env
  405. /\ UNCHANGED inEvent
  406. NextEnv ==
  407. IF ~envRunning THEN
  408. UNCHANGED <<inEvent, envRunning>>
  409. ELSE
  410. \/ OnScFinishedEv
  411. \/ OnGlobalTimeoutTicker
  412. \/ OnAddPeerEv
  413. \/ OnTrySchedule
  414. \/ OnStatusResponseEv
  415. \/ OnBlockResponseEv
  416. \/ OnNoBlockResponseEv
  417. \/ OnRemovePeerEv
  418. \/ OnPcBlockProcessed
  419. \/ OnPcBlockVerificationFailure
  420. (* ----------------------------------------------------------------------------------------------*)
  421. (* The system is the composition of the environment and the schedule *)
  422. (* ----------------------------------------------------------------------------------------------*)
  423. Init == turn = "environment" /\ InitEnv /\ InitSc
  424. FlipTurn ==
  425. turn' = (
  426. IF turn = "scheduler" THEN
  427. "environment"
  428. ELSE
  429. "scheduler"
  430. )
  431. \* scheduler and environment alternate their steps (synchronous composition introduces more states)
  432. Next ==
  433. /\ FlipTurn
  434. /\ IF turn = "scheduler" THEN
  435. /\ NextSc
  436. /\ inEvent' = noEvent
  437. /\ UNCHANGED envRunning
  438. ELSE
  439. /\ NextEnv
  440. /\ outEvent' = noEvent
  441. /\ UNCHANGED <<scRunning, scheduler>>
  442. Spec == Init /\ [][Next]_vars /\ WF_turn(FlipTurn)
  443. (* ----------------------------------------------------------------------------------------------*)
  444. (* Invariants *)
  445. (* ----------------------------------------------------------------------------------------------*)
  446. TypeOK ==
  447. /\ turn \in {"scheduler", "environment"}
  448. /\ inEvent \in InEvents
  449. /\ envRunning \in BOOLEAN
  450. /\ outEvent \in OutEvents
  451. /\ scheduler \in [
  452. initHeight: Heights,
  453. height: Heights \cup {ultimateHeight + 1},
  454. peers: SUBSET PeerIDs,
  455. peerHeights: [PeerIDs -> Heights \cup {None}],
  456. peerStates: [PeerIDs -> PeerStates],
  457. blocks: SUBSET Heights,
  458. blockStates: [Heights -> BlockStates],
  459. pendingBlocks: [Heights -> PeerIDs \cup {None}],
  460. receivedBlocks: [Heights -> PeerIDs \cup {None}]
  461. ]
  462. (* ----------------------------------------------------------------------------------------------*)
  463. (* Helpers for Properties *)
  464. (* ----------------------------------------------------------------------------------------------*)
  465. NoFailuresAndTimeouts ==
  466. /\ inEvent.type /= "bcRemovePeer"
  467. /\ inEvent.type /= "bcNoBlockResponse"
  468. /\ inEvent.type /= "pcBlockVerificationFailure"
  469. \* simulate good peer behavior using this formula. Useful to show termination in the presence of good peers.
  470. GoodResponse ==
  471. \/ inEvent.type
  472. \in {"bcAddNewPeer", "bcStatusResponse", "bcBlockResponse", "pcBlockProcessed", "rTrySchedule"}
  473. \/ ~envRunning
  474. \* all blocks from initHeight up to max peer height have been processed
  475. AllRequiredBlocksProcessed ==
  476. LET maxH == Max(scheduler.height, maxHeightScheduler(scheduler)) IN
  477. LET processedBlocks == {h \in scheduler.initHeight.. maxH-1: scheduler.blockStates[h] = "blockStateProcessed"} IN
  478. scheduler.height >= maxH /\ Cardinality(processedBlocks) = scheduler.height - scheduler.initHeight
  479. IncreaseHeight ==
  480. (scheduler'.height > scheduler.height) \/ (scheduler.height >= ultimateHeight)
  481. (* ----------------------------------------------------------------------------------------------*)
  482. (* Expected properties *)
  483. (* ----------------------------------------------------------------------------------------------*)
  484. (* *)
  485. (* 1. TerminationWhenNoAdvance - termination if there are no correct peers. *)
  486. (* The model assumes the "noAdvance" timer expires and the "tNoAdvanceExp" event is received *)
  487. (* *)
  488. TerminationWhenNoAdvance ==
  489. (inEvent.type = "tNoAdvanceExp")
  490. => <>(outEvent.type = "scFinishedEv" /\ outEvent.error = "timeout")
  491. (* ----------------------------------------------------------------------------------------------*)
  492. (* *)
  493. (* 2. TerminationGoodPeers - *)
  494. (* termination when IncreaseHeight holds true, fastsync is progressing, all blocks processed *)
  495. (* *)
  496. TerminationGoodPeers ==
  497. (/\ scheduler.height < ultimateHeight
  498. /\ <>[]GoodResponse
  499. /\[]<>(<<IncreaseHeight>>_<<scheduler, turn>>)
  500. )
  501. => <>(outEvent.type = "scFinishedEv" /\ AllRequiredBlocksProcessed)
  502. (* This property is violated. It shows that the precondition of TerminationGoodPeers is not *)
  503. (* always FALSE *)
  504. TerminationGoodPeersPre ==
  505. (/\ scheduler.height < ultimateHeight
  506. /\ <>[]GoodResponse
  507. /\[]<>(<<IncreaseHeight>>_<<scheduler, turn>>)
  508. )
  509. => FALSE
  510. (* ----------------------------------------------------------------------------------------------*)
  511. (* 3. TerminationAllCases - *)
  512. (* any peer behavior, either terminates with all blocks processed or times out *)
  513. TerminationAllCases ==
  514. (/\ scheduler.height < ultimateHeight
  515. /\(([]<> (<<IncreaseHeight>>_<<scheduler, turn>>)) \/ <>(inEvent.type = "tNoAdvanceExp"))
  516. )
  517. => <>(outEvent.type = "scFinishedEv" /\ (AllRequiredBlocksProcessed \/ outEvent.error = "timeout"))
  518. (* This property is violated. It shows that the precondition of TerminationAllCases is not *)
  519. (* always FALSE *)
  520. TerminationAllCasesPre ==
  521. (/\ scheduler.height < ultimateHeight
  522. /\(([]<> (<<IncreaseHeight>>_<<scheduler, turn>>)) \/ <>(inEvent.type = "tNoAdvanceExp"))
  523. )
  524. => FALSE
  525. (* This property is violated. TLC output shows an example of increasing heights in the scheduler *)
  526. SchedulerIncreasePre ==
  527. []<>(<<IncreaseHeight>>_<<scheduler, turn>>)
  528. => FALSE
  529. =============================================================================
  530. \* Modification History
  531. \* Last modified Thu Apr 16 13:21:33 CEST 2020 by ancaz
  532. \* Created Sat Feb 08 13:12:30 CET 2020 by ancaz