Add timeout handler

This commit is contained in:
Giacomo Pasini 2023-03-30 10:54:07 +02:00
parent 0adbf47306
commit 0603b98076
No known key found for this signature in database
GPG Key ID: FC08489D2D895D4B
1 changed files with 55 additions and 40 deletions

View File

@ -97,7 +97,6 @@ class Timeout:
view: View
high_qc: AggregateQc
```
## Local Variables
Participants in the protocol are expected to mainting the following data in addition to the DAG of received proposal:
* `current_view`
@ -109,8 +108,8 @@ Participants in the protocol are expected to mainting the following data in addi
CURRENT_VIEW: View
LOCAL_HIGH_QC: Qc
LATEST_COMMITTED_VIEW: View
COLLECTION: Q?
SAFE_BLOCKS: Set[Block]
LAST_VIEW_TIMEOUT_QC: TimeoutQc
```
@ -155,12 +154,17 @@ These are the core events necessary for the Carnot consensus protocol. In respon
* receive a supermajority of votes for block b -> `vote(b, votes)`
Preconditions:
* `b in SAFE_BLOCKS`
* `local_timeout(b.view)` never called
* 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)`
* `local_timeout(b.view)` never called
* `current_time() - time(last view update) > TIMEOUT` and received new overlay -> `local_timeout(last view, new_overlay)`
* leader for view v and leader supermajority for previous proposal -> `propose_block(v, votes)`
* receive a supermajority of timeouts for view v -> `timeout(v, timeouts)`
Preconditions:
* `local_timeout(v)` already called
### Receive block
@ -202,6 +206,8 @@ def safe_block(block: Block):
```
```python
# FIX_ME: Don't think we need to specify this as a function if we don't use
# LAST_COMMITTED_VIEW
# Commit a grand parent if the grandparent and
# the parent have been added during two consecutive views.
def try_to_commit_grand_parent(block: Block):
@ -241,16 +247,20 @@ def update_high_qc(qc: Qc):
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
assert supermajority(votes)
assert all(child_committee(vote.id) for vote in votes)
assert all(vote.block == block for vote in votes)
vote = create_vote(votes)
if member_of_root():
vote.qc = build_qc(collection[vote.block])
vote.qc = build_qc(votes)
send(vote, leader(CURRENT_VIEW + 1))
else:
send(vote, parent_committee())
# Q: what about a node that is joining later and does not
# have access to votes? how does it commit blocks?
current_view += 1
reset_timer()
try_to_commit_grandparent(block)
@ -259,10 +269,8 @@ def vote(block: Block, votes: Set[Vote]):
### 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
def forward_vote(vote: Vote):
assert vote.block in SAFE_BLOCKS
assert child_committe(vote.id)
# already supermajority
@ -270,50 +278,57 @@ def forward_vote(block: Block, vote: Vote):
# 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))
send(vote, leader(vote.block.view + 1))
```
### Propose block
```python
def propose_block(view: View, votes: Set[Vote]):
def propose_block(view: View, quorum: Set[Vote] | Set[TimeoutMsg]):
assert leader(view)
assert leader_supermajority(votes)
assert all(vote.view == view - 1 for vote in votes)
assert leader_supermajority(quorum)
qc = build_qc(votes)
block = build_block(qc)
broadcast(block)
```
### Receive Timeout
```python
# Failure Case
def receive(timeout: Timeout):
# download the missing block
if timeout.high_qc().block is missing:
block = download(timeout.high_qc.block)
receive(block)
# It's an old message. Ignore it.
if timeout.view < CURRENT_VIEW:
return
update_high_qc(timeout.high_qc())
if member_of_internal_com():
COLLECTION[timeout.view].append(timeout)
if supermajority(timeout.view):
new_view_qc = build_qc(COLLECTION[timeout.view])
if member_of_root():
send(new_view_qc, leader(CURRENT_VIEW +1))
CURRENT_VIEW += 1
else:
send(new_view_qc, parent_committee())
```
### Timeout
```python
def timeout():
raise NotImplementedError
def local_timeout(new_overlay: Overlay):
# make it so we don't vote or forward any more vote after this
LAST_TIMEOUT_VIEW = CURRENT_VIEW
# TODO: change overlay
if member_of_leaf():
timeout_msg = create_timeout(CURRENT_VIEW, LOCAL_HIGH_QC, LAST_TIMEOUT_VIEW_QC)
send(timeout_msg, parent_committee())
```
### Receive
this is called *after* local_timeout
```python
def timeout(view: View, msgs: Set[TimeoutMsg]):
assert supermajority(msgs)
assert all(child_committee(msg.id) for msg in msgs)
assert all(timeout.view == view for timeout in msgs)
if CURRENT_VIEW > view:
return
if view <= LAST_VIEW_TIMEOUT_QC.view:
return
if view > LOCAL_HIGH_QC.view:
LOCAL_HIGH_QC = timeout_Msg.high_qc
timeout_qc = create_timeout_qc(msgs)increment_view_timeout_qc(timeout_qc.view)
LAST_VIEW_TIMEOUT_QC = timeout_qc
send(timeout_qc, own_committee()) ####helps nodes to sync quicker but not required
if member_of_root():
send(timeout_qc, leader(view+1))
else:
send(timeout_qc, parent_committee())
```