├── .gitignore
├── .gitlab-ci.yml
├── Dockerfile
├── LICENSE.txt
├── README.md
├── aleph
├── __init__.py
├── actions
│ ├── __init__.py
│ ├── create_unit.py
│ └── poset_syncing.py
├── config.py
├── const.py
├── crypto
│ ├── __init__.py
│ ├── byte_utils.py
│ ├── crp.py
│ ├── keys.py
│ ├── threshold_coin.py
│ └── threshold_signatures.py
├── data_structures
│ ├── __init__.py
│ ├── poset.py
│ ├── tx.py
│ ├── unit.py
│ └── userDB.py
├── log_analyzer
│ ├── __init__.py
│ ├── dumped_poset_analyzer.py
│ ├── generate_bar_plots.py
│ ├── log_analyzer.py
│ └── run_analyzer.py
├── main.py
├── network
│ ├── __init__.py
│ ├── channel.py
│ ├── network.py
│ └── tx_traffic.py
├── process.py
├── test
│ ├── __init__.py
│ ├── data
│ │ ├── __init__.py
│ │ ├── light_nodes_public_keys
│ │ └── simple.dag
│ ├── test_message_signing.py
│ ├── test_poset_below.py
│ ├── test_poset_break_ties.py
│ ├── test_poset_compliance.py
│ ├── test_poset_create_unit.py
│ ├── test_poset_floor.py
│ ├── test_poset_level.py
│ ├── test_poset_threshold_coin.py
│ ├── test_threshold_coin.py
│ ├── test_threshold_signatures.py
│ └── test_unit_signing.py
└── utils
│ ├── __init__.py
│ ├── dag.py
│ ├── dag_utils.py
│ ├── generic_test.py
│ ├── plot.py
│ └── timer.py
├── benchmarks
├── signing.py
├── threshold_coin.py
└── toss_coin.py
├── docs
├── Makefile
├── build
│ └── html
│ │ ├── _static
│ │ ├── basic.css
│ │ ├── classic.css
│ │ ├── default.css
│ │ ├── doctools.js
│ │ ├── documentation_options.js
│ │ ├── jquery-3.2.1.js
│ │ ├── jquery.js
│ │ ├── language_data.js
│ │ ├── pygments.css
│ │ ├── searchtools.js
│ │ ├── sidebar.js
│ │ ├── underscore-1.3.1.js
│ │ └── underscore.js
│ │ ├── actions
│ │ ├── actions.html
│ │ ├── create_unit.html
│ │ └── poset_syncing.html
│ │ ├── crypto
│ │ ├── crp.html
│ │ ├── crypto.html
│ │ ├── keys.html
│ │ ├── threshold_coin.html
│ │ └── threshold_signatures.html
│ │ ├── data_structures
│ │ ├── data_structures.html
│ │ ├── poset.html
│ │ ├── tx.html
│ │ └── unit.html
│ │ ├── genindex.html
│ │ ├── index.html
│ │ ├── intro.html
│ │ ├── network
│ │ ├── channel.html
│ │ ├── net.html
│ │ └── network.html
│ │ ├── objects.inv
│ │ ├── process.html
│ │ ├── py-modindex.html
│ │ ├── search.html
│ │ └── searchindex.js
├── make.bat
└── source
│ ├── actions
│ ├── actions.rst
│ ├── create_unit.rst
│ └── poset_syncing.rst
│ ├── aleph_1920x1080.jpg
│ ├── conf.py
│ ├── crypto
│ ├── crp.rst
│ ├── crypto.rst
│ ├── keys.rst
│ ├── threshold_coin.rst
│ └── threshold_signatures.rst
│ ├── data_structures
│ ├── data_structures.rst
│ ├── poset.rst
│ ├── tx.rst
│ └── unit.rst
│ ├── index.rst
│ ├── intro.rst
│ ├── network
│ ├── channel.rst
│ ├── net.rst
│ └── network.rst
│ └── process.rst
├── examples
├── plot.py
├── random_forking_10_30_5.txt
└── random_nonforking_10_30.txt
├── experiments
├── aws
│ ├── aws_deps.sh
│ ├── const.py
│ ├── fabfile.py
│ ├── key_pairs
│ │ ├── aleph.fingerprint
│ │ ├── aleph.pem
│ │ └── aleph.pem.pub
│ ├── light_nodes_public_keys
│ ├── set_env.sh
│ ├── setup.sh
│ ├── shell.py
│ └── utils.py
└── simple_ec2_test.py
├── reports
├── external
│ └── generate_plots.py
└── internal
│ ├── bar_plots
│ ├── big
│ │ ├── bytes_sent_per_sec.png
│ │ ├── create_delay.png
│ │ ├── create_ord_del.png
│ │ ├── decision_height.png
│ │ ├── memory_MiB.png
│ │ ├── n_maximal.png
│ │ ├── n_parents.png
│ │ ├── poset_decision_height.png
│ │ ├── sync_delay.png
│ │ ├── time_create.png
│ │ ├── time_per_sync.png
│ │ ├── txps.png
│ │ └── units_sent_sync.png
│ ├── final_exp1
│ │ ├── bytes_sent_per_sec.png
│ │ ├── create_delay.png
│ │ ├── create_ord_del.png
│ │ ├── decision_height.png
│ │ ├── n_maximal.png
│ │ ├── n_parents.png
│ │ ├── n_units_decision.png
│ │ ├── poset_decision_height.png
│ │ ├── sync_delay.png
│ │ ├── time_per_sync.png
│ │ └── units_sent_sync.png
│ ├── final_exp2
│ │ ├── bytes_sent_per_sec.png
│ │ ├── create_ord_del.png
│ │ ├── memory_MiB.png
│ │ ├── time_per_sync.png
│ │ └── txps.png
│ └── levels.png
│ └── report.tex
├── setup.py
└── tests
├── byzantine_linear_ordering_network.py
├── byzantine_process.py
├── linear_ordering.py
├── linear_ordering_network.py
├── make_keys.py
├── process_network.py
├── read_dumped_poset.py
└── threshold_coin_distribution.py
/.gitignore:
--------------------------------------------------------------------------------
1 | __*
2 | !__init__.py
3 | *.log
4 | .cache
5 | *.png
6 | *.out
7 | *.txt
8 | *.aux
9 | *.bbl
10 | *.blg
11 | *.pdf
12 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | image: alpine:latest
2 |
3 | pages:
4 | script:
5 | - mkdir public
6 | - cp -r docs/build/html/* public
7 | artifacts:
8 | paths:
9 | - public
10 | only:
11 | - master
12 | - devel
13 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.7-alpine
2 |
3 | # prepare a build environment
4 | RUN apk update
5 | RUN apk add --no-cache \
6 | bash \
7 | make \
8 | gcc \
9 | g++ \
10 | musl-dev \
11 | bison \
12 | flex \
13 | mpc \
14 | gmp-dev \
15 | openssl-dev \
16 | git \
17 | libpng-dev \
18 | libffi-dev \
19 | freetype-dev
20 |
21 | WORKDIR /root
22 | RUN wget https://crypto.stanford.edu/pbc/files/pbc-0.5.14.tar.gz && \
23 | tar -xvf pbc-0.5.14.tar.gz && \
24 | cd pbc-0.5.14 && ./configure && make >/dev/null 2>&1 && make install
25 |
26 | RUN git clone https://github.com/JHUISI/charm.git && cd charm && ./configure.sh && make install >/dev/null 2>&1
27 |
28 | COPY setup.py /root
29 | RUN pip install --no-cache-dir --upgrade pip && pip install --no-cache-dir -e .
30 |
31 | # build succesfull
32 |
33 | COPY aleph /root/aleph
34 | WORKDIR /root/aleph
35 |
36 | ENV N_CORES=1 N=16
37 |
38 | RUN rm -rf __pycache__ unitTest/__pycache__
39 |
40 | CMD pytest -n $N_CORES unitTest
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Aleph Zero: Aleph Protocol Proof-of-Concept
2 |
3 | 
4 |
5 | Aleph is an asynchronous and Byzantine fault tolerant DAG-based consensus protocol aimed at ordering messages (transactions). It has been designed to operate continuously under conditions where there are no bound on message-delivery delay and under the assumption that there is a significant probability of malicious behavior, making it an excellent fit for blockchain-related applications. For more information, check [webpage](https://alephzero.org).
6 |
7 | This repository is a proof-of-concept implementation, it is not meant for production deployment. It is released as a reference for the main implementation in Go that will be published in the future. The initial version of the repository was based on an [old paper](https://arxiv.org/abs/1810.05256), while the more recent one relies on a [new paper](https://arxiv.org/abs/1908.05156).
8 |
9 | # Results from experiments run on AWS
10 |
11 | The following results come from experiments performed on 128 nodes of Amazon Web Services distributed uniformly between 8 different regions on 5 continents.
12 | We tested the performance under various loads of the system:
13 |
14 | |load|txps|latency|
15 | |---|---:|---:|
16 | | small | 72.1 | 5.1s|
17 | | big | 9476.7 | 15.8s |
18 | | heavy | 93419.0 | 20.5s |
19 |
20 | For more results, details of the setup, and discussions check our [reports](https://gitlab.com/alephledger/proof-of-concept/tree/master/reports).
21 |
22 | # Documentation
23 |
24 | [The documentation](https://alephledger.gitlab.io/proof-of-concept) describes only the algorithmic and technical parts of the implementation. For the conceptual understanding of the protocol and the implementation please read the above papers.
25 |
26 | # Merge requests
27 |
28 | This repository is only an exploratory playground and we are done with it now. No further developments will be made on our side. If you want to play with it, go ahead and fork it!
29 |
30 | # Installation
31 |
32 | The implementation requires Python 3.7. The following script creates a virtual environment for Python 3.7 in home directory and installs Aleph and its dependencies there. The script was prepared for Ubuntu 18.04.
33 | - run `bash experiments/aws/setup.sh`
34 | - enter py37 env `source $HOME/p37/bin/activate`
35 | - install aleph `pip install -e .`
36 | Remember to activate py37 env and `export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib` in every terminal you play with Aleph.
37 |
38 | # Unit tests
39 | To run unit tests please use the following command: `pytest-xdist -n $n_cores aleph`
40 |
41 | # Experiments
42 |
43 | There are two types of experiments that can be performed:
44 | 1. Local: run `python tests/linear_ordering_network.py`
45 | 2. Remote using AWS EC2:
46 | - Create an account on AWS and set up credentials and a default region as described [here](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html#configuration).
47 | - Then, go to `cd experiments/aws` and install additional packages `bash aws_deps.sh`.
48 | - Run `python shell.py`. This opens a shell with procedures orchestrating experiments. The main procedure is
49 | `run_protocol(n_processes, regions, restricted, instance_type)` that runs `n_processes` in specified `regions`, where some of the regions can be `restricted`, and uses EC2 machines of `instance_type`.
50 | - The most basic experiment can be run with `run_protocol(8, badger_regions(), {}, 't2.micro')`. It spawns 8 machines in 8 different regions on 5 continents. As of time of writing, AWS EC2 was providing users with a limited time of free usage of machines of type `t2.micro` and some quota for free storage and data transfer, so such an experiment can be conducted free of charge.
51 | - The parameters of the protocol are defined in the file `const.py`.
52 | - To check whether an experiment has finished, use the procedure `reached_max_level` that returns the number of instances that finished their run.
53 | - After the experiment is finished, the logs containing useful data of the experiment can be downloaded with `get_logs`.
54 |
55 | # Analyzing logs
56 | After collecting the logs, the performance can be analyzed as follows:
57 | 1. A single log with data on instance labeled with pid
58 | `python aleph/log_analyzer/run_analyzer.py aleph.log pid`
59 | 2. All logs
60 | `python aleph/log_analyzer/run_analyzer.py ALL log-dir [report-dir]`
61 |
62 | # License
63 | Aleph Python implementation is released under an LGPL version 3 license. See the `LICENSE.txt` for details.
64 |
--------------------------------------------------------------------------------
/aleph/__init__.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 |
--------------------------------------------------------------------------------
/aleph/actions/__init__.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | from .create_unit import create_unit
15 | from .poset_syncing import poset_info, units_to_send, dehash_parents
16 |
--------------------------------------------------------------------------------
/aleph/actions/create_unit.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | '''This module implements unit creation.'''
15 |
16 | import random
17 | import logging
18 |
19 | from aleph.data_structures.unit import Unit
20 | import aleph.const as consts
21 |
22 | def _create_dealing_unit(poset, creator_id, txs):
23 | U = Unit(creator_id, [], txs)
24 | if poset.use_tcoin:
25 | poset.add_tcoin_to_dealing_unit(U)
26 | return U
27 |
28 | def _nonforking_creator(poset, V):
29 | return len(poset.max_units_per_process[V.creator_id]) == 1
30 |
31 | def _parent_candidates(poset, parents, level):
32 | return list(reversed([V for V in poset.max_units if V.level == level and V not in parents and _nonforking_creator(poset, V)]))
33 |
34 | def _combine_parents(parents, new_parents):
35 | if not new_parents:
36 | return parents
37 | level = new_parents[0].level
38 | lower_parents = [V for V in parents if V.level <= level]
39 | higher_parents = [V for V in parents if V.level > level]
40 | return lower_parents + new_parents + higher_parents
41 |
42 | def _pick_more_parents(poset, parents, level, num_parents):
43 | parent_candidates = _parent_candidates(poset, parents, level)
44 | non_visible_primes = poset.get_all_prime_units_by_level(level)
45 | for V in parents:
46 | non_visible_primes = [W for W in non_visible_primes if not poset.below(W, V)]
47 | new_parents = []
48 | for V in parent_candidates:
49 | if len(new_parents) + len(parents) == num_parents:
50 | return _combine_parents(parents, new_parents)
51 | # the 3 lines below can be optimized (by a constant factor) but it is left as it is for simplicity
52 | if any(poset.below(W, V) for W in non_visible_primes):
53 | new_parents.append(V)
54 | non_visible_primes = [W for W in non_visible_primes if not poset.below(W, V)]
55 | return _combine_parents(parents, new_parents)
56 |
57 | def create_unit(poset, creator_id, txs, num_parents = None):
58 | '''
59 | Creates a new unit and stores txs in it. It uses only maximal units in the poset as parents (with the possible exception for the self_predecessor).
60 | This parent selection strategy has the following properties:
61 | - the created unit satisfies the expand-primes rule,
62 | - the main objective is for the new unit to see as many units as possible, but at the same time to not have too many parents
63 | NOTE: this strategy is most effective when num_parents is quite high, ideally unrestricted.
64 |
65 | :param Poset poset: poset in which the new unit is created
66 | :param int creator_id: id of process creating the new unit
67 | :param list txs: list of correct transactions
68 | :param int num_parents: maximum number of distinct parents (lower bound is always 2)
69 | :returns: the new-created unit, or None if it is not possible to create a compliant unit using this strategy
70 | '''
71 | num_parents = consts.N_PARENTS if num_parents is None else num_parents
72 | logger = logging.getLogger(consts.LOGGER_NAME)
73 | logger.info(f"create: {creator_id} attempting to create a unit.")
74 |
75 | if not poset.max_units_per_process[creator_id]:
76 | U = _create_dealing_unit(poset, creator_id, txs)
77 | logger.info(f"create: {creator_id} created its dealing unit.")
78 | return U
79 |
80 | assert len(poset.max_units_per_process[creator_id]) == 1, "It appears we have created a fork."
81 |
82 | # choose parents for the new unit
83 | U_self_predecessor = poset.max_units_per_process[creator_id][0]
84 | level_predecessor = U_self_predecessor.level
85 | level_poset = poset.level_reached
86 | parents = [U_self_predecessor]
87 | # we start by picking parents of level = level_poset
88 | parents = _pick_more_parents(poset, parents, level_poset, num_parents)
89 |
90 | if level_poset > level_predecessor and len(parents) < num_parents:
91 | # We expect here that level_poset = level_predecessor + 1.
92 | # We will add a bunch of parents of level = level_predecessor, the reason to do that is to make the new unit "see" as many
93 | # units as possible. This in turn is to make the poset "better connected" which helps in fast proofs of popularity for timing units.
94 | parents = _pick_more_parents(poset, parents, level_predecessor, num_parents)
95 |
96 | if len(parents) < 2:
97 | return None
98 |
99 | U = Unit(creator_id, parents, txs)
100 |
101 | if poset.use_tcoin:
102 | # we need to call prepare_unit to fill some fields necessary for determining which coin shares to add
103 | poset.prepare_unit(U)
104 | if poset.is_prime(U) and U.level >= consts.ADD_SHARES:
105 | poset.add_coin_shares(U)
106 |
107 | return U
108 |
--------------------------------------------------------------------------------
/aleph/config.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | import aleph.const as consts
15 |
16 | import charm.toolbox.pairinggroup
17 | PAIRING_GROUP = charm.toolbox.pairinggroup.PairingGroup('MNT224')
18 | # initialize group generator
19 | GENERATOR = PAIRING_GROUP.hash('gengen', charm.toolbox.pairinggroup.G2)
20 | # precompute exponentiation table to speed up computations
21 | GENERATOR.initPP()
22 |
23 | import pickle
24 | pickle.DEFAULT_PROTOCOL = 4
25 |
26 | import logging
27 | log_format = '[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s [%(filename)s:%(lineno)d]'
28 | logging.basicConfig(filename='other.log',
29 | level=logging.DEBUG,
30 | format=log_format,
31 | filemode='w')
32 | logger = logging.getLogger(consts.LOGGER_NAME)
33 | logger.setLevel(logging.DEBUG)
34 | fh = logging.FileHandler('aleph.log', mode='w')
35 | fh.setLevel(logging.DEBUG)
36 | fh.setFormatter(logging.Formatter(log_format))
37 | logger.addHandler(fh)
38 |
39 | import time
40 | # use gmt time for logging
41 | logging.Formatter.converter = time.gmtime
42 |
--------------------------------------------------------------------------------
/aleph/const.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | N_PARENTS = 10 # maximal number of parents a unit can have
15 |
16 | CREATE_DELAY = 2.0 # delay after creating a new unit
17 | STEP_SIZE = 0.14 # a number in (0,1) describing how aggresive is the create_delay adjusting mechanism, large = aggresive
18 |
19 | SYNC_INIT_DELAY = 0.015625 # delay after initianing a sync with other processes
20 |
21 |
22 | N_RECV_SYNC = 10 # number of allowed parallel received syncs
23 | N_INIT_SYNC = 10 # number of allowed parallel initiated syncs
24 |
25 | TXPU = 1 # number of transactions per unit
26 | TX_LIMIT = 1000000 # limit of all txs generated for one process
27 |
28 | LEVEL_LIMIT = 20 # maximal level after which process shuts down
29 | UNITS_LIMIT = None # maximal number of units that are constructed
30 | SYNCS_LIMIT = None # maximal number of syncs that are performed
31 |
32 | USE_TCOIN = 1 # whether to use threshold coin
33 | PRECOMPUTE_POPULARITY = 0 # precompute popularity proof to ease computational load of Poset.compute_vote procedure
34 | ADAPTIVE_DELAY = 1 # whether to use the adaptive strategy of determining create_delay
35 |
36 | VOTING_LEVEL = 3 # level at which the first voting round occurs, this is "t" from the write-up
37 | PI_DELTA_LEVEL = 12 # level at which to switch from the "fast" to the pi_delta algorithm
38 | ADD_SHARES = PI_DELTA_LEVEL - 1 # level at which to start adding coin shares to units, it's safe to make it PI_DELTA_LEVEL - 1
39 |
40 | HOST_IP = '127.0.0.1' # default ip address of a process
41 | HOST_PORT = 8888 # default port of incoming syncs
42 |
43 | LOGGER_NAME = 'aleph' # name of our logger and logfile
44 | TX_SOURCE = 'tx_source_gen' # source of txs
45 |
--------------------------------------------------------------------------------
/aleph/crypto/__init__.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | from .crp import CommonRandomPermutation
15 | from .byte_utils import xor, sha3_hash, extract_bit
16 | from .keys import SigningKey, VerifyKey
17 | from .threshold_coin import ThresholdCoin
18 | from .threshold_signatures import generate_keys, SecretKey, VerificationKey
19 |
--------------------------------------------------------------------------------
/aleph/crypto/byte_utils.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | import hashlib
15 |
16 |
17 | def extract_bit(bytestring, bit_index):
18 | '''
19 | Returns the bit_index'th bit of bytestring.
20 | It is assumed that bit_index < 8*len(bytestring) since bytestring is an array of bytes, each 8 bits long.
21 |
22 | :param bytes bytestring: bytestring from which the bit is to be extracted
23 | :param int bit_index: index of bit to be extracted.
24 | '''
25 | assert bit_index < 8*len(bytestring), "Attempting to extract a bit with too large of an index."
26 |
27 | byte_index = bit_index // 8
28 | index_within_byte = bit_index % 8
29 | mask = (1 << index_within_byte)
30 | if bytestring[byte_index] & mask:
31 | return 1
32 | return 0
33 |
34 |
35 | def sha3_hash(bytestring):
36 | '''
37 | Returns the sha3_256 hash of the bytestring.
38 |
39 | :param bytes bytestring: bytestring of which the hash is calculated.
40 | '''
41 | return hashlib.sha3_256(bytestring).digest()
42 |
43 |
44 | def xor(bytes1, bytes2):
45 | '''
46 | Returns a xor of two bytestrings bytes1, bytes2. The length of the result is the max of their lengths.
47 | If one of them is shorter it is rotated cyclically to obtain a string of matching length.
48 |
49 | :param bytes bytes1: xor's first argument
50 | :param bytes bytes2: xor's second argument
51 | '''
52 | assert bytes1 and bytes2, "An attempt to xor an empty bytestring"
53 | if len(bytes1) < len(bytes2):
54 | bytes1, bytes2 = bytes2, bytes1
55 |
56 | result = list(bytes1[:])
57 | len_bytes2 = len(bytes2)
58 | for i in range(len(bytes1)):
59 | result[i] ^= bytes2[i % len_bytes2]
60 |
61 | return bytes(result)
62 |
--------------------------------------------------------------------------------
/aleph/crypto/crp.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | from .byte_utils import xor
15 | from .byte_utils import sha3_hash
16 |
17 |
18 | class CommonRandomPermutation:
19 | '''
20 | This class represents common random permutation defined in the whitepaper.
21 |
22 | :param list public_keys: list of all public keys in hex format
23 | :param function hashing_function: hashing function used for generating common random permutations -- assumed to take input and output a bytestring if None hashing_function is provided then it uses sha3_hash i.e. hashlib.sha512
24 | '''
25 |
26 | def __init__(self, public_keys_hex, hashing_function=None):
27 | self.public_keys_hex = public_keys_hex
28 | self.hashing_function = hashing_function
29 | self.cache = {}
30 | self.cache_size = 20
31 |
32 | def _hash(self, bytestring):
33 | if self.hashing_function is None:
34 | return sha3_hash(bytestring)
35 | return self.hashing_function(bytestring)
36 |
37 | def add_to_cache(self, level, permutation):
38 | '''
39 | Adds an element to cache and shrinks the size of cache if the size becomes > 2*cache_size
40 |
41 | :param int level: level for which permutation is cached
42 | :param list permutation: permutation of the set n to be cached
43 | '''
44 |
45 | self.cache[level] = permutation
46 | if len(self.cache) > 2*self.cache_size:
47 | # the cache grew too large -- fetch its elements sorted from highest indices to lowest
48 | cache_items = list(reversed(sorted(self.cache.items())))
49 | # remove all but cache_size largest indices
50 | self.cache = dict(cache_items[:self.cache_size])
51 |
52 | def index_of(self, item, level):
53 | '''
54 | Outputs the position in the permutation crp[level] of item.
55 |
56 | :param int item: the number whose position we seek
57 | :param int level: the level of the permutation of interest
58 | :returns: the position of item in the permutation at this level
59 | '''
60 | sigma = self.__getitem__(level)
61 | return sigma.index(item)
62 |
63 | def __getitem__(self, level):
64 | '''
65 | Returns common random permutation for level level.
66 |
67 | :param int level: level for which the permutation is returned
68 | '''
69 | if level in self.cache:
70 | return self.cache[level]
71 |
72 | xor_all = bytes([0])
73 | for pk in self.public_keys_hex:
74 | xor_all = xor(xor_all, pk)
75 |
76 | seeds = [self._hash(pk + (str(level).encode())) for pk in self.public_keys_hex]
77 | seeds = [xor(xor_all, x) for x in seeds]
78 |
79 | indexed_seeds = zip(seeds, range(len(seeds)))
80 | indexed_seeds = sorted(list(indexed_seeds))
81 | permutation = [ind for (seed, ind) in indexed_seeds]
82 | self.add_to_cache(level, permutation)
83 | return permutation
84 |
--------------------------------------------------------------------------------
/aleph/crypto/keys.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | import nacl.signing
15 |
16 |
17 | class SigningKey:
18 | '''
19 | Implements signing (private) key.
20 |
21 | :param string seed_hex: seed used for instantiating this class; if None a key is generated.
22 | '''
23 |
24 | def __init__(self, seed_hex=None):
25 | if seed_hex is not None:
26 | self.secret_key = nacl.signing.SigningKey(seed_hex, encoder=nacl.encoding.HexEncoder)
27 | else:
28 | self.secret_key = nacl.signing.SigningKey.generate()
29 |
30 | def sign(self, message):
31 | '''
32 | Returns signed message.
33 |
34 | :param bytes message: message to be signed
35 | '''
36 |
37 | if isinstance(message, str):
38 | message = message.encode()
39 |
40 | return self.secret_key.sign(message).signature
41 |
42 | def to_hex(self):
43 | ''' Returns hex representation of the secret. It is used for serialization. '''
44 |
45 | return nacl.encoding.HexEncoder.encode(self.secret_key._seed)
46 |
47 |
48 | class VerifyKey:
49 | ''' Implements verification (public) key.
50 |
51 | :param nacl.signing.VerifyKey verify_key: key used to instantiate this class
52 | '''
53 |
54 | def __init__(self, verify_key):
55 |
56 | assert isinstance(verify_key, nacl.signing.VerifyKey), 'Wrong class used to instantiation, use nacl.signing.VerifyKey'
57 | self.verify_key = verify_key
58 |
59 | @staticmethod
60 | def from_SigningKey(secret_key):
61 | '''
62 | A factory generating object of this class from secret_key
63 |
64 | :param SigningKey secret_key: key used to create VerifyKey
65 | :returns: VerifyKey object
66 | '''
67 |
68 | return VerifyKey(secret_key.secret_key.verify_key)
69 |
70 | @staticmethod
71 | def from_hex(verify_key_hex):
72 | '''
73 | A vactory generating object of this class from hex representation
74 |
75 | :param string verify_key_hex: hex representation of VerifyKey
76 | :returns: VerifyKey object
77 | '''
78 |
79 | return VerifyKey(nacl.signing.VerifyKey(verify_key_hex, encoder=nacl.encoding.HexEncoder))
80 |
81 | def verify_signature(self, signature, message):
82 | '''
83 | Verifies signature of the message.
84 |
85 | :param bytes signature: signature to verify
86 | :param bytes message: message that was supposedly signed
87 | :returns: True if signature is correct, False otherwise
88 | '''
89 |
90 | if isinstance(message, str):
91 | message = message.encode()
92 | try:
93 | self.verify_key.verify(message, signature)
94 | except nacl.exceptions.BadSignatureError:
95 | return False
96 |
97 | return True
98 |
99 | def to_hex(self):
100 | '''
101 | Returns hex representation of this object. It is used for serialization.
102 | '''
103 |
104 | return self.verify_key.encode(encoder=nacl.encoding.HexEncoder)
105 |
--------------------------------------------------------------------------------
/aleph/crypto/threshold_coin.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | from base64 import decodebytes
15 | from random import randrange
16 | from charm.core.math.pairing import hashPair
17 |
18 |
19 | class ThresholdCoin:
20 | '''
21 | Implements dual threshold coin described in the whitepaper.
22 |
23 | :param int dealer_id: identification number of a process dealing this coin, from 0 to n-1
24 | :param int process_id: identification number of a process using this coin
25 | :param int n_processes: number of processes
26 | :param int threshold: number of shares required to toss the coin, has to satisfy n_processes//3 < threshold <= n_processes
27 | :param VerifyKey verification_key: key for combining shares
28 | :param SigningKey secret_key: key for generating a share of a coin toss
29 | '''
30 |
31 | def __init__(self, dealer_id, process_id, n_processes, threshold, secret_key, verification_key):
32 | self.dealer_id = dealer_id
33 | self.process_id = process_id
34 | self.n_processes = n_processes
35 | self.threshold = threshold
36 | self.secret_key = secret_key
37 | self.verification_key = verification_key
38 |
39 | def check_validity(self):
40 | '''
41 | Checks if this threshold coin is valid.
42 | '''
43 |
44 | msg_hash = self.verification_key.hash_fct(str(randrange(0, 1000)))
45 | coin_share = self.secret_key.generate_share(msg_hash)
46 |
47 | return self.verification_key.verify_share(coin_share, self.process_id, msg_hash)
48 |
49 | def create_coin_share(self, nonce):
50 | '''
51 | :param int nonce: nonce for the coin share
52 | :returns: coin share for the nonce
53 | '''
54 | msg_hash = self.verification_key.hash_fct(str(nonce))
55 | coin_share = self.secret_key.generate_share(msg_hash)
56 |
57 | return coin_share
58 |
59 | def verify_coin_share(self, coin_share, process_id, nonce):
60 | '''
61 | :param CoinShare coin_share: coin_share which validity is checked
62 | :param int process_id: identification number of a process that generated the coin_share
63 | :param int nonce: nonce for which the coin_share was generated
64 | :returns: True if coin_share is valid and False otherwise
65 | '''
66 |
67 | msg_hash = self.verification_key.hash_fct(str(nonce))
68 |
69 | return self.verification_key.verify_share(coin_share, process_id, msg_hash)
70 |
71 |
72 | def combine_coin_shares(self, shares, nonce):
73 | '''
74 | Combines the coin shares by forming a threshold signature and taking its 1st bit, subsequently it verifies the result.
75 | NOTE: combining shares should always succeed except when some of the shares were invalid or the dealer was dishonest, in which case the toss might be biased and should ideally be discarded
76 |
77 | :param dict shares: keys are processes ids, values are shares (group G1 elements)
78 | :param string nonce: the nonce the shares were created for -- necessary for verifying the result of combining
79 | :returns: pair (int, bool) : (coin toss in {0,1}) , (whether combining shares was succesful)
80 | '''
81 |
82 | # there are enough shares of a coin
83 | assert len(shares) == self.threshold, 'Not enough shares for combining'
84 |
85 | signature = self.verification_key.combine_shares(shares)
86 | hex_string = hashPair(signature).decode()
87 | # we just use the first bit as the coin toss
88 | coin_value = bytes.fromhex(hex_string)[0] % 2
89 |
90 | # verify the result
91 | nonce_hash = self.verification_key.hash_fct(nonce)
92 | correctness = self.verification_key.verify_signature(signature, nonce_hash)
93 |
94 | return (coin_value, correctness)
95 |
--------------------------------------------------------------------------------
/aleph/crypto/threshold_signatures.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | from functools import reduce
15 |
16 | from charm.toolbox.pairinggroup import ZR, G1, pair
17 |
18 | from aleph.config import PAIRING_GROUP, GENERATOR
19 |
20 | # The implementation is based on: Boldyreva, 2002 https://eprint.iacr.org/2002/118.pdf
21 | # Possible alternative implementation: Shoup, 2000 http://eprint.iacr.org/1999/011
22 |
23 |
24 | def generate_keys(n_parties, threshold):
25 | '''
26 | Generates one verification key and n_parties secret keys.
27 |
28 | :param int n_parties: number of parties that need secret keys
29 | :param int threshold: number of signature shares required for generating a signature
30 | '''
31 |
32 | group = PAIRING_GROUP
33 |
34 | # pick a generator of the group
35 | gen = GENERATOR
36 |
37 | # pick a set of coefficients
38 | coef = group.random(ZR, threshold)
39 | secret = coef[-1]
40 |
41 | # generate secret keys
42 | sks = [_poly(coef, x) for x in range(1, n_parties+1)]
43 |
44 | # generate underlying verification keys
45 | vk = gen ** secret
46 | vks = [gen ** scr for scr in sks]
47 |
48 | verification_key = VerificationKey(threshold, vk, vks)
49 | secret_keys = [SecretKey(sk) for sk in sks]
50 |
51 | return verification_key, secret_keys
52 |
53 |
54 | class VerificationKey:
55 | '''
56 | An object used for verifying shares and signatures and for combining shares into signatures.
57 |
58 | :param int threshold: number of signature shares needed to generate a signature
59 | :param int vk: global verification key
60 | :param list vks: verification keys corresponding to secret keys of all parties
61 | '''
62 |
63 | def __init__(self, threshold, vk, vks):
64 | self.threshold = threshold
65 | self.vk = vk
66 | self.vks = vks
67 |
68 | self.group = PAIRING_GROUP
69 | self.gen = GENERATOR
70 |
71 | def hash_fct(self, msg):
72 | '''
73 | Hash function used for hashing messages into group G1.
74 |
75 | :param string msg: message to be hashed
76 | :returns: element of G1 group
77 | '''
78 |
79 | return self.group.hash(msg, G1)
80 |
81 | def lagrange(self, S, i):
82 | '''
83 | Lagrange interpolation.
84 |
85 | :param list S: list of values for numerator
86 | :param int i: special value for denumerator
87 | '''
88 |
89 | one = self.group.init(ZR, 1)
90 | S = sorted(S)
91 | num = reduce(lambda x, y: x*y, [0 - j - 1 for j in S if j != i], one)
92 | den = reduce(lambda x, y: x*y, [i - j for j in S if j != i], one)
93 |
94 | return num/den
95 |
96 | def verify_share(self, share, i, msg_hash):
97 | '''
98 | Verifies if a share generated by i-th party is valid.
99 |
100 | :param int share: share of a signature of a hash of a message
101 | :param int i: index number of a party
102 | :param int msg_hash: hash of a message that is signed
103 | '''
104 | return pair(share, self.gen) == pair(msg_hash, self.vks[i])
105 |
106 | def verify_signature(self, signature, msg_hash):
107 | '''
108 | Verifies if signature is valid.
109 |
110 | :param int signature: signature of msg_hash to be chacked
111 | :param int msg_hash: hash of a message corresponding to signature.
112 | '''
113 | return pair(signature, self.gen) == pair(msg_hash, self.vk)
114 |
115 | def combine_shares(self, shares):
116 | '''
117 | Combines shares into a signature of a message.
118 |
119 | :param dict shares: shares of a signature to be produced
120 | '''
121 | assert len(shares) == self.threshold
122 | R = shares.keys()
123 | return reduce(lambda x,y: x*y, [share ** self.lagrange(R, i) for i, share in shares.items()], 1)
124 |
125 | def hash_msg(self, msg):
126 | '''
127 | Hashes a message before signing.
128 |
129 | :param bytes msg: message to be hashed
130 | '''
131 | return self.hash_fct(msg)
132 |
133 |
134 | class SecretKey:
135 | '''
136 | An object used for generating shares of a signature of a message.
137 |
138 | :param int sk: secret used for signing
139 | '''
140 |
141 | def __init__(self, sk):
142 |
143 | self.sk = sk
144 |
145 | def generate_share(self, msg_hash):
146 | '''
147 | Generates a share of a signature of a hash of a message.
148 |
149 | :param int msg_hash: hash of a message which signature share is generated
150 | '''
151 | return msg_hash ** self.sk
152 |
153 | def _poly(coefs, x):
154 | '''
155 | Evaluates a polynomial given by coefficients at some point.
156 |
157 | :param list coefs: list of coefficients
158 | :param int x: evaluation point
159 | '''
160 | return reduce(lambda y, coef: x*y+coef, coefs, 0)
161 |
--------------------------------------------------------------------------------
/aleph/data_structures/__init__.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | '''
15 | Data structures used in the protocol.
16 | '''
17 |
18 | from .poset import Poset
19 | from .userDB import UserDB
20 | from .unit import Unit, pretty_hash
21 | from .tx import Tx
22 |
--------------------------------------------------------------------------------
/aleph/data_structures/tx.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | class Tx(object):
15 | '''A class representing a single transaction, that is an act of sending some number of tokens from one user to another.'''
16 | '''
17 | This class stores a transactions issued by some user
18 |
19 | :param str issuer: public key of the issuer of the transaction
20 | :param str receiver: public key of the receiver of the transaction
21 | :param int amount: amount to be sent to the receiver
22 | '''
23 |
24 | __slots__ = ['issuer', 'receiver', 'amount']
25 |
26 | def __init__(self, issuer, receiver, amount):
27 | self.issuer = issuer
28 | self.receiver = receiver
29 | self.amount = amount
30 |
31 |
32 | def __getstate__(self):
33 | return (self.issuer, self.receiver, self.amount)
34 |
35 |
36 | def __setstate__(self, state):
37 | self.issuer, self.receiver, self.amount = state
38 |
39 |
40 | def __str__(self):
41 | tx_string = 'Issuer: ' + str(self.issuer) + '\n'
42 | tx_string += 'Receiver: ' + str(self.receiver) + '\n'
43 | tx_string += 'Amount: ' + str(self.amount) + '\n'
44 | return tx_string
45 |
46 | __repr__ = __str__
47 |
48 |
49 | def __eq__(self, other):
50 | return (isinstance(other, Tx) and self.issuer == other.issuer
51 | and self.receiver == other.receiver
52 | and self.amount == other.amount)
53 |
54 |
55 | def __hash__(self):
56 | return hash(str(self))
57 |
--------------------------------------------------------------------------------
/aleph/data_structures/userDB.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | import logging
15 |
16 |
17 | class UserDB:
18 | '''
19 | This class is used to store information about user accounts: their balances and last succesful transactions.
20 | '''
21 |
22 |
23 | def __init__(self, initial_balances_and_indices = []):
24 | '''
25 | Creates a user data base that contains user balances (by public key) and their last validated transaction.
26 | :param list initial_balances_and_indices: a list of triples consisting of a user's public key, their intial balance and the index of the last transaction performed by them
27 | '''
28 | self.user_balance = {}
29 | self.user_last_transaction_index = {}
30 | for public_key, balance, index in initial_balances_and_indices:
31 | self.user_balance[public_key] = balance
32 | self.user_last_transaction_index[public_key] = index
33 |
34 |
35 | def account_balance(self, user_public_key):
36 | '''
37 | Get the balance of the given user.
38 | :param str user_public_key: the public key of the user
39 | '''
40 | return self.user_balance.get(user_public_key, 0)
41 |
42 |
43 | def last_transaction(self, user_public_key):
44 | '''
45 | Get the index of the last transaction issued by the given user.
46 | :param str user_public_key: the public key of the user
47 | '''
48 | return self.user_last_transaction_index.get(user_public_key, -1)
49 |
50 |
51 | def check_transaction_correctness(self, tx):
52 | '''
53 | Check the correctness of a given transaction.
54 | :param Tx tx: the transaction to check
55 | :returns: True if the transaction has index one higher than the last transaction made by its issuer and the balance allows for the transaction, False otherwise
56 | '''
57 | issuer_balance = self.user_balance.get(tx.issuer, 0)
58 | issuer_last_transaction = self.user_last_transaction_index.get(tx.issuer, -1)
59 | return tx.amount >= 0 and issuer_balance >= tx.amount and tx.index == issuer_last_transaction + 1
60 |
61 |
62 | def apply_transaction(self, tx):
63 | '''
64 | Performs the given transaction if it is valid.
65 | :param Tx tx: the transaction to perform
66 | '''
67 | if self.check_transaction_correctness(tx):
68 | self.user_balance[tx.issuer] -= tx.amount
69 | self.user_balance[tx.receiver] += tx.amount
70 | self.user_last_transaction_index[tx.issuer] += 1
71 |
72 |
--------------------------------------------------------------------------------
/aleph/log_analyzer/__init__.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | '''
15 | Tools for extracting statistics from logs.
16 | '''
17 |
18 | from .log_analyzer import LogAnalyzer
19 |
--------------------------------------------------------------------------------
/aleph/log_analyzer/run_analyzer.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | from aleph.log_analyzer import LogAnalyzer
15 | import os
16 | import sys
17 |
18 | def prepare_common_stats(process_stats, rep_dir):
19 | '''
20 | Write basic stats common to all processes to a file.
21 | '''
22 | # NOTE: the txps in this stat is calculated as the ratio:
23 | # (total n of txs validated by timing units up to lvl L) / (time to find the timing unit at lvl L)
24 | # where L is the highest level for which a timing unit has been established
25 |
26 | n_stats = len(process_stats)
27 | rep_path = os.path.join(rep_dir, "common-stats.txt")
28 | with open(rep_path, "w") as rep_file:
29 |
30 | fields = ['latency', 'txps']
31 | header = ''.join( s.ljust(20) for s in ['name', 'median', 'min (proc_id)', 'max (proc_id)'])
32 | print(header)
33 | rep_file.write(header + '\n')
34 | for field in fields:
35 | process_stats.sort(key = lambda x: x[field])
36 | line = field.ljust(20)
37 | median = process_stats[n_stats//2][field]
38 | line += (f'{median:.3f}').ljust(20)
39 | min_val, min_proc = process_stats[0][field], process_stats[0]['process_id']
40 | line += (f'{min_val:.3f} ({min_proc})').ljust(20)
41 | max_val, max_proc = process_stats[-1][field], process_stats[-1]['process_id']
42 | line += (f'{max_val:.3f} ({max_proc})').ljust(20)
43 | print(line)
44 | rep_file.write(line + '\n')
45 |
46 |
47 | def print_help():
48 | print( "Use one of:\n"
49 | "1) python run_analyzer.py ALL logs_dir reports_dir\n"
50 | " Analyzes all logs in logs_dir and writes report to reports_dir\n"
51 | " If reports_dir is not provided it uses reports_dir = logs_dir\n"
52 | "2) python run_analyzer.py log_file [process_id]\n"
53 | " Analyzes the log_file using process_id (optional)\n"
54 | " Providing process_id is mandatory if the log is shared by multiple processes\n"
55 | )
56 |
57 |
58 | def analyze_one_log():
59 | path = sys.argv[1]
60 | if len(sys.argv) == 3:
61 | process_id = int(sys.argv[2])
62 | else:
63 | print('No process id provided -- assuming that the log comes from one process only.')
64 | process_id = None
65 |
66 | analyzer = LogAnalyzer(path, process_id, generate_plots = False)
67 | if not analyzer.analyze():
68 | print('Failed because the log does not even contain the Process start message.')
69 | sys.exit(0)
70 | process_id = analyzer.process_id
71 | analyzer.prepare_basic_report('.')
72 | analyzer.prepare_report_per_process('.')
73 |
74 |
75 | def analyze_all_dir():
76 | log_dir = sys.argv[2]
77 | if len(sys.argv) > 3:
78 | rep_dir = sys.argv[3]
79 | same_dir = False
80 | else:
81 | rep_dir = log_dir
82 | same_dir = True
83 |
84 | if not os.path.isdir(log_dir):
85 | print(f"No such directory {log_dir}.")
86 | sys.exit(0)
87 |
88 | print("Entering.", log_dir)
89 |
90 | if not os.path.isdir(rep_dir):
91 | print(f"No such directory {rep_dir}. Creating.")
92 | os.makedirs(rep_dir, exist_ok=True)
93 |
94 | if same_dir and os.path.isdir(os.path.join(rep_dir, 'txt-basic')):
95 | print("Already analyzed. Skipping.")
96 | return
97 |
98 | list_logs = os.listdir(log_dir)
99 | # do not parse other.log etc.
100 | list_logs = sorted([log_file for log_file in list_logs if os.path.basename(log_file).find("aleph") != -1])
101 | process_stats = []
102 | for ind, log_name in enumerate(list_logs):
103 |
104 | path = os.path.join(log_dir, log_name)
105 | print(f'Analyzing {path}...')
106 | if ind == 0:
107 | generate_plots = True
108 | print('Will generate plots only for this log file.')
109 | else:
110 | generate_plots = False
111 |
112 | analyzer = LogAnalyzer(path, generate_plots = generate_plots)
113 | if not analyzer.analyze():
114 | print('Failed because the log does not even contain the Process start message.')
115 | continue
116 | process_id = analyzer.process_id
117 | print(f"{ind}: Process' {process_id} log analyzed.\n")
118 | analyzer.prepare_basic_report(rep_dir)
119 | analyzer.prepare_report_per_process(rep_dir)
120 |
121 | stats = {'process_id' : process_id}
122 | stats['latency'] = analyzer.get_unit_latency()
123 | stats['txps'] = analyzer.get_txps_till_last_timing_unit()
124 | process_stats.append(stats)
125 |
126 | prepare_common_stats(process_stats, rep_dir)
127 |
128 |
129 |
130 | def parse_args_and_run():
131 | if len(sys.argv) >= 3 and sys.argv[1] == 'ALL':
132 | analyze_all_dir()
133 | elif len(sys.argv) in [2,3]:
134 | analyze_one_log()
135 | else:
136 | print_help()
137 |
138 |
139 | if __name__ == '__main__':
140 | parse_args_and_run()
141 |
142 |
--------------------------------------------------------------------------------
/aleph/main.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | import asyncio
15 | import logging
16 | import multiprocessing
17 | import random
18 | import sys
19 |
20 | from optparse import OptionParser
21 |
22 | from aleph.crypto.keys import SigningKey, VerifyKey
23 | from aleph.data_structures import UserDB, Tx
24 | from aleph.network import tx_listener, tx_source_gen
25 | from aleph.process import Process
26 |
27 | import aleph.const as consts
28 |
29 |
30 | def _read_ip_addresses(ip_addresses_path):
31 | with open(ip_addresses_path, 'r') as f:
32 | return [line[:-1] for line in f]
33 |
34 |
35 | def _read_signing_keys(signing_keys_path):
36 | with open(signing_keys_path, 'r') as f:
37 | hexes = [line[:-1].encode() for line in f]
38 | return [SigningKey(hexed) for hexed in hexes]
39 |
40 |
41 | def _sort_and_get_my_pid(public_keys, signing_keys, my_ip, ip_addresses):
42 | ind = ip_addresses.index(my_ip)
43 | my_pk = public_keys[ind]
44 |
45 | pk_hexes = [pk.to_hex() for pk in public_keys]
46 | arg_sort = [i for i, _ in sorted(enumerate(pk_hexes), key = lambda x: x[1])]
47 | public_keys = [public_keys[i] for i in arg_sort]
48 | signing_keys = [signing_keys[i] for i in arg_sort]
49 | ip_addresses = [ip_addresses[i] for i in arg_sort]
50 |
51 | return public_keys.index(my_pk), public_keys, signing_keys, ip_addresses
52 |
53 |
54 | def _log_consts():
55 | logger = logging.getLogger(consts.LOGGER_NAME)
56 | consts_names = ['N_PARENTS', 'USE_TCOIN', 'CREATE_DELAY', 'SYNC_INIT_DELAY', 'TXPU', 'LEVEL_LIMIT', 'UNITS_LIMIT']
57 | consts_values = []
58 | for const_name in consts_names:
59 | consts_values.append(f'{const_name}={consts.__dict__[const_name]}')
60 | logger.info('; '.join(consts_values))
61 |
62 |
63 | async def main():
64 | '''
65 | A task to run as a single member of the Aleph committee.
66 | '''
67 | _log_consts()
68 |
69 | signing_keys = _read_signing_keys('signing_keys')
70 | ip_addresses = _read_ip_addresses('ip_addresses')
71 | with open('my_ip', 'r') as f:
72 | my_ip = f.readline().strip()
73 |
74 | assert len(ip_addresses) == len(signing_keys), 'number of hosts and signing keys dont match!!!'
75 | public_keys = [VerifyKey.from_SigningKey(sk) for sk in signing_keys]
76 |
77 | process_id, public_keys, signing_keys, ip_addresses = _sort_and_get_my_pid(public_keys, signing_keys, my_ip, ip_addresses)
78 | addresses = [(ip, consts.HOST_PORT) for ip in ip_addresses]
79 |
80 | sk, pk = signing_keys[process_id], public_keys[process_id]
81 |
82 | n_processes = len(ip_addresses)
83 | userDB = None
84 |
85 | recv_address = None
86 | if consts.TX_SOURCE == 'tx_source_gen':
87 | tx_source = tx_source_gen(consts.TX_LIMIT, consts.TXPU, process_id)
88 | else:
89 | tx_source = tx_listener
90 |
91 | process = Process(n_processes,
92 | process_id,
93 | sk, pk,
94 | addresses,
95 | public_keys,
96 | recv_address,
97 | userDB,
98 | tx_source)
99 |
100 | await process.run()
101 |
102 |
103 | if __name__ == '__main__':
104 | asyncio.run(main())
105 |
--------------------------------------------------------------------------------
/aleph/network/__init__.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | from .network import Network
15 | from .tx_traffic import tx_listener, tx_source_gen, tx_generator
16 |
--------------------------------------------------------------------------------
/aleph/network/channel.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | import asyncio
15 | import logging
16 |
17 | import aleph.const as consts
18 |
19 |
20 | class RejectException(Exception):
21 | pass
22 |
23 |
24 | class Channel:
25 | ''' Simple class representing an asynchronous communication channel through the network. Suitable for both a case when I am initiating connection (see open()) or when someone else does that (see connect()).
26 |
27 | Class representing an asynchronous communication channel through the network. Suitable for both a case when we are
28 | initiating a connection (see open()) or when someone else does that (see connect()).
29 | :param int owner_id: process ID of the owner of the channel
30 | :param int peer_id: process ID of the recipient (other end) of the channel
31 | :param tuple peer_address: pair (IP, port) with peer's address
32 |
33 | '''
34 |
35 | REJECT = b'REJECT'
36 |
37 | def __init__(self, owner_id, peer_id, peer_address):
38 | self.owner_id = owner_id
39 | self.peer_id = peer_id
40 | self.address = peer_address
41 | self.active = asyncio.Event()
42 | self.in_use = asyncio.Lock()
43 | self.reader = None
44 | self.writer = None
45 |
46 | @staticmethod
47 | async def receive_handshake(reader, writer):
48 | '''Receive handshake from an unknown process and find out their process_id.'''
49 |
50 | data = await reader.readuntil()
51 | return int(data.rstrip(b'\n'))
52 |
53 | def send_handshake(self):
54 | '''Introduce yourself (send process_id) to newly connected process.'''
55 |
56 | self.writer.write(f'{self.owner_id}\n'.encode())
57 |
58 | def connect(self, reader, writer):
59 | '''Activate channel by connecting existing reader and writer to it.'''
60 |
61 | self.reader = reader
62 | self.writer = writer
63 | self.active.set()
64 |
65 | def is_active(self):
66 | '''Guess what...'''
67 |
68 | return self.active.is_set()
69 |
70 | async def reject(self):
71 | '''Send REJECT message.'''
72 |
73 | if self.is_active():
74 | self.writer.write(self.REJECT)
75 | self.writer.write(b'\n')
76 | await self.writer.drain()
77 |
78 | async def read(self):
79 | '''
80 | Read data from the channel.
81 | If channel has not been activated yet, block and wait.
82 | If obtained REJECT message, raise RejectException.
83 | '''
84 |
85 | await self.active.wait()
86 |
87 | data = await self.reader.readuntil()
88 | data = data.rstrip(b'\n')
89 | if data == self.REJECT:
90 | raise RejectException()
91 | n_bytes = int(data)
92 | data = await self.reader.readexactly(n_bytes)
93 | return data
94 |
95 | async def write(self, data):
96 | '''Send data through the channel.'''
97 |
98 | if not self.is_active():
99 | await self.open()
100 |
101 | self.writer.write(str(len(data)).encode())
102 | self.writer.write(b'\n')
103 | self.writer.write(data)
104 | await self.writer.drain()
105 |
106 | async def open(self):
107 | '''Activate the channel by opening a new connection to the peer.'''
108 |
109 | logger = logging.getLogger(consts.LOGGER_NAME)
110 | logger.info(f'sync_open_chan {self.owner_id} | Opening connection to {self.peer_id} - start')
111 | while True:
112 | fut = asyncio.open_connection(*self.address)
113 | try:
114 | self.reader, self.writer = await asyncio.wait_for(fut, timeout=1)
115 | break
116 | except (asyncio.TimeoutError, ConnectionRefusedError):
117 | logger.info(f'sync_open_chan {self.owner_id} | Opening connection to {self.peer_id} - failed')
118 | await asyncio.sleep(1)
119 |
120 | logger.info(f'sync_open_chan {self.owner_id} | Opening connection to {self.peer_id} - succeded')
121 |
122 | self.send_handshake()
123 | self.active.set()
124 |
125 | async def close(self):
126 | '''Close the channel (unused for now).'''
127 |
128 | if self.is_active():
129 | self.writer.close()
130 | await self.writer.wait_closed()
131 | self.reader, self.writer = None, None
132 | self.active.clear()
133 |
--------------------------------------------------------------------------------
/aleph/network/tx_traffic.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | import logging
15 | import pickle
16 | import pkg_resources
17 | import random
18 | import socket
19 | import socketserver
20 |
21 | from time import sleep, perf_counter as get_time
22 |
23 | from aleph.data_structures import Tx
24 | import aleph.const as consts
25 |
26 |
27 | def tx_listener(listen_addr, queue):
28 | '''
29 | Start a TCP server on *listen_addr* and listen for incoming transactions.
30 | Put batches (lists) of transactions on *queue* every consts.CREATE_DELAY seconds or when the tx limit per unit (consts.TXPU)
31 | is reached, whichever comes first.
32 | '''
33 | tx_buffer = []
34 | prev_put_time = get_time()
35 |
36 | class TCPHandler(socketserver.BaseRequestHandler):
37 | def handle(self):
38 | nonlocal tx_buffer, prev_put_time
39 | logger.info(f'tx_server_establish | Connection with {self.client_address}')
40 |
41 | data = self.request.recv(1024)
42 | tx = pickle.loads(data)
43 | tx_buffer.append(tx)
44 |
45 | logger.info(f'tx_server_receive | Received from {self.client_address}')
46 |
47 | if len(tx_buffer) == consts.TXPU or get_time() - prev_put_time > consts.CREATE_DELAY:
48 | prev_put_time = get_time()
49 | logger.info(f'tx_server_enqueue | Putting {len(tx_buffer)} txs on queue')
50 | queue.put(tx_buffer)
51 | tx_buffer = []
52 |
53 | logger = logging.getLogger(consts.LOGGER_NAME)
54 | logger.info(f'tx_server_start | Starting on {listen_addr}')
55 |
56 | with socketserver.TCPServer(listen_addr, TCPHandler) as server:
57 | server.serve_forever()
58 |
59 |
60 | def tx_source_gen(batch_size, txpu, seed=27091986, filename=None):
61 | '''
62 | Produces a simple tx generator.
63 | :param int batch_size: number of txs for a process to input into the system.
64 | :param int txpu: number of txs to be included in one unit.
65 | :param int seed: seed for random generator.
66 | :param str filename: path to file with names of txs senders and recipients (each in a separate line).
67 | If None, aleph/test/data/light_nodes_public_keys is used.
68 | '''
69 |
70 | if filename is None:
71 | filename = pkg_resources.resource_stream('aleph.test.data', 'light_nodes_public_keys')
72 | lines = [line.decode() for line in filename.readlines()]
73 | else:
74 | with open(filename) as f:
75 | lines = f.readlines()
76 | ln_public_keys = [line.rstrip('\n') for line in lines]
77 |
78 | def _tx_source(dummy, queue):
79 | '''
80 | Generates transactions in bundles of size txpu till batch_size is reached
81 | :param dummy: dummy argument needed for comatibility of args list with tx_listener()
82 | :param queue queue: queue for newly generated txs
83 | '''
84 | # ensure that batches are different
85 | random.seed(seed)
86 |
87 | produced = 0
88 | while produced < batch_size:
89 | offset = min(txpu, batch_size - produced)
90 |
91 | txs = []
92 | for _ in range(offset):
93 | source, target = random.sample(ln_public_keys, k=2)
94 | amount = random.randint(1, 30000)
95 | txs.append(Tx(source, target, amount))
96 |
97 | produced += offset
98 | queue.put(txs, block=True)
99 |
100 | return _tx_source
101 |
102 |
103 | def tx_generator(committee_addresses, signing_keys, txps):
104 | '''
105 | Generate random transactions indefinitely.
106 | Issuer and receiver of each transactions are random integers 0 <= i <= len(signing_keys)
107 |
108 | NOTE: this is an old version of TX generator that is still used by some tests.
109 | '''
110 | n_light_nodes = len(signing_keys)
111 | counter = 0
112 | starts = get_time()
113 |
114 | while True:
115 | if counter == txps:
116 | counter = 0
117 | delta = get_time() - starts
118 | if delta < 1:
119 | sleep(1 - delta)
120 | starts = get_time()
121 |
122 | issuer_id = random.randrange(0, n_light_nodes)
123 | receiver_id = random.choice([uid for uid in range(n_light_nodes) if uid != issuer_id])
124 | amount = random.randrange(1, 100)
125 | tx = Tx(issuer_id, receiver_id, amount)
126 | data = pickle.dumps(tx)
127 |
128 | sent = False
129 | while not sent:
130 | com_addr = random.choice(committee_addresses)
131 |
132 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
133 | try:
134 | sock.connect(com_addr)
135 | sock.sendall(data)
136 | sent = True
137 | except:
138 | # assume any failure means that others have stopped
139 | return
140 |
141 | counter += 1
142 |
--------------------------------------------------------------------------------
/aleph/test/__init__.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 |
--------------------------------------------------------------------------------
/aleph/test/data/__init__.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 |
--------------------------------------------------------------------------------
/aleph/test/data/simple.dag:
--------------------------------------------------------------------------------
1 | format standard
2 | 4
3 | 0- 0
4 | 1- 1
5 | 2- 2
6 | 1-R 1 1- 2-
7 | 2-J 2 2- 1-R
8 | 3- 3
9 | 3-M 3 3- 1-R
10 | 1-Z 1 1-R 3-M
11 | 2-R 2 2-J 1-Z
12 | 1-I 1 1-Z 2-R
13 | 0-X 0 0- 1-I
14 | 2-A 2 2-R 1-I
15 | 3-V 3 3-M 2-R
16 | 1-X 1 1-I 3-V
17 | 2-S 2 2-A 1-X
18 | 1-V 1 1-X 2-S
19 | 3-J 3 3-V 1-X
20 | 2-D 2 2-S 3-J
21 | 3-R 3 3-J 2-D
22 | 1-T 1 1-V 3-R
23 | 0-O 0 0-X 1-T
24 | 2-O 2 2-D 1-T
25 | 1-J 1 1-T 2-O
26 | 3-X 3 3-R 1-T
27 | 2-B 2 2-O 3-X
28 | 3-L 3 3-X 2-B
29 | 1-VG 1 1-J 3-L
30 | 2-L 2 2-B 1-VG
31 | 3-K 3 3-L 2-L
32 | 1-Q 1 1-VG 2-L
33 |
--------------------------------------------------------------------------------
/aleph/test/test_message_signing.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | import random
15 | import string
16 |
17 | from aleph.crypto import SigningKey, VerifyKey
18 |
19 | def test_true():
20 | '''
21 | Test whether correct message signatures are accepted.
22 | '''
23 | sk = SigningKey()
24 | vk = VerifyKey.from_SigningKey(sk)
25 | for _ in range(10):
26 | n = random.randint(50,200)
27 | msg = ''.join(random.choices(string.printable, k=n))
28 | if random.randint(0,1):
29 | msg = msg.encode()
30 | sign = sk.sign(msg)
31 | assert vk.verify_signature(sign, msg)
32 |
33 |
34 | def test_false():
35 | '''
36 | Test whether incorrect message signatures are rejected.
37 | '''
38 | sk = SigningKey()
39 | vk = VerifyKey.from_SigningKey(sk)
40 | for _ in range(10):
41 | n = random.randint(50,200)
42 | msg = ''.join(random.choices(string.printable, k=n))
43 | if random.randint(0,1):
44 | msg = msg.encode()
45 | sign = sk.sign(msg)
46 | # drop one random character in the message to produce a different message
47 | k = random.randint(0, n-1)
48 | msg = msg[:k] + msg[k+1:]
49 | assert not vk.verify_signature(sign, msg)
50 |
--------------------------------------------------------------------------------
/aleph/test/test_poset_below.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | from aleph.data_structures import Unit, Poset
15 | from aleph.utils import dag_utils
16 | from aleph.utils.generic_test import generate_and_check_dag
17 | import random
18 |
19 |
20 | def test_trivial_single_level_below():
21 | '''
22 | A simple manual test that makes sure that poset.max_units_per_process are correctly set when only dealing units are present
23 | and that after adding one additional unit to the poset, Poset.below returns correct results.
24 | '''
25 |
26 | # start by creating a poset with 4 processes and add the 4 dealing units to it
27 | n_processes = 4
28 | poset = Poset(n_processes = n_processes, use_tcoin = False)
29 | dealing_units_per_process = [Unit(creator_id = i, parents = [], txs = []) for i in range(n_processes)]
30 | for i in range(n_processes):
31 | poset.prepare_unit(dealing_units_per_process[i])
32 | poset.add_unit(dealing_units_per_process[i])
33 |
34 | # make sure that dealing units are set as maximal units in the poset
35 | for i in range(n_processes):
36 | assert poset.max_units_per_process[i][0] is dealing_units_per_process[i]
37 |
38 | U0 = poset.max_units_per_process[0][0]
39 | U1 = poset.max_units_per_process[1][0]
40 | U2 = poset.max_units_per_process[2][0]
41 | U3 = poset.max_units_per_process[3][0]
42 |
43 | # add one new unit with parents U0, U1 to the poset
44 | U = Unit(creator_id = 0, parents = [U0, U1], txs = [])
45 | poset.prepare_unit(U)
46 | poset.add_unit(U)
47 |
48 | assert poset.below(U0, U)
49 | assert poset.above(U, U0)
50 | assert poset.below(U1, U)
51 | assert not poset.below(U2, U)
52 | assert not poset.below(U3, U)
53 |
54 |
55 | def test_small_nonforking_below():
56 | generate_and_check_dag(
57 | checks= [check_all_pairs_below],
58 | n_processes = 5,
59 | n_units = 50,
60 | repetitions = 30,
61 | )
62 |
63 |
64 | def test_large_nonforking_below():
65 | generate_and_check_dag(
66 | checks= [check_all_pairs_below],
67 | n_processes = 100,
68 | n_units = 200,
69 | repetitions = 1,
70 | )
71 |
72 |
73 | def test_small_forking_below():
74 | n_processes = 5
75 | generate_and_check_dag(
76 | checks= [check_all_pairs_below],
77 | n_processes = n_processes,
78 | n_units = 50,
79 | repetitions = 30,
80 | forking = lambda: random.randint(0, n_processes)
81 | )
82 |
83 | def test_large_forking_below():
84 | n_processes = 100
85 | generate_and_check_dag(
86 | checks= [check_all_pairs_below],
87 | n_processes = n_processes,
88 | n_units = 200,
89 | repetitions = 1,
90 | forking = lambda: random.randint(0, n_processes)
91 | )
92 |
93 |
94 | def check_all_pairs_below(dag):
95 | '''
96 | Create a poset from a dag and test (U <= V) for all pairs of units U, V against the implementation in the DAG class.
97 | '''
98 | poset, unit_dict = dag_utils.poset_from_dag(dag)
99 |
100 | for nodeU, U in unit_dict.items():
101 | for nodeV, V in unit_dict.items():
102 | assert poset.below(U,V) == dag.is_reachable(nodeU, nodeV), f"Problem with {nodeU} and {nodeV}"
103 |
104 |
--------------------------------------------------------------------------------
/aleph/test/test_poset_break_ties.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | from aleph.utils import dag_utils
15 | from aleph.utils.generic_test import generate_and_check_dag
16 | import random
17 | from itertools import combinations
18 |
19 |
20 | def lower_cone(poset, U):
21 | '''
22 | :param Poset poset: the considered poset
23 | :param Unit U: the unit whose lower cone is to be computed
24 | :returns: The set of all units that are below U in poset.
25 | '''
26 | ret = set([U])
27 | for P in U.parents:
28 | ret |= lower_cone(poset,P)
29 | return ret
30 |
31 |
32 | def check_total_order_vs_below(poset, units):
33 | '''
34 | Tests whether the linear ordering output by break_ties is compatible with the poset.
35 | :param Poset poset: the considered poset
36 | :param list units: a list of units to be tested
37 | '''
38 | total_order = poset.break_ties(units)
39 | for U,V in combinations(total_order, 2):
40 | assert not poset.above(U,V)
41 |
42 |
43 | def check_total_order_invariance(poset, units, repetitions=3):
44 | '''
45 | Tests whether the linear ordering output by break_ties does not depend on the initial ordering of the input.
46 | :param Poset poset: the considered poset
47 | :param list units: a list of units to be tested
48 | :param int repetitions: the number of tests to perform
49 | '''
50 | total_order = poset.break_ties(units)
51 | for _ in range(repetitions):
52 | random.shuffle(units)
53 | test = poset.break_ties(units)
54 | assert total_order == test
55 |
56 |
57 | def check_break_ties_for_units(poset, units):
58 | '''
59 | Run the correctness tests of break ties on a given poset for a given set of units.
60 | :param Poset poset: the considered poset
61 | :param list units: a list of units to be tested
62 | '''
63 | check_total_order_vs_below(poset, units)
64 | check_total_order_invariance(poset, units)
65 |
66 |
67 | def check_break_ties(dag):
68 | '''
69 | Takes a dag as input, turns it into a poset and runs tests for break ties:
70 | - on all units,
71 | - on the lower-cone of a random unit.
72 | :param DAG dag: a dag to run the test
73 | '''
74 | poset, __ = dag_utils.poset_from_dag(dag)
75 | check_break_ties_for_units(poset, list(poset.units.values()))
76 | random_unit = random.choice(list(poset.units.values()))
77 | check_break_ties_for_units(poset, list(lower_cone(poset, random_unit)))
78 |
79 |
80 | def test_small_nonforking_break_ties():
81 | generate_and_check_dag(
82 | checks= [check_break_ties],
83 | n_processes = 5,
84 | n_units = 50,
85 | repetitions = 20,
86 | )
87 |
88 |
89 | def test_large_nonforking_break_ties():
90 | generate_and_check_dag(
91 | checks= [check_break_ties],
92 | n_processes = 30,
93 | n_units = 500,
94 | repetitions = 1,
95 | )
96 |
97 |
98 | def test_large_forking_break_ties():
99 | generate_and_check_dag(
100 | checks= [check_break_ties],
101 | n_processes = 30,
102 | n_units = 500,
103 | repetitions = 1,
104 | forking = lambda: 3
105 | )
106 |
--------------------------------------------------------------------------------
/aleph/test/test_poset_compliance.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | from aleph.data_structures import Unit, Poset
15 | from aleph.utils import dag_utils
16 | import random
17 | import pytest
18 |
19 |
20 | def check_compliance_vs_pattern(dag, topological_list, pattern, compliance_rules = None):
21 | '''
22 | Create a poset from dag and make sure that whenever a new unit is added, the answer to the check_compliance test agrees with
23 | a precomputed set of answers: a pattern.
24 | :param DAG dag: the dag against which we want to test the check_compliance procedure
25 | :param list topological_list: a list of nodes in dag in a topological order
26 | :param list pattern: the list of answers (True, False) to the compliance test for the subsequent nodes in topological_list
27 | :param dict compliance_rules: which compliance rule should be activated in the poset
28 | '''
29 | unit_dict = {}
30 | poset = Poset(n_processes = dag.n_processes, compliance_rules = compliance_rules, use_tcoin = False)
31 |
32 | for node, answer in zip(topological_list, pattern):
33 | U = Unit(creator_id = dag.pid(node), parents = [unit_dict[parent] for parent in dag.parents(node)], txs = [])
34 | unit_dict[node] = U
35 | poset.prepare_unit(U)
36 | assert poset.check_compliance(U) == answer, f"Node {node} was problematic."
37 | poset.add_unit(U)
38 |
39 |
40 |
41 | def test_small_random_compliance():
42 | '''
43 | Generates dags that and uses them to test the check_compliance method (with standard compliance rules) in poset.
44 | Every dag is generated as follows:
45 | 1) phase 1: generate a certain number of units making sure that all of them comply to the rules
46 | 2) phase 2: generate units one by one until we find one that *does not* comply to the rules (in a prespecified way)
47 | Thus the last unit should be rejected by check compliance, whereas the remaining ones should be accepted.
48 | '''
49 | random.seed(123456789)
50 | repetitions = 800
51 | properties = ['expand_primes']
52 | for violated_property in properties:
53 | for rep in range(repetitions):
54 | n_processes = random.randint(4, 5)
55 | n_units = random.randint(0, n_processes*2)
56 | if violated_property == 'forker_muting':
57 | n_forkers = random.randint(1, n_processes)
58 | else:
59 | n_forkers = random.randint(0, n_processes)
60 | constraints_ensured = {property : True for property in properties}
61 | constraints_ensured['distinct_parents'] = True
62 | constraints_ensured['forker_muting'] = True
63 | constraints_violated = {violated_property : False}
64 | constraints_violated['distinct_parents'] = True
65 | constraints_violated['forker_muting'] = True
66 | dag, topological_list = dag_utils.generate_random_violation(n_processes, n_units, n_forkers,
67 | constraints_ensured, constraints_violated)
68 | # construct the pattern: only the last unit should fail the compliance test
69 | pattern = [True] * len(topological_list)
70 | pattern[-1] = False
71 | check_compliance_vs_pattern(dag, topological_list, pattern)
72 |
73 |
74 |
75 | def test_large_random_compliance():
76 | '''
77 | The same as test_small_random_compliance() but tests larger posets.
78 | '''
79 | random.seed(123456789)
80 | repetitions = 20
81 | properties = ['expand_primes']
82 | for violated_property in properties:
83 | for rep in range(repetitions):
84 | n_processes = random.randint(30, 80)
85 | n_units = random.randint(0, n_processes*2)
86 | if violated_property == 'forker_muting':
87 | n_forkers = random.randint(1, n_processes//3)
88 | else:
89 | n_forkers = random.randint(0, n_processes//3)
90 | constraints_ensured = {property : True for property in properties}
91 | constraints_ensured['distinct_parents'] = True
92 | constraints_ensured['forker_muting'] = True
93 | constraints_violated = {violated_property : False}
94 | constraints_violated['distinct_parents'] = True
95 | constraints_violated['forker_muting'] = True
96 | dag, topological_list = dag_utils.generate_random_violation(n_processes, n_units, n_forkers,
97 | constraints_ensured, constraints_violated)
98 | pattern = [True] * len(topological_list)
99 | pattern[-1] = False
100 | check_compliance_vs_pattern(dag, topological_list, pattern)
101 |
--------------------------------------------------------------------------------
/aleph/test/test_poset_create_unit.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | from aleph.utils.generic_test import simulate_with_checks
15 | import random
16 |
17 | def test_create_unit_small():
18 | random.seed(123456789)
19 | repetitions = 50
20 | for rep in range(repetitions):
21 | n_processes = random.randint(4, 15)
22 | n_units = random.randint(0, n_processes*5)
23 | simulate_with_checks(
24 | n_processes,
25 | n_units,
26 | )
27 |
28 |
29 | def test_create_unit_large():
30 | random.seed(123456789)
31 | repetitions = 5
32 | for rep in range(repetitions):
33 | n_processes = random.randint(30, 80)
34 | n_units = random.randint(0, n_processes*3)
35 | simulate_with_checks(
36 | n_processes,
37 | n_units,
38 | )
39 |
--------------------------------------------------------------------------------
/aleph/test/test_poset_floor.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | from aleph.data_structures import Unit, Poset
15 | from aleph.utils import dag_utils
16 | from aleph.utils.generic_test import generate_and_check_dag
17 | import random
18 |
19 | def create_poset_foundation(n_processes):
20 | '''
21 | Creates a layer of n_processes dealing units and adds them to a poset.
22 | :returns: the resulting poset
23 | '''
24 | poset = Poset(n_processes = n_processes, use_tcoin = False)
25 | empty_floor = [[] for _ in range(n_processes)]
26 |
27 | bottom_units_per_process = [Unit(creator_id = i, parents = [], txs = []) for i in range(n_processes)]
28 | for i in range(n_processes):
29 | poset.prepare_unit(bottom_units_per_process[i])
30 | poset.add_unit(bottom_units_per_process[i])
31 | return poset
32 |
33 | def test_trivial_single_level():
34 | '''
35 | Tests floors for dealing units.
36 | '''
37 | n_processes = 4
38 | poset = create_poset_foundation(n_processes)
39 | for i in range(n_processes):
40 | checkedUnit = poset.max_units_per_process[i][0]
41 | for j in range(n_processes):
42 | assert checkedUnit.floor[j] == ([checkedUnit] if j == i else [])
43 |
44 |
45 | def test_simple_tower():
46 | '''
47 | A simple manual test for floors.
48 | '''
49 | n_processes = 4
50 | poset = create_poset_foundation(n_processes)
51 |
52 | foundation_units = [unit[0] for unit in poset.max_units_per_process]
53 |
54 | U01 = Unit(creator_id = 0, parents = [foundation_units[0], foundation_units[1]], txs = [])
55 | poset.prepare_unit(U01)
56 | poset.add_unit(U01)
57 | U02 = Unit(creator_id = 0, parents = [U01, foundation_units[2]], txs = [])
58 | poset.prepare_unit(U02)
59 | poset.add_unit(U02)
60 | U03 = Unit(creator_id = 0, parents = [U02, foundation_units[3]], txs = [])
61 | poset.prepare_unit(U03)
62 | poset.add_unit(U03)
63 | for j in range(n_processes):
64 | assert U03.floor[j] == ([U03] if j == 0 else [foundation_units[j]])
65 | assert U02.floor == [[U02], [foundation_units[1]], [foundation_units[2]], []]
66 | assert U01.floor == [[U01], [foundation_units[1]], [], []]
67 | for i in range(n_processes):
68 | for j in range(n_processes):
69 | assert foundation_units[i].floor[j] == ([foundation_units[i]] if j == i else [])
70 |
71 | def check_all_floors(dag):
72 | '''
73 | Given a dag, a poset is generated and the floor of every unit is tested against the implementation in DAG.
74 | '''
75 | poset, unit_dict = dag_utils.poset_from_dag(dag)
76 | for nodeU, U in unit_dict.items():
77 | for [tile, other] in zip(U.floor, [[unit_dict[nodeV] for nodeV in nodes] for nodes in dag.floor(nodeU)]):
78 | assert set(tile) == set(other)
79 |
80 | def test_small_nonforking():
81 | generate_and_check_dag(
82 | checks= [check_all_floors],
83 | n_processes = 5,
84 | n_units = 50,
85 | repetitions = 30,
86 | )
87 |
88 | def test_large_nonforking():
89 | generate_and_check_dag(
90 | checks= [check_all_floors],
91 | n_processes = 100,
92 | n_units = 200,
93 | repetitions = 1,
94 | )
95 |
96 | def test_small_forking():
97 | n_processes = 5
98 | generate_and_check_dag(
99 | checks= [check_all_floors],
100 | n_processes = n_processes,
101 | n_units = 50,
102 | repetitions = 30,
103 | forking = lambda: random.randint(0, n_processes)
104 | )
105 |
106 | def test_large_forking():
107 | n_processes = 100
108 | generate_and_check_dag(
109 | checks= [check_all_floors],
110 | n_processes = n_processes,
111 | n_units = 200,
112 | repetitions = 1,
113 | forking = lambda: random.randint(0, n_processes)
114 | )
115 |
--------------------------------------------------------------------------------
/aleph/test/test_poset_level.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | from aleph.utils import dag_utils
15 | from aleph.utils.generic_test import generate_and_check_dag
16 | import random
17 |
18 |
19 |
20 | def test_small_nonforking_below():
21 | generate_and_check_dag(
22 | checks= [check_all_levels],
23 | n_processes = 5,
24 | n_units = 50,
25 | repetitions = 1,
26 | )
27 |
28 | def test_large_nonforking_below():
29 | generate_and_check_dag(
30 | checks= [check_all_levels],
31 | n_processes = 30,
32 | n_units = 100,
33 | repetitions = 1,
34 | )
35 |
36 |
37 | def test_small_forking_below():
38 | generate_and_check_dag(
39 | checks= [check_all_levels],
40 | n_processes = 5,
41 | n_units = 50,
42 | repetitions = 30,
43 | forking = lambda: 1
44 | )
45 |
46 |
47 | def test_large_forking_below():
48 | generate_and_check_dag(
49 | checks= [check_all_levels],
50 | n_processes = 30,
51 | n_units = 100,
52 | repetitions = 10,
53 | forking = lambda: 2
54 | )
55 |
56 | def test_specific_dag():
57 | from pkg_resources import resource_stream
58 | check_all_levels(dag_utils.dag_from_stream(resource_stream("aleph.test.data", "simple.dag")))
59 |
60 | def check_all_levels(arg):
61 | '''
62 | Create a poset from a dag and check if levels agree.
63 | '''
64 | poset, unit_dict = dag_utils.poset_from_dag(arg)
65 |
66 | for nodeU, U in unit_dict.items():
67 | assert U.level == arg.levels[nodeU], f"Node {nodeU} has broken level!"
68 |
--------------------------------------------------------------------------------
/aleph/test/test_poset_threshold_coin.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | from aleph.utils.generic_test import simulate_with_checks
15 | import aleph.const as consts
16 | import random
17 |
18 |
19 | def toss_for_prime(U, poset, dag, results, additional_args):
20 | '''
21 | For every prime unit of level>=7 try to toss a coin for some random prime at level>=3 that is >=4 levels below.
22 | '''
23 | if additional_args is None:
24 | primes = []
25 | else:
26 | primes = additional_args
27 | if poset.is_prime(U) and U.level>=3:
28 | primes.append(U)
29 | if U.level>=7:
30 | random.shuffle(primes)
31 | # find any prime unit that is >=4 levels below U
32 | for U_c in primes:
33 | if U_c.level<=U.level - 4:
34 | results.append((U.level, poset.toss_coin(U_c, U)))
35 | break
36 | return primes
37 |
38 |
39 | def test_threshold_coin_toss():
40 | '''
41 | Test whether the toss_coin code succeeds (i.e. really whether it terminates with no exception).
42 | '''
43 | n_processes = 6
44 | n_units = 320
45 | results = simulate_with_checks(
46 | n_processes,
47 | n_units,
48 | post_prepare = toss_for_prime,
49 | use_tcoin = True,
50 | seed = 0)
51 | # the poset should be high enough so that toss_for_prime produces some coin tosses
52 | assert len(results) > 0
53 | # we should reach beyond const.ADD_SHARES so that at least one coin toss happens by combining shares and not using simple_coin
54 | assert results[-1][0] > consts.ADD_SHARES, "Too low poset generated"
55 |
56 |
57 |
--------------------------------------------------------------------------------
/aleph/test/test_threshold_coin.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | import random
15 |
16 | from aleph.crypto.threshold_coin import ThresholdCoin
17 | from aleph.crypto.threshold_signatures import generate_keys
18 |
19 |
20 | def test_tcoin():
21 | '''
22 | A simple test for generating and extracting threshold coin tosses.
23 | '''
24 | n_parties, threshold = 10, 5
25 | VK, SKs = generate_keys(n_parties, threshold)
26 |
27 | dealer_id = random.randint(0, n_parties)
28 | TCs = [ThresholdCoin(dealer_id, pid, n_parties, threshold, SK, VK) for pid, SK in enumerate(SKs)]
29 |
30 | nonce = random.randint(0, 100000)
31 |
32 | # generate coin shares of all parties
33 | shares = [TC.create_coin_share(nonce) for TC in TCs]
34 |
35 | # verify all shares
36 | for i, share in enumerate(shares):
37 | pid = random.randrange(n_parties)
38 | assert TCs[pid].verify_coin_share(share, i, nonce)
39 |
40 | _shares = {i: shares[i] for i in random.sample(range(n_parties), threshold)}
41 |
42 | pid = random.randrange(n_parties)
43 | assert TCs[pid].combine_coin_shares(_shares, str(nonce))[0] in [0, 1]
44 |
--------------------------------------------------------------------------------
/aleph/test/test_threshold_signatures.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | from random import sample
15 |
16 | from aleph.crypto.threshold_signatures import generate_keys
17 |
18 |
19 | def test_combine_signature():
20 | '''
21 | Performs the following:
22 | - creates a threshold signature (5 out of 10) for a message.
23 | - verifies all the shares
24 | - combines all the shares into a signature
25 | - verifies the signature
26 | '''
27 | n_parties, threshold = 10, 5
28 | VK, SKs = generate_keys(n_parties, threshold)
29 |
30 | msg = 'there is no spoon'
31 | msg_hash = VK.hash_msg(msg)
32 |
33 | # generate signature shares of all parties
34 | shares = [SK.generate_share(msg_hash) for SK in SKs]
35 | _shares = {i: shares[i] for i in sample(range(n_parties), threshold)}
36 |
37 | # check if all shares are valid
38 | for i, share in _shares.items():
39 | assert VK.verify_share(share, i, msg_hash)
40 |
41 | # combine shares and check if the signature is valid
42 | signature = VK.combine_shares(_shares)
43 |
44 | assert VK.verify_signature(signature, msg_hash)
45 |
--------------------------------------------------------------------------------
/aleph/test/test_unit_signing.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | from aleph.crypto.keys import SigningKey, VerifyKey
15 | from aleph.data_structures import Unit
16 | from aleph.process import Process
17 |
18 |
19 | def test_signing():
20 | '''
21 | Tests whether a correct signature of a Unit.bytestring() is positively verified.
22 | '''
23 | sk = SigningKey()
24 | vk = VerifyKey.from_SigningKey(sk)
25 | U = Unit(0,[],[])
26 | msg = U.bytestring()
27 | signature = sk.sign(msg)
28 | assert vk.verify_signature(signature, msg)
29 |
30 | def test_process_signing():
31 | '''
32 | Tests whether a process correctly signs a Unit.
33 | '''
34 | process_id = 0
35 | n_processes = 100
36 | sk = SigningKey()
37 | vk = VerifyKey.from_SigningKey(sk)
38 | dummy_keys = [VerifyKey.from_SigningKey(SigningKey()) for _ in range(n_processes)]
39 | dummy_keys[0] = vk
40 | dummy_addresses = [(None, None) for _ in range(n_processes)]
41 | process = Process(n_processes, process_id, sk, vk, dummy_addresses, dummy_keys, None)
42 | U = Unit(0,[],[])
43 | process.sign_unit(U)
44 |
45 | msg = U.bytestring()
46 | assert vk.verify_signature(U.signature, msg)
47 |
48 |
--------------------------------------------------------------------------------
/aleph/utils/__init__.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | from .dag import DAG
15 | from . import dag_utils
16 | from unittest import mock
17 | from .timer import timer
18 |
--------------------------------------------------------------------------------
/aleph/utils/plot.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | import warnings
15 |
16 | with warnings.catch_warnings():
17 | warnings.filterwarnings("ignore",category=DeprecationWarning)
18 | import networkx as nx
19 | from networkx.drawing.nx_agraph import graphviz_layout
20 |
21 | import numpy as np
22 | import logging
23 | logging.getLogger('matplotlib').setLevel(logging.WARNING)
24 | import matplotlib.pyplot as plt
25 |
26 | from aleph.utils.dag_utils import dag_from_poset
27 |
28 |
29 |
30 | def plot_dag(dag):
31 | '''
32 | Given a DAG instance dag, generates and shows its plot.
33 | '''
34 | G = nx.DiGraph()
35 | height, creator = {}, {}
36 | branch = {pid:{} for pid in range(dag.n_processes)}
37 | self_descendant, self_predecessor = {}, {}
38 |
39 | for unit in dag.sorted():
40 | # add the unit to networkx representation
41 | G.add_node(unit)
42 | creator_id = dag.pid(unit)
43 |
44 | # set height[unit]
45 | height[unit] = max([height[parent] for parent in dag.parents(unit)], default=-1) + 1
46 |
47 | for parent in dag.parents(unit):
48 | # add an edge to the networkx representation
49 | G.add_edge(unit, parent)
50 |
51 | # set self_predecessor[unit]
52 | self_predecessor[unit] = dag.self_predecessor(creator_id, dag.parents(unit))
53 | # set self_descendant
54 | if self_predecessor[unit]:
55 | predecessor = self_predecessor[unit]
56 | if predecessor in self_descendant:
57 | self_descendant[predecessor].append(unit)
58 | else:
59 | self_descendant[predecessor] = [unit]
60 |
61 | # set branch[creator_id][unit]
62 | if self_predecessor[unit] is None:
63 | branch[creator_id][unit] = 0
64 | elif len(self_descendant[self_predecessor[unit]]) == 1:
65 | branch[creator_id][unit] = branch[creator_id][self_predecessor[unit]]
66 | else:
67 | branch[creator_id][unit] = max(branch[creator_id].values())+1
68 |
69 | pos = {}
70 |
71 | # find positions of units in the plot
72 | # we plot units created by a given process vertically
73 | # we use height[unit] for its height in the plot
74 | x = dict(zip(range(dag.n_processes), np.linspace(27, 243, dag.n_processes)))
75 | dx = x[1]-x[0]
76 | for pid in range(dag.n_processes):
77 | units_per_pid = [unit for unit in dag if pid == dag.pid(unit)]
78 | x_per_pid = []
79 | heights = [height[unit] for unit in units_per_pid]
80 | err = 0
81 | spaces = 60 * np.array(heights) + err + 70
82 | y = dict(zip(units_per_pid, spaces))
83 |
84 | n_branches = len(set(branch[pid].values()))
85 | branch_x = np.linspace(-dx/2+5, dx/2-5, n_branches)
86 | for unit in units_per_pid:
87 | pos_y = y[unit]
88 | pos_x = x[dag.pid(unit)]
89 | if n_branches > 1:
90 | pos_x += branch_x[branch[pid][unit]]
91 |
92 | x_per_pid.append(pos_x)
93 | pos[unit] = (pos_x, pos_y)
94 |
95 | color_values = np.linspace(0, 1, dag.n_processes+1)[1:]
96 | color_map = dict(zip(range(dag.n_processes), color_values))
97 | color_map[-1] = 0
98 | node_color = [color_map[dag.pid(unit)] for unit in G.nodes()]
99 | nx.draw(G, pos, with_labels=True, arrows=True, node_color=node_color, node_size=1000, cmap=plt.get_cmap('jet'))
100 | plt.show()
101 |
102 |
103 | def plot_poset(poset):
104 | '''
105 | Given a poset, generates and shows its plot.
106 | '''
107 | dag, _ = dag_from_poset(poset)
108 | plot_dag(dag)
109 |
--------------------------------------------------------------------------------
/aleph/utils/timer.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | import gc
15 | from logging import Logger
16 | from time import perf_counter as get_time
17 |
18 |
19 | class timer:
20 | """
21 | A simple timer for small snippets of code, usable as a context manager.
22 |
23 | Usage:
24 |
25 | with timer('group', 'somename'):
26 | code1
27 | code2
28 | code3
29 |
30 | with timer('group', 'somename', disable_gc=True): #disables garbage collector in the whole block (this is the default setting)
31 | code1
32 | ...
33 |
34 | timer.write_summary(where, groups)
35 | #*where* can be a Logger instance, None (stdout - default) or any object with callable write() attribute
36 | #*groups* - print summary only for chosen groups. By default prints all groups
37 |
38 | timer.reset(group)
39 | #forgets about everything that was recorded with timers from a given group. If *group* is None, forgets everything
40 | """
41 |
42 | results = {}
43 |
44 | def __init__(self, group, name, disable_gc=True):
45 | self.group = group
46 | self.name = name
47 | self.disable_gc = disable_gc
48 |
49 |
50 | def __enter__(self):
51 | if self.disable_gc:
52 | self.old_gc = gc.isenabled()
53 | gc.disable()
54 | self.start = get_time()
55 | return self
56 |
57 |
58 | def __exit__(self, exc_type, exc_val, exc_tb):
59 | end = get_time()
60 | if self.disable_gc and self.old_gc:
61 | gc.enable()
62 | if self.group not in self.results:
63 | self.results[self.group] = {}
64 | g = self.results[self.group]
65 | if self.name not in g:
66 | g[self.name] = 0.0
67 | g[self.name] += end - self.start
68 |
69 |
70 | @classmethod
71 | def write_summary(cls, where=None, groups=None):
72 | if where is None:
73 | write = print
74 | elif isinstance(where, Logger):
75 | write = where.info
76 | elif hasattr(where, 'write') and callable(where.write):
77 | write = where.write
78 |
79 | groups = groups or list(sorted(cls.results.keys()))
80 |
81 | for group in groups:
82 | if group in cls.results:
83 | for name, time in cls.results[group].items():
84 | write(f'timer {str(group)} | {name} took {time:.6f} s')
85 |
86 |
87 | @classmethod
88 | def reset(cls, group=None):
89 | if group is None:
90 | cls.results = {}
91 | elif group in cls.results:
92 | del cls.results[group]
93 |
94 |
--------------------------------------------------------------------------------
/benchmarks/signing.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | import coincurve
15 | import nacl.signing
16 | from time import time
17 |
18 | bench_size = 100000
19 |
20 | print('bench size', bench_size)
21 |
22 | messages = [str(i).encode() for i in range(bench_size)]
23 |
24 | sk_cc = coincurve.PrivateKey()
25 |
26 | print('benchmarking signing')
27 |
28 | time_cc = time()
29 | signs_cc = []
30 | for msg in messages:
31 | signs_cc.append(sk_cc.sign(msg))
32 |
33 | time_cc = round(time() - time_cc, 2)
34 |
35 | sk_nc = nacl.signing.SigningKey.generate()
36 |
37 | time_nc = time()
38 | signs_nc = []
39 | for msg in messages:
40 | signs_nc.append(sk_nc.sign(msg))
41 |
42 | time_nc = round(time() - time_nc, 2)
43 |
44 | print(f'coincurve {time_cc} nacl {time_nc}')
45 |
46 |
47 | print('benchmarking verification')
48 |
49 | vk_cc = coincurve.PublicKey.from_secret(sk_cc.secret)
50 | signs_msg_cc = zip(signs_cc, messages)
51 | time_cc = time()
52 | for sig, msg in signs_msg_cc:
53 | vk_cc.verify(sig, msg)
54 |
55 | time_cc = round(time() - time_cc, 2)
56 |
57 | vk_nc = sk_nc.verify_key
58 | signs_msg_nc = zip(signs_nc, messages)
59 | time_nc = time()
60 | for sig, msg in signs_msg_nc:
61 | vk_nc.verify(sig) == msg
62 |
63 | time_nc = round(time() - time_nc, 2)
64 |
65 | print(f'coincurve {time_cc} nacl {time_nc}')
66 |
--------------------------------------------------------------------------------
/benchmarks/threshold_coin.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | from time import time
15 | from tqdm import tqdm
16 | from random import randint, sample
17 |
18 | from aleph.crypto import ThresholdCoin, generate_keys
19 |
20 | n_parties, threshold = 1000, 667
21 | VK, SKs = generate_keys(n_parties, threshold)
22 |
23 | dealer_id = randint(0, n_parties)
24 | TCs = [ThresholdCoin(dealer_id, pid, n_parties, threshold, SK, VK) for pid, SK in enumerate(SKs)]
25 |
26 | n_examples = 1000
27 |
28 | results, times_gen, times_combine = [], [], []
29 |
30 | for _ in tqdm(range(n_examples)):
31 | nonce = randint(0, 100000)
32 |
33 | # generate coin shares of all parties
34 | start = time()
35 | shares = [TC.create_coin_share(nonce) for TC in TCs]
36 | times_gen.append(time()-start)
37 |
38 | _shares = {i:shares[i] for i in sample(range(n_parties), threshold)}
39 |
40 | start = time()
41 | results.append(TCs[0].combine_coin_shares(_shares))
42 | times_combine.append(time()-start)
43 | print('time needed for generating one share:', round(sum(times_gen)/len(times_gen)/1000, 4))
44 | print('time needed for combining shares', round(sum(times_combine)/len(times_combine), 4))
45 | print('mean value: ', sum(results)/len(results))
46 |
--------------------------------------------------------------------------------
/benchmarks/toss_coin.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | import random
15 | from time import time
16 | from joblib import Parallel, delayed
17 |
18 | from aleph.utils.generic_test import simulate_with_checks
19 |
20 | def time_coin_toss(U, poset, dag, results, additional_args):
21 | if additional_args is None:
22 | levels, U_c = set(), None
23 | else:
24 | levels, U_c = additional_args
25 | if U.level not in levels:
26 | levels.add(U.level)
27 | if U.level == 3:
28 | U_c = U
29 | if U_c is not None and U.level-U_c.level >= 4:
30 | start = time()
31 | poset.toss_coin(U_c, U)
32 | end = time()-start
33 | results.append(end)
34 | return levels, U_c
35 |
36 | def measure_time(n_jobs, n_processes, n_units, n_forkers):
37 | print('n_jobs', n_jobs, '\nn_processes', n_processes, '\nn_units', n_units, '\nn_forkers', n_forkers)
38 | print('dispatching workers')
39 | start = time()
40 | results = Parallel(n_jobs=n_jobs)(
41 | delayed(simulate_with_checks)(
42 | n_processes,
43 | n_units,
44 | n_forkers,
45 | post_prepare = time_coin_toss,
46 | seed = round(time())+i
47 | ) for i in range(n_jobs))
48 | delta = time()-start
49 | if delta < 60:
50 | print('work done in', round(delta, 2))
51 | else:
52 | print('work done in', round(delta/60, 2))
53 | all_sum, all_len = 0, 0
54 | for res in results:
55 | all_sum += sum(res)
56 | all_len += len(res)
57 | print(all_len, all_sum/all_len, [len(res) for res in results])
58 |
59 | if __name__ == '__main__':
60 | measure_time(8, 4, 1000, 0)
61 | measure_time(8, 100, 50000, 0)
62 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = source
9 | BUILDDIR = build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/docs/build/html/_static/classic.css:
--------------------------------------------------------------------------------
1 | /*
2 | * classic.css_t
3 | * ~~~~~~~~~~~~~
4 | *
5 | * Sphinx stylesheet -- classic theme.
6 | *
7 | * :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
8 | * :license: BSD, see LICENSE for details.
9 | *
10 | */
11 |
12 | @import url("basic.css");
13 |
14 | /* -- page layout ----------------------------------------------------------- */
15 |
16 | body {
17 | font-family: sans-serif;
18 | font-size: 100%;
19 | background-color: #11303d;
20 | color: #000;
21 | margin: 0;
22 | padding: 0;
23 | }
24 |
25 | div.document {
26 | background-color: #1c4e63;
27 | }
28 |
29 | div.documentwrapper {
30 | float: left;
31 | width: 100%;
32 | }
33 |
34 | div.bodywrapper {
35 | margin: 0 0 0 230px;
36 | }
37 |
38 | div.body {
39 | background-color: #ffffff;
40 | color: #000000;
41 | padding: 0 20px 30px 20px;
42 | }
43 |
44 | div.footer {
45 | color: #ffffff;
46 | width: 100%;
47 | padding: 9px 0 9px 0;
48 | text-align: center;
49 | font-size: 75%;
50 | }
51 |
52 | div.footer a {
53 | color: #ffffff;
54 | text-decoration: underline;
55 | }
56 |
57 | div.related {
58 | background-color: #133f52;
59 | line-height: 30px;
60 | color: #ffffff;
61 | }
62 |
63 | div.related a {
64 | color: #ffffff;
65 | }
66 |
67 | div.sphinxsidebar {
68 | }
69 |
70 | div.sphinxsidebar h3 {
71 | font-family: 'Trebuchet MS', sans-serif;
72 | color: #ffffff;
73 | font-size: 1.4em;
74 | font-weight: normal;
75 | margin: 0;
76 | padding: 0;
77 | }
78 |
79 | div.sphinxsidebar h3 a {
80 | color: #ffffff;
81 | }
82 |
83 | div.sphinxsidebar h4 {
84 | font-family: 'Trebuchet MS', sans-serif;
85 | color: #ffffff;
86 | font-size: 1.3em;
87 | font-weight: normal;
88 | margin: 5px 0 0 0;
89 | padding: 0;
90 | }
91 |
92 | div.sphinxsidebar p {
93 | color: #ffffff;
94 | }
95 |
96 | div.sphinxsidebar p.topless {
97 | margin: 5px 10px 10px 10px;
98 | }
99 |
100 | div.sphinxsidebar ul {
101 | margin: 10px;
102 | padding: 0;
103 | color: #ffffff;
104 | }
105 |
106 | div.sphinxsidebar a {
107 | color: #98dbcc;
108 | }
109 |
110 | div.sphinxsidebar input {
111 | border: 1px solid #98dbcc;
112 | font-family: sans-serif;
113 | font-size: 1em;
114 | }
115 |
116 |
117 |
118 | /* -- hyperlink styles ------------------------------------------------------ */
119 |
120 | a {
121 | color: #355f7c;
122 | text-decoration: none;
123 | }
124 |
125 | a:visited {
126 | color: #355f7c;
127 | text-decoration: none;
128 | }
129 |
130 | a:hover {
131 | text-decoration: underline;
132 | }
133 |
134 |
135 |
136 | /* -- body styles ----------------------------------------------------------- */
137 |
138 | div.body h1,
139 | div.body h2,
140 | div.body h3,
141 | div.body h4,
142 | div.body h5,
143 | div.body h6 {
144 | font-family: 'Trebuchet MS', sans-serif;
145 | background-color: #f2f2f2;
146 | font-weight: normal;
147 | color: #20435c;
148 | border-bottom: 1px solid #ccc;
149 | margin: 20px -20px 10px -20px;
150 | padding: 3px 0 3px 10px;
151 | }
152 |
153 | div.body h1 { margin-top: 0; font-size: 200%; }
154 | div.body h2 { font-size: 160%; }
155 | div.body h3 { font-size: 140%; }
156 | div.body h4 { font-size: 120%; }
157 | div.body h5 { font-size: 110%; }
158 | div.body h6 { font-size: 100%; }
159 |
160 | a.headerlink {
161 | color: #c60f0f;
162 | font-size: 0.8em;
163 | padding: 0 4px 0 4px;
164 | text-decoration: none;
165 | }
166 |
167 | a.headerlink:hover {
168 | background-color: #c60f0f;
169 | color: white;
170 | }
171 |
172 | div.body p, div.body dd, div.body li, div.body blockquote {
173 | text-align: justify;
174 | line-height: 130%;
175 | }
176 |
177 | div.admonition p.admonition-title + p {
178 | display: inline;
179 | }
180 |
181 | div.admonition p {
182 | margin-bottom: 5px;
183 | }
184 |
185 | div.admonition pre {
186 | margin-bottom: 5px;
187 | }
188 |
189 | div.admonition ul, div.admonition ol {
190 | margin-bottom: 5px;
191 | }
192 |
193 | div.note {
194 | background-color: #eee;
195 | border: 1px solid #ccc;
196 | }
197 |
198 | div.seealso {
199 | background-color: #ffc;
200 | border: 1px solid #ff6;
201 | }
202 |
203 | div.topic {
204 | background-color: #eee;
205 | }
206 |
207 | div.warning {
208 | background-color: #ffe4e4;
209 | border: 1px solid #f66;
210 | }
211 |
212 | p.admonition-title {
213 | display: inline;
214 | }
215 |
216 | p.admonition-title:after {
217 | content: ":";
218 | }
219 |
220 | pre {
221 | padding: 5px;
222 | background-color: #eeffcc;
223 | color: #333333;
224 | line-height: 120%;
225 | border: 1px solid #ac9;
226 | border-left: none;
227 | border-right: none;
228 | }
229 |
230 | code {
231 | background-color: #ecf0f3;
232 | padding: 0 1px 0 1px;
233 | font-size: 0.95em;
234 | }
235 |
236 | th {
237 | background-color: #ede;
238 | }
239 |
240 | .warning code {
241 | background: #efc2c2;
242 | }
243 |
244 | .note code {
245 | background: #d6d6d6;
246 | }
247 |
248 | .viewcode-back {
249 | font-family: sans-serif;
250 | }
251 |
252 | div.viewcode-block:target {
253 | background-color: #f4debf;
254 | border-top: 1px solid #ac9;
255 | border-bottom: 1px solid #ac9;
256 | }
257 |
258 | div.code-block-caption {
259 | color: #efefef;
260 | background-color: #1c4e63;
261 | }
--------------------------------------------------------------------------------
/docs/build/html/_static/default.css:
--------------------------------------------------------------------------------
1 | @import url("classic.css");
2 |
--------------------------------------------------------------------------------
/docs/build/html/_static/documentation_options.js:
--------------------------------------------------------------------------------
1 | var DOCUMENTATION_OPTIONS = {
2 | URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
3 | VERSION: '0.1',
4 | LANGUAGE: 'None',
5 | COLLAPSE_INDEX: false,
6 | FILE_SUFFIX: '.html',
7 | HAS_SOURCE: true,
8 | SOURCELINK_SUFFIX: '.txt',
9 | NAVIGATION_WITH_KEYS: false
10 | };
--------------------------------------------------------------------------------
/docs/build/html/_static/pygments.css:
--------------------------------------------------------------------------------
1 | .highlight .hll { background-color: #ffffcc }
2 | .highlight { background: #eeffcc; }
3 | .highlight .c { color: #408090; font-style: italic } /* Comment */
4 | .highlight .err { border: 1px solid #FF0000 } /* Error */
5 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */
6 | .highlight .o { color: #666666 } /* Operator */
7 | .highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */
8 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */
9 | .highlight .cp { color: #007020 } /* Comment.Preproc */
10 | .highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */
11 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */
12 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */
13 | .highlight .gd { color: #A00000 } /* Generic.Deleted */
14 | .highlight .ge { font-style: italic } /* Generic.Emph */
15 | .highlight .gr { color: #FF0000 } /* Generic.Error */
16 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
17 | .highlight .gi { color: #00A000 } /* Generic.Inserted */
18 | .highlight .go { color: #333333 } /* Generic.Output */
19 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
20 | .highlight .gs { font-weight: bold } /* Generic.Strong */
21 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
22 | .highlight .gt { color: #0044DD } /* Generic.Traceback */
23 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */
24 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
25 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
26 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */
27 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
28 | .highlight .kt { color: #902000 } /* Keyword.Type */
29 | .highlight .m { color: #208050 } /* Literal.Number */
30 | .highlight .s { color: #4070a0 } /* Literal.String */
31 | .highlight .na { color: #4070a0 } /* Name.Attribute */
32 | .highlight .nb { color: #007020 } /* Name.Builtin */
33 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */
34 | .highlight .no { color: #60add5 } /* Name.Constant */
35 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */
36 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */
37 | .highlight .ne { color: #007020 } /* Name.Exception */
38 | .highlight .nf { color: #06287e } /* Name.Function */
39 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */
40 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
41 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */
42 | .highlight .nv { color: #bb60d5 } /* Name.Variable */
43 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */
44 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */
45 | .highlight .mb { color: #208050 } /* Literal.Number.Bin */
46 | .highlight .mf { color: #208050 } /* Literal.Number.Float */
47 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */
48 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */
49 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */
50 | .highlight .sa { color: #4070a0 } /* Literal.String.Affix */
51 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */
52 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */
53 | .highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */
54 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
55 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */
56 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
57 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */
58 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
59 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */
60 | .highlight .sr { color: #235388 } /* Literal.String.Regex */
61 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */
62 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */
63 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */
64 | .highlight .fm { color: #06287e } /* Name.Function.Magic */
65 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */
66 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */
67 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */
68 | .highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */
69 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */
--------------------------------------------------------------------------------
/docs/build/html/_static/sidebar.js:
--------------------------------------------------------------------------------
1 | /*
2 | * sidebar.js
3 | * ~~~~~~~~~~
4 | *
5 | * This script makes the Sphinx sidebar collapsible.
6 | *
7 | * .sphinxsidebar contains .sphinxsidebarwrapper. This script adds
8 | * in .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton
9 | * used to collapse and expand the sidebar.
10 | *
11 | * When the sidebar is collapsed the .sphinxsidebarwrapper is hidden
12 | * and the width of the sidebar and the margin-left of the document
13 | * are decreased. When the sidebar is expanded the opposite happens.
14 | * This script saves a per-browser/per-session cookie used to
15 | * remember the position of the sidebar among the pages.
16 | * Once the browser is closed the cookie is deleted and the position
17 | * reset to the default (expanded).
18 | *
19 | * :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
20 | * :license: BSD, see LICENSE for details.
21 | *
22 | */
23 |
24 | $(function() {
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | // global elements used by the functions.
34 | // the 'sidebarbutton' element is defined as global after its
35 | // creation, in the add_sidebar_button function
36 | var bodywrapper = $('.bodywrapper');
37 | var sidebar = $('.sphinxsidebar');
38 | var sidebarwrapper = $('.sphinxsidebarwrapper');
39 |
40 | // for some reason, the document has no sidebar; do not run into errors
41 | if (!sidebar.length) return;
42 |
43 | // original margin-left of the bodywrapper and width of the sidebar
44 | // with the sidebar expanded
45 | var bw_margin_expanded = bodywrapper.css('margin-left');
46 | var ssb_width_expanded = sidebar.width();
47 |
48 | // margin-left of the bodywrapper and width of the sidebar
49 | // with the sidebar collapsed
50 | var bw_margin_collapsed = '.8em';
51 | var ssb_width_collapsed = '.8em';
52 |
53 | // colors used by the current theme
54 | var dark_color = $('.related').css('background-color');
55 | var light_color = $('.document').css('background-color');
56 |
57 | function sidebar_is_collapsed() {
58 | return sidebarwrapper.is(':not(:visible)');
59 | }
60 |
61 | function toggle_sidebar() {
62 | if (sidebar_is_collapsed())
63 | expand_sidebar();
64 | else
65 | collapse_sidebar();
66 | }
67 |
68 | function collapse_sidebar() {
69 | sidebarwrapper.hide();
70 | sidebar.css('width', ssb_width_collapsed);
71 | bodywrapper.css('margin-left', bw_margin_collapsed);
72 | sidebarbutton.css({
73 | 'margin-left': '0',
74 | 'height': bodywrapper.height()
75 | });
76 | sidebarbutton.find('span').text('»');
77 | sidebarbutton.attr('title', _('Expand sidebar'));
78 | document.cookie = 'sidebar=collapsed';
79 | }
80 |
81 | function expand_sidebar() {
82 | bodywrapper.css('margin-left', bw_margin_expanded);
83 | sidebar.css('width', ssb_width_expanded);
84 | sidebarwrapper.show();
85 | sidebarbutton.css({
86 | 'margin-left': ssb_width_expanded-12,
87 | 'height': bodywrapper.height()
88 | });
89 | sidebarbutton.find('span').text('«');
90 | sidebarbutton.attr('title', _('Collapse sidebar'));
91 | document.cookie = 'sidebar=expanded';
92 | }
93 |
94 | function add_sidebar_button() {
95 | sidebarwrapper.css({
96 | 'float': 'left',
97 | 'margin-right': '0',
98 | 'width': ssb_width_expanded - 28
99 | });
100 | // create the button
101 | sidebar.append(
102 | '
Welcome to the documentation of AlephZero Proof-of-Concept implementation in Python.
49 |
The AlephZero protocol is a consensus protocol that is well-suited for usage in a blockchain project.
50 | For the detailed description, check the first version of the paper and the second version of the `paper <>`_.
51 |
This documentation describes the most important classes and methods of the implementation and assumes the thorought understanding of the papers.
47 | Please activate JavaScript to enable the search
48 | functionality.
49 |
50 |
51 |
52 | From here you can search these documents. Enter your search
53 | words into the box below and click "search". Note that the search
54 | function will automatically search for all of the words. Pages
55 | containing fewer words won't appear in the result list.
56 |
88 |
92 |
93 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=source
11 | set BUILDDIR=build
12 |
13 | if "%1" == "" goto help
14 |
15 | %SPHINXBUILD% >NUL 2>NUL
16 | if errorlevel 9009 (
17 | echo.
18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
19 | echo.installed, then set the SPHINXBUILD environment variable to point
20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
21 | echo.may add the Sphinx directory to PATH.
22 | echo.
23 | echo.If you don't have Sphinx installed, grab it from
24 | echo.http://sphinx-doc.org/
25 | exit /b 1
26 | )
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/docs/source/actions/actions.rst:
--------------------------------------------------------------------------------
1 | Actions
2 | -------------------------
3 |
4 | .. toctree::
5 | :maxdepth: 3
6 |
7 | create_unit
8 | poset_syncing
9 |
--------------------------------------------------------------------------------
/docs/source/actions/create_unit.rst:
--------------------------------------------------------------------------------
1 | Create Unit
2 | -------------------------
3 |
4 | .. automodule:: aleph.actions.create_unit
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/actions/poset_syncing.rst:
--------------------------------------------------------------------------------
1 | Poset Syncing
2 | -------------------------
3 |
4 | .. automodule:: aleph.actions.poset_syncing
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/aleph_1920x1080.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aleph-zero-foundation/Proof-of-Concept/4eb50a9d946a8f2ac2483e3acd5af1e88b4214d8/docs/source/aleph_1920x1080.jpg
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | # Configuration file for the Sphinx documentation builder.
15 | #
16 | # This file only contains a selection of the most common options. For a full
17 | # list see the documentation:
18 | # http://www.sphinx-doc.org/en/master/config
19 |
20 | # -- Path setup --------------------------------------------------------------
21 |
22 | # If extensions (or modules to document with autodoc) are in another directory,
23 | # add these directories to sys.path here. If the directory is relative to the
24 | # documentation root, use os.path.abspath to make it absolute, like shown here.
25 | #
26 | import os
27 | import sys
28 | sys.path.insert(0, os.path.abspath('../../aleph'))
29 |
30 |
31 | # -- Project information -----------------------------------------------------
32 |
33 | project = 'AlephZero'
34 | copyright = '2019, Michal Swietek'
35 | author = 'Michal Swietek'
36 |
37 | # The full version, including alpha/beta/rc tags
38 | release = '0.1'
39 |
40 |
41 | # -- General configuration ---------------------------------------------------
42 |
43 | # Add any Sphinx extension module names here, as strings. They can be
44 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
45 | # ones.
46 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.coverage']
47 |
48 | # Add any paths that contain templates here, relative to this directory.
49 | templates_path = ['_templates']
50 |
51 | # List of patterns, relative to source directory, that match files and
52 | # directories to ignore when looking for source files.
53 | # This pattern also affects html_static_path and html_extra_path.
54 | exclude_patterns = []
55 |
56 |
57 | # -- Options for HTML output -------------------------------------------------
58 |
59 | # The theme to use for HTML and HTML Help pages. See the documentation for
60 | # a list of builtin themes.
61 | #
62 | html_theme = 'default'
63 |
64 | # Add any paths that contain custom static files (such as style sheets) here,
65 | # relative to this directory. They are copied after the builtin static files,
66 | # so a file named "default.css" will overwrite the builtin "default.css".
67 | html_static_path = ['_static']
68 |
--------------------------------------------------------------------------------
/docs/source/crypto/crp.rst:
--------------------------------------------------------------------------------
1 | Common Random Permutation
2 | -------------------------
3 |
4 | .. automodule:: aleph.crypto.crp
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/crypto/crypto.rst:
--------------------------------------------------------------------------------
1 | Crypto
2 | -------------------------
3 |
4 | .. toctree::
5 | :maxdepth: 3
6 |
7 | crp
8 | keys
9 | threshold_signatures
10 | threshold_coin
11 |
--------------------------------------------------------------------------------
/docs/source/crypto/keys.rst:
--------------------------------------------------------------------------------
1 | Signing Keys
2 | -------------------------
3 |
4 | .. automodule:: aleph.crypto.keys
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/crypto/threshold_coin.rst:
--------------------------------------------------------------------------------
1 | Threshold Coin
2 | -------------------------
3 |
4 | .. automodule:: aleph.crypto.threshold_coin
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/crypto/threshold_signatures.rst:
--------------------------------------------------------------------------------
1 | Threshold Signatures
2 | -------------------------
3 |
4 | .. automodule:: aleph.crypto.threshold_signatures
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/data_structures/data_structures.rst:
--------------------------------------------------------------------------------
1 | Data structures
2 | -------------------------
3 |
4 | .. toctree::
5 | :maxdepth: 3
6 |
7 | unit
8 | poset
9 | tx
10 |
--------------------------------------------------------------------------------
/docs/source/data_structures/poset.rst:
--------------------------------------------------------------------------------
1 | Poset
2 | -------------------------
3 |
4 | .. automodule:: aleph.data_structures.poset
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/data_structures/tx.rst:
--------------------------------------------------------------------------------
1 | Tx
2 | -------------------------
3 |
4 | .. automodule:: aleph.data_structures.tx
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/data_structures/unit.rst:
--------------------------------------------------------------------------------
1 | Unit
2 | -------------------------
3 |
4 | .. automodule:: aleph.data_structures.unit
5 | :members:
6 |
7 |
--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
1 | Welcome to AlephZero's documentation!
2 | =====================================
3 |
4 | .. toctree::
5 | :maxdepth: 3
6 | :caption: Contents:
7 | :numbered:
8 |
9 | intro
10 | process
11 | data_structures/data_structures
12 | network/net
13 | actions/actions
14 | crypto/crypto
15 |
16 |
17 | Indices and tables
18 | ==================
19 |
20 | * :ref:`genindex`
21 | * :ref:`modindex`
22 | * :ref:`search`
23 |
--------------------------------------------------------------------------------
/docs/source/intro.rst:
--------------------------------------------------------------------------------
1 | Introduction
2 | =========================
3 |
4 | Welcome to the documentation of Aleph Proof-of-Concept implementation in Python.
5 |
6 | The Aleph protocol is a consensus protocol that is well-suited for usage in a blockchain project.
7 | For the detailed description, check the first version of the `paper `_ and the second version of the `paper `_.
8 |
9 | This documentation describes the most important classes and methods of the implementation and assumes the thorought understanding of the papers.
10 |
--------------------------------------------------------------------------------
/docs/source/network/channel.rst:
--------------------------------------------------------------------------------
1 | Channel
2 | -------------------------
3 |
4 | .. automodule:: aleph.network.channel
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/network/net.rst:
--------------------------------------------------------------------------------
1 | Network
2 | -------------------------
3 |
4 | .. toctree::
5 | :maxdepth: 3
6 |
7 | channel
8 | network
9 |
--------------------------------------------------------------------------------
/docs/source/network/network.rst:
--------------------------------------------------------------------------------
1 | Network
2 | -------------------------
3 |
4 | .. automodule:: aleph.network.network
5 | :members:
6 |
--------------------------------------------------------------------------------
/docs/source/process.rst:
--------------------------------------------------------------------------------
1 | Process
2 | -------------------------
3 |
4 | .. automodule:: aleph.process
5 | :members:
6 |
--------------------------------------------------------------------------------
/examples/plot.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | import sys
15 |
16 | from aleph.utils.dag_utils import dag_from_file, generate_random_forking, generate_random_nonforking
17 | from aleph.utils.plot import plot_dag
18 |
19 | path = ""
20 | if len(sys.argv) == 2:
21 | path = sys.argv[1]
22 | elif len(sys.argv) == 3:
23 | n_processes, n_units = int(sys.argv[1]), int(sys.argv[2])
24 | path = 'random_nonforking_{}_{}.txt'.format(n_processes, n_units)
25 | generate_random_nonforking(n_processes, n_units, path)
26 | elif len(sys.argv) == 4:
27 | n_processes, n_units, n_forkers = int(sys.argv[1]), int(sys.argv[2]), int(sys.argv[3])
28 | path = 'random_forking_{}_{}_{}.txt'.format(n_processes, n_units, n_forkers)
29 | generate_random_forking(n_processes, n_units, n_forkers, path)
30 | else:
31 | print("Either provide a file to plot or number of processes, units and (optionally) forkers.")
32 | sys.exit(1)
33 | dag = dag_from_file(path)
34 | plot_dag(dag)
35 |
--------------------------------------------------------------------------------
/examples/random_forking_10_30_5.txt:
--------------------------------------------------------------------------------
1 | format standard
2 | 10
3 | 0,8 8
4 | 0,2 2
5 | 0,7 7
6 | 0,3 3
7 | 1,7 7 0,7 0,3
8 | 2,7 7 0,2 1,7
9 | 1,8 8 0,8 2,7
10 | 0,1 1
11 | 2,8 8 1,8 0,1
12 | 0,0 0
13 | 1,3 3 1,7 0,0
14 | 0,6 6
15 | 2,3 3 1,3 0,6
16 | 3,7 7 0,1 1,8
17 | 1,0 0 2,3 3,7
18 | 0,9 9
19 | 1,3,1 3 0,9 1,8
20 | 2,0 0 1,0 1,3,1
21 | 1,1,1 1 1,0 0,3
22 | 1,0,1 0 2,3 0,2
23 | 0,4 4
24 | 1,2 2 0,2 0,3
25 | 1,3,2 3 0,2 1,2
26 | 2,2 2 1,3,2 3,7
27 | 1,1,2 1 0,4 2,2
28 | 1,1 1 1,0 1,2
29 | 1,6 6 1,1 3,7
30 | 1,3,3 3 0,0 1,7
31 | 2,0,1 0 1,6 1,3,3
32 | 1,4 4 1,2 0,4
33 | 0,5 5
34 | 2,3,1 3 1,3,2 0,1
35 | 3,3 3 0,5 2,3,1
36 | 4,3 3 1,4 3,3
37 | 1,9 9 1,0 0,9
38 | 3,3,1 3 1,9 0,1
39 | 4,7 7 1,9 2,3,1
40 | 1,1,3 1 4,7 3,3
41 | 2,0,2 0 1,9 1,1,3
42 | 2,0,3 0 1,6 2,3,1
43 |
--------------------------------------------------------------------------------
/examples/random_nonforking_10_30.txt:
--------------------------------------------------------------------------------
1 | format standard
2 | 10
3 | 0,7 7
4 | 0,0 0
5 | 1,7 7 0,7 0,0
6 | 0,9 9
7 | 2,7 7 1,7 0,9
8 | 0,1 1
9 | 0,2 2
10 | 0,8 8
11 | 1,2 2 0,2 0,8
12 | 1,1 1 0,1 1,2
13 | 0,4 4
14 | 1,4 4 0,4 0,2
15 | 1,8 8 0,8 1,4
16 | 0,5 5
17 | 2,8 8 1,8 0,5
18 | 2,1 1 1,1 2,8
19 | 3,1 1 2,1 2,8
20 | 1,5 5 0,5 0,0
21 | 0,3 3
22 | 0,6 6
23 | 1,3 3 0,3 0,6
24 | 2,5 5 1,5 1,3
25 | 4,1 1 3,1 2,5
26 | 3,5 5 2,5 1,2
27 | 4,5 5 3,5 2,8
28 | 2,4 4 1,4 0,0
29 | 3,4 4 2,4 0,9
30 | 1,6 6 0,6 0,0
31 | 1,0 0 0,0 1,6
32 | 4,4 4 3,4 1,0
33 | 1,9 9 0,9 1,6
34 | 2,2 2 1,2 1,9
35 | 2,0 0 1,0 2,2
36 | 2,6 6 1,6 2,8
37 | 3,2 2 2,2 2,6
38 | 3,8 8 2,8 2,6
39 | 2,3 3 1,3 2,2
40 | 3,3 3 2,3 2,6
41 | 4,8 8 3,8 3,3
42 | 2,9 9 1,9 4,8
43 |
--------------------------------------------------------------------------------
/experiments/aws/aws_deps.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | set -e
4 |
5 | print () {
6 | echo -e "\033[0;32m$@\033[0m"
7 | }
8 |
9 | print installing parallel
10 | sudo apt-get install -y parallel
11 |
12 | print enterintg virtenv p37
13 |
14 | source $HOME/p37/bin/activate
15 |
16 | print installing fabric, boto3, ipython
17 | pip install fabric boto3 ipython
18 |
--------------------------------------------------------------------------------
/experiments/aws/const.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | N_PARENTS = 10 # maximal number of parents a unit can have
15 |
16 | CREATE_DELAY = 2.0 # delay after creating a new unit
17 | STEP_SIZE = 0.14 # a number in (0,1) describing how aggresive is the create_delay adjusting mechanism, large = aggresive
18 |
19 | SYNC_INIT_DELAY = 0.015625 # delay after initianing a sync with other processes
20 |
21 | N_RECV_SYNC = 10 # number of allowed parallel received syncs
22 | N_INIT_SYNC = 10 # number of allowed parallel initiated syncs
23 |
24 | TXPU = 1 # number of transactions per unit
25 | TX_LIMIT = 1000000 # limit of all txs generated for one process
26 |
27 | LEVEL_LIMIT = 20 # maximal level after which process shuts down
28 | UNITS_LIMIT = None # maximal number of units that are constructed
29 | SYNCS_LIMIT = None # maximal number of syncs that are performed
30 |
31 | USE_TCOIN = 1 # whether to use threshold coin
32 | PRECOMPUTE_POPULARITY = 0 # precompute popularity proof to ease computational load of Poset.compute_vote procedure
33 | ADAPTIVE_DELAY = 0 # whether to use the adaptive strategy of determining create_delay
34 |
35 | VOTING_LEVEL = 3 # level at which the first voting round occurs, this is "t" from the write-up
36 | PI_DELTA_LEVEL = 12 # level at which to switch from the "fast" to the pi_delta algorithm
37 | ADD_SHARES = PI_DELTA_LEVEL - 1 # level at which to start adding coin shares to units, it's safe to make it PI_DELTA_LEVEL - 1
38 |
39 | HOST_IP = '127.0.0.1' # default ip address of a process
40 | HOST_PORT = 8888 # default port of incoming syncs
41 |
42 | LOGGER_NAME = 'aleph' # name of our logger and logfile
43 | TX_SOURCE = 'tx_source_gen' # source of txs
44 |
--------------------------------------------------------------------------------
/experiments/aws/key_pairs/aleph.fingerprint:
--------------------------------------------------------------------------------
1 | eb:8f:e8:c5:cf:d6:23:f5:da:e4:81:b1:76:2d:d3:bc
--------------------------------------------------------------------------------
/experiments/aws/key_pairs/aleph.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEowIBAAKCAQEAqVPgYvWw4Q9XeuPWYxMurO90cWuruaWPTRGA0UHc/qUBVfNN
3 | WZdPJTtlCG8vd3p5nmfBC0WSa0YLQjFcsYlQ7+ueec9v7ZDErxCCG8cuqAV/yieZ
4 | kL/lgZJF9Q8F4x5bRu9XASI5ZAMSyQOAGigOBfPXNagaUgD+lQ/FcyozVC6QXz7K
5 | fYZDfvJAPpB6ywFNbkM8mKtLqMsnV8uV7X/s6ohDsiW+sIVQq/3Xh2CQ/kUJndmy
6 | 7/+DV3vRemiCSnfFPXAsPZv0zsG4bl9pqWOBNgbLyRTEF8hMiihHPh/ZgsFGLeeu
7 | B3F2tqJ1oIHNhRoEK4sMR7W2Xmt7Q408DgkEawIDAQABAoIBABj3XAtbTIQyneRp
8 | uY+MIYKwH8wlYwGRbqqfXQ39lxcYvzSsFgiSrcHAmyn+MtJ8BXictbLyxB77Ceft
9 | nUw0AfIOHKS7ODaf9NAXMAfme3Ocs/XbluHn2XAcYCI839JYBH9WBM8O+8VKSrfG
10 | w1mX9FFeXan0qkTTDrZtx+AuJblQktUKDFddL27rbyORRFNIFoXn/PNa8nxDPJfe
11 | UFR5uMB8wSq2OuYztlsPJkRmcETQ9oZy6R6eKLfIVrbAJ9qKkrgmCYYCqb0QFUx9
12 | in6BQ3ZcbDVXBrsty7MmYjxbJc5Tli2wkdL5sxiNFqqZ6wBU0RHViq3QVMVKCo6n
13 | rAkIwVECgYEA2eVG91AAGbXnJEjpYSaVEbdB2pB33ffTtC4sCTvanMft1jBg66sE
14 | EI2PATZp70p1V7SqnRqVbST7WK75KI8PammQBbDh0CUZMLv3k2F/p4fGb5kVyYby
15 | nkrp6yH5zj7vBDKXAAbaol0JD8bzPnNxKZor5TXutBOHE9hMrqXz8tkCgYEAxvBQ
16 | igv4YQvqLVa89aaIAL31n7mFHCrXQ9ExVZFnE01ZZacWMas6SbcfxiQebr/96nZM
17 | sG6CStBD19k29TMrmZHqOf6CQAK0kyBocmAO0pBlpZr3vo3agWwzHdvXJOW8JjyF
18 | R+Jz9ouqbU1yij2dsI88bvYlJh7yFA3b403VXuMCgYAuja/V0v/Sfmy98d365bVf
19 | rZmiSs9xWyueCWdbKbCRYp3L71Ylw8oAh14QUkHb4Dql3HG/Uxit0wLodanCIp6W
20 | i6s7rAjy4Zo6MU657FDLyFR7L2faQLzBWRwVIpMun3+NlAly9gPsQJhZCJabtyps
21 | SwYp2JYBX1KyyLnJbqdqMQKBgEHhPVqp4iQJNRC0o7jZ4DoWriup3TnZuhXcRfEn
22 | SLIssEkojwH9yTy2JxCPz4Avll+5s2XOAtnRpFXrufZzbqHZn257KRkXjEMYvV37
23 | D42NggvYiOiSw2SOt1LRBatTZP5oll6a0Ho6AENYHXFIPFtG4+V/IAhCwToA6Oua
24 | T+mvAoGBAKlzHYX0S65xzIXmaEGhOnSTwcX0J2O2R8J7yliyaQDf/qTTFv6WYBjg
25 | OJxnRFynUHf04mOWUkHc5rb8dMD2pgrDWoHg1hAPaGi8OAa5T/ctl2SEQ+oG7rBY
26 | LCqwoa7bcxp0rEzaNoe5T6S5PGFKgFCE8ONcKPMaZ9rhAuYe0hsW
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/experiments/aws/key_pairs/aleph.pem.pub:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqVPgYvWw4Q9XeuPWYxMu
3 | rO90cWuruaWPTRGA0UHc/qUBVfNNWZdPJTtlCG8vd3p5nmfBC0WSa0YLQjFcsYlQ
4 | 7+ueec9v7ZDErxCCG8cuqAV/yieZkL/lgZJF9Q8F4x5bRu9XASI5ZAMSyQOAGigO
5 | BfPXNagaUgD+lQ/FcyozVC6QXz7KfYZDfvJAPpB6ywFNbkM8mKtLqMsnV8uV7X/s
6 | 6ohDsiW+sIVQq/3Xh2CQ/kUJndmy7/+DV3vRemiCSnfFPXAsPZv0zsG4bl9pqWOB
7 | NgbLyRTEF8hMiihHPh/ZgsFGLeeuB3F2tqJ1oIHNhRoEK4sMR7W2Xmt7Q408DgkE
8 | awIDAQAB
9 | -----END PUBLIC KEY-----
10 |
--------------------------------------------------------------------------------
/experiments/aws/set_env.sh:
--------------------------------------------------------------------------------
1 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
2 | export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/lib
3 | export PYTHONPATH=.:/home/ubuntu/proof-of-concept
4 | alias python=python3.7
5 |
6 |
--------------------------------------------------------------------------------
/experiments/aws/setup.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | set -e
4 |
5 | # install in home dir
6 | cd
7 |
8 | echo update > setup.log
9 | sudo apt update
10 |
11 | echo install from ubuntu repo >> setup.log
12 | sudo DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential make flex bison zip unzip virtualenv libgmp-dev libmpc-dev libssl1.0-dev
13 | sudo DEBIAN_FRONTEND=noninteractive apt-get install -y python3-dev python3.7-dev python3-pip
14 |
15 | echo setting up env >> setup.log
16 | virtualenv --python=python3.7 p37
17 | source p37/bin/activate
18 |
19 | echo install from pip repo >> setup.log
20 | pip install setuptools pytest-xdist pynacl networkx numpy matplotlib joblib
21 |
22 | echo install pbc >> setup.log
23 | wget https://crypto.stanford.edu/pbc/files/pbc-0.5.14.tar.gz
24 | tar -xvf pbc-0.5.14.tar.gz
25 | cd pbc-0.5.14
26 | ./configure
27 | make
28 | sudo make install
29 | cd
30 |
31 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
32 | export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/lib
33 |
34 | echo install charm >> setup.log
35 | git clone https://github.com/JHUISI/charm.git
36 | cd charm
37 | ./configure.sh
38 | make
39 | pip install .
40 | cd
41 |
42 | echo done >> setup.log
43 |
--------------------------------------------------------------------------------
/experiments/simple_ec2_test.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | import asyncio
15 | import multiprocessing
16 | import random
17 |
18 | from optparse import OptionParser
19 |
20 | from aleph.crypto.keys import SigningKey, VerifyKey
21 | from aleph.data_structures import UserDB, Tx
22 | from aleph.process import Process
23 |
24 |
25 | def read_hosts_ip(hosts_path):
26 | with open(hosts_path, 'r') as f:
27 | return [line[:-1] for line in f]
28 |
29 |
30 | def read_signing_keys(signing_keys_path):
31 | with open(signing_keys_path, 'r') as f:
32 | hexes = [line[:-1].encode() for line in f]
33 | return [SigningKey(hexed) for hexed in hexes]
34 |
35 |
36 | def prepare_DB():
37 | random.seed(1729)
38 | initial_balances_and_indices = []
39 | with open('light_nodes_public_keys', 'r') as f:
40 | ln_public_keys = [line[:-1] for line in f]
41 | for i in range(len(ln_public_keys)):
42 | initial_balances_and_indices.append((ln_public_keys[i], random.randrange(10000, 100000), -1))
43 |
44 | return UserDB(initial_balances_and_indices)
45 |
46 |
47 | def sort_and_get_my_pid(public_keys, signing_keys, hosts_ip):
48 | with open('my_ip') as f:
49 | my_ip = f.readline().strip()
50 | ind = hosts_ip.index(my_ip)
51 | my_pk = public_keys[ind]
52 |
53 | pk_hexes = [pk.to_hex() for pk in public_keys]
54 | arg_sort = [i for i, _ in sorted(enumerate(pk_hexes), key = lambda x: x[1])]
55 | public_keys = [public_keys[i] for i in arg_sort]
56 | signing_keys = [signing_keys[i] for i in arg_sort]
57 | hosts_ip = [hosts_ip[i] for i in arg_sort]
58 |
59 | return public_keys.index(my_pk), public_keys, signing_keys, hosts_ip
60 |
61 |
62 | def tx_source_gen(process_id, n_processes, batch_size, txpu):
63 | '''
64 | Produces a tx generator and ensures that all processes choose batches from the same set.
65 | :param int batch_size: number of txs for a process to input into the system.
66 | :param int n_processes: number of parties.
67 | :param int txpu: number of txs to be included in one unit.
68 | '''
69 |
70 | def _tx_source(tx_receiver_address, tx_queue):
71 | '''
72 | Generates transactions in bundles of size txpu till batch_size is reached
73 | :param None tx_receiver_address: needed only for comatibility of args list with network.tx_listener
74 | :param queue tx_queue: queue for newly generated txs
75 | '''
76 | # ensure that batches are different
77 | random.seed(process_id)
78 | with open('light_nodes_public_keys', 'r') as f:
79 | ln_public_keys = [line[:-1] for line in f]
80 |
81 | proposed = 0
82 | while proposed.
12 | '''
13 |
14 | from setuptools import setup, find_packages
15 |
16 | setup(
17 | name="aleph",
18 | version="0.0.1",
19 | packages=find_packages(),
20 | install_requires=[
21 | "charm-crypto==0.50",
22 | "pynacl",
23 | "networkx",
24 | "numpy",
25 | "matplotlib",
26 | "parse",
27 | "psutil",
28 | "joblib",
29 | "pytest-xdist",
30 | "tqdm"
31 | ],
32 | license="",
33 | package_data={"aleph.test.data": ["simple.dag", "light_nodes_public_keys"]},
34 | )
35 |
--------------------------------------------------------------------------------
/tests/linear_ordering.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | import asyncio
15 | import multiprocessing
16 | import random
17 |
18 | from aleph.data_structures import Unit
19 | from aleph.process import Process
20 | from aleph.crypto.keys import SigningKey, VerifyKey
21 | from aleph.actions import create_unit
22 |
23 |
24 | def translate_parents_and_copy(U, hashes_to_units):
25 | '''
26 | Takes a unit from the poset of a process A and the mapping hashes->Units for the poset for process B.
27 | Returns a new unit (with correct references to corresponding parent units in B) to be added to B's poset.
28 | The new unit has all the data in the floor/level/... fields erased.
29 | '''
30 | parent_hashes = [V.hash() for V in U.parents]
31 | parents = [hashes_to_units[V] for V in parent_hashes]
32 | U_new = Unit(U.creator_id, parents, U.transactions(), U.signature, U.coin_shares)
33 | return U_new
34 |
35 |
36 | n_processes = 16
37 | n_units = 1000
38 | use_tcoin = True
39 | processes = []
40 | host_ports = [8900+i for i in range(n_processes)]
41 | addresses = [('127.0.0.1', port) for port in host_ports]
42 | recv_addresses = [('127.0.0.1', 9100+i) for i in range(n_processes)]
43 |
44 | signing_keys = [SigningKey() for _ in range(n_processes)]
45 | public_keys = [VerifyKey.from_SigningKey(sk) for sk in signing_keys]
46 |
47 | for process_id in range(n_processes):
48 | sk = signing_keys[process_id]
49 | pk = public_keys[process_id]
50 | new_process = Process(n_processes, process_id, sk, pk, addresses, public_keys, recv_addresses[process_id], None, use_tcoin)
51 | processes.append(new_process)
52 |
53 |
54 | for unit_no in range(n_units):
55 | while True:
56 | creator_id = random.choice(range(n_processes))
57 | process = processes[creator_id]
58 | new_unit = create_unit(process.poset, creator_id, [])
59 | if new_unit is None:
60 | continue
61 |
62 | process.poset.prepare_unit(new_unit)
63 | assert process.poset.check_compliance(new_unit), "A unit created by this process is not passing the compliance test!"
64 | process.sign_unit(new_unit)
65 | process.add_unit_to_poset(new_unit)
66 |
67 | for process_id in range(n_processes):
68 | if process_id != creator_id:
69 | hashes_to_units = processes[process_id].poset.units
70 | U = translate_parents_and_copy(new_unit, hashes_to_units)
71 | processes[process_id].add_unit_to_poset(U)
72 | break
73 |
74 | if unit_no%50 == 0:
75 | print(f"Adding unit no {unit_no} out of {n_units}.")
76 |
--------------------------------------------------------------------------------
/tests/linear_ordering_network.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | import asyncio
15 | import socket
16 |
17 | from aleph.network import tx_source_gen
18 | from aleph.process import Process
19 | from aleph.crypto.keys import SigningKey, VerifyKey
20 |
21 | import aleph.const as consts
22 |
23 |
24 | async def main():
25 | n_processes = 20
26 | consts.USE_TCOIN = 0
27 | consts.UNITS_LIMIT = None
28 | consts.LEVEL_LIMIT = 100
29 | consts.N_PARENTS = n_processes
30 |
31 | processes = []
32 | host_ports = [8900+i for i in range(n_processes)]
33 | local_ip = socket.gethostbyname(socket.gethostname())
34 | addresses = [(local_ip, port) for port in host_ports]
35 | recv_addresses = [(local_ip, 9100+i) for i in range(n_processes)]
36 |
37 | signing_keys = [SigningKey() for _ in range(n_processes)]
38 | public_keys = [VerifyKey.from_SigningKey(sk) for sk in signing_keys]
39 |
40 | tasks = []
41 | userDB = None
42 |
43 | for process_id in range(n_processes):
44 | sk = signing_keys[process_id]
45 | pk = public_keys[process_id]
46 | new_process = Process(n_processes,
47 | process_id,
48 | sk, pk,
49 | addresses,
50 | public_keys,
51 | None,
52 | userDB,
53 | tx_source_gen(batch_size=3, txpu=1, seed=process_id))
54 | processes.append(new_process)
55 | tasks.append(asyncio.create_task(new_process.run()))
56 |
57 | await asyncio.gather(*tasks)
58 |
59 |
60 | if __name__ == '__main__':
61 | asyncio.run(main())
62 |
--------------------------------------------------------------------------------
/tests/make_keys.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | #!/usr/bin/env python3
15 | from aleph.crypto.keys import SigningKey, VerifyKey
16 |
17 | def save_keys(keys, filename):
18 | with open(filename, "w") as the_file:
19 | for key in keys:
20 | the_file.write(key.decode('utf8')+"\n")
21 |
22 | def save_priv_keys(priv_keys, i):
23 | save_keys(priv_keys, "key" + str(i) + ".secret")
24 |
25 | def save_pub_keys(pub_keys):
26 | save_keys(pub_keys, "keys.public")
27 |
28 | if __name__ == '__main__':
29 | import sys
30 | assert len(sys.argv) == 3
31 | n_machines = int(sys.argv[1])
32 | processes_per_machine = int(sys.argv[2])
33 | pub_keys = []
34 | for i in range(n_machines):
35 | priv_keys = []
36 | for _ in range(processes_per_machine):
37 | priv_key = SigningKey()
38 | pub_keys.append(VerifyKey.from_SigningKey(priv_key).to_hex())
39 | priv_keys.append(priv_key.to_hex())
40 | save_priv_keys(priv_keys, i)
41 | save_pub_keys(pub_keys)
42 |
--------------------------------------------------------------------------------
/tests/read_dumped_poset.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | from aleph.utils import dag_utils
15 | import time, os, sys
16 | import random
17 |
18 | # path to a file with a dag dumped from poset using the dump_to_file() method
19 | file_name = 'poset_1.dag'
20 | if len(sys.argv) > 1:
21 | file_name = sys.argv[1]
22 |
23 | if not os.path.exists(file_name):
24 | print(f"The file {file_name} doesn't exist. Aborting.")
25 | exit(0)
26 |
27 |
28 | dag = dag_utils.dag_from_file(file_name)
29 | print(f"Dag consists of {len(dag)} units.")
30 | # retrieve the list of units in the same order as they were added to the poset
31 | units_list = dag.get_node_list_as_added()
32 |
33 | print("level (pr. units) (min pr. units) (avg n vis. below)")
34 | for level in range(1000):
35 | primes = dag.get_prime_units_by_level(level)
36 | if primes == []:
37 | break
38 | min_primes = [U for U in primes if all(dag.level(V) < dag.level(U) for V in dag.parents(U))]
39 |
40 | n_visible_below = []
41 | for U in primes:
42 | below = dag.get_prime_units_by_level(level-1)
43 | cnt = sum(dag.is_reachable(V, U) for V in below)
44 | n_visible_below.append(cnt)
45 |
46 | avg_n_visible = sum(n_visible_below)/len(n_visible_below)
47 | print(f"{level: <15} {len(primes): <15} {len(min_primes): <15} {avg_n_visible:<15.2f}")
48 |
49 |
50 | for U in units_list:
51 | parents = dag.parents(U)
52 | if parents != []:
53 | self_predecessor = parents[0]
54 | level_now = dag.level(U)
55 | level_prev = dag.level(self_predecessor)
56 | if level_now - level_prev > 1:
57 | creator_id = dag.pid(U)
58 | print(f"Process {creator_id} jumped from level {level_prev} to {level_now}.")
59 |
60 |
61 | tries = 10**4
62 | timer_start = time.time()
63 | for _ in range(tries):
64 | U1 = random.choice(units_list)
65 | U2 = random.choice(units_list)
66 | res = dag.is_reachable(U1, U2)
67 |
68 | timer_stop = time.time()
69 | print(f"Executed {tries} random below queries, total time {timer_stop - timer_start:.4f}")
70 |
71 | for U in units_list:
72 | if dag.get_node_info(U, "timing"):
73 | print(U, "is the timing unit at level", dag.level(U))
74 |
75 |
--------------------------------------------------------------------------------
/tests/threshold_coin_distribution.py:
--------------------------------------------------------------------------------
1 | '''
2 | This is a Proof-of-Concept implementation of Aleph Zero consensus protocol.
3 | Copyright (C) 2019 Aleph Zero Team
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or
6 | (at your option) any later version.
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 | GNU General Public License for more details.
9 |
10 | You should have received a copy of the GNU General Public License
11 | along with this program. If not, see .
12 | '''
13 |
14 | from aleph.utils.generic_test import simulate_with_checks
15 | import random
16 | from joblib import Parallel, delayed
17 | from time import time
18 |
19 | def report_coin_toss(U, poset, dag, results, additional_args):
20 | if additional_args is None:
21 | levels, U_c = set(), None
22 | else:
23 | levels, U_c = additional_args
24 | if U.level not in levels:
25 | levels.add(U.level)
26 | if U.level == 3:
27 | U_c = U
28 | if U_c is not None and U.level-U_c.level >= 4:
29 | results.append(poset.toss_coin(U_c, U))
30 | return levels, U_c
31 |
32 |
33 | def check_distribution(n_jobs, n_processes, n_units, n_forkers):
34 | print('n_jobs', n_jobs, '\nn_processes', n_processes, '\nn_units', n_units, '\nn_forkers', n_forkers)
35 | print('dispatching workers')
36 | start = time()
37 | results = Parallel(n_jobs=n_jobs)(
38 | delayed(simulate_with_checks)(
39 | n_processes,
40 | n_units,
41 | n_forkers,
42 | post_prepare = report_coin_toss,
43 | use_tcoin = True,
44 | seed = round(time())+i
45 | ) for i in range(n_jobs))
46 | print('work done in', round(time()-start,2))
47 |
48 | all_sum, all_len = 0, 0
49 | for res in results:
50 | all_sum += sum(res)
51 | all_len += len(res)
52 | if all_len:
53 | print(all_len, all_sum/all_len)
54 |
55 |
56 | if __name__ == '__main__':
57 | check_distribution(8, 16, 4000, 0)
58 |
--------------------------------------------------------------------------------