示例#1
0
    def __enter__(self):
        n, t, my_id = self.n, self.t, self.my_id
        send, recv = self.get_send_recv(f"{self.tag}-AVSS")
        g, h, pks, sk = get_avss_params(n, t, my_id)
        crs = [g, h]
        self.avss_instance = HbAvssLight(pks, sk, crs, n, t, my_id, send, recv)
        self.avss_instance.__enter__()
        self.tasks.append(asyncio.create_task(self._runner()))

        send, recv = self.get_send_recv(f"{self.tag}-AVSS_VALUE_PROCESSOR")
        pk, sks = dealer(n, t + 1, seed=17)
        self.avss_value_processor = AvssValueProcessor(
            pk,
            sks[my_id],
            n,
            t,
            my_id,
            send,
            recv,
            self.avss_instance.output_queue.get,
            self.avss_value_processor_chunk_size,
        )
        self.avss_value_processor.__enter__()
        self.tasks.append(asyncio.create_task(self._extract()))
        return self
示例#2
0
async def test_add_to_output_queue(
    n, t, b, input_next_ids, output_next_ids, per_dealer_input, output_queue_vals
):
    """
    Each row is a test case.
    This test runs on only one node.
    This is to test the part of code which runs after ACS i.e. all nodes see the same
    view of the output list and they have to add a set of values in the output queue
    in the same order such that each batch of values has values from at least `n-t`
    dealers.



    input_next_idx:     This denotes the index which has the next value to be added to
                        the output queue for a particular dealer. Basically this
                        represents the state of already outputted values.
    output_next_ids:    This denotes the next index to return after the values have been
                        added to the output queue. This is to verify the output.
    per_dealer_input:   This is the count of values that have been received by this node
                        per dealer. We take this list and add as many values as the
                        counts in the output list. The output list is the one which gets
                        updated after ACS and all nodes have the same view of the output
                        list.
    output_queue_vals:  This is for verification. This denotes the order in which the
                        output values should be available in the queue. THe first
                        character is the node who dealt the value and the second
                        character is the count starting from 0 for each dealer.


    We want to verify the AVSS Value Proessor Output Order.

    |  0  |  1  |  2  |  3  |
    -------------------------
    | 00  | 10  | 20  | 30  |  => Each row is a batch.
    | 01  | 11  | 21  | 31  |
    |     | 12  | 22  | 32  |
    |     |     | 23  | 33  |
    |     |     |     | 34  |
    |     |     |     | 35  |

    Counts => [2, 3, 4, 6]
    Sorted in ascending order:
    max_values_to_output_per_dealer = pending_counts[t] = t_th idx value = 3
    Batch 1 => 00, 10, 20, 30
    Batch 2 => 01, 11, 21, 31
    Batch 3 => 12, 22, 32
    AVSS Value Proessor Output Order => 00, 10, 20, 30, 01, 11, 21, 31, 12, 22, 32
    """
    avss_proc = AvssValueProcessor(None, None, n, t, 0, None, None, None, b)
    avss_proc.next_idx_to_return_per_dealer = input_next_ids
    for i in range(n):
        for j in range(per_dealer_input[i]):
            avss_proc.outputs_per_dealer[i].append(f"{i}{j}")
    avss_proc._add_to_output_queue()
    assert output_next_ids == avss_proc.next_idx_to_return_per_dealer
    assert len(output_queue_vals) == avss_proc.output_queue.qsize()
    for val in output_queue_vals:
        assert val == avss_proc.output_queue.get_nowait()
示例#3
0
async def test_with_agreed_values_on_another_node_with_input(k, acs_outputs):
    n, t, sender_id = 4, 1, 1

    input_q = asyncio.Queue()
    with AvssValueProcessor(None, None, n, t, 0, None, None, input_q.get) as proc:
        proc._process_acs_output(acs_outputs)

        # 0th node has not received any AVSSed value from node 1 yet
        assert [len(proc.inputs_per_dealer[i]) for i in range(n)] == [0, 0, 0, 0]
        # 0th node should however know that one value sent by 1st node has been agreed
        assert [len(proc.outputs_per_dealer[i]) for i in range(n)] == [0, k, 0, 0]
        for i in range(k):
            assert type(proc.outputs_per_dealer[sender_id][i]) is asyncio.Future
            # This value is not yet available
            assert not proc.outputs_per_dealer[sender_id][i].done()

        # This is set by another method and should not have been updated.
        assert all(proc.next_idx_to_return_per_dealer[i] == 0 for i in range(n))

        for i in range(k):
            value = (sender_id, i, i)  # dealer_id, avss_id, value
            input_q.put_nowait(value)  # Make the 0th node receive the value now

        await asyncio.sleep(0.1)  # Give the recv loop a chance to run

        # 0th node has received the AVSSed value from node 1
        assert [len(proc.inputs_per_dealer[i]) for i in range(n)] == [0, k, 0, 0]
        # 0th node already knows that one value sent by 1st node has been agreed
        assert [len(proc.outputs_per_dealer[i]) for i in range(n)] == [0, k, 0, 0]
        for i in range(k):
            assert proc.outputs_per_dealer[sender_id][i].done()
            assert (await proc.outputs_per_dealer[sender_id][i]) == i

        # This is set by another method and should not have been updated.
        assert all(proc.next_idx_to_return_per_dealer[i] == 0 for i in range(n))
