diff --git a/carnot/spec.md b/carnot/spec.md index ab4d54f..88b34b3 100644 --- a/carnot/spec.md +++ b/carnot/spec.md @@ -6,6 +6,10 @@ In addition, all types can be expected to have their invariants checked by the t 'Q:' is used to indicate unresolved questions. Notation is loosely based on CDDL. +Similar to the Carnot algorithm, this specification will be event-based, prescribing the actions to perform in response to relevant events in the Carnot consensus. +Events should be processed one at a time, picking any from the available ones. +As for ordering between events, there are some constraints (e.g. can't process a proposal before it's parent) which will likely form a DAG of relations. The expectation is that an implementation will respect these requirements by processing only events which have all preconditions satisfied. + ## Messages A critical piece in the protocol, these are the different kind of messages used by participants during the protocol execution. * `Block`: propose a new block @@ -106,6 +110,7 @@ CURRENT_VIEW: View LOCAL_HIGH_QC: Qc LATEST_COMMITTED_VIEW: View COLLECTION: Q? +SAFE_BLOCKS: Set[Block] ``` @@ -140,38 +145,37 @@ The following functions are expected to be available to participants during the -## Core functions +## Core events + +These are the core events necessary for the Carnot consensus protocol. In response to such events a participant is expected to execute the corresponding handler action. + +* receive block b -> `receive_block(b)` + Preconditions: + * `b.parent() in SAFE_BLOCKS` +* receive a supermajority of votes for block b -> `vote(b, votes)` + Preconditions: + * `b in SAFE_BLOCKS` +* receive a vote v for block b when a supermajority of votes already exists -> `forward_votes(b, v)` + Preconditions: + * `b in SAFE_BLOCKS` + * `vote(b, some_votes)` already called and `v not in some_votes` +* `current_time() - time(last view update) > TIMEOUT` -> `timeout(last view)` +* leader for view v and leader supermajority for previous proposal -> `propose_block(v, votes)` + -These are the core functions necessary for the Carnot consensus protocol, to be executed in response of incoming messages, except for `timeout` which is triggered by a participant configurable timer. - ### Receive block ```python3 def receive_block(block: Block): - if block.id() is known or block.view <=LATEST_COMMITTED_VIEW: - return + # checking preconditions + assert block.parent() in SAFE_BLOCKS - # Recursively make sure that we process blocks in order - if block.parent() is missing: - parent: Block = download(block.parent()) - receive(parent) + if block.id() in SAFE_BLOCKS or block.view <= LATEST_COMMITTED_VIEW: + return if safe_block(block): - # This is not in the original spec, but - # let's validate I have this clear. - assert block.view == current_view - + SAFE_BLOCKS.add(block) update_high_qc(block.qc) - - vote = create_vote() - if member_of_leaf_committee(): - if member_of_root_committee(): - send(vote, leader(current_view + 1)) - else: - send(vote, parent_commitee()) - current_view += 1 - reset_timer() - try_to_commit_grandparent(block) ``` ##### Auxiliary functions @@ -231,78 +235,54 @@ def update_high_qc(qc: Qc): # Q: same thing about missing views ``` -### Receive Vote - -Q: this whole function needs to be revised +### Vote ```python +def vote(block: Block, votes: Set[Vote]): + # check preconditions + assert block in SAFE_BLOCKS + assert supermajority(votes) # should also check votes are from children only -def receive_vote(vote: Vote): - if vote.block is missing: - block = download(vote.block) - receive(block) + vote = create_vote(votes) - # Q: we should probably return if we already received this vote - if member_of_internal_com() and not_member_of_root(): - if child_commitee(vote.voter): - COLLECTION[vote.block].append(vote) - else: - # Q: not returning here would mean it's extremely easy to - # trigger building a new vote in the following branches - return - - if supermajority(COLLECTION[vote.block]): - # Q: should we send it to everyone in the committee? - self_vote = build_vote() - send(self_vote, parent_committee) - # Q: why here? - current_view += 1 - reset_timer() - # Q: why do we do this here? - try_to_commit_grand_parent(block) - if member_of_root(): - if child_commitee(vote.voter): - COLLECTION[vote.block].append(vote) - else: - # Q: not returning here would mean it's extremely easy to - # trigger building a new vote in the following branches - return - - if supermajority(COLLECTION[vote.block]): - # Q: The vote to send is not the one received but - # the one build by this participant, right? - self_vote = build_vote(); - qc = build_qc(collection[vote.block]) - self_vote.qc=qc - send(self_vote, leader(current_view + 1)) - # Q: why here? - current_view += 1 - reset_timer() - # Q: why here? - try_to_commit_grandparent(block) + vote.qc = build_qc(collection[vote.block]) + send(vote, leader(CURRENT_VIEW + 1)) + else: + send(vote, parent_committee()) + + current_view += 1 + reset_timer() + try_to_commit_grandparent(block) +``` - # Q: this means that we send a message for every incoming - # message after the threshold has been reached, i.e. a vote - # from a node in the leaf committee can trigger - # at least height(tree) messages. - if morethanSsupermajority(collection[vote.block]): - # just forward the vote to the leader - # Q: But then childcommitte(vote.voter) would return false - # in the leader, as it's a granchild, not a child - send(vote, leader(current_view + 1)) +### Forward vote +```python +# Q: how can you reach 2/3 of total votes if you're missing the qc +# from the other root committe +def forward_vote(block: Block, vote: Vote): + assert block in SAFE_BLOCKS + assert child_committe(vote.id) + # already supermajority - if leader(view): # Q? Which view? CURRENT_VIEW or vote.view? - if vote.view < CURRENT_VIEW - 1: - return + if member_of_root(): + # just forward the vote to the leader + # Q: But then childcommitte(vote.voter) would return false + # in the leader, as it's a granchild, not a child + send(vote, leader(current_view + 1)) +``` - # Q: No filtering? I can just create a key and vote? - COLLECTION[vote.block].append(vote) - if supermajority(collection[vote.block]): - qc = build_qc(collection[vote.block]) - block = build_block(qc) - broadcast(block) +### Propose block +```python +def propose_block(view: View, votes: Set[Vote]): + assert leader(view) + assert leader_supermajority(votes) + assert all(vote.view == view - 1 for vote in votes) + + qc = build_qc(votes) + block = build_block(qc) + broadcast(block) ``` ### Receive Timeout