Copyright © 2021-2023 Enterprise Ethereum Alliance.
This document provides the specification of the Byzantine-fault-tolerant (BFT) blockchain consensus protocol QBFT. QBFT provides the following features:
QBFT is based on the BFT agreement protocol presented in [IBFT-Moniz].
This section describes the status of this document at the time of its publication. Newer documents may supersede this document.
This Specification is copyright © 2021-2023 Enterprise Ethereum Alliance Incorporated (EEA). It is made available under the terms of the Apache License version 2.0. [Apache2], and unless required by applicable law or agreed to in writing, this specification is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
This the QBFT Blockchain Consensus Algorithm Specification version 1. This version of the specification does not address at least the following issues:
Please send any comments to the EEA at https://entethalliance.org/contact/.
The specification of QBFT is written using the verification-aware language [Dafny] and comprises the following files: 1.1 node.dfy, 1.2 node_auxiliary_functions.dfy, 1.3 types.dfy and 1.4 lemmas.dfy.
Overall, the specification is constructed following the [TLA]-style approach of using boolean-valued functions (predicates) over old and new states to specify state transition systems, and it uses existential quantifiers to hide the variables that should not appear in the final specification. Specifically,
The type NodeState
in 1.3 types.dfy models the state of a QBFT node.
The predicate
NodeInit
(defined in 1.1 node.dfy) specifies the set of possible initial states for a QBFT node as follows. The set of all possible initial states for a QBT node with identifier id
deployed in a system with configuration c
corresponds the set of all and only those states s
such that NodeInit(s, c, id)
evaluates to true
.
The predicate
NodeNext
(defined in 1.1 node.dfy) specifies how the state of a node should evolve and which messages should be transmitted every time that its local clock ticks.
Specifically, if a node's state is current
and it receives the set of messages inQbftMessages
at the next tick of its local clock, then the node must transition a state next
and send messages as specified by a set outQbftMessages
such that NodeNext(current, inQbftMessages, next, outQbftMessages)
evaluates to true
.
outQbftMessages
specifies the set of messages to be sent as follows.
Each element mwr
of outQbftMessages
indicates that message mwr.message
is to be sent to node with identifier mwr.recipient
.
The predicate
IsValidQbftBehaviour
(configuration, id, qbftNodeBehaviour)
(defined in 1.1 node.dfy) provides the actual specification of the QBFT protocol.
Specifically, IsValidQbftBehaviour(configuration, id, qbftNodeBehaviour)
returns true
if and only if qbftNodeBehaviour
is an admissible QBFT behaviour for a QBFT node with id id
deployed in a system with configuration configuration
.
A QBFT behaviour describes the set of QBFT messages sent and the evolution of the blockchain exposed by a node in response to a sequence of input messages.
A node complies with the QBFT specification, that is, correctly implements the QBFT specification, only if all of its possible QBFT behaviours are admissible QBFT behaviours.
QBFT behaviours are represented by the type QbftNodeBehaviour
in 1.3 types.dfy which is constituted by the following components:
initialBlockchain
exposed by a QBFT node at the very beginning of the protocol execution and
steps
where each step is constituted bymessagesReceived
, andmessagesToSend
and the blockchain newBlockchain
to be exposed by the node after receiving the set of messages messagesReceived
.This file is the main corpus of the QBFT specification and it includes the following items:
The definition of the predicate
which provides the overall specification of the QBFT protocol;IsValidQbftBehaviour
The definition of the predicate
which specifies the set of possible initial states for a QBFT node.NodeInit
The definition of the predicate
which specifies how the state of a node should evolve and which messages should be transmitted every time that its local clock ticks.
NodeNext
NodeNext
delegates the actual specification of the possible state transitions and message transmissions on the triggering of event E
, where E
can be either the reception of a message or a timer expiry, to the predicate
Upon
E
.
The definitions of the various predicates specifying the possible state transitions and message transmissions for the various events.
1 /** 2 * Copyright © 2021 EEA Inc. 3 * 4 * This document is available under the terms of the Apache 2.0 License 5 * (http://www.apache.org/licenses/LICENSE-2.0.html) 6 */ 7 include "types.dfy" 8 include "node_auxiliary_functions.dfy" 9 include "lemmas.dfy" 10 11 module L1_Spec 12 { 13 import opened L1_SpecTypes 14 import opened L1_AuxiliaryFunctionsAndLemmas 15 import opened L1_Lemmas 16 17 18 /** ======================================================================= 19 * QBFT SPECIFICATION 20 ========================================================================*/ 21 /** 22 * 23 * This function provides the overall specification of the QBFT protocol. 24 * 25 * @param configuration The system configuration 26 * @param id The QBFT node id. 27 * @param qbftNodeBehaviour QBFT Behaviour 28 * 29 * @returns `true` if and only if `qbftNodeBehaviour` is a valid QBFT behaviour 30 * for a QBFT node with id `id` deployed in a system with configuration 31 * `configuration. 32 * 33 */ 34 predicate IsValidQbftBehaviour( 35 configuration: Configuration, 36 id: Address, 37 qbftNodeBehaviour: QbftNodeBehaviour 38 ) 39 { 40 exists nt: iseq<NodeState> :: 41 && NodeInit(nt[0], configuration, id) 42 && qbftNodeBehaviour.initialBlockchain == nt[0].blockchain 43 && (forall i:nat :: 44 IsValidQbftBehaviourStep(qbftNodeBehaviour, nt ,i) 45 ) 46 } 47 48 /** 49 * This function defines the conditions of validity for a single QBFT 50 * behaviour step 51 * 52 * @param qbftNodeBehaviour QBFT Behaviour including the step for which to 53 * verify validity. 54 * @param nt Infinite sequence of QBFT states associated with the behaviour 55 * `qbftNodeBehaviour`. 56 * @param i Index of the step for which to verify validity 57 * 58 * @returns `true` if and only if the `i`-th step in `qbftNodeBehaviour` 59 * corresponds to a valid transition between state `nt[i]` and 60 * state `nt[i+1]`. 61 */ 62 predicate IsValidQbftBehaviourStep( 63 qbftNodeBehaviour: QbftNodeBehaviour, 64 nt: iseq<NodeState>, 65 i: nat 66 ) 67 { 68 var step := qbftNodeBehaviour.steps[i]; 69 && validNodeState(nt[i]) 70 && NodeNext(nt[i], step.messagesReceived, nt[i+1], step.messagesToSend) 71 && step.newBlockchain == nt[i+1].blockchain 72 } 73 74 /** ======================================================================= 75 * QBFT NODE STATE TRANSITION AND MESSAGE TRANSMISSION SPECIFICATION 76 ========================================================================*/ 77 /** 78 * Initial State Specification. 79 * 80 * This predicate specifies the set of allowed initial states for a QBFT 81 * node. 82 * 83 * @param state A QBFT node state. 84 * @param c A QBFT network configuration. 85 * @param id A QBFT node identifier. 86 * 87 * @returns `true` if and only if `state` is a valid initial QBFT state for 88 * a node with id `id` deployed in a QBFT network with 89 * configuration `c`. 90 */ 91 predicate NodeInit(state:NodeState, c:Configuration, id:Address) 92 { 93 && state.blockchain == [c.genesisBlock] 94 && |validators([c.genesisBlock])| > 0 95 && state.round == 0 96 && state.localTime == 0 97 && state.id == id 98 && state.configuration == c 99 && state.messagesReceived == {} 100 && state.proposalAcceptedForCurrentRound == None 101 && state.lastPreparedBlock == None 102 && state.lastPreparedRound == None 103 && state.timeLastRoundStart == 0 104 } 105 106 /** 107 * Next State and Message Transmission Specification. 108 * 109 * This predicate specifies how the state of a QBFT node should evolve and 110 * which messages should be transmitted every time that its system clock 111 * ticks. 112 * 113 * @param current Current, or old, QBFT node state. 114 * @param inQbftMessages The of messages received when the node clock ticks. 115 * @param next Next state QBFT node state. 116 * @param outQbftMessages Set of messages with recipient to be sent by the 117 * QBFT node. 118 * 119 * @returns `true` if and only if a QBFT node with state `current` that 120 * receives the set of messages `inQbftMessages` on the next tick 121 * of its local clock can transition to state `next` and send the 122 * set of messages with recipient `outQbftMessages`. 123 */ 124 predicate NodeNext( 125 current:NodeState, 126 inQbftMessages: set<QbftMessage>, 127 next:NodeState, 128 outQbftMessages: set<QbftMessageWithRecipient> 129 ) 130 requires validNodeState(current) 131 { 132 var newMessagesReceived := current.messagesReceived + inQbftMessages; 133 134 var currentWithNewMessagesReceived := 135 current.( 136 messagesReceived := newMessagesReceived, 137 localTime := current.localTime + 1 138 ); 139 140 // `next` and `outQbftMessages` must be the result result of the 141 // composition of as many `NodeNextSubStep` predicates as possible until 142 // only stuttering actions are possible, i.e. until `NodeNextSubStep` is 143 // `true` only if the state provided for the `next` parameter matches 144 // the state provided for the `current` parameter and the 145 // `outQbftMessages` parameter corresponds to the empty set. 146 exists s:seq<NodeState>, o:seq<set<QbftMessageWithRecipient>> :: 147 && |s| >= 2 148 && |o| == |s| - 1 149 && s[0] == currentWithNewMessagesReceived 150 && s[|s|-1] == next 151 && (forall i | 0 <= i < |s| - 1 :: 152 && validNodeState(s[i]) 153 && NodeNextSubStep(s[i], s[i+1], o[i]) 154 ) 155 && (forall afterNext, messages | afterNext != s[|s|-1] :: 156 !( 157 && validNodeState(s[|s|-1]) 158 && NodeNextSubStep(s[|s|-1], afterNext, messages) 159 ) 160 ) 161 && outQbftMessages == setUnionOnSeq(o) 162 } 163 164 /** 165 * Single Step State and Message Transmission Specification 166 * 167 * This predicate specifies how the state of a QBFT node should evolve and 168 * which messages should be transmitted for any single event, like the 169 * reception of a message of a specific type or the expiry of a specific 170 * timer, that may occur when its local clock ticks. The predicate assumes 171 * that `current.messagesReceived` corresponds to the set of all and only 172 * messages received so far by the node. 173 * 174 * 175 * @param current Current, or old, QBFT node state. 176 * @param next Next state QBFT node state. 177 * @param outQbftMessages Set of messages with recipient to be sent by the 178 * QBFT node. 179 * 180 * @returns `true` if and only if a QBFT node with state `current` that has 181 * received the set of messages `current.messagesReceived` can 182 * transition to state `next` and send the set of messages with 183 * recipient `outQbftMessages` when its local clock ticks. 184 */ 185 predicate NodeNextSubStep( 186 current:NodeState, 187 next: NodeState, 188 189 outQbftMessages: set<QbftMessageWithRecipient> 190 ) 191 requires validNodeState(current) 192 { 193 || ( 194 && current.id in validators(current.blockchain) 195 && ( 196 || UponBlockTimeout(current, next, outQbftMessages) 197 || UponProposal(current, next, outQbftMessages) 198 || UponPrepare(current, next, outQbftMessages) 199 || UponCommit(current, next, outQbftMessages) 200 || UponRoundChange(current, next, outQbftMessages) 201 || UponRoundTimeout(current, next, outQbftMessages) 202 ) 203 ) 204 || UponNewBlock(current, next, outQbftMessages) 205 || ( 206 && next == current 207 && outQbftMessages == {} 208 ) 209 } 210 211 /** ======================================================================= 212 * QBFT SINGLE EVENT STATE TRANSITION AND MESSAGE TRANSMISSION SPECIFICATION 213 * 214 * Each of the Upon<EventName> predicates in the remainder of this file 215 * specifies how the state of a QBFT node should evolve and which messages 216 * should be transmitted when the event <EventName> occurs the next local 217 * clock tick. 218 * 219 * An <EventName> that end in `Timeout` indicates the expiry of a time, all 220 * other event <EventName>s indicate the reception of a specific QBFT 221 * message, i.e. `UponProposal` is the predicate associate with the 222 * reception of a Proposal message. 223 * 224 * All Upon<EventName> predicates have the same parameters and the same 225 * return value. Namely, 226 * @param current Current, or old, QBFT node state. 227 * @param next Next state QBFT node state. 228 * @param outQbftMessages Set of messages with recipient to be sent by the 229 * QBFT node. 230 * 231 * @returns `true` if and only if a QBFT node with state `current` that has 232 * received the set of messages `current.messagesReceived` can 233 * transition to state `next` and send the set of messages with 234 * recipient `outQbftMessages` when event <EventName> occurs on its 235 * next local clock tick. 236 ========================================================================*/ 237 238 predicate UponBlockTimeout( 239 current:NodeState, 240 next: NodeState, 241 242 outQbftMessages: set<QbftMessageWithRecipient> 243 ) 244 requires validNodeState(current) 245 { 246 if && current.round == 0 247 && proposer(0,current.blockchain) == current.id 248 && current.localTime >= 249 current.blockchain[|current.blockchain|-1].header.timestamp + 250 current.configuration.blockTime 251 then 252 var block := getNewBlock(current, 0); 253 254 var proposal := 255 Proposal( 256 signProposal( 257 UnsignedProposal( 258 |current.blockchain|, 259 0, 260 digest(block)), 261 current.id), 262 block, 263 {}, 264 {}); 265 266 && outQbftMessages == 267 Multicast( 268 validators(current.blockchain), 269 proposal 270 ) 271 272 && next == current.( 273 messagesReceived := current.messagesReceived + {proposal} 274 ) 275 else 276 false 277 278 } 279 280 predicate UponProposal( 281 current:NodeState, 282 next: NodeState, 283 284 outQbftMessages: set<QbftMessageWithRecipient>) 285 requires validNodeState(current) 286 { 287 if exists m | m in current.messagesReceived :: isValidProposal(m, current) 288 then 289 var m :| && m in current.messagesReceived 290 && isValidProposal(m, current); 291 292 var newRound := m.proposalPayload.unsignedPayload.round; 293 294 var prepare := 295 Prepare( 296 signPrepare( 297 UnsignedPrepare( 298 |current.blockchain|, 299 newRound, 300 digest(m.proposedBlock)), 301 current.id 302 ) 303 ); 304 305 && outQbftMessages == 306 Multicast( 307 validators(current.blockchain), 308 prepare 309 ) 310 311 && next == current.( 312 round := newRound, 313 proposalAcceptedForCurrentRound := Optional(m), 314 timeLastRoundStart := 315 if m.proposalPayload.unsignedPayload.round > current.round then 316 current.localTime 317 else 318 current.timeLastRoundStart , 319 messagesReceived := current.messagesReceived + {prepare} 320 ) 321 322 else 323 false 324 } 325 326 predicate UponPrepare( 327 current:NodeState, 328 next: NodeState, 329 330 outQbftMessages: set<QbftMessageWithRecipient> 331 ) 332 requires validNodeState(current) 333 { 334 if && exists QofP:: 335 && optionIsPresent(current.proposalAcceptedForCurrentRound) 336 337 && isNonStrictSubSetOf( 338 QofP, 339 validPreparesForHeightRoundAndDigest( 340 current.messagesReceived, 341 |current.blockchain|, 342 current.round, 343 digest(optionGet(current.proposalAcceptedForCurrentRound).proposedBlock), 344 validators(current.blockchain) 345 ) 346 ) 347 && |QofP| >= quorum(|validators(current.blockchain)|) 348 349 && !exists m :: && m in current.messagesReceived 350 && m.Commit? 351 && var uPayload := m.commitPayload.unsignedPayload; 352 && uPayload.height == |current.blockchain| 353 && uPayload.round == current.round 354 && recoverSignedCommitAuthor(m.commitPayload) == current.id 355 then 356 var proposedBlock := optionGet(current.proposalAcceptedForCurrentRound).proposedBlock; 357 358 var commit := 359 Commit( 360 signCommit( 361 UnsignedCommit( 362 |current.blockchain|, 363 current.round, 364 signHash(hashBlockForCommitSeal(proposedBlock), current.id), 365 digest(proposedBlock)), 366 current.id 367 ) 368 ); 369 370 && outQbftMessages == 371 Multicast( 372 validators(current.blockchain), 373 commit 374 ) 375 376 && next == current.( 377 lastPreparedBlock := Optional(proposedBlock), 378 lastPreparedRound := Optional(current.round), 379 messagesReceived := current.messagesReceived + {commit} 380 ) 381 382 else 383 false 384 } 385 386 function getCommitSealsFromCommitMessages( 387 msgs: set<QbftMessage> 388 ): set<Signature> 389 requires forall m | m in msgs :: m.Commit? 390 { 391 set m:QbftMessage | m in msgs :: m.commitPayload.unsignedPayload.commitSeal 392 } 393 394 predicate UponCommit( 395 current:NodeState, 396 next: NodeState, 397 398 outQbftMessages: set<QbftMessageWithRecipient> 399 ) 400 requires validNodeState(current) 401 { 402 if current.proposalAcceptedForCurrentRound.Optional? then 403 var allValidCommitsForCurrentHeightAndRound := 404 validCommitsForHeightRoundAndDigest( 405 current.messagesReceived, 406 |current.blockchain|, 407 current.round, 408 current.proposalAcceptedForCurrentRound.value.proposedBlock, 409 validators(current.blockchain)); 410 411 if exists QofC:: 412 && isNonStrictSubSetOf(QofC, allValidCommitsForCurrentHeightAndRound) 413 && |QofC| >= quorum(|validators(current.blockchain)|) 414 then 415 // The following statement is required only to let the varifier 416 // prove the existence of the var QofC below 417 lemmaIfSubSetOfGreaterSizeExistsThenSmallSubsetExists(allValidCommitsForCurrentHeightAndRound, quorum(|validators(current.blockchain)|)); 418 419 var QofC:| 420 && isNonStrictSubSetOf(QofC, allValidCommitsForCurrentHeightAndRound) 421 && |QofC| == quorum(|validators(current.blockchain)|); 422 423 var proposedBlock:Block := current.proposalAcceptedForCurrentRound.value.proposedBlock; 424 425 var blockWithAddedCommitSeals := 426 proposedBlock.( 427 header := proposedBlock.header.( 428 commitSeals := getCommitSealsFromCommitMessages(QofC) 429 ) 430 ); 431 432 var newBlock := NewBlock( blockWithAddedCommitSeals); 433 434 && outQbftMessages == 435 Multicast( 436 current.configuration.nodes, 437 newBlock 438 ) 439 440 && next == current.( 441 blockchain := current.blockchain + [proposedBlock], 442 round := 0, 443 proposalAcceptedForCurrentRound := Optional.None, 444 lastPreparedBlock := Optional.None, 445 lastPreparedRound := Optional.None, 446 timeLastRoundStart := current.localTime, 447 messagesReceived := current.messagesReceived + {newBlock} 448 ) 449 450 else 451 false 452 else 453 false 454 } 455 456 predicate UponNewBlock( 457 current:NodeState, 458 next: NodeState, 459 460 outQbftMessages: set<QbftMessageWithRecipient> 461 ) 462 requires validNodeState(current) 463 { 464 if exists m | m in current.messagesReceived :: validNewBlockMessage(current.blockchain,m) 465 then 466 var m :| m in current.messagesReceived && validNewBlockMessage(current.blockchain,m); 467 468 && next == current.( 469 blockchain := current.blockchain + [m.block], 470 round := 0, 471 proposalAcceptedForCurrentRound := Optional.None, 472 lastPreparedBlock := Optional.None, 473 lastPreparedRound := Optional.None, 474 timeLastRoundStart := current.localTime 475 ) 476 477 && outQbftMessages == {} 478 else 479 false 480 } 481 482 predicate UponRoundTimeout( 483 current:NodeState, 484 next: NodeState, 485 486 outQbftMessages: set<QbftMessageWithRecipient> 487 ) 488 requires validNodeState(current) 489 { 490 if current.localTime >= current.timeLastRoundStart + roundTimeout(current.round) 491 then 492 var newRound := current.round + 1; 493 var roundChange := createRoundChange(current, newRound); 494 495 && outQbftMessages == 496 Multicast( 497 validators(current.blockchain), 498 roundChange 499 ) 500 501 && next == current.( 502 round := newRound, 503 proposalAcceptedForCurrentRound := Optional.None, 504 messagesReceived := current.messagesReceived + {roundChange} 505 ) 506 else 507 false 508 } 509 510 predicate UponRoundChange( 511 current:NodeState, 512 next: NodeState, 513 514 outQbftMessages: set<QbftMessageWithRecipient>) 515 requires validNodeState(current) 516 { 517 if hasReceivedProposalJustificationForLeadingRound(current) 518 then 519 var roundChanges, 520 prepares, 521 newRound:nat, 522 block 523 :| 524 isProposalJustificationForLeadingRound(roundChanges, prepares, newRound, block, current); 525 526 var proposal := 527 Proposal( 528 signProposal( 529 UnsignedProposal( 530 |current.blockchain|, 531 newRound, 532 digest(block)), 533 current.id), 534 block, 535 extractSignedRoundChanges(roundChanges), 536 extractSignedPrepares(prepares)); 537 538 && outQbftMessages == 539 Multicast( 540 validators(current.blockchain), 541 proposal 542 ) 543 544 && next == current.( 545 round := newRound, 546 proposalAcceptedForCurrentRound := Optional.None, 547 timeLastRoundStart := 548 if newRound > current.round then 549 current.localTime 550 else 551 current.timeLastRoundStart, 552 messagesReceived := current.messagesReceived + {proposal} 553 ) 554 555 else if 556 exists Frc:set<SignedRoundChange> :: 557 && isNonStrictSubSetOf(Frc, receivedSignedRoundChangesForCurrentHeightAndFutureRounds(current)) 558 && |getSetOfRoundChangeSenders(Frc)| >= f(|validators(current.blockchain)|) + 1 559 then 560 // The following lemma is not really part of the specification. 561 // It is only required to prove that the set `Frc` below exists. 562 lemmaToProveExistenceOfSetFrc( 563 receivedSignedRoundChangesForCurrentHeightAndFutureRounds(current), 564 f(|validators(current.blockchain)|) + 1 ); 565 566 var Frc:set<SignedRoundChange> :| 567 && isNonStrictSubSetOf(Frc, receivedSignedRoundChangesForCurrentHeightAndFutureRounds(current)) 568 && |getSetOfRoundChangeSenders(Frc)| == f(|validators(current.blockchain)|) + 1; 569 570 var newRound := minSet(set rcm | rcm in Frc :: rcm.unsignedPayload.round); 571 572 573 var roundChange := createRoundChange(current, newRound); 574 575 && outQbftMessages == 576 Multicast( 577 validators(current.blockchain), 578 roundChange 579 ) 580 581 && next == current.( 582 round := newRound, 583 proposalAcceptedForCurrentRound := Optional.None, 584 messagesReceived := current.messagesReceived + {roundChange} 585 ) 586 else 587 false 588 589 } 590 } 591
This files defines the functions used in 1.1 node.dfy.
1 /** 2 * Copyright © 2021 EEA Inc. 3 * 4 * This document is available under the terms of the Apache 2.0 License 5 * (http://www.apache.org/licenses/LICENSE-2.0.html) 6 */ 7 include "types.dfy" 8 include "lemmas.dfy" 9 10 module L1_AuxiliaryFunctionsAndLemmas 11 { 12 import opened L1_SpecTypes 13 14 /** ======================================================================= 15 * CRYPTOGRAPHIC PRIMITIVES 16 * 17 * This section contains the declarations of the various cryptographic 18 * primitives used throughout the specification along with `lemma`s listing 19 * the minimum set of properties that each of them must satisfy. 20 * 21 * - The functions `sign<MessageType>(msg, author)` evaluate to a 22 * <MessageType> message signed by `author` with content `msg`. 23 * 24 * - The function `recoverSigned<MessagetType>Author(msg)` evaluate to the 25 * signer of the <MessageType> message `msg`. 26 * 27 * - The lemmas `lemmaSigned<MessageType>` list the minimum set of 28 * properties that any implementations of the `sign<MessageType>` and 29 * `recoverSigned<MessagetType>Author` functions used in the 30 * implementation of the QBFT specification must satisfy. 31 * 32 * - NOTE: In addition to the various properties of the formally stated in 33 * this specification, the `sign<MessageType>` and 34 * `recoverSigned<MessagetType>Author` functions must also ensure that the 35 * signatures cannot be forged by Byzantine nodes. This property can be 36 * formally expressed by providing a formal model of the systems 37 * (networks) that the QBFT protocol is intended to be deployed on. In the 38 * future, the QBFT specification may be extended to include such model. 39 * 40 ========================================================================*/ 41 42 /** 43 * @returns The digest of the block `block`. 44 */ 45 function digest(block:Block):Hash 46 47 /** 48 * This lemma lists the minimum set of properties that any implementation of 49 * the `digest` function used in the implementation of the QBFT 50 * specification must satisfy. 51 */ 52 lemma {:axiom} lemmaDigest() 53 ensures forall b1, b2 :: digest(b1) == digest(b2) ==> b1 == b2 54 55 56 function signProposal(msg:UnsignedProposal, author: Address): SignedProposal 57 function recoverSignedProposalAuthor(msg:SignedProposal): Address 58 lemma {:axiom} lemmaSignedProposal() 59 ensures forall m,a :: recoverSignedProposalAuthor(signProposal(m,a)) == a 60 ensures forall m1,m2,a1,a2 :: (m1 != m2 || a1 != a2) ==> signProposal(m1,a1) != signProposal(m2,a2) 61 ensures forall m: SignedProposal :: signProposal(m.unsignedPayload,recoverSignedProposalAuthor(m)) == m 62 ensures forall m,a :: signProposal(m,a).unsignedPayload == m 63 64 function signPrepare(msg:UnsignedPrepare, author: Address): SignedPrepare 65 function recoverSignedPrepareAuthor(msg:SignedPrepare): Address 66 lemma {:axiom} lemmaSignedPrepare() 67 ensures forall m,a :: recoverSignedPrepareAuthor(signPrepare(m,a)) == a 68 ensures forall m1,m2,a1,a2 :: (m1 != m2 || a1 != a2) ==> signPrepare(m1,a1) != signPrepare(m2,a2) 69 ensures forall m: SignedPrepare :: signPrepare(m.unsignedPayload,recoverSignedPrepareAuthor(m)) == m 70 ensures forall m,a :: signPrepare(m,a).unsignedPayload == m 71 72 function signCommit(msg:UnsignedCommit, author: Address): SignedCommit 73 function recoverSignedCommitAuthor(msg:SignedCommit): Address 74 lemma {:axiom} lemmaSignedCommit() 75 ensures forall m,a :: recoverSignedCommitAuthor(signCommit(m,a)) == a 76 ensures forall m1,m2,a1,a2 :: (m1 != m2 || a1 != a2) ==> signCommit(m1,a1) != signCommit(m2,a2) 77 ensures forall m: SignedCommit :: signCommit(m.unsignedPayload,recoverSignedCommitAuthor(m)) == m 78 ensures forall m,a :: signCommit(m,a).unsignedPayload == m 79 80 function signRoundChange(msg:UnsignedRoundChange, author: Address): SignedRoundChange 81 function recoverSignedRoundChangeAuthor(msg:SignedRoundChange): Address 82 lemma {:axiom} lemmaSignedRoundChange() 83 ensures forall m,a :: recoverSignedRoundChangeAuthor(signRoundChange(m,a)) == a 84 ensures forall m1,m2,a1,a2 :: (m1 != m2 || a1 != a2) ==> signRoundChange(m1,a1) != signRoundChange(m2,a2) 85 ensures forall m: SignedRoundChange :: signRoundChange(m.unsignedPayload,recoverSignedRoundChangeAuthor(m)) == m 86 ensures forall m,a :: signRoundChange(m,a).unsignedPayload == m 87 88 function signHash(hash:Hash, author: Address): Signature 89 function recoverSignedHashAuthor(hash:Hash, signature: Signature): Address 90 lemma {:axiom} lemmaSignedHash() 91 ensures forall h,a :: recoverSignedHashAuthor(h, signHash(h,a)) == a 92 ensures forall h1,h2,a1,a2 :: (h1 != h2 || a1 != a2) ==> signHash(h1,a1) != signHash(h2,a2) 93 ensures forall h,s :: signHash(h,recoverSignedHashAuthor(h,s)) == s 94 95 /** ======================================================================= 96 * UNDEFINED FUNCTIONS 97 * 98 * This section includes those functions for which no actual definition is 99 * provided as that is beyond the scope of this specification. 100 ========================================================================*/ 101 102 /** 103 * @return `true` if and only if `block` is a valid `RawBlock` for the 104 * `RawBlockchain` `blockchain`. 105 * 106 * @note This predicate is expected to check that `block` is valid from an 107 * blockchain application level perspective. This contrasts with the 108 * predicate `ValidNewBlock` defined below which checks that a block 109 * is valid from a consensus protocol perspective. 110 */ 111 predicate validateRawEthereumBlock(blockchain:RawBlockchain, block:RawBlock) 112 113 /** 114 * @return The set of nodes, or validators, responsible for deciding on the 115 * block to be appended to the `RawBlockchain` `rb`. 116 */ 117 function validatorsOnRawBlockchain(rb: RawBlockchain): seq<Address> 118 119 /** 120 * @returns The minimum value in set `S`. 121 */ 122 function minSet(S:set<nat>): nat 123 ensures minSet(S) in S 124 ensures forall e | e in S :: e >= minSet(S) 125 126 function getNewBlock(nodeState:NodeState, round:nat): Block 127 ensures && getNewBlock(nodeState,round).header.roundNumber == round 128 && |validators(nodeState.blockchain + [getNewBlock(nodeState,round)])| > 0 129 && getNewBlock(nodeState,round).header.height == |nodeState.blockchain| 130 131 /** ======================================================================= 132 * GENERAL FUNCTIONS 133 * 134 * This section defines those functions that, while used as part of the QBFT 135 * specification, are general in nature 136 ========================================================================*/ 137 138 /** 139 * @return `true` if and only if the `Optional` value `o` is not empty. 140 */ 141 predicate optionIsPresent<T>(o:Optional<T>) 142 { 143 o.Optional? 144 } 145 146 /** 147 * @returns The value carried by the `Optional` value `o'. 148 * 149 * @requires The `Optional` value `o` is not empty. 150 */ 151 function optionGet<T>(o:Optional<T>): T 152 requires optionIsPresent(o) 153 { 154 o.value 155 } 156 157 /** 158 * @returns The union of all sets included in `sets`. 159 */ 160 function setUnion<T(!new)>(sets:set<set<T>>):set<T> 161 ensures forall e :: ((exists s | s in sets :: e in s) <==> e in setUnion(sets)) 162 { 163 if sets == {} then 164 {} 165 else 166 var s :| s in sets; 167 s + setUnion(sets - {s}) 168 } 169 170 /** 171 * @returns The union of all sets included in `sets`. 172 */ 173 function setUnionOnSeq<T>(sets:seq<set<T>>):set<T> 174 { 175 if sets == [] then 176 {} 177 else 178 sets[0] + setUnionOnSeq(sets[1..]) 179 } 180 181 /** 182 * @returns 2 ^ `exp`. 183 */ 184 function powerOf2(exp:nat):nat 185 { 186 if exp == 0 then 187 1 188 else 189 2 * powerOf2(exp-1) 190 } 191 192 /** 193 * Function wrapping the Dafny set inclusion operator `<=` to make the spec more 194 * readable by people who are not much familiar with Dafny who may not be aware of 195 * the semantic of the `<=` operator when applied to sets. 196 * 197 * @returns true iff `subSet` is a non-strict subset of `setIncludingTheSubset` 198 */ 199 predicate isNonStrictSubSetOf<T>(subSet: set<T>, setIncludingTheSubset: set<T>) 200 { 201 subSet <= setIncludingTheSubset 202 } 203 204 /** ======================================================================= 205 * QBFT GENERAL FUNCTIONS 206 * 207 * This section defines those functions that are specific to the QBFT 208 * protocol and that are used throughout the specification 209 ========================================================================*/ 210 211 /** 212 * @returns A `RawBlock` equivalent to `Block` `b`. 213 */ 214 function fromBlockToRawBlock( 215 b: Block 216 ) : RawBlock 217 { 218 RawBlock( 219 RawBlockHeader( 220 b.header.proposer, 221 b.header.height, 222 b.header.timestamp 223 ), 224 b.body, 225 b.transactions 226 ) 227 } 228 229 /** 230 * @returns A `RawBlockchain` equivalent to `Blockchain` `bc`. 231 */ 232 function fromBlockchainToRawBlockchain(bc:Blockchain): RawBlockchain 233 { 234 seq(|bc|, (i: nat) requires i < |bc| => fromBlockToRawBlock(bc[i])) 235 } 236 237 /** 238 * @return The set of nodes, or validators, responsible for deciding on the 239 * block to be appended to the `Blockchain` `blockchain`. 240 * 241 * @note The definition of this function models the requirement that any two 242 * `Blockchain`s that have the same length where each block in one 243 * blockchain is identical to the block in the other blockchain at the 244 * same position except for the field `commitSeals` evaluate to the 245 * same set of validators. 246 */ 247 function validators(blockchain:Blockchain): seq<Address> 248 { 249 validatorsOnRawBlockchain(fromBlockchainToRawBlockchain(blockchain)) 250 } 251 252 /** 253 * @return `true` if and only if `block` is a valid `Block` for the 254 * `Blockchain` `blockchain`. 255 * 256 * @note The definition of this function models the requirement that the 257 * field `commitSeals` of `Block`s does not account when evaluating 258 * block validity from a blockchain application level perspective. 259 */ 260 predicate validateEthereumBlock(blockchain:Blockchain, block:Block) 261 { 262 validateRawEthereumBlock(fromBlockchainToRawBlockchain(blockchain), fromBlockToRawBlock(block)) 263 } 264 265 /** 266 * @returns The maximum number of Byzantine validators allowed in a network 267 * of `setSize` validators 268 */ 269 function f(setSize:nat):nat 270 { 271 if setSize == 0 then 272 0 273 else 274 (setSize - 1) / 3 275 } 276 277 /** 278 * @returns The minimum size that any two subsets of validators for a 279 * network with `setSize` validators must have to guarantee that 280 * their intersection includes at least one honest validator under 281 * the assumption that no more than `f(setSeize)` validators are 282 * Byzantine. 283 */ 284 function quorum(setSize:nat):nat 285 { 286 (setSize*2 - 1) / 3 + 1 287 } 288 289 /** 290 * @returns The round timeout for round number `round`. 291 */ 292 function roundTimeout(round:nat):nat 293 { 294 powerOf2(round) 295 } 296 297 298 /** 299 * @returns `block` with `roundNumber` set to `newRound` 300 */ 301 function replaceRoundInBlock(block: Block, newRound: nat): Block 302 { 303 block.( 304 header := block.header.( 305 roundNumber := newRound 306 ) 307 ) 308 } 309 310 /** 311 * @returns The hash of a block identical to `block` except for the field 312 * `commitSeals` which is set to the empty set. 313 */ 314 function hashBlockForCommitSeal(block:Block): Hash 315 { 316 digest( 317 block.( 318 header:= block.header.( 319 commitSeals := {}) 320 ) 321 ) 322 } 323 324 /** 325 * @returns The identifier of the validator that should propose the block to 326 * be appended to `blockchain` via a Proposal message for round 327 * `ibftRound`. 328 */ 329 function proposer(ibftRound:nat, blockchain:Blockchain): Address 330 requires |blockchain| > 0 331 requires |blockchain| > 1 ==> blockchain[|blockchain|-1].header.proposer in validators(blockchain[..|blockchain|-1]) 332 requires |validators(blockchain)| > 0 333 { 334 var roundZeroIndex: nat := 335 if |blockchain| == 1 then 336 0 337 else 338 var previousBlockIndex':nat :| 339 && previousBlockIndex' < |validators(blockchain[..|blockchain|-1])| 340 && validators(blockchain[..|blockchain|-1])[previousBlockIndex'] == blockchain[|blockchain|-1].header.proposer; 341 342 previousBlockIndex' + 1 343 ; 344 345 validators(blockchain)[ 346 (roundZeroIndex + ibftRound) % |validators(blockchain)| 347 ] 348 } 349 350 /** 351 * @returns A set of `QbftMessageWithRecipient` with the field `message` set 352 * to the parameter `m` and the field `recipient` set to the 353 * parameter `recipient. 354 * 355 * @note This function is used in the specification to moel message 356 * multicast. 357 */ 358 function Multicast(recipients:seq<Address>, m:QbftMessage): set<QbftMessageWithRecipient> 359 { 360 set r | r in recipients :: QbftMessageWithRecipient(m,r) 361 } 362 363 /** 364 * @returns An `Optional` datatype representing the digest of the `Optional` 365 * `block` parameter. 366 */ 367 function digestOptionalBlock(block: Optional<Block>): Optional<Hash> 368 { 369 if optionIsPresent(block) then 370 Optional(digest(optionGet(block))) 371 else 372 Optional.None 373 } 374 375 /** ======================================================================= 376 * PROPOSAL AND ROUND-CHANGE FUNCTIONS 377 * 378 * This section defines those functions that are used in the definition of 379 * the `UponProposal` and `UponRoundChange` predicates. 380 ========================================================================*/ 381 /** 382 * @returns The set of all Prepare messages included in `current.messagesReceived`. 383 */ 384 function receivedPrepares(current:NodeState): set<QbftMessage> 385 { 386 set m | 387 && m in current.messagesReceived 388 && m.Prepare? 389 } 390 391 function extractSignedPreparesEx(messages:set<QbftMessage>): set<SignedPrepare> 392 { 393 (set m | && m in messages 394 && m.Prepare? 395 :: 396 m.preparePayload) 397 + 398 setUnion( 399 set m | && m in messages 400 && ( 401 || m.Proposal? 402 || m.RoundChange? 403 ) 404 :: 405 m.roundChangeJustification 406 ) 407 } 408 409 /** 410 * @returns The set of signed Prepare payloads included in the set of 411 * Prepare messages `messages`. 412 */ 413 // TBD: Remove precondition? 414 function extractSignedPrepares(messages:set<QbftMessage>): set<SignedPrepare> 415 requires forall m | m in messages :: m.Prepare? 416 { 417 set m | m in messages 418 :: 419 m.preparePayload 420 } 421 422 /** 423 * @returns The set of all RoundChange messages included in `current.messagesReceived`. 424 */ 425 function receivedRoundChanges(current:NodeState): set<QbftMessage> 426 { 427 set m | 428 && m in current.messagesReceived 429 && m.RoundChange? 430 431 } 432 433 /** 434 * @returns The set of signed RoundChange payloads included in messages in 435 * `current.messagesReceived` that have round higher than 436 * `current.round`. 437 */ 438 function receivedSignedRoundChangesForCurrentHeightAndFutureRounds(current:NodeState): set<SignedRoundChange> 439 { 440 set m | 441 && m in current.messagesReceived 442 && m.RoundChange? 443 && var uPayload := m.roundChangePayload.unsignedPayload; 444 && uPayload.height == |current.blockchain| 445 && uPayload.round > current.round 446 :: 447 m.roundChangePayload 448 449 } 450 451 /** 452 * @returns The set of signed RoundChange payloads included in the set of 453 * RoundChange messages `messages`. 454 */ 455 function extractSignedRoundChanges(messages:set<QbftMessage>): set<SignedRoundChange> 456 requires forall m | m in messages :: m.RoundChange? 457 { 458 set m | m in messages 459 :: 460 m.roundChangePayload 461 } 462 463 /** 464 * @returns The set of all node identifiers that have signed one or more 465 * RoundChange messages in `roundChanges`. 466 */ 467 function getSetOfRoundChangeSenders(roundChanges: set<SignedRoundChange>): set<Address> 468 { 469 set m | m in roundChanges :: recoverSignedRoundChangeAuthor(m) 470 } 471 472 /** 473 * @returns The set of all blocks included in the set of RoundChange messages `messages`. 474 */ 475 function receivedBlocksInRoundChanges(messages:set<QbftMessage>): set<Block> 476 requires forall m | m in messages :: m.RoundChange? 477 { 478 set m | 479 && m in messages 480 && optionIsPresent(m.proposedBlockForNextRound) 481 :: 482 optionGet(m.proposedBlockForNextRound) 483 484 } 485 486 /** 487 * @returns `true` if and only if `block` is a valid block for a Proposal 488 * message for round `round` that does not carry any RoundChange 489 * Justification under the assumption that the current blockchain is 490 * `blockchain`. 491 */ 492 predicate validateNonPreparedBlock( 493 block: Block, 494 blockchain: Blockchain, 495 round: nat 496 ) 497 requires StateBlockchainInvariant(blockchain) 498 { 499 && validateEthereumBlock(blockchain, block) 500 && block.header.proposer == proposer(round, blockchain) 501 && |validators(blockchain + [block])| > 0 502 } 503 504 /** 505 * @returns `true` if and only if `sPayload` is a valid signed RoundChange 506 * payload for height `height`, round `round` under the assumption 507 * that the current set of validators is `validators`. 508 */ 509 predicate validRoundChange( 510 sPayload: SignedRoundChange, 511 height: nat, 512 round: nat, 513 validators: seq<Address>) 514 { 515 var uPayload := sPayload.unsignedPayload; 516 517 && uPayload.height == height 518 && uPayload.round == round 519 && (if && !optionIsPresent(uPayload.preparedRound) 520 && !optionIsPresent(uPayload.preparedValue) 521 then 522 true 523 else if && optionIsPresent(uPayload.preparedRound) 524 && optionIsPresent(uPayload.preparedValue) 525 then 526 optionGet(uPayload.preparedRound) < round 527 else 528 false) 529 && recoverSignedRoundChangeAuthor(sPayload) in validators 530 } 531 532 /** 533 * @returns `true` if and only if `sPayload` is one of the signed 534 * RoundChange payloads with the highest round in the set 535 * `roundChanges`. 536 */ 537 predicate isHighestPrepared(sPayload:SignedRoundChange, roundChanges:set<SignedRoundChange>) 538 { 539 && optionIsPresent(sPayload.unsignedPayload.preparedRound) 540 && optionIsPresent(sPayload.unsignedPayload.preparedValue) 541 && forall m | m in roundChanges :: 542 optionIsPresent(m.unsignedPayload.preparedRound) ==> 543 optionGet(m.unsignedPayload.preparedRound) <= optionGet(sPayload.unsignedPayload.preparedRound) 544 545 } 546 547 /** 548 * @returns `true` if and only if `roundChanges` and `prepares` are, 549 * respectively, valid Proposal and RoundChange justifications for 550 * a Proposal message for height `height`, round `round` including 551 * block `block` undef the following assumptions: (i) the only 552 * possible set of blocks that a node can justify is 553 * `setOfAvailableBlocks`, (ii) the validation function to be used 554 * in the case that `prepares` is empty is `validateBlock`, (iii) 555 * the leader for `round` is `roundLeader` and (iv) the current set 556 * of validators is `validators`. 557 */ 558 predicate isProposalJustification( 559 roundChanges:set<SignedRoundChange>, 560 prepares:set<SignedPrepare>, 561 setOfAvailableBlocks: set<Block>, 562 height: nat, 563 round: nat, 564 block: Block, 565 validateBlock: Block -> bool, 566 roundLeader: Address, 567 validators:seq<Address>) 568 { 569 if round == 0 then 570 && validateBlock(block) 571 && block.header.roundNumber == 0 572 && block.header.height == height 573 && block.header.proposer == roundLeader 574 else 575 && |getSetOfRoundChangeSenders(roundChanges)| >= quorum(|validators|) 576 && (forall m | m in roundChanges :: validRoundChange(m,height,round,validators)) 577 && if (forall m | m in roundChanges :: && !optionIsPresent(m.unsignedPayload.preparedRound) 578 && !optionIsPresent(m.unsignedPayload.preparedValue)) 579 then 580 && validateBlock(block) 581 && block.header.roundNumber == round 582 && block.header.height == height 583 && block.header.proposer == roundLeader 584 else 585 && block in setOfAvailableBlocks 586 && block.header.roundNumber == round 587 && block.header.height == height 588 && |prepares| >= quorum(|validators|) 589 && exists rcm | rcm in roundChanges :: 590 && isHighestPrepared(rcm,roundChanges) 591 && var proposedBlockWithOldRound := 592 replaceRoundInBlock( 593 block, 594 optionGet(rcm.unsignedPayload.preparedRound) 595 ); 596 && optionGet(rcm.unsignedPayload.preparedValue) == digest(proposedBlockWithOldRound) 597 && (forall pm | pm in prepares :: 598 validSignedPrepareForHeightRoundAndDigest( 599 pm, 600 height, 601 optionGet(rcm.unsignedPayload.preparedRound), 602 optionGet(rcm.unsignedPayload.preparedValue), 603 validators 604 )) 605 } 606 607 /** 608 * @returns `true` if and only if `m` is a valid Proposal message for a QBFT node with state `current. 609 */ 610 predicate isValidProposal(m:QbftMessage, current:NodeState) 611 requires validNodeState(current) 612 { 613 && m.Proposal? 614 && var roundLeader := proposer(m.proposalPayload.unsignedPayload.round,current.blockchain); 615 && m.proposalPayload.unsignedPayload.height == |current.blockchain| 616 && recoverSignedProposalAuthor(m.proposalPayload) == roundLeader 617 && isProposalJustification( 618 m.proposalJustification, 619 m.roundChangeJustification, 620 {m.proposedBlock}, 621 |current.blockchain|, 622 m.proposalPayload.unsignedPayload.round, 623 m.proposedBlock, 624 b => validateNonPreparedBlock(b,current.blockchain,m.proposalPayload.unsignedPayload.round), 625 roundLeader, 626 validators(current.blockchain) 627 ) 628 // NOTE: This check is not required by the QBFT paper as the message structure is a bit different 629 && digest(m.proposedBlock) == m.proposalPayload.unsignedPayload.digest 630 && ( 631 || m.proposalPayload.unsignedPayload.round > current.round 632 || ( 633 && !optionIsPresent(current.proposalAcceptedForCurrentRound) 634 && m.proposalPayload.unsignedPayload.round == current.round 635 ) 636 ) 637 } 638 639 /** 640 * @returns `true` if and only if `roundChanges` and `prepares` are, 641 * respectively, valid Proposal and RoundChange justifications for 642 * a Proposal message with round `newRound` including block `block` 643 * under the assumption that the current QBFT node state is 644 * `current`. 645 */ 646 predicate isReceivedProposalJustification( 647 roundChanges: set<QbftMessage>, 648 prepares: set<QbftMessage>, 649 newRound: nat, 650 block: Block, 651 current:NodeState) 652 requires validNodeState(current) 653 { 654 && isNonStrictSubSetOf(roundChanges, receivedRoundChanges(current)) 655 && (forall m | m in prepares :: m.Prepare?) 656 && isNonStrictSubSetOf(extractSignedPrepares(prepares), extractSignedPreparesEx(current.messagesReceived)) 657 && isProposalJustification( 658 extractSignedRoundChanges(roundChanges), 659 extractSignedPrepares(prepares), 660 receivedBlocksInRoundChanges(roundChanges), 661 |current.blockchain|, 662 newRound, 663 block, 664 b => && b == getNewBlock(current, newRound), 665 proposer(newRound,current.blockchain), 666 validators(current.blockchain)) 667 } 668 669 /** 670 * @returns `true` if and only if a QBFT node with state `current` has 671 * received a valid Proposal Justification for a round where the QBFT node 672 * is the leader. 673 */ 674 predicate hasReceivedProposalJustificationForLeadingRound(current: NodeState) 675 requires validNodeState(current) 676 { 677 exists 678 roundChanges: set<QbftMessage>, 679 prepares: set<QbftMessage>, 680 newRound: nat, 681 block: Block 682 :: 683 && isProposalJustificationForLeadingRound(roundChanges, prepares, newRound, block, current) 684 } 685 686 /** 687 * @returns `true` if and only if a QBFT node with state `current` has 688 * received a valid Proposal Justification for `newRound` where the 689 * QBFT node is the leader in `newRound` and, either `newRound` is 690 * strictly higher than the current round or it is equal to the 691 * current round and no proposal has been accepted for the current 692 * round 693 */ 694 predicate isProposalJustificationForLeadingRound( 695 roundChanges: set<QbftMessage>, 696 prepares: set<QbftMessage>, 697 newRound: nat, 698 block: Block, 699 current: NodeState 700 ) 701 requires validNodeState(current) 702 { 703 && isReceivedProposalJustification(roundChanges, prepares, newRound, block, current) 704 && proposer(newRound, current.blockchain) == current.id 705 && ( 706 || newRound > current.round 707 || ( 708 && !optionIsPresent(current.proposalAcceptedForCurrentRound) 709 && newRound == current.round 710 ) 711 ) 712 } 713 714 /** 715 * @returns The RoundChange Justification that a QBFT node with state 716 * `current` has received since adopting its current blockchain, or 717 * the empty set if no Round Change Justification has been received 718 * since. 719 */ 720 function getRoundChangeJustification(current:NodeState): set<SignedPrepare> 721 requires validNodeState(current) 722 { 723 if !optionIsPresent(current.lastPreparedBlock) then 724 {} 725 else 726 var QofP: set<QbftMessage> :| 727 && isNonStrictSubSetOf( 728 QofP, 729 validPreparesForHeightRoundAndDigest( 730 current.messagesReceived, 731 |current.blockchain|, 732 optionGet(current.lastPreparedRound), 733 digest(optionGet(current.lastPreparedBlock)), 734 validators(current.blockchain) 735 ) 736 ) 737 && |QofP| >= quorum(|validators(current.blockchain)|); 738 739 set m | m in QofP :: m.preparePayload 740 } 741 742 /** 743 * @returns The RoundChange message that a QBFT node with state `current` 744 * would send to move to round `newRound`. 745 */ 746 function createRoundChange( 747 current: NodeState, 748 newRound:nat 749 ): 750 QbftMessage 751 requires validNodeState(current) 752 { 753 RoundChange( 754 signRoundChange( 755 UnsignedRoundChange( 756 |current.blockchain|, 757 newRound, 758 digestOptionalBlock(current.lastPreparedBlock), 759 current.lastPreparedRound), 760 current.id), 761 current.lastPreparedBlock, 762 getRoundChangeJustification(current) 763 ) 764 } 765 766 /** ======================================================================= 767 * PREPARE VALIDATION FUNCTIONS 768 * 769 * This section defines those functions that are used to determine whether a 770 * Prepare message is valid. 771 ========================================================================*/ 772 /** 773 * @returns `true` if and only if `sPayload` is a valid signed Prepare 774 * payload for height `height`, round `round`, block digest 775 * `digest` under the assumption that the set of validators is 776 * `validators'. 777 */ 778 predicate validSignedPrepareForHeightRoundAndDigest( 779 sPayload: SignedPrepare, 780 height:nat, 781 round:nat, 782 digest:Hash, 783 validators: seq<Address> 784 ) 785 { 786 var uPayload := sPayload.unsignedPayload; 787 && uPayload.height == height 788 && uPayload.round == round 789 && uPayload.digest == digest 790 && recoverSignedPrepareAuthor(sPayload) in validators 791 } 792 793 /** 794 * @returns The set of all valid Prepare messages for height `height, round 795 * `round`, block digest `digest` that are included in the set 796 * `messagesReceived` under the assumption that the set of 797 * validators is `validators`. 798 */ 799 function validPreparesForHeightRoundAndDigest( 800 messagesReceived: set<QbftMessage>, 801 height:nat, 802 round:nat, 803 digest:Hash, 804 validators: seq<Address> 805 ): set<QbftMessage> 806 { 807 808 set m | && m in messagesReceived 809 && m.Prepare? 810 && validSignedPrepareForHeightRoundAndDigest( 811 m.preparePayload, 812 height, 813 round, 814 digest, 815 validators 816 ) 817 } 818 819 /** ======================================================================= 820 * COMMIT VALIDATION FUNCTIONS 821 * 822 * This section defines those functions that are used to determine whether a 823 * Commit message is valid. 824 ========================================================================*/ 825 /** 826 * @returns `true` if and only if `sPayload` is a valid signed Commit 827 * payload for height `height`, round `round`, block 828 * `proposedBlock` under the assumption that the set of validators 829 * is `validators'. 830 */ 831 predicate validateCommit(commit:SignedCommit, height:nat, round:nat, proposedBlock:Block, validators:seq<Address>) 832 { 833 var uPayload := commit.unsignedPayload; 834 835 && uPayload.height == height 836 && uPayload.round == round 837 && uPayload.digest == digest(proposedBlock) 838 && recoverSignedHashAuthor(hashBlockForCommitSeal(proposedBlock), uPayload.commitSeal) == recoverSignedCommitAuthor(commit) 839 && recoverSignedCommitAuthor(commit) in validators 840 } 841 842 /** 843 * @returns The set of all valid Commit messages for height `height, round 844 * `round`, block `proposedBlock` that are included in the set 845 * `messagesReceived` under the assumption that the set of 846 * validators is `validators`. 847 */ 848 function validCommitsForHeightRoundAndDigest( 849 messagesReceived: set<QbftMessage>, 850 height:nat, 851 round:nat, 852 proposedBlock:Block, 853 validators: seq<Address> 854 ): set<QbftMessage> 855 { 856 857 set m | && m in messagesReceived 858 && m.Commit? 859 && validateCommit( 860 m.commitPayload, 861 height, 862 round, 863 proposedBlock, 864 validators 865 ) 866 } 867 868 /** ======================================================================= 869 * NEW-BLOCK VALIDATION FUNCTIONS 870 * 871 * This section defines those functions that are used to determine whether a 872 * NewBlock message is valid. 873 ========================================================================*/ 874 /** 875 * @returns `true` if and only if block `block` carries a valid set of 876 * commit seals that justify appending it to blockchain 877 * `blockchain`. 878 */ 879 predicate ValidNewBlock(blockchain:Blockchain, block: Block) 880 { 881 && block.header.height == |blockchain| 882 && |block.header.commitSeals| >= quorum(|validators(blockchain)|) 883 && (forall s | s in block.header.commitSeals :: recoverSignedHashAuthor(hashBlockForCommitSeal(block),s) in validators(blockchain)) 884 } 885 886 /** 887 * @return `true` if and only if `msg` is a NewBlock message including a 888 * block that carries a valid set of commit seals that justify 889 * appending it to blockchain `blockchain`. 890 */ 891 predicate validNewBlockMessage(blockchain: Blockchain, msg:QbftMessage) 892 { 893 && msg.NewBlock? 894 && ValidNewBlock(blockchain, msg.block) 895 } 896 897 /** ======================================================================= 898 * NON-SPECIFICATION FUNCTIONS 899 * 900 * This section defines those functions that are used in the specification 901 * but that are used for verification purposes and not specification 902 * purposes. 903 ========================================================================*/ 904 predicate validNodeState(nodeState: NodeState) 905 { 906 && (optionIsPresent(nodeState.proposalAcceptedForCurrentRound) ==> optionGet(nodeState.proposalAcceptedForCurrentRound).Proposal?) 907 && (!optionIsPresent(nodeState.lastPreparedRound) <==> !optionIsPresent(nodeState.lastPreparedBlock)) 908 && (optionIsPresent(nodeState.lastPreparedRound) ==> 909 exists QofP :: 910 && isNonStrictSubSetOf( 911 QofP, 912 validPreparesForHeightRoundAndDigest( 913 nodeState.messagesReceived, 914 |nodeState.blockchain|, 915 optionGet(nodeState.lastPreparedRound), 916 digest(optionGet(nodeState.lastPreparedBlock)), 917 validators(nodeState.blockchain) 918 ) 919 ) 920 && |QofP| >= quorum(|validators(nodeState.blockchain)|) 921 ) 922 && StateBlockchainInvariant(nodeState.blockchain) 923 } 924 925 predicate StateBlockchainInvariant(blockchain: Blockchain) 926 { 927 && |blockchain| > 0 928 && (|blockchain| > 1 ==> blockchain[|blockchain|-1].header.proposer in validators(blockchain[..|blockchain|-1])) 929 && |validators(blockchain)| > 0 930 } 931 }
This file defines all the types used throughout the specification.
1 /** 2 * Copyright © 2021 EEA Inc. 3 * 4 * This document is available under the terms of the Apache 2.0 License 5 * (http://www.apache.org/licenses/LICENSE-2.0.html) 6 */ 7 module L1_SpecTypes 8 { 9 /** ======================================================================= 10 * UNDEFINED TYPES 11 * 12 * This section includes the declarations of those types that are left 13 * undefined in this specification. 14 ========================================================================*/ 15 type {:extern "Address"} Address(==,!new) 16 17 type {:extern "BlockBody"} BlockBody(==,!new) 18 19 type {:extern "Transaction"} Transaction(==,!new) 20 21 type {:extern "Hash"} Hash(==,!new) 22 23 type {:extern "Signature"} Signature(==,!new) 24 25 /** ======================================================================= 26 * BLOCKCHAIN TYPES 27 * 28 * This section includes the declaration of the types that are related to 29 * sequences of blocks. 30 * 31 * - The only difference between `Block` and `RawBlock` is that the header 32 * of a `RawBlock` does not include the field `commitSeals`. 33 ========================================================================*/ 34 type Blockchain = seq<Block> 35 36 datatype BlockHeader = BlockHeader ( 37 proposer: Address, 38 roundNumber: nat, 39 commitSeals: set<Signature>, 40 height:nat, 41 timestamp: nat 42 ) 43 44 datatype Block = Block ( 45 header: BlockHeader, 46 body: BlockBody, 47 transactions: seq<Transaction> 48 ) 49 50 type RawBlockchain = seq<RawBlock> 51 52 datatype RawBlockHeader = RawBlockHeader ( 53 proposer: Address, 54 height:nat, 55 timestamp: nat 56 ) 57 58 datatype RawBlock = RawBlock ( 59 header: RawBlockHeader, 60 body: BlockBody, 61 transactions: seq<Transaction> 62 ) 63 64 /** ======================================================================= 65 * MESSAGE TYPES 66 * 67 * This section includes the declaration of those types that are used to 68 * define the various QBFT messages. 69 * 70 * - `Unsigned*` types represent messages without signature. 71 * - `Signed*` types represent messages carrying a signature. 72 * - `QbtMessage` is the datatype representing any QBFT message. 73 * - `QbtMessageWithRecipient` is the datatype used to indicate which node 74 * a message should be sent to. 75 * 76 ========================================================================*/ 77 datatype UnsignedProposal = UnsignedProposal ( 78 height:nat, 79 round:nat, 80 digest: Hash 81 ) 82 83 datatype UnsignedPrepare = UnsignedPrepare ( 84 height:nat, 85 round:nat, 86 digest:Hash 87 ) 88 89 datatype UnsignedCommit = UnsignedCommit ( 90 height:nat, 91 round:nat, 92 commitSeal: Signature, 93 digest:Hash 94 ) 95 96 datatype UnsignedRoundChange = UnsignedRoundChange ( 97 height:nat, 98 round:nat, 99 preparedValue: Optional<Hash>, 100 preparedRound: Optional<nat> 101 ) 102 103 datatype SignedProposal = SignedProposal ( 104 unsignedPayload: UnsignedProposal, 105 signature: Signature 106 ) 107 108 datatype SignedPrepare = SignedPrepare( 109 unsignedPayload: UnsignedPrepare, 110 signature: Signature 111 ) 112 113 datatype SignedCommit = SignedCommit( 114 unsignedPayload: UnsignedCommit, 115 signature: Signature 116 ) 117 118 datatype SignedRoundChange = SignedRoundChange( 119 unsignedPayload: UnsignedRoundChange, 120 signature: Signature 121 ) 122 123 datatype QbftMessage = 124 | Proposal( 125 proposalPayload: SignedProposal, 126 proposedBlock:Block, 127 proposalJustification: set<SignedRoundChange>, 128 roundChangeJustification: set<SignedPrepare> 129 ) 130 | Prepare( 131 preparePayload: SignedPrepare 132 ) 133 | Commit( 134 commitPayload: SignedCommit 135 ) 136 | RoundChange( 137 roundChangePayload: SignedRoundChange, 138 proposedBlockForNextRound: Optional<Block>, 139 roundChangeJustification: set<SignedPrepare> 140 ) 141 | NewBlock( 142 block: Block 143 ) 144 145 datatype QbftMessageWithRecipient = QbftMessageWithRecipient( 146 message: QbftMessage, 147 recipient: Address 148 ) 149 150 /** ======================================================================= 151 * STATE TYPES 152 * 153 * This section includes the declaration of the types that are used in the 154 * representation of the state of a QBFT node. 155 * 156 ========================================================================*/ 157 158 /** 159 * This type groups the configuration parameters for a QBFT network. 160 */ 161 datatype Configuration = Configuration( 162 nodes: seq<Address>, // Ordered set of the ids of all the nodes 163 genesisBlock : Block, 164 blockTime: nat 165 ) 166 167 /** 168 * This type represents the state associated with any QBFT node. 169 */ 170 datatype NodeState = NodeState ( 171 blockchain: Blockchain, 172 round: nat, 173 localTime: nat, 174 id: Address, 175 configuration: Configuration, 176 messagesReceived: set<QbftMessage>, 177 proposalAcceptedForCurrentRound: Optional<QbftMessage>, 178 lastPreparedBlock: Optional<Block>, 179 lastPreparedRound: Optional<nat>, 180 timeLastRoundStart: nat 181 ) 182 183 /** ======================================================================= 184 * GENERAL TYPES 185 * 186 * This section includes the declaration of the types that have general 187 * usage. 188 * 189 ========================================================================*/ 190 /** 191 * This type is used to represent a single value that may be not present. 192 */ 193 datatype Optional<T> = Optional(value:T) | None 194 195 type iseq<T> = x: imap<nat, T> | forall i: nat :: i in x.Keys witness * 196 197 /** ======================================================================= 198 * BEHAVIOUR TYPES 199 * 200 * This section includes the declaration of the types that are used to 201 * describe QBFT node behaviours. 202 * 203 ========================================================================*/ 204 205 /** 206 * This type represents a step of a QBFT node behaviour. 207 * 208 * On a step, a node receives the set of messages `messageReceived` and in 209 * response it sends the set of messages `messagesToSend` and set its 210 * blockchain to `newBlockchain`. 211 */ 212 datatype QbftSpecificationStep = QbftSpecificationStep( 213 messagesReceived: set<QbftMessage>, 214 newBlockchain: Blockchain, 215 messagesToSend: set<QbftMessageWithRecipient> 216 ) 217 218 /** 219 * This type represents a QBFT node behaviour. 220 * 221 * `initialBlockchain` is the blockchain exposed by the QBFT node 222 * at the very beginning and `steps` is the infinite sequence of steps composing 223 * the behaviour of the QBFT node. 224 */ 225 datatype QbftNodeBehaviour = QbftNodeBehaviour( 226 initialBlockchain: Blockchain, 227 steps: iseq<QbftSpecificationStep> 228 ) 229 }
This file does not provide any specification. It just contains lemmas that are used in the UponCommit
and UponRoundChange
predicates in 1.1 node.dfy when declaring sets via such-that assignment (:|
) as the verifier is not able to automatically prove the existence of such sets.
1 /** 2 * Copyright © 2021-2022 EEA Inc. 3 * 4 * This document is available under the terms of the Apache 2.0 License 5 * (http://www.apache.org/licenses/LICENSE-2.0.html) 6 * 7 * This file does provide any specification. It just contains lemmas that are 8 * used in the `UponCommit` and `UponRoundChange` predicates in `node.dfy` when 9 * declaring sets via such-that assignment (`:|`) as the verifier is not able to 10 * automatically prove the existence of such sets. 11 */ 12 include "types.dfy" 13 include "node_auxiliary_functions.dfy" 14 15 module L1_Lemmas 16 { 17 import opened L1_SpecTypes 18 import opened L1_AuxiliaryFunctionsAndLemmas 19 20 lemma lemmaIfMappedSubSetOfGreaterSizeExistsThenMappedSmallSubsetExistsHelper1<T,T2>(s:set<T>, s2: set<T2>, f:T -> T2) 21 requires s2 == set m | m in s :: f(m) 22 ensures |s2| <= |s| 23 { 24 if s != {} 25 { 26 var e :| e in s; 27 var news := s - {e}; 28 var news2 := set m | m in news :: f(m); 29 lemmaIfMappedSubSetOfGreaterSizeExistsThenMappedSmallSubsetExistsHelper1(news, news2,f); 30 31 if f(e) in news2 32 { 33 assert s2 == news2; 34 } 35 else 36 { 37 assert |news2| < |s|; 38 assert f(e) in s2; 39 assert s2 == news2 + {f(e)}; 40 assert |s2| == |news2| + 1; 41 } 42 } 43 } 44 45 lemma lemmaIfMappedSubSetOfGreaterSizeExistsThenMappedSmallSubsetExistsHelper2<T,T2>(a:set<T>, b:set<T>, c:set<T2>, d:set<T2>, f:T -> T2) 46 requires b < a 47 requires |a| == |b| + 1 48 requires c == set m | m in a :: f(m) 49 requires d == set m | m in b :: f(m) 50 ensures |d| == |c| || |c| == |d| + 1 51 { 52 var diff := a - b; 53 assert |diff| == 1; 54 var e :| e in diff; 55 assert |diff-{e}| == 0; 56 // assert d - {e} == {}; 57 assert diff == {e}; 58 assert b + {e} == a; 59 60 if f(e) in d 61 { 62 assert d == c; 63 assert |d| == |c|; 64 } 65 else 66 { 67 assert c == d + {f(e)}; 68 } 69 } 70 71 lemma lemmaIfMappedSubSetOfGreaterSizeExistsThenMappedSmallSubsetExistsHelper3<T,T2>(sM: set<T>, s1:set<T>, f:set<T> -> set<T2>, v:nat) returns (s2:set<T>) 72 requires |f(s1)| >= v 73 requires s1 <= sM 74 requires forall a :: |f(a)| <= |a| 75 requires forall a,b | b < a && |a| == |b| + 1 :: |f(a)| == |f(b)| || |f(a)| == |f(b)| + 1 76 ensures s2 <= s1 77 ensures |f(s2)| == v 78 { 79 if s1 == {} 80 { 81 s2 := s1; 82 } 83 else if |f(s1)| == v 84 { 85 s2 := s1; 86 } 87 else 88 { 89 var e :| e in s1; 90 var news := s1 - {e}; 91 assert |news| == |s1| - 1; 92 93 var s2r := lemmaIfMappedSubSetOfGreaterSizeExistsThenMappedSmallSubsetExistsHelper3(sM,news,f,v); 94 s2 := s2r; 95 } 96 } 97 98 lemma lemmaIfMappedSubSetOfGreaterSizeExistsThenMappedSmallSubsetExistsGeneric<T, T2>( 99 s1: set<T>, 100 fSetMap: set<T> -> set<T2>, 101 fElMap: T -> T2, 102 v: nat 103 ) 104 requires exists s1' :: s1' <= s1 && |fSetMap(s1')| >= v 105 // requires fSetMap == ((s: set<T>) => set m | m in s :: fElMap(m)) 106 requires forall s1' :: fSetMap(s1') == set m | m in s1' :: fElMap(m) 107 ensures exists s2 :: s2 <= s1 && |fSetMap(s2)| == v 108 { 109 forall a 110 ensures |fSetMap(a)| <= |a| 111 { 112 lemmaIfMappedSubSetOfGreaterSizeExistsThenMappedSmallSubsetExistsHelper1(a, fSetMap(a), fElMap); 113 } 114 115 forall a,b | b < a && |a| == |b| + 1 116 ensures |fSetMap(a)| == |fSetMap(b)| || |fSetMap(a)| == |fSetMap(b)| + 1 117 { 118 lemmaIfMappedSubSetOfGreaterSizeExistsThenMappedSmallSubsetExistsHelper2(a,b,fSetMap(a),fSetMap(b),fElMap); 119 } 120 121 var s1' :| s1' <= s1 && |fSetMap(s1')| >= v; 122 var s2 := lemmaIfMappedSubSetOfGreaterSizeExistsThenMappedSmallSubsetExistsHelper3(s1, s1', fSetMap, v); 123 } 124 125 lemma lemmaToProveExistenceOfSetFrc( 126 signedRoundChangesReceived: set<SignedRoundChange>, 127 v: nat 128 ) 129 requires exists s1 :: s1 <= signedRoundChangesReceived && |getSetOfRoundChangeSenders(s1)| >= v 130 ensures exists s3 :: s3 <= signedRoundChangesReceived && |getSetOfRoundChangeSenders(s3)| == v 131 { 132 lemmaIfMappedSubSetOfGreaterSizeExistsThenMappedSmallSubsetExistsGeneric(signedRoundChangesReceived, getSetOfRoundChangeSenders, recoverSignedRoundChangeAuthor, v); 133 } 134 135 lemma lemmaIfSubSetOfGreaterSizeExistsThenSmallSubsetExistsHelper<T>(s1:set<T>, s2:set<T>, sz1:nat) returns (s3:set<T>) 136 requires s1 <= s2 137 requires |s1| >= sz1 138 ensures s3 <= s2 139 ensures |s3| == sz1 140 { 141 if |s1| == sz1 142 { 143 s3 := s1; 144 } 145 else 146 { 147 var e :| e in s1; 148 var s1Sub := s1 - {e}; 149 s3 := lemmaIfSubSetOfGreaterSizeExistsThenSmallSubsetExistsHelper(s1Sub,s2,sz1); 150 } 151 } 152 153 lemma lemmaIfSubSetOfGreaterSizeExistsThenSmallSubsetExists<T>(s2:set<T>, sz1:nat) 154 requires exists s1 :: && s1 <= s2 155 && |s1| >= sz1 156 ensures exists s3 :: && s3 <= s2 157 && |s3| == sz1 158 { 159 var s1 :| && s1 <= s2 160 && |s1| >= sz1; 161 var s3 := lemmaIfSubSetOfGreaterSizeExistsThenSmallSubsetExistsHelper(s1,s2,sz1); 162 } 163 164 }
The following is a list of terms defined in this Specification.
The EEA acknowledges and thanks the many people who contributed to the development of this version of the specification. Please advise us of any errors or omissions.
Enterprise Ethereum is built on top of Ethereum, and we are grateful to the entire community who develops Ethereum, for their work and their ongoing collaboration to helps us maintain as much compatibility as possible with the Ethereum ecosystem.