From 74833e4dab55cf4482e9c722fdd4757106a20ae3 Mon Sep 17 00:00:00 2001 From: Chen Zhu Date: Mon, 2 Mar 2020 16:18:06 -0800 Subject: [PATCH] Add VABA implementation and test --- docker-compose.yml | 11 +- honeybadgerbft/core/leaderelection.py | 104 +++++++++ honeybadgerbft/core/pbbroadcast.py | 72 +++++++ honeybadgerbft/core/pbbroadcast_4s.py | 115 ++++++++++ honeybadgerbft/core/vaba.py | 226 ++++++++++++++++++++ test/test_leaderelection.py | 139 ++++++++++++ test/test_pbbroadcast.py | 279 ++++++++++++++++++++++++ test/test_pbbroadcast_4s.py | 280 ++++++++++++++++++++++++ test/test_vaba.py | 295 ++++++++++++++++++++++++++ 9 files changed, 1520 insertions(+), 1 deletion(-) create mode 100644 honeybadgerbft/core/leaderelection.py create mode 100644 honeybadgerbft/core/pbbroadcast.py create mode 100644 honeybadgerbft/core/pbbroadcast_4s.py create mode 100644 honeybadgerbft/core/vaba.py create mode 100644 test/test_leaderelection.py create mode 100644 test/test_pbbroadcast.py create mode 100644 test/test_pbbroadcast_4s.py create mode 100644 test/test_vaba.py diff --git a/docker-compose.yml b/docker-compose.yml index b9fa421d..2a3b8616 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,16 @@ services: dockerfile: Dockerfile volumes: - .:/usr/local/src/HoneyBadgerBFT - command: pytest -v --cov=honeybadgerbft + command: pytest -s test/test_vaba.py + + honeybadger-test: + build: + context: . + dockerfile: Dockerfile + volumes: + - .:/usr/local/src/HoneyBadgerBFT + command: bash + builddocs: build: context: . diff --git a/honeybadgerbft/core/leaderelection.py b/honeybadgerbft/core/leaderelection.py new file mode 100644 index 00000000..9ae1a13c --- /dev/null +++ b/honeybadgerbft/core/leaderelection.py @@ -0,0 +1,104 @@ +import logging + +from honeybadgerbft.crypto.threshsig.boldyreva import serialize +from collections import defaultdict +from gevent import Greenlet +from gevent.queue import Queue +import hashlib + +logger = logging.getLogger(__name__) + + +def hash(x): + return hashlib.sha256(x).digest() + +class CommonCoinFailureException(Exception): + """Raised for common coin failures.""" + pass + + +def hash(x): + return hashlib.sha256(x).digest() + + +def leader_election(sid, pid, N, f, PK, SK, broadcast, receive): + """A leader election phase same as common coin logic + + :param sid: a unique instance id + :param pid: my id number + :param N: number of parties + :param f: fault tolerance, :math:`f+1` shares needed to get the coin + :param PK: ``boldyreva.TBLSPublicKey`` + :param SK: ``boldyreva.TBLSPrivateKey`` + :param broadcast: broadcast channel + :param receive: receive channel + :return: a function ``getCoin()``, where ``getCoin(r)`` blocks + """ + assert PK.k == f+1 + assert PK.l == N # noqa: E741 + received = defaultdict(dict) + outputQueue = defaultdict(lambda: Queue(1)) + + def _recv(): + while True: # main receive loop + logger.debug(f'entering loop', + extra={'nodeid': pid, 'epoch': '?'}) + # New shares for some round r, from sender i + (i, (_, r, sig)) = receive() + logger.debug(f'received i, _, r, sig: {i, _, r, sig}', + extra={'nodeid': pid, 'epoch': r}) + assert i in range(N) + assert r >= 0 + if i in received[r]: + print("redundant coin sig received", (sid, pid, i, r)) + continue + + h = PK.hash_message(str((sid, r))) + + # TODO: Accountability: Optimistically skip verifying + # each share, knowing evidence available later + try: + PK.verify_share(sig, i, h) + except AssertionError: + print("Signature share failed!", (sid, pid, i, r)) + continue + + received[r][i] = sig + + # After reaching the threshold, compute the output and + # make it available locally + logger.debug( + f'if len(received[r]) == f + 1: {len(received[r]) == f + 1}', + extra={'nodeid': pid, 'epoch': r}, + ) + if len(received[r]) == f + 1: + + # Verify and get the combined signature + sigs = dict(list(received[r].items())[:f+1]) + sig = PK.combine_shares(sigs) + assert PK.verify_signature(sig, h) + + # Compute the bit from the least bit of the hash + bit = hash(serialize(sig))[0] % N + logger.debug(f'put bit {bit} in output queue', + extra={'nodeid': pid, 'epoch': r}) + outputQueue[r].put_nowait(bit) + + # greenletPacker(Greenlet(_recv), 'shared_coin', (pid, N, f, broadcast, receive)).start() + Greenlet(_recv).start() + + def getCoin(round): + """Gets a coin. + + :param round: the epoch/round. + :returns: a coin. + + """ + # I have to do mapping to 1..l + h = PK.hash_message(str((sid, round))) + logger.debug(f"broadcast {('COIN', round, SK.sign(h))}", + extra={'nodeid': pid, 'epoch': round}) + broadcast(('COIN', round, SK.sign(h))) + return outputQueue[round].get() + + return getCoin diff --git a/honeybadgerbft/core/pbbroadcast.py b/honeybadgerbft/core/pbbroadcast.py new file mode 100644 index 00000000..f172ff10 --- /dev/null +++ b/honeybadgerbft/core/pbbroadcast.py @@ -0,0 +1,72 @@ +import logging +from gevent import Greenlet +from gevent.queue import Queue + +logger = logging.getLogger(__name__) + + +def pbbroadcast(id, j, pid, N, f, leader, input, receive, send, broadcast, PK, SK): + S = {} + output_queue = Queue(1) + input_value = [] + stop = False + + def _recv(): + (i, (_, cmd, v)) = receive() + assert i == leader + nonlocal stop + + logger.debug(f'follower received i, _, r, sig: {i, _, cmd, v}', + extra={'nodeid': pid, 'epoch': j}) + if cmd == 'send': + if stop == False: + h = PK.hash_message(str((id, v))) + stop = True + output_queue.put_nowait(v) + # logger.debug(f'follower acked i, id, ack, sig: {i, id, "ack", SK.sign(h)}', + # extra={'nodeid': pid, 'epoch': j}) + send(leader, (id, 'ack', SK.sign(h))) + + + else: + assert cmd == 'abandon' + stop = True + + def _leader_recv(): + while True: + (i, (_, cmd, v)) = receive() + if cmd == 'send': + h = PK.hash_message(str((id, v))) + send(leader, (id, 'ack', SK.sign(h))) + continue + (i, (_, ack, sig_share)) = (i, (_, cmd, v)) + logger.debug(f'leader received i, _, r, sig: {i, _, ack, sig_share}', + extra={'nodeid': pid, 'epoch': j}) + assert ack == 'ack' + h = PK.hash_message(str((id, input_value[0]))) + try: + PK.verify_share(sig_share, i, h) + except AssertionError: + print("Signature share failed!", (id, pid, i, j)) + continue + S[i] = sig_share + + if len(S) == 2 * f + 1: + sigs = dict(list(S.items())[:2 * f + 1]) + sig = PK.combine_shares(sigs) + assert PK.verify_signature(sig, h) + logger.debug(f'put sig {sig} in output queue', + extra={'nodeid': pid, 'epoch': j}) + output_queue.put_nowait(sig) + + if leader == pid: + # Todo: add signature value + v = input() + input_value.append(v) + Greenlet(_leader_recv).start() + broadcast((id, 'send', v)) + + else: + Greenlet(_recv).start() + + return output_queue.get() diff --git a/honeybadgerbft/core/pbbroadcast_4s.py b/honeybadgerbft/core/pbbroadcast_4s.py new file mode 100644 index 00000000..88d4cc33 --- /dev/null +++ b/honeybadgerbft/core/pbbroadcast_4s.py @@ -0,0 +1,115 @@ +import logging +from gevent import Greenlet +from gevent.queue import Queue + +logger = logging.getLogger(__name__) + + +def pbbroadcast_4s(id, pid, N, f, leader, input, receive, send, broadcast, PK, SK): + S = [{} for _ in range(4)] + output_queue = Queue(1) + key = Queue(1) + lock = Queue(1) + commit = Queue(1) + input_value = [] + stop = [False, False, False, False] + + def _recv(): + while True: + (i, (_, j, cmd, (v, sig_ex, sig_in))) = receive() + assert i == leader + assert _ == id + nonlocal stop + + logger.debug(f'follower received i, _, r, sig: {i, _, j, cmd, v, sig_ex, sig_in}', + extra={'nodeid': pid, 'epoch': j}) + if cmd == 'send': + if stop[j] == False and ex_sbc_validation(_, j, v, sig_ex, sig_in): + h = PK.hash_message(str((id, j, v))) + stop[j] = True + if j == 1: + key.put_nowait((id, (v, sig_in))) + elif j == 2: + lock.put_nowait((id, (v, sig_in))) + elif j == 3: + commit.put_nowait((id, (v, sig_in))) + output_queue.put_nowait(v) + logger.debug(f'follower acked i, id, ack, sig: {i, id, j, "ack", SK.sign(h)}', + extra={'nodeid': pid, 'epoch': j}) + send(leader, (id, j, 'ack', SK.sign(h))) + + + else: + assert cmd == 'abandon' + stop[j] = True + + def ex_sbc_validation(id, j, v, sig_ex, sig_in): + if j == 0 and ex_bc_validation(id, v, sig_ex): + return True + if j > 0 and threshold_validate(id, j - 1, v, sig_in): + return True + return False + + def ex_bc_validation(id, v, sig_ex): + # TODO: add external validation logic + return True + + def threshold_validate(id, j, v, sig_in): + h = PK.hash_message(str((id, j, v))) + try: + PK.verify_signature(sig_in, h) + return True + except AssertionError: + print("threshold_validate failure!") + return False + + def _leader_recv(): + while True: + (i, (_, j, cmd, v)) = receive() + + assert _ == id + if cmd == 'send': + logger.debug(f'leader received i, _, r, sig: {i, _, j, cmd, v}', + extra={'nodeid': pid, 'epoch': j}) + h = PK.hash_message(str((id, j, v[0]))) + send(leader, (id, j, 'ack', SK.sign(h))) + continue + + # if message command is ack + (i, (_, j, ack, sig_share)) = (i, (_, j, cmd, v)) + # logger.debug(f'leader received i, _, r, sig: {i, _, j, ack, sig_share}', + # extra={'nodeid': pid, 'epoch': j}) + assert ack == 'ack' + h = PK.hash_message(str((id, j, input_value[0]))) + try: + PK.verify_share(sig_share, i, h) + print("Signature share succeeded!", (id, pid, i, j, input_value[0])) + except AssertionError: + print("Signature share failed!", (id, pid, i, j, input_value[0])) + continue + S[j][i] = sig_share + + if len(S[j]) == 2 * f + 1: + sigs = dict(list(S[j].items())[:2 * f + 1]) + sig = PK.combine_shares(sigs) + assert PK.verify_signature(sig, h) + logger.debug(f'put sig {sig} in output queue', + extra={'nodeid': pid, 'epoch': j}) + output_queue.put_nowait(sig) + + if leader == pid: + v = input() + sig_ex = None + sig_in = None + input_value.append(v) + Greenlet(_leader_recv).start() + for j in range(4): + print("round %d broadcast start\n" % (j)) + broadcast((id, j, 'send', (v, sig_ex, sig_in))) + sig_in = output_queue.get() + return sig_in + + else: + Greenlet(_recv).start() + + return output_queue.get() diff --git a/honeybadgerbft/core/vaba.py b/honeybadgerbft/core/vaba.py new file mode 100644 index 00000000..277fcb20 --- /dev/null +++ b/honeybadgerbft/core/vaba.py @@ -0,0 +1,226 @@ +import logging + +import gevent +from gevent import Greenlet +from gevent.queue import Queue + +logger = logging.getLogger(__name__) +UPPERROUNDLIMIT = 3 + + +def vaba(id, pid, N, f, input, receive, send, broadcast, PK, SK, ex_ba_validation, election): + S = [[{} for _ in range(4)] for _ in range(UPPERROUNDLIMIT)] + L = [None for _ in range(UPPERROUNDLIMIT)] + broadcast_sig_queue = Queue(1) + broadcast_output_queue = Queue(1) + output = Queue(N) + view_change_count = [0 for _ in range(UPPERROUNDLIMIT)] + j = 0 + sig = [(0, None) for _ in range(UPPERROUNDLIMIT)] + Dkey = [[None] * UPPERROUNDLIMIT for _ in range(N)] + Dlock = [[None] * UPPERROUNDLIMIT for _ in range(N)] + Dcommit = [[None] * UPPERROUNDLIMIT for _ in range(N)] + v = [None for _ in range(UPPERROUNDLIMIT)] + v[0] = input() + key = (0,v[0],None) + lock = 0 + bc_done = [0 for _ in range(UPPERROUNDLIMIT)] + bc_skip = [{} for _ in range(UPPERROUNDLIMIT)] + stop = [[[False, False, False, False] for _ in range(UPPERROUNDLIMIT)] for _ in range(N)] + skip = [False for _ in range(UPPERROUNDLIMIT)] + + def handle_broadcast(m, nodeid): + ((_id, _pid, _j), rounds, (_v, (_sig_ex, _sig_in))) = m + assert _j == j + assert _id == id + assert _pid == nodeid + logger.debug( + f'follower received message "send": id, i ,j, rounds, v, sig_ex, sig_in: {_id, _pid, _j, rounds, _v, _sig_ex, _sig_in}', + extra={'nodeid': pid, 'epoch': rounds}) + if stop[nodeid][j][rounds] == False and ex_sbc_validation(_id, _pid, _j, rounds, _v, _sig_ex, _sig_in): + h = PK.hash_message(str(((_id, _pid, _j), rounds, _v))) + stop[nodeid][j][rounds] = True + if rounds == 1: + Dkey[_pid][j] = (_v, _sig_in) + elif rounds == 2: + Dlock[_pid][j] = (_v, _sig_in) + elif rounds == 3: + Dcommit[_pid][j] = (_v, _sig_in) + logger.debug(f'follower acked _pid, _id, rounds, ack, sig_share: {_pid, _id, rounds, "ack", SK.sign(h)}', + extra={'nodeid': pid, 'epoch': rounds}) + send(nodeid, ('ack', ((_id, _pid, j), rounds, SK.sign(h)))) + + def handle_ack(m, nodeid): + ((_id, _pid, _j), rounds, sig_share) = m + assert _pid == pid + logger.debug(f'leader received: {nodeid, "ack", _id, _pid, _j, rounds, sig_share}', + extra={'nodeid': pid, 'epoch': rounds}) + h = PK.hash_message(str(((_id, _pid, j), rounds, v[j]))) + if share_validate(sig_share, nodeid, h): + logger.debug(f'Signature share succeeded! {nodeid, _id, _pid, _j, rounds, sig_share}', + extra={'nodeid': pid, 'epoch': rounds}) + else: + logger.debug(f'Signature share failed! {nodeid, _id, _pid, _j, rounds, sig_share}', + extra={'nodeid': pid, 'epoch': rounds}) + + S[j][rounds][nodeid] = sig_share + + if len(S[j][rounds]) == 2 * f + 1: + sigs = dict(list(S[j][rounds].items())[:2 * f + 1]) + sig_combine = PK.combine_shares(sigs) + assert PK.verify_signature(sig_combine, h) + logger.debug(f'put sig {sig_combine} in broadcast_sig_queue', + extra={'nodeid': pid, 'epoch': rounds}) + broadcast_sig_queue.put_nowait(sig_combine) + + def handle_done(msg, nodeid): + (_id, _j, (_v, sig_combined)) = msg + assert _j == j + logger.debug(f'Received Done Message! {nodeid, _id, _v, sig_combined}', + extra={'nodeid': pid, 'epoch': j}) + h = PK.hash_message(str(((_id, nodeid, _j), 3, _v))) + if threshold_validate(h, sig_combined): + bc_done[j] += 1 + if bc_done[j] == 2 * f + 1: + h = PK.hash_message(str((_id, 'skip', j))) + broadcast(('skip_share', (j, SK.sign(h)))) + + def handle_skip_share(msg, nodeid): + (_j, sig_share) = msg + h = PK.hash_message(str((id, 'skip', j))) + assert _j == j + if share_validate(sig_share, nodeid, h): + logger.debug(f'Signature share succeeded! {nodeid, id, pid, j, sig_share}', + extra={'nodeid': pid, 'epoch': j}) + bc_skip[j][nodeid] = sig_share + if len(bc_skip[j]) == 2 * f + 1: + sigs = dict(list(bc_skip[j].items())[:2 * f + 1]) + sig_combine = PK.combine_shares(sigs) + assert PK.verify_signature(sig_combine, h) + logger.debug(f'broadcast skip message {id ,"skip", j, sig_combine}', + extra={'nodeid': pid, 'epoch': j}) + broadcast(('skip',(id,j,sig_combine))) + + def handle_skip(msg, nodeid): + (_id, _j, sig_combine) = msg + assert _id == id + assert _j == j + + h = PK.hash_message(str((id, 'skip', j))) + if threshold_validate(h, sig_combine): + logger.debug(f'Signature share succeeded! {nodeid, id, pid, j, sig_combine}', + extra={'nodeid': pid, 'epoch': j}) + if not skip[j]: + skip[j] = True + broadcast(('skip', (id, j, sig_combine))) + else: + skip[j] = True + + def handle_view_change(msg, nodeid): + nonlocal key, lock + (_id, _j, (v2, sig2),(v3, sig3),(v4, sig4)) = msg + assert _j == j + + view_change_count[j] += 1 + logger.debug(f'View change id, j, v2, v3, v4 {_id, _j, v2, v3, v4}', + extra={'nodeid': pid, 'epoch': j}) + if L[j] is None: + L[j] = election(j) + if v4 is not None and threshold_validate(PK.hash_message(str(((id, L[j], j), 2, v4))),sig4): + output.put_nowait(v4) + if v3 is not None and j > lock and threshold_validate(PK.hash_message(str(((id, L[j], j), 1, v3))),sig3): + lock = j + if v2 is not None and j > key[0] and threshold_validate(PK.hash_message(str(((id, L[j], j), 0, v2))),sig2): + key = (j, v2, sig2) + + + def _recv(): + while True: + (nodeid, (cmd, msg)) = receive() + + # message dispatcher + if cmd == 'send': + handle_broadcast(msg, nodeid) + elif cmd == 'ack': + handle_ack(msg, nodeid) + elif cmd == 'done': + handle_done(msg, nodeid) + elif cmd == 'skip_share': + handle_skip_share(msg, nodeid) + elif cmd == 'skip': + handle_skip(msg, nodeid) + elif cmd == 'view_change': + handle_view_change(msg, nodeid) + + def ex_sbc_validation(id, pid, j, rounds, v, sig_ex, sig_in): + if rounds == 0 and ex_bc_validation(id, pid, j, v, sig_ex): + return True + h = PK.hash_message(str(((id, pid, j), rounds - 1, v))) + if rounds > 0 and threshold_validate(h, sig_in): + return True + return False + + def ex_bc_validation(id, pid, j, v, sig_ex): + # TODO: add external validation logic + return True + + def threshold_validate(h, sig_in): + try: + PK.verify_signature(sig_in, h) + return True + except AssertionError: + print("threshold_validate failure!") + return False + + def share_validate(sig_share, nodeid, h): + try: + PK.verify_share(sig_share, nodeid, h) + return True + except AssertionError: + return False + + def pbbroadcast(): + nonlocal id, pid, j, v, sig + sig_in = None + for rounds in range(4): + print("View %d: rounds %d broadcast start\n" % (j, rounds)) + broadcast(('send', ((id, pid, j), rounds, (v[j], (sig[j], sig_in))))) + sig_in = broadcast_sig_queue.get() + broadcast_output_queue.put_nowait(sig_in) + + + Greenlet(_recv).start() + while True: + # Broadcast Phase + Greenlet(pbbroadcast).start() + + # wait for skip messages + while broadcast_output_queue.empty() and not skip[j]: + gevent.sleep(0) + if not skip[j]: + broadcast(('done', (id, j, (v[j], broadcast_output_queue.get())))) + while not skip[j]: + gevent.sleep(0) + # logger.debug(f'Broadcast Result Dkey: {Dkey}\n Dlock: {Dlock}\n Dcommit: {Dcommit}', + # extra={'nodeid': pid, 'epoch': j}) + + # abandon all the broadcast + for k in range(N): + for rounds in range(4): + stop[k][j][rounds] = True + + # leader election + if L[j] is None: + L[j] = election(j) + + broadcast(('view_change', (id, j, Dkey[L[j]][j],Dlock[L[j]][j], Dcommit[L[j]][j]))) + + while view_change_count[j] <= 2 * f + 1: + gevent.sleep(0) + + v[j + 1] = key[1] + sig[j + 1] = (key[0], key[2]) + j += 1 + + if not output.empty(): + return output.get() diff --git a/test/test_leaderelection.py b/test/test_leaderelection.py new file mode 100644 index 00000000..d5ebae2e --- /dev/null +++ b/test/test_leaderelection.py @@ -0,0 +1,139 @@ +import unittest +import gevent +import random +from gevent.queue import Queue +from honeybadgerbft.core.commoncoin import shared_coin +from honeybadgerbft.core.leaderelection import leader_election +from honeybadgerbft.crypto.threshsig.boldyreva import dealer + +def simple_router(N, maxdelay=0.01, seed=None): + """Builds a set of connected channels, with random delay + @return (receives, sends) + """ + rnd = random.Random(seed) + #if seed is not None: print 'ROUTER SEED: %f' % (seed,) + + queues = [Queue() for _ in range(N)] + + def makeBroadcast(i): + def _send(j, o): + delay = rnd.random() * maxdelay + #print 'BC %8s [%2d -> %2d] %2.1f' % (o[0], i, j, delay*1000) + gevent.spawn_later(delay, queues[j].put, (i,o)) + #queues[j].put((i, o)) + def _bc(o): + for j in range(N): _send(j, o) + return _bc + + def makeRecv(j): + def _recv(): + (i,o) = queues[j].get() + #print 'RECV %8s [%2d -> %2d]' % (o[0], i, j) + return (i,o) + return _recv + + return ([makeBroadcast(i) for i in range(N)], + [makeRecv(j) for j in range(N)]) + + +def byzantine_router(N, maxdelay=0.01, seed=None, **byzargs): + """Builds a set of connected channels, with random delay. + + :return: (receives, sends) endpoints. + """ + rnd = random.Random(seed) + #if seed is not None: print 'ROUTER SEED: %f' % (seed,) + + queues = [Queue() for _ in range(N)] + + def makeBroadcast(i): + def _send(j, o): + delay = rnd.random() * maxdelay + gevent.spawn_later(delay, queues[j].put, (i,o)) + def _bc(o): + for j in range(N): _send(j, o) + return _bc + + def makeRecv(j): + def _recv(): + return queues[j].get() + + def _recv_redundant(): + i, o = queues[j].get() + if i == 3 and o[1] == 1: + o = list(o) + o[1] -= 1 + o = tuple(o) + return (i,o) + + def _recv_fail_pk_verify_share(): + (i,o) = queues[j].get() + if i == 3 and o[1] == 1: + o = list(o) + o[1] += 1 + o = tuple(o) + return (i,o) + + if j == byzargs.get('node') and byzargs.get('sig_redundant'): + return _recv_redundant + if j == byzargs.get('node') and byzargs.get('sig_err'): + return _recv_fail_pk_verify_share + return _recv + + return ([makeBroadcast(i) for i in range(N)], + [makeRecv(j) for j in range(N)]) + + +### Test +def _test_leaderelection(N=4, f=1, seed=None): + # Generate keys + PK, SKs = dealer(N, f+1, random.seed()) + sid = 'sidABC' + # Test everything when runs are OK + #if seed is not None: print 'SEED:', seed + rnd = random.Random(seed) + router_seed = rnd.random() + sends, recvs = simple_router(N, seed=router_seed) + coins = [leader_election(sid, i, N, f, PK, SKs[i], sends[i], recvs[i]) for i in range(N)] + + for i in range(1): + threads = [gevent.spawn(c, i) for c in coins] + gevent.joinall(threads) + assert len(set([t.value for t in threads])) == 1 + return True + + +def test_leaderelection(): + _test_leaderelection(4,1) + + +def test_when_signature_share_verify_fails(): + N = 4 + f = 1 + seed = None + PK, SKs = dealer(N, f+1) + sid = 'sidA' + rnd = random.Random(seed) + router_seed = rnd.random() + sends, recvs = byzantine_router(N, seed=router_seed, node=2, sig_err=True) + coins = [shared_coin(sid, i, N, f, PK, SKs[i], sends[i], recvs[i]) for i in range(N)] + for i in range(2): + threads = [gevent.spawn(c, i) for c in coins] + gevent.joinall(threads) + assert len(set([t.value for t in threads])) == 1 + + +def test_when_redundant_signature_share_is_received(): + N = 4 + f = 1 + seed = None + PK, SKs = dealer(N, f+1) + sid = 'sidA' + rnd = random.Random(seed) + router_seed = rnd.random() + sends, recvs = byzantine_router(N, seed=seed, node=2, sig_redundant=True) + coins = [shared_coin(sid, i, N, f, PK, SKs[i], sends[i], recvs[i]) for i in range(N)] + for i in range(2): + threads = [gevent.spawn(c, i) for c in coins] + gevent.joinall(threads) + assert len(set([t.value for t in threads])) == 1 diff --git a/test/test_pbbroadcast.py b/test/test_pbbroadcast.py new file mode 100644 index 00000000..4f945309 --- /dev/null +++ b/test/test_pbbroadcast.py @@ -0,0 +1,279 @@ +import random + +import gevent +from gevent import Greenlet +from gevent.queue import Queue +from pytest import mark, raises + +from honeybadgerbft.core.pbbroadcast import pbbroadcast + +### RBC +from honeybadgerbft.crypto.threshsig.boldyreva import dealer + +def simple_router(N, maxdelay=0.01, seed=None): + """Builds a set of connected channels, with random delay + @return (receives, sends) + """ + rnd = random.Random(seed) + #if seed is not None: print 'ROUTER SEED: %f' % (seed,) + + queues = [Queue() for _ in range(N)] + + def makeSend(i): + def _send(j, o): + delay = rnd.random() * maxdelay + #print 'SEND %8s [%2d -> %2d] %.2f' % (o[0], i, j, delay) + gevent.spawn_later(delay, queues[j].put, (i,o)) + #queues[j].put((i, o)) + return _send + + def makeBroadcast(i): + def _send(j, o): + delay = rnd.random() * maxdelay + #print 'SEND %8s [%2d -> %2d] %.2f' % (o[0], i, j, delay) + gevent.spawn_later(delay, queues[j].put, (i,o)) + #queues[j].put((i, o)) + + def _bc(o): + for j in range(N): + _send(j,o) + return _bc + + def makeRecv(j): + def _recv(): + (i,o) = queues[j].get() + #print 'RECV %8s [%2d -> %2d]' % (o[0], i, j) + return (i,o) + return _recv + + return ([makeSend(i) for i in range(N)], + [makeBroadcast(k) for k in range(N)], + [makeRecv(j) for j in range(N)]) + + +# def byzantine_router(N, maxdelay=0.01, seed=None, **byzargs): +# """Builds a set of connected channels, with random delay, +# and possibly byzantine behavior. +# """ +# rnd = random.Random(seed) +# queues = [Queue() for _ in range(N)] +# +# def makeSend(i): +# def _send(j, o): +# delay = rnd.random() * maxdelay +# if i == byzargs.get('byznode'): +# if o[0] == byzargs.get('message_type'): +# screwed_up = list(o) +# if o[0] in ('VAL', 'ECHO'): +# screwed_up[3] = 'screw it' +# o = tuple(screwed_up) +# if byzargs.get('invalid_message_type'): +# byz_o = list(o) +# byz_o[0] = byzargs.get('invalid_message_type') +# o = tuple(byz_o) +# if (byzargs.get('fake_sender') and +# o[0] == 'VAL' and i == byzargs.get('byznode')): +# gevent.spawn_later(delay, queues[j].put, ((i + 1) % 4, o)) +# elif byzargs.get('slow_echo') and i != 2: +# if o[0] == 'READY': +# gevent.spawn_later(delay*0.001, queues[j].put, (i, o)) +# elif o[0] == 'ECHO': +# gevent.spawn_later(delay*10, queues[j].put, (i, o)) +# else: +# gevent.spawn_later(delay, queues[j].put, (i, o)) +# else: +# gevent.spawn_later(delay, queues[j].put, (i, o)) +# if byzargs.get('redundant_message_type') == o[0]: +# gevent.spawn_later(delay, queues[j].put, (i, o)) +# +# return _send +# +# def makeRecv(j): +# def _recv(): +# i, o = queues[j].get() +# return i ,o +# return _recv +# +# return ([makeSend(i) for i in range(N)], +# [makeRecv(j) for j in range(N)]) + + + + + +def _test_rbc2(N=4, f=1, leader=None, seed=None): + # Crash up to f nodes + #if seed is not None: print 'SEED:', seed + sid = 'sidA' + rnd = random.Random(seed) + router_seed = rnd.random() + if leader is None: leader = rnd.randint(0,N-1) + sends, broadcasts, recvs = simple_router(N, seed=router_seed) + threads = [] + PK, SKs = dealer(N, 2 * f + 1, random.seed()) + + leader_input = Queue(1) + + for i in range(N): + input = leader_input.get if i == leader else None + t = Greenlet(pbbroadcast, sid, 0, i, N, f, leader, input, recvs[i], sends[i], broadcasts[i], PK, SKs[i]) + t.start() + threads.append(t) + + m = b"Hello!asdfasdfasdfasdfasdfsadf" + leader_input.put(m) + gevent.sleep(0) # Let the leader get out its first message + + # Crash f of the nodes + crashed = set() + #print 'Leader:', leader + # for _ in range(f): + # i = rnd.choice(range(N)) + # crashed.add(i) + # threads[i].kill() + # threads[i].join() + #print 'Crashed:', crashed + gevent.joinall(threads) + for i,t in enumerate(threads): + if i not in crashed and i != leader: assert t.value == m + + +# @mark.parametrize('seed', range(20)) +# @mark.parametrize('N,f', ((4, 1), (5, 1), (8, 2))) +@mark.parametrize('seed', range(1)) +@mark.parametrize('N,f', [(4, 1)]) +def test_rbc2(N, f, seed): + _test_rbc2(N=N, f=f, seed=seed) +# +# +# @mark.parametrize('seed', range(20)) +# @mark.parametrize('tag', ('VAL', 'ECHO')) +# @mark.parametrize('N,f', ((4, 1), (5, 1), (8, 2))) +# def test_rbc_when_merkle_verify_fails(N, f, tag, seed): +# rnd = random.Random(seed) +# leader = rnd.randint(0, N-1) +# byznode = 1 +# sends, recvs = byzantine_router( +# N, seed=seed, byznode=byznode, message_type=tag) +# threads = [] +# leader_input = Queue(1) +# for pid in range(N): +# sid = 'sid{}'.format(leader) +# input = leader_input.get if pid == leader else None +# t = Greenlet(reliablebroadcast, sid, pid, N, f, leader, input, recvs[pid], sends[pid]) +# t.start() +# threads.append(t) +# +# m = b"Hello! This is a test message." +# leader_input.put(m) +# completed_greenlets = gevent.joinall(threads, timeout=0.5) +# expected_rbc_result = None if leader == byznode and tag == 'VAL' else m +# assert all([t.value == expected_rbc_result for t in threads]) +# +# +# @mark.parametrize('seed', range(3)) +# @mark.parametrize('N,f', ((4, 1), (5, 1), (8, 2))) +# def test_rbc_receives_val_from_sender_not_leader(N, f, seed): +# rnd = random.Random(seed) +# leader = rnd.randint(0, N-1) +# sends, recvs = byzantine_router( +# N, seed=seed, fake_sender=True, byznode=leader) +# threads = [] +# leader_input = Queue(1) +# for pid in range(N): +# sid = 'sid{}'.format(leader) +# input = leader_input.get if pid == leader else None +# t = Greenlet(reliablebroadcast, sid, pid, N, f, leader, input, recvs[pid], sends[pid]) +# t.start() +# threads.append(t) +# +# m = "Hello! This is a test message." +# leader_input.put(m) +# completed_greenlets = gevent.joinall(threads, timeout=0.5) +# expected_rbc_result = None +# assert all([t.value == expected_rbc_result for t in threads]) +# +# +# @mark.parametrize('seed', range(2)) +# @mark.parametrize('tag', ('ECHO', 'READY')) +# @mark.parametrize('N,f', ((4, 1),)) +# def test_rbc_with_redundant_message(N, f, tag, seed): +# rnd = random.Random(seed) +# leader = rnd.randint(0, N-1) +# sends, recvs = byzantine_router(N, seed=seed, redundant_message_type=tag) +# threads = [] +# leader_input = Queue(1) +# for pid in range(N): +# sid = 'sid{}'.format(leader) +# input = leader_input.get if pid == leader else None +# t = Greenlet(reliablebroadcast, sid, pid, N, f, +# leader, input, recvs[pid], sends[pid]) +# t.start() +# threads.append(t) +# +# m = b"Hello! This is a test message." +# leader_input.put(m) +# completed_greenlets = gevent.joinall(threads, timeout=0.5) +# expected_rbc_result = m +# assert all([t.value == expected_rbc_result for t in threads]) +# +# +# @mark.parametrize('seed', range(1)) +# @mark.parametrize('N,f', ((4, 1),)) +# def test_rbc_decode_in_echo_handling_step(N, f, seed): +# """The goal of this test is to simply force the decode operation +# to take place upon rception of an ECHO message, (when other +# necessary conditions are met), as opposed to the operation taking +# place upon reception of a READY message. +# +# The test is perhaps hackish at best, but nevertheless does achieve +# its intent. +# +# The test slows down the broadcasting of ECHO messages, meanwhile +# speeding up the broadcasting of READY messages. +# """ +# rnd = random.Random(seed) +# leader = rnd.randint(0, N-1) +# sends, recvs = byzantine_router(N, seed=seed, slow_echo=True) +# threads = [] +# leader_input = Queue(1) +# for pid in range(N): +# sid = 'sid{}'.format(leader) +# input = leader_input.get if pid == leader else None +# t = Greenlet(reliablebroadcast, sid, pid, N, f, +# leader, input, recvs[pid], sends[pid]) +# t.start() +# threads.append(t) +# +# m = b"Hello! This is a test message." +# leader_input.put(m) +# completed_greenlets = gevent.joinall(threads, timeout=1) +# expected_rbc_result = m +# assert all([t.value == expected_rbc_result for t in threads]) +# +# +# @mark.parametrize('seed', range(2)) +# @mark.parametrize('tag', ('CHECKTHISOUT!', 'LETSGO!')) +# @mark.parametrize('N,f', ((4, 1),)) +# def test_rbc_with_invalid_message(N, f, tag, seed): +# rnd = random.Random(seed) +# leader = rnd.randint(0, N-1) +# sends, recvs = byzantine_router(N, seed=seed, invalid_message_type=tag) +# threads = [] +# leader_input = Queue(1) +# for pid in range(N): +# sid = 'sid{}'.format(leader) +# input = leader_input.get if pid == leader else None +# t = Greenlet(reliablebroadcast, sid, pid, N, f, +# leader, input, recvs[pid], sends[pid]) +# t.start() +# threads.append(t) +# +# m = "Hello! This is a test message." +# leader_input.put(m) +# completed_greenlets = gevent.joinall(threads, timeout=0.5) +# expected_rbc_result = None +# assert all([t.value == expected_rbc_result for t in threads]) + + +# TODO: Test more edge cases, like Byzantine behavior diff --git a/test/test_pbbroadcast_4s.py b/test/test_pbbroadcast_4s.py new file mode 100644 index 00000000..cb55b599 --- /dev/null +++ b/test/test_pbbroadcast_4s.py @@ -0,0 +1,280 @@ +import random + +import gevent +from gevent import Greenlet +from gevent.queue import Queue +from pytest import mark, raises + + +from honeybadgerbft.core.pbbroadcast_4s import pbbroadcast_4s + +### RBC +from honeybadgerbft.crypto.threshsig.boldyreva import dealer + +def simple_router(N, maxdelay=0.01, seed=None): + """Builds a set of connected channels, with random delay + @return (receives, sends) + """ + rnd = random.Random(seed) + #if seed is not None: print 'ROUTER SEED: %f' % (seed,) + + queues = [Queue() for _ in range(N)] + + def makeSend(i): + def _send(j, o): + delay = rnd.random() * maxdelay + #print 'SEND %8s [%2d -> %2d] %.2f' % (o[0], i, j, delay) + gevent.spawn_later(delay, queues[j].put, (i,o)) + #queues[j].put((i, o)) + return _send + + def makeBroadcast(i): + def _send(j, o): + delay = rnd.random() * maxdelay + #print 'SEND %8s [%2d -> %2d] %.2f' % (o[0], i, j, delay) + gevent.spawn_later(delay, queues[j].put, (i,o)) + #queues[j].put((i, o)) + + def _bc(o): + for j in range(N): + _send(j,o) + return _bc + + def makeRecv(j): + def _recv(): + (i,o) = queues[j].get() + #print 'RECV %8s [%2d -> %2d]' % (o[0], i, j) + return (i,o) + return _recv + + return ([makeSend(i) for i in range(N)], + [makeBroadcast(k) for k in range(N)], + [makeRecv(j) for j in range(N)]) + + +# def byzantine_router(N, maxdelay=0.01, seed=None, **byzargs): +# """Builds a set of connected channels, with random delay, +# and possibly byzantine behavior. +# """ +# rnd = random.Random(seed) +# queues = [Queue() for _ in range(N)] +# +# def makeSend(i): +# def _send(j, o): +# delay = rnd.random() * maxdelay +# if i == byzargs.get('byznode'): +# if o[0] == byzargs.get('message_type'): +# screwed_up = list(o) +# if o[0] in ('VAL', 'ECHO'): +# screwed_up[3] = 'screw it' +# o = tuple(screwed_up) +# if byzargs.get('invalid_message_type'): +# byz_o = list(o) +# byz_o[0] = byzargs.get('invalid_message_type') +# o = tuple(byz_o) +# if (byzargs.get('fake_sender') and +# o[0] == 'VAL' and i == byzargs.get('byznode')): +# gevent.spawn_later(delay, queues[j].put, ((i + 1) % 4, o)) +# elif byzargs.get('slow_echo') and i != 2: +# if o[0] == 'READY': +# gevent.spawn_later(delay*0.001, queues[j].put, (i, o)) +# elif o[0] == 'ECHO': +# gevent.spawn_later(delay*10, queues[j].put, (i, o)) +# else: +# gevent.spawn_later(delay, queues[j].put, (i, o)) +# else: +# gevent.spawn_later(delay, queues[j].put, (i, o)) +# if byzargs.get('redundant_message_type') == o[0]: +# gevent.spawn_later(delay, queues[j].put, (i, o)) +# +# return _send +# +# def makeRecv(j): +# def _recv(): +# i, o = queues[j].get() +# return i ,o +# return _recv +# +# return ([makeSend(i) for i in range(N)], +# [makeRecv(j) for j in range(N)]) + + + + + +def _test_pbbroadcast_s4(N=4, f=1, leader=None, seed=None): + # Crash up to f nodes + #if seed is not None: print 'SEED:', seed + sid = 'sidA' + rnd = random.Random(seed) + router_seed = rnd.random() + if leader is None: leader = rnd.randint(0,N-1) + sends, broadcasts, recvs = simple_router(N, seed=router_seed) + threads = [] + PK, SKs = dealer(N, 2 * f + 1, random.seed()) + + leader_input = Queue(1) + + for i in range(N): + input = leader_input.get if i == leader else None + t = Greenlet(pbbroadcast_4s, sid, i, N, f, leader, input, recvs[i], sends[i], broadcasts[i], PK, SKs[i]) + t.start() + threads.append(t) + + m = b"Hello!asdfasdfasdfasdfasdfsadf" + leader_input.put(m) + gevent.sleep(0) # Let the leader get out its first message + + # Crash f of the nodes + crashed = set() + #print 'Leader:', leader + # for _ in range(f): + # i = rnd.choice(range(N)) + # crashed.add(i) + # threads[i].kill() + # threads[i].join() + #print 'Crashed:', crashed + gevent.joinall(threads) + for i,t in enumerate(threads): + if i not in crashed and i != leader: assert t.value == m + + +# @mark.parametrize('seed', range(20)) +# @mark.parametrize('N,f', ((4, 1), (5, 1), (8, 2))) +@mark.parametrize('seed', range(1)) +@mark.parametrize('N,f', [(4, 1)]) +def test_pbbroadcast_s4(N, f, seed): + _test_pbbroadcast_s4(N=N, f=f, seed=seed) +# +# +# @mark.parametrize('seed', range(20)) +# @mark.parametrize('tag', ('VAL', 'ECHO')) +# @mark.parametrize('N,f', ((4, 1), (5, 1), (8, 2))) +# def test_rbc_when_merkle_verify_fails(N, f, tag, seed): +# rnd = random.Random(seed) +# leader = rnd.randint(0, N-1) +# byznode = 1 +# sends, recvs = byzantine_router( +# N, seed=seed, byznode=byznode, message_type=tag) +# threads = [] +# leader_input = Queue(1) +# for pid in range(N): +# sid = 'sid{}'.format(leader) +# input = leader_input.get if pid == leader else None +# t = Greenlet(reliablebroadcast, sid, pid, N, f, leader, input, recvs[pid], sends[pid]) +# t.start() +# threads.append(t) +# +# m = b"Hello! This is a test message." +# leader_input.put(m) +# completed_greenlets = gevent.joinall(threads, timeout=0.5) +# expected_rbc_result = None if leader == byznode and tag == 'VAL' else m +# assert all([t.value == expected_rbc_result for t in threads]) +# +# +# @mark.parametrize('seed', range(3)) +# @mark.parametrize('N,f', ((4, 1), (5, 1), (8, 2))) +# def test_rbc_receives_val_from_sender_not_leader(N, f, seed): +# rnd = random.Random(seed) +# leader = rnd.randint(0, N-1) +# sends, recvs = byzantine_router( +# N, seed=seed, fake_sender=True, byznode=leader) +# threads = [] +# leader_input = Queue(1) +# for pid in range(N): +# sid = 'sid{}'.format(leader) +# input = leader_input.get if pid == leader else None +# t = Greenlet(reliablebroadcast, sid, pid, N, f, leader, input, recvs[pid], sends[pid]) +# t.start() +# threads.append(t) +# +# m = "Hello! This is a test message." +# leader_input.put(m) +# completed_greenlets = gevent.joinall(threads, timeout=0.5) +# expected_rbc_result = None +# assert all([t.value == expected_rbc_result for t in threads]) +# +# +# @mark.parametrize('seed', range(2)) +# @mark.parametrize('tag', ('ECHO', 'READY')) +# @mark.parametrize('N,f', ((4, 1),)) +# def test_rbc_with_redundant_message(N, f, tag, seed): +# rnd = random.Random(seed) +# leader = rnd.randint(0, N-1) +# sends, recvs = byzantine_router(N, seed=seed, redundant_message_type=tag) +# threads = [] +# leader_input = Queue(1) +# for pid in range(N): +# sid = 'sid{}'.format(leader) +# input = leader_input.get if pid == leader else None +# t = Greenlet(reliablebroadcast, sid, pid, N, f, +# leader, input, recvs[pid], sends[pid]) +# t.start() +# threads.append(t) +# +# m = b"Hello! This is a test message." +# leader_input.put(m) +# completed_greenlets = gevent.joinall(threads, timeout=0.5) +# expected_rbc_result = m +# assert all([t.value == expected_rbc_result for t in threads]) +# +# +# @mark.parametrize('seed', range(1)) +# @mark.parametrize('N,f', ((4, 1),)) +# def test_rbc_decode_in_echo_handling_step(N, f, seed): +# """The goal of this test is to simply force the decode operation +# to take place upon rception of an ECHO message, (when other +# necessary conditions are met), as opposed to the operation taking +# place upon reception of a READY message. +# +# The test is perhaps hackish at best, but nevertheless does achieve +# its intent. +# +# The test slows down the broadcasting of ECHO messages, meanwhile +# speeding up the broadcasting of READY messages. +# """ +# rnd = random.Random(seed) +# leader = rnd.randint(0, N-1) +# sends, recvs = byzantine_router(N, seed=seed, slow_echo=True) +# threads = [] +# leader_input = Queue(1) +# for pid in range(N): +# sid = 'sid{}'.format(leader) +# input = leader_input.get if pid == leader else None +# t = Greenlet(reliablebroadcast, sid, pid, N, f, +# leader, input, recvs[pid], sends[pid]) +# t.start() +# threads.append(t) +# +# m = b"Hello! This is a test message." +# leader_input.put(m) +# completed_greenlets = gevent.joinall(threads, timeout=1) +# expected_rbc_result = m +# assert all([t.value == expected_rbc_result for t in threads]) +# +# +# @mark.parametrize('seed', range(2)) +# @mark.parametrize('tag', ('CHECKTHISOUT!', 'LETSGO!')) +# @mark.parametrize('N,f', ((4, 1),)) +# def test_rbc_with_invalid_message(N, f, tag, seed): +# rnd = random.Random(seed) +# leader = rnd.randint(0, N-1) +# sends, recvs = byzantine_router(N, seed=seed, invalid_message_type=tag) +# threads = [] +# leader_input = Queue(1) +# for pid in range(N): +# sid = 'sid{}'.format(leader) +# input = leader_input.get if pid == leader else None +# t = Greenlet(reliablebroadcast, sid, pid, N, f, +# leader, input, recvs[pid], sends[pid]) +# t.start() +# threads.append(t) +# +# m = "Hello! This is a test message." +# leader_input.put(m) +# completed_greenlets = gevent.joinall(threads, timeout=0.5) +# expected_rbc_result = None +# assert all([t.value == expected_rbc_result for t in threads]) + + +# TODO: Test more edge cases, like Byzantine behavior diff --git a/test/test_vaba.py b/test/test_vaba.py new file mode 100644 index 00000000..4c5808dd --- /dev/null +++ b/test/test_vaba.py @@ -0,0 +1,295 @@ +import random + +import gevent +from gevent import Greenlet +from gevent.queue import Queue +from pytest import mark, raises +from datetime import datetime + + +from honeybadgerbft.core.vaba import vaba +from honeybadgerbft.core.leaderelection import leader_election + + +### RBC +from honeybadgerbft.crypto.threshsig.boldyreva import dealer + +def simple_router(N, maxdelay=0.01, seed=None): + """Builds a set of connected channels, with random delay + @return (receives, sends) + """ + rnd = random.Random(seed) + #if seed is not None: print 'ROUTER SEED: %f' % (seed,) + + queues = [Queue() for _ in range(N)] + + def makeSend(i): + def _send(j, o): + delay = rnd.random() * maxdelay + #print 'SEND %8s [%2d -> %2d] %.2f' % (o[0], i, j, delay) + gevent.spawn_later(delay, queues[j].put, (i,o)) + #queues[j].put((i, o)) + return _send + + def makeBroadcast(i): + def _send(j, o): + delay = rnd.random() * maxdelay + #print 'SEND %8s [%2d -> %2d] %.2f' % (o[0], i, j, delay) + gevent.spawn_later(delay, queues[j].put, (i,o)) + #queues[j].put((i, o)) + + def _bc(o): + for j in range(N): + _send(j,o) + return _bc + + def makeRecv(j): + def _recv(): + (i,o) = queues[j].get() + #print 'RECV %8s [%2d -> %2d]' % (o[0], i, j) + return (i,o) + return _recv + + return ([makeSend(i) for i in range(N)], + [makeBroadcast(k) for k in range(N)], + [makeRecv(j) for j in range(N)]) + + +# def byzantine_router(N, maxdelay=0.01, seed=None, **byzargs): +# """Builds a set of connected channels, with random delay, +# and possibly byzantine behavior. +# """ +# rnd = random.Random(seed) +# queues = [Queue() for _ in range(N)] +# +# def makeSend(i): +# def _send(j, o): +# delay = rnd.random() * maxdelay +# if i == byzargs.get('byznode'): +# if o[0] == byzargs.get('message_type'): +# screwed_up = list(o) +# if o[0] in ('VAL', 'ECHO'): +# screwed_up[3] = 'screw it' +# o = tuple(screwed_up) +# if byzargs.get('invalid_message_type'): +# byz_o = list(o) +# byz_o[0] = byzargs.get('invalid_message_type') +# o = tuple(byz_o) +# if (byzargs.get('fake_sender') and +# o[0] == 'VAL' and i == byzargs.get('byznode')): +# gevent.spawn_later(delay, queues[j].put, ((i + 1) % 4, o)) +# elif byzargs.get('slow_echo') and i != 2: +# if o[0] == 'READY': +# gevent.spawn_later(delay*0.001, queues[j].put, (i, o)) +# elif o[0] == 'ECHO': +# gevent.spawn_later(delay*10, queues[j].put, (i, o)) +# else: +# gevent.spawn_later(delay, queues[j].put, (i, o)) +# else: +# gevent.spawn_later(delay, queues[j].put, (i, o)) +# if byzargs.get('redundant_message_type') == o[0]: +# gevent.spawn_later(delay, queues[j].put, (i, o)) +# +# return _send +# +# def makeRecv(j): +# def _recv(): +# i, o = queues[j].get() +# return i ,o +# return _recv +# +# return ([makeSend(i) for i in range(N)], +# [makeRecv(j) for j in range(N)]) + +def _make_election(N=4, f=1, seed=None): + # Generate keys + PK, SKs = dealer(N, f+1, random.seed(datetime.now())) + sid = 'sidA' + rnd = random.Random(seed) + router_seed = rnd.random() + _, sends, recvs = simple_router(N, seed=seed) + return [leader_election(sid, i, N, f, PK, SKs[i], sends[i], recvs[i]) for i in range(N)] + + + +def _test_vaba(N=4, f=1, leader=None, seed=None): + # Crash up to f nodes + #if seed is not None: print 'SEED:', seed + sid = 'sidA' + rnd = random.Random(seed) + router_seed = rnd.random() + if leader is None: leader = rnd.randint(0,N-1) + sends, broadcasts, recvs = simple_router(N, seed=router_seed) + threads = [] + PK, SKs = dealer(N, 2 * f + 1, random.seed()) + + inputs = [Queue(1) for _ in range(N)] + m = b"Hello!VABA" + + elections = _make_election() + + def ex_ba_validation(): + return True + + for i in range(N): + input = inputs[i].get + t = Greenlet(vaba, sid, i, N, f, input, recvs[i], sends[i], broadcasts[i], PK, SKs[i], ex_ba_validation, elections[i]) + t.start() + threads.append(t) + inputs[i].put_nowait(m) + gevent.sleep(0) # Let the leader get out its first message + + # Crash f of the nodes + crashed = set() + # print 'Leader:', leader + # for _ in range(f): + # i = rnd.choice(range(N)) + # crashed.add(i) + # threads[i].kill() + # threads[i].join() + # print 'Crashed:', crashed + gevent.joinall(threads) + for i,t in enumerate(threads): + if i not in crashed: assert t.value == m + # assert len(set([t.value for t in threads])) == 1 + + +# @mark.parametrize('seed', range(20)) +# @mark.parametrize('N,f', ((4, 1), (5, 1), (8, 2))) +@mark.parametrize('seed', range(1)) +@mark.parametrize('N,f', [(4, 1)]) +def test_pbbroadcast_s4(N, f, seed): + _test_vaba(N=N, f=f, seed=seed) +# +# +# @mark.parametrize('seed', range(20)) +# @mark.parametrize('tag', ('VAL', 'ECHO')) +# @mark.parametrize('N,f', ((4, 1), (5, 1), (8, 2))) +# def test_rbc_when_merkle_verify_fails(N, f, tag, seed): +# rnd = random.Random(seed) +# leader = rnd.randint(0, N-1) +# byznode = 1 +# sends, recvs = byzantine_router( +# N, seed=seed, byznode=byznode, message_type=tag) +# threads = [] +# leader_input = Queue(1) +# for pid in range(N): +# sid = 'sid{}'.format(leader) +# input = leader_input.get if pid == leader else None +# t = Greenlet(reliablebroadcast, sid, pid, N, f, leader, input, recvs[pid], sends[pid]) +# t.start() +# threads.append(t) +# +# m = b"Hello! This is a test message." +# leader_input.put(m) +# completed_greenlets = gevent.joinall(threads, timeout=0.5) +# expected_rbc_result = None if leader == byznode and tag == 'VAL' else m +# assert all([t.value == expected_rbc_result for t in threads]) +# +# +# @mark.parametrize('seed', range(3)) +# @mark.parametrize('N,f', ((4, 1), (5, 1), (8, 2))) +# def test_rbc_receives_val_from_sender_not_leader(N, f, seed): +# rnd = random.Random(seed) +# leader = rnd.randint(0, N-1) +# sends, recvs = byzantine_router( +# N, seed=seed, fake_sender=True, byznode=leader) +# threads = [] +# leader_input = Queue(1) +# for pid in range(N): +# sid = 'sid{}'.format(leader) +# input = leader_input.get if pid == leader else None +# t = Greenlet(reliablebroadcast, sid, pid, N, f, leader, input, recvs[pid], sends[pid]) +# t.start() +# threads.append(t) +# +# m = "Hello! This is a test message." +# leader_input.put(m) +# completed_greenlets = gevent.joinall(threads, timeout=0.5) +# expected_rbc_result = None +# assert all([t.value == expected_rbc_result for t in threads]) +# +# +# @mark.parametrize('seed', range(2)) +# @mark.parametrize('tag', ('ECHO', 'READY')) +# @mark.parametrize('N,f', ((4, 1),)) +# def test_rbc_with_redundant_message(N, f, tag, seed): +# rnd = random.Random(seed) +# leader = rnd.randint(0, N-1) +# sends, recvs = byzantine_router(N, seed=seed, redundant_message_type=tag) +# threads = [] +# leader_input = Queue(1) +# for pid in range(N): +# sid = 'sid{}'.format(leader) +# input = leader_input.get if pid == leader else None +# t = Greenlet(reliablebroadcast, sid, pid, N, f, +# leader, input, recvs[pid], sends[pid]) +# t.start() +# threads.append(t) +# +# m = b"Hello! This is a test message." +# leader_input.put(m) +# completed_greenlets = gevent.joinall(threads, timeout=0.5) +# expected_rbc_result = m +# assert all([t.value == expected_rbc_result for t in threads]) +# +# +# @mark.parametrize('seed', range(1)) +# @mark.parametrize('N,f', ((4, 1),)) +# def test_rbc_decode_in_echo_handling_step(N, f, seed): +# """The goal of this test is to simply force the decode operation +# to take place upon rception of an ECHO message, (when other +# necessary conditions are met), as opposed to the operation taking +# place upon reception of a READY message. +# +# The test is perhaps hackish at best, but nevertheless does achieve +# its intent. +# +# The test slows down the broadcasting of ECHO messages, meanwhile +# speeding up the broadcasting of READY messages. +# """ +# rnd = random.Random(seed) +# leader = rnd.randint(0, N-1) +# sends, recvs = byzantine_router(N, seed=seed, slow_echo=True) +# threads = [] +# leader_input = Queue(1) +# for pid in range(N): +# sid = 'sid{}'.format(leader) +# input = leader_input.get if pid == leader else None +# t = Greenlet(reliablebroadcast, sid, pid, N, f, +# leader, input, recvs[pid], sends[pid]) +# t.start() +# threads.append(t) +# +# m = b"Hello! This is a test message." +# leader_input.put(m) +# completed_greenlets = gevent.joinall(threads, timeout=1) +# expected_rbc_result = m +# assert all([t.value == expected_rbc_result for t in threads]) +# +# +# @mark.parametrize('seed', range(2)) +# @mark.parametrize('tag', ('CHECKTHISOUT!', 'LETSGO!')) +# @mark.parametrize('N,f', ((4, 1),)) +# def test_rbc_with_invalid_message(N, f, tag, seed): +# rnd = random.Random(seed) +# leader = rnd.randint(0, N-1) +# sends, recvs = byzantine_router(N, seed=seed, invalid_message_type=tag) +# threads = [] +# leader_input = Queue(1) +# for pid in range(N): +# sid = 'sid{}'.format(leader) +# input = leader_input.get if pid == leader else None +# t = Greenlet(reliablebroadcast, sid, pid, N, f, +# leader, input, recvs[pid], sends[pid]) +# t.start() +# threads.append(t) +# +# m = "Hello! This is a test message." +# leader_input.put(m) +# completed_greenlets = gevent.joinall(threads, timeout=0.5) +# expected_rbc_result = None +# assert all([t.value == expected_rbc_result for t in threads]) + + +# TODO: Test more edge cases, like Byzantine behavior