Skip to content

Commit 6ccea7f

Browse files
authored
Merge pull request #9 from sbellem/issue-8-BA-next-est-value
Handle case where BA value is not equal to coin
2 parents d41a9bd + 3de89bf commit 6ccea7f

File tree

3 files changed

+107
-22
lines changed

3 files changed

+107
-22
lines changed

honeybadgerbft/core/binaryagreement.py

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from gevent.event import Event
33
from collections import defaultdict
44

5-
from honeybadgerbft.exceptions import RedundantMessageError
5+
from honeybadgerbft.exceptions import RedundantMessageError, AbandonedNodeError
66

77

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

124-
if len(values) == 1:
125-
v = next(iter(values))
126-
if v == s:
127-
if already_decided is None:
128-
already_decided = v
129-
decide(v)
130-
# print('[sid:%s] [pid:%d] DECIDED %d in round %d' % (sid,pid,v,r))
131-
elif already_decided == v:
132-
# Here corresponds to a proof that if one party
133-
# decides at round r, then in all the following
134-
# rounds, everybody will propose r as an
135-
# estimation. (Lemma 2, Lemma 1) An abandoned
136-
# party is a party who has decided but no enough
137-
# peers to help him end the loop. Lemma: # of
138-
# abandoned party <= t
139-
# print('[sid:%s] [pid:%d] QUITTING in round %d' % (sid,pid,r)))
140-
_thread_recv.kill()
141-
return
142-
est = v
143-
else:
144-
est = s
124+
try:
125+
est, already_decided = set_new_estimate(
126+
values=values,
127+
s=s,
128+
already_decided=already_decided,
129+
decide=decide,
130+
)
131+
except AbandonedNodeError:
132+
# print('[sid:%s] [pid:%d] QUITTING in round %d' % (sid,pid,r)))
133+
_thread_recv.kill()
134+
return
135+
145136
r += 1
137+
138+
139+
def set_new_estimate(*, values, s, already_decided, decide):
140+
if len(values) == 1:
141+
v = next(iter(values))
142+
if v == s:
143+
if already_decided is None:
144+
already_decided = v
145+
decide(v)
146+
elif already_decided == v:
147+
# Here corresponds to a proof that if one party
148+
# decides at round r, then in all the following
149+
# rounds, everybody will propose r as an
150+
# estimation. (Lemma 2, Lemma 1) An abandoned
151+
# party is a party who has decided but no enough
152+
# peers to help him end the loop. Lemma: # of
153+
# abandoned party <= t
154+
raise AbandonedNodeError
155+
est = v
156+
else:
157+
est = s
158+
return est, already_decided

honeybadgerbft/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,7 @@ class UnknownTagError(BroadcastError):
1212

1313
class RedundantMessageError(BroadcastError):
1414
"""Raised when a rdundant message is received."""
15+
16+
17+
class AbandonedNodeError(HoneybadgerbftError):
18+
"""Raised when a node does not have enough peer to carry on a distirbuted task."""

test/test_binaryagreement.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,3 +272,71 @@ def test_binaryagreement():
272272
reason='Place holder for https://github.com/amiller/HoneyBadgerBFT/issues/59')
273273
def test_issue59_attack():
274274
raise NotImplementedError("Placeholder test failure for Issue #59")
275+
276+
277+
@mark.parametrize('values,s,already_decided,expected_est,'
278+
'expected_already_decided,expected_output', (
279+
({0}, 0, None, 0, 0, 0),
280+
({1}, 1, None, 1, 1, 1),
281+
))
282+
def test_set_next_round_estimate_with_decision(values, s, already_decided,
283+
expected_est, expected_already_decided, expected_output):
284+
from honeybadgerbft.core.binaryagreement import set_new_estimate
285+
decide = Queue()
286+
updated_est, updated_already_decided = set_new_estimate(
287+
values=values,
288+
s=s,
289+
already_decided=already_decided,
290+
decide=decide.put,
291+
)
292+
assert updated_est == expected_est
293+
assert updated_already_decided == expected_already_decided
294+
assert decide.get() == expected_output
295+
296+
297+
@mark.parametrize('values,s,already_decided,'
298+
'expected_est,expected_already_decided', (
299+
({0}, 0, 1, 0, 1),
300+
({0}, 1, None, 0, None),
301+
({0}, 1, 0, 0, 0),
302+
({0}, 1, 1, 0, 1),
303+
({1}, 0, None, 1, None),
304+
({1}, 0, 0, 1, 0),
305+
({1}, 0, 1, 1, 1),
306+
({1}, 1, 0, 1, 0),
307+
({0, 1}, 0, None, 0, None),
308+
({0, 1}, 0, 0, 0, 0),
309+
({0, 1}, 0, 1, 0, 1),
310+
({0, 1}, 1, None, 1, None),
311+
({0, 1}, 1, 0, 1, 0),
312+
({0, 1}, 1, 1, 1, 1),
313+
))
314+
def test_set_next_round_estimate(values, s, already_decided,
315+
expected_est, expected_already_decided):
316+
from honeybadgerbft.core.binaryagreement import set_new_estimate
317+
decide = Queue()
318+
updated_est, updated_already_decided = set_new_estimate(
319+
values=values,
320+
s=s,
321+
already_decided=already_decided,
322+
decide=decide.put,
323+
)
324+
assert updated_est == expected_est
325+
assert updated_already_decided == expected_already_decided
326+
assert decide.empty()
327+
328+
329+
@mark.parametrize('values,s,already_decided', (
330+
({0}, 0, 0),
331+
({1}, 1, 1),
332+
))
333+
def test_set_next_round_estimate_raises(values, s, already_decided):
334+
from honeybadgerbft.core.binaryagreement import set_new_estimate
335+
from honeybadgerbft.exceptions import AbandonedNodeError
336+
with raises(AbandonedNodeError):
337+
updated_est, updated_already_decided = set_new_estimate(
338+
values=values,
339+
s=s,
340+
already_decided=already_decided,
341+
decide=None,
342+
)

0 commit comments

Comments
 (0)