示例#4
0
async def test_acs_output(n, t, output_counts, next_idx, acs_outputs):
    my_id = 0

    input_q = asyncio.Queue()
    with AvssValueProcessor(None, None, n, t, my_id, None, None, input_q.get) as proc:
        proc._process_acs_output(acs_outputs)
        assert [len(proc.outputs_per_dealer[i]) for i in range(n)] == output_counts
        for i in range(n):
            for output in proc.outputs_per_dealer[i]:
                assert type(output) is asyncio.Future

        # These are set by another method and shouldn't have been updated.
        assert all(len(proc.inputs_per_dealer[i]) == 0 for i in range(n))
        assert proc.next_idx_to_return_per_dealer == next_idx
示例#5
0
async def test_with_agreed_values_on_same_node_with_input(k, acs_outputs):
    n, t, my_id = 4, 1, 0

    input_q = asyncio.Queue()
    with AvssValueProcessor(None, None, n, t, my_id, None, None, input_q.get) as proc:
        for i in range(k):
            value = (my_id, i, i)  # dealer_id, avss_id, value
            input_q.put_nowait(value)
        await asyncio.sleep(0.1)  # Give the recv loop a chance to run
        proc._process_acs_output(acs_outputs)

        assert [len(proc.inputs_per_dealer[i]) for i in range(n)] == [k, 0, 0, 0]
        assert [len(proc.outputs_per_dealer[i]) for i in range(n)] == [k, 0, 0, 0]
        for i in range(k):
            assert type(proc.outputs_per_dealer[my_id][i]) is asyncio.Future
            assert proc.outputs_per_dealer[my_id][i].done()
            assert (await proc.outputs_per_dealer[my_id][i]) == i

        # This is set by another method and should not have been updated.
        assert all(proc.next_idx_to_return_per_dealer[i] == 0 for i in range(n))
示例#6
0
async def test_avss_value_processor_with_diff_inputs(test_router):
    n, t = 4, 1
    sends, recvs, _ = test_router(n)

    node_inputs = [
        [(0, 0, "00"), (1, 0, "10"), (2, 0, "20")],
        [(0, 0, "01")],
        [(0, 0, "02"), (2, 0, "22"), (3, 0, "32")],
        [(3, 0, "33")],
    ]

    get_tasks = [None] * n
    pk, sks = dealer(n, t + 1)
    avss_value_procs = [None] * n
    input_qs = [None] * n
    with ExitStack() as stack:
        for i in range(n):
            input_qs[i] = asyncio.Queue()
            for node_input in node_inputs[i]:
                input_qs[i].put_nowait(node_input)

            avss_value_procs[i] = AvssValueProcessor(
                pk, sks[i], n, t, i, sends[i], recvs[i], input_qs[i].get
            )
            stack.enter_context(avss_value_procs[i])
            get_tasks[i] = asyncio.create_task(avss_value_procs[i].get())

        futures = await asyncio.gather(*get_tasks)
        for i, future in enumerate(futures):
            assert type(future) is asyncio.Future
            if i == 3:
                # node 3 does not receive the value dealt from node 0
                assert not future.done()
            else:
                # all other nodes have received the value dealt from node 0
                assert (await future) == f"0{i}"

        # this is based on node_inputs
        inputs = [[1, 1, 1, 0], [1, 0, 0, 0], [1, 0, 1, 1], [0, 0, 0, 1]]
        # this is based on the fact that only values dealt by 0 and 2 have been agreed
        outputs = [[1, 0, 1, 1], [1, 0, 1, 1], [1, 0, 1, 1], [1, 0, 1, 1]]

        for j, proc in enumerate(avss_value_procs):
            assert [len(proc.inputs_per_dealer[i]) for i in range(n)] == inputs[j]
            assert [len(proc.outputs_per_dealer[i]) for i in range(n)] == outputs[j]
            # 1 value was already retrieved, two values and 1 batch delimiter expected
            assert proc.output_queue.qsize() == 3
            # The values from 0, 2 and 3 have been added to the queue so their
            # next indices should be updated
            assert proc.next_idx_to_return_per_dealer == [1, 0, 1, 1]

        for i in range(n):
            if i in [0, 2]:
                # only nodes 0 and 2 have received the value dealt by 2
                # executing this sequentially also ensurs that ACS is not run again
                # since this value is already available
                assert (await (await avss_value_procs[i].get())) == f"2{i}"
            else:
                # nodes 1 and 3 have not received the value dealt by 2
                assert not (await avss_value_procs[i].get()).done()

        for j, proc in enumerate(avss_value_procs):
            assert [len(proc.inputs_per_dealer[i]) for i in range(n)] == inputs[j]
            assert [len(proc.outputs_per_dealer[i]) for i in range(n)] == outputs[j]
            # values from node 0 and 1 have been requested
            assert proc.next_idx_to_return_per_dealer == [1, 0, 1, 1]
