Skip to content

Commit

Permalink
Extract local compute phase of BA into a function
Browse files Browse the repository at this point in the history
Also handles case where BA value is not equal to coin

fixes amiller#8

Except for the blocking operation of getting the coin, the code that is
extracted into a function corresponds to phase 3 described in [MMR14];
lines 7-11 in fig. 2 (A BV-broadcast-based algorithm implementing binary
consensus ...).
  • Loading branch information
sbellem committed Jul 5, 2018
1 parent d41a9bd commit 3de89bf
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 22 deletions.
57 changes: 35 additions & 22 deletions honeybadgerbft/core/binaryagreement.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from gevent.event import Event
from collections import defaultdict

from honeybadgerbft.exceptions import RedundantMessageError
from honeybadgerbft.exceptions import RedundantMessageError, AbandonedNodeError


def binaryagreement(sid, pid, N, f, coin, input, decide, broadcast, receive):
Expand Down Expand Up @@ -121,25 +121,38 @@ def _recv():
# Block until receiving the common coin value
s = coin(r)

if len(values) == 1:
v = next(iter(values))
if v == s:
if already_decided is None:
already_decided = v
decide(v)
# print('[sid:%s] [pid:%d] DECIDED %d in round %d' % (sid,pid,v,r))
elif already_decided == v:
# Here corresponds to a proof that if one party
# decides at round r, then in all the following
# rounds, everybody will propose r as an
# estimation. (Lemma 2, Lemma 1) An abandoned
# party is a party who has decided but no enough
# peers to help him end the loop. Lemma: # of
# abandoned party <= t
# print('[sid:%s] [pid:%d] QUITTING in round %d' % (sid,pid,r)))
_thread_recv.kill()
return
est = v
else:
est = s
try:
est, already_decided = set_new_estimate(
values=values,
s=s,
already_decided=already_decided,
decide=decide,
)
except AbandonedNodeError:
# print('[sid:%s] [pid:%d] QUITTING in round %d' % (sid,pid,r)))
_thread_recv.kill()
return

r += 1


def set_new_estimate(*, values, s, already_decided, decide):
if len(values) == 1:
v = next(iter(values))
if v == s:
if already_decided is None:
already_decided = v
decide(v)
elif already_decided == v:
# Here corresponds to a proof that if one party
# decides at round r, then in all the following
# rounds, everybody will propose r as an
# estimation. (Lemma 2, Lemma 1) An abandoned
# party is a party who has decided but no enough
# peers to help him end the loop. Lemma: # of
# abandoned party <= t
raise AbandonedNodeError
est = v
else:
est = s
return est, already_decided
4 changes: 4 additions & 0 deletions honeybadgerbft/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ class UnknownTagError(BroadcastError):

class RedundantMessageError(BroadcastError):
"""Raised when a rdundant message is received."""


class AbandonedNodeError(HoneybadgerbftError):
"""Raised when a node does not have enough peer to carry on a distirbuted task."""
68 changes: 68 additions & 0 deletions test/test_binaryagreement.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,3 +272,71 @@ def test_binaryagreement():
reason='Place holder for https://github.com/amiller/HoneyBadgerBFT/issues/59')
def test_issue59_attack():
raise NotImplementedError("Placeholder test failure for Issue #59")


@mark.parametrize('values,s,already_decided,expected_est,'
'expected_already_decided,expected_output', (
({0}, 0, None, 0, 0, 0),
({1}, 1, None, 1, 1, 1),
))
def test_set_next_round_estimate_with_decision(values, s, already_decided,
expected_est, expected_already_decided, expected_output):
from honeybadgerbft.core.binaryagreement import set_new_estimate
decide = Queue()
updated_est, updated_already_decided = set_new_estimate(
values=values,
s=s,
already_decided=already_decided,
decide=decide.put,
)
assert updated_est == expected_est
assert updated_already_decided == expected_already_decided
assert decide.get() == expected_output


@mark.parametrize('values,s,already_decided,'
'expected_est,expected_already_decided', (
({0}, 0, 1, 0, 1),
({0}, 1, None, 0, None),
({0}, 1, 0, 0, 0),
({0}, 1, 1, 0, 1),
({1}, 0, None, 1, None),
({1}, 0, 0, 1, 0),
({1}, 0, 1, 1, 1),
({1}, 1, 0, 1, 0),
({0, 1}, 0, None, 0, None),
({0, 1}, 0, 0, 0, 0),
({0, 1}, 0, 1, 0, 1),
({0, 1}, 1, None, 1, None),
({0, 1}, 1, 0, 1, 0),
({0, 1}, 1, 1, 1, 1),
))
def test_set_next_round_estimate(values, s, already_decided,
expected_est, expected_already_decided):
from honeybadgerbft.core.binaryagreement import set_new_estimate
decide = Queue()
updated_est, updated_already_decided = set_new_estimate(
values=values,
s=s,
already_decided=already_decided,
decide=decide.put,
)
assert updated_est == expected_est
assert updated_already_decided == expected_already_decided
assert decide.empty()


@mark.parametrize('values,s,already_decided', (
({0}, 0, 0),
({1}, 1, 1),
))
def test_set_next_round_estimate_raises(values, s, already_decided):
from honeybadgerbft.core.binaryagreement import set_new_estimate
from honeybadgerbft.exceptions import AbandonedNodeError
with raises(AbandonedNodeError):
updated_est, updated_already_decided = set_new_estimate(
values=values,
s=s,
already_decided=already_decided,
decide=None,
)

0 comments on commit 3de89bf

Please sign in to comment.