示例#7
0
class PreProcessingBase(ABC):
    PERIOD_IN_SECONDS = 3

    def __init__(
        self,
        n,
        t,
        my_id,
        send,
        recv,
        tag,
        batch_size=10,
        avss_value_processor_chunk_size=1,
    ):
        self.n, self.t, self.my_id = n, t, my_id
        self.tag = tag
        self.avss_value_processor_chunk_size = avss_value_processor_chunk_size

        # Batch size of values to AVSS from a node
        self.batch_size = batch_size
        # Minimum number of values before triggering another set of AVSSes
        self.low_watermark = self.batch_size

        self.output_queue = asyncio.Queue()

        # Create a mechanism to split the `send` and `recv` channels based on `tag`
        subscribe_recv_task, subscribe = subscribe_recv(recv)
        self.tasks = [subscribe_recv_task]

        def _get_send_recv(tag):
            return wrap_send(tag, send), subscribe(tag)

        self.get_send_recv = _get_send_recv

    async def get(self):
        return await self.output_queue.get()

    @abstractmethod
    def _get_input_batch(self):
        raise NotImplementedError

    async def _trigger_and_wait_for_avss(self, avss_id):
        inputs = self._get_input_batch()
        assert type(inputs) in [tuple, list]
        avss_tasks = []
        avss_tasks.append(
            asyncio.create_task(
                self.avss_instance.avss_parallel(
                    avss_id, len(inputs), values=inputs, dealer_id=self.my_id
                )
            )
        )
        for i in range(self.n):
            if i != self.my_id:
                avss_tasks.append(
                    asyncio.create_task(
                        self.avss_instance.avss_parallel(
                            avss_id, len(inputs), dealer_id=i
                        )
                    )
                )
        await asyncio.gather(*avss_tasks)

    async def _runner(self):
        counter = 0
        logging.debug("[%d] Starting preprocessing runner: %s", self.my_id, self.tag)
        while True:
            # If the number of values in the output queue are below the lower
            # watermark then we want to trigger the next set of AVSSes.
            if self.output_queue.qsize() < self.low_watermark:
                logging.debug("[%d] Starting AVSS Batch: %d", self.my_id, counter)
                await self._trigger_and_wait_for_avss(counter)
                logging.debug("[%d] AVSS Batch Completed: %d", self.my_id, counter)
                counter += 1
            # Wait for sometime before checking again.
            await asyncio.sleep(PreProcessingBase.PERIOD_IN_SECONDS)

    async def _get_output_batch(self, group_size=1):
        for i in range(self.batch_size):
            batch = []
            while True:
                value = await self.avss_value_processor.get()
                if value is None:
                    break
                batch.append(value)
            assert len(batch) / group_size >= self.n - self.t
            assert len(batch) / group_size <= self.n
            yield batch

    async def _extract(self):
        raise NotImplementedError

    def __enter__(self):
        n, t, my_id = self.n, self.t, self.my_id
        send, recv = self.get_send_recv(f"{self.tag}-AVSS")
        g, h, pks, sk = get_avss_params(n, t, my_id)
        crs = [g, h]
        self.avss_instance = HbAvssLight(pks, sk, crs, n, t, my_id, send, recv)
        self.avss_instance.__enter__()
        self.tasks.append(asyncio.create_task(self._runner()))

        send, recv = self.get_send_recv(f"{self.tag}-AVSS_VALUE_PROCESSOR")
        pk, sks = dealer(n, t + 1, seed=17)
        self.avss_value_processor = AvssValueProcessor(
            pk,
            sks[my_id],
            n,
            t,
            my_id,
            send,
            recv,
            self.avss_instance.output_queue.get,
            self.avss_value_processor_chunk_size,
        )
        self.avss_value_processor.__enter__()
        self.tasks.append(asyncio.create_task(self._extract()))
        return self

    def __exit__(self, *args):
        for task in self.tasks:
            task.cancel()
        self.avss_instance.__exit__(*args)
        self.avss_value_processor.__exit__(*args)