mirror of https://github.com/nucypher/nucypher.git
commit
f2f8b27f88
|
@ -1,29 +0,0 @@
|
|||
name: '👷 Run Demo'
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- development
|
||||
|
||||
jobs:
|
||||
run-demo:
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.7'
|
||||
|
||||
- name: Install docker compose
|
||||
run: pip3 install docker-compose
|
||||
|
||||
- name: Start containers
|
||||
run: docker-compose -f ./scripts/ci/docker-compose.yml build nucypher-ci-dev
|
||||
|
||||
- name: Run demo ursula fleet, finnegan's wake demo
|
||||
run: ./scripts/ci/run_finnegans_wake_demo_docker-ci.sh
|
|
@ -5,7 +5,7 @@ Description="Run 'Ursula', A NuCypher Staking Node."
|
|||
User=ubuntu
|
||||
Type=simple
|
||||
Environment="NUCYPHER_KEYSTORE_PASSWORD={{ursula_password.stdout}}"
|
||||
ExecStart={{ virtualenv_path }}/bin/nucypher ursula run --debug --network {{ nucypher_network_domain }} --federated-only --teacher {{ seed_node_metadata.checksum_address }}@https://{{ seed_node_metadata.rest_host }}:{{seed_node_metadata.rest_port}}
|
||||
ExecStart={{ virtualenv_path }}/bin/nucypher ursula run --debug --network {{ nucypher_network_domain }} --teacher {{ seed_node_metadata.checksum_address }}@https://{{ seed_node_metadata.rest_host }}:{{seed_node_metadata.rest_port}}
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
|
@ -1,130 +0,0 @@
|
|||
version: '3'
|
||||
|
||||
# runs 8 ursulas, each on a different "host"
|
||||
# similar to real world conditions
|
||||
|
||||
# ex. docker-compose -f 8-federated-ursulas.yml up
|
||||
|
||||
services:
|
||||
nucypher-dev:
|
||||
volumes:
|
||||
- ../..:/code
|
||||
ports:
|
||||
- 11500
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: dev/docker/Dockerfile
|
||||
image: dev:nucypher
|
||||
networks:
|
||||
nucypher_net:
|
||||
ipv4_address: 172.28.1.0
|
||||
container_name: nucypher-dev
|
||||
ursula1:
|
||||
volumes:
|
||||
- ../..:/code
|
||||
ports:
|
||||
- 11500
|
||||
image: dev:nucypher
|
||||
command: nucypher ursula run --dev --federated-only --rest-host 172.28.1.1 --rest-port 11500
|
||||
networks:
|
||||
nucypher_net:
|
||||
ipv4_address: 172.28.1.1
|
||||
container_name: ursula1
|
||||
ursula2:
|
||||
volumes:
|
||||
- ../..:/code
|
||||
ports:
|
||||
- 11500
|
||||
image: dev:nucypher
|
||||
depends_on:
|
||||
- ursula1
|
||||
command: nucypher ursula run --dev --federated-only --rest-host 172.28.1.2 --rest-port 11500 --teacher 172.28.1.1:11500
|
||||
networks:
|
||||
nucypher_net:
|
||||
ipv4_address: 172.28.1.2
|
||||
container_name: ursula2
|
||||
ursula3:
|
||||
volumes:
|
||||
- ../..:/code
|
||||
ports:
|
||||
- 11500
|
||||
image: dev:nucypher
|
||||
depends_on:
|
||||
- ursula1
|
||||
command: nucypher ursula run --dev --federated-only --rest-host 172.28.1.3 --rest-port 11500 --teacher 172.28.1.1:11500
|
||||
networks:
|
||||
nucypher_net:
|
||||
ipv4_address: 172.28.1.3
|
||||
container_name: ursula3
|
||||
ursula4:
|
||||
volumes:
|
||||
- ../..:/code
|
||||
ports:
|
||||
- 11500
|
||||
image: dev:nucypher
|
||||
depends_on:
|
||||
- ursula1
|
||||
command: nucypher ursula run --dev --federated-only --rest-host 172.28.1.4 --rest-port 11500 --teacher 172.28.1.1:11500
|
||||
networks:
|
||||
nucypher_net:
|
||||
ipv4_address: 172.28.1.4
|
||||
container_name: ursula4
|
||||
ursula5:
|
||||
volumes:
|
||||
- ../..:/code
|
||||
ports:
|
||||
- 11500
|
||||
image: dev:nucypher
|
||||
depends_on:
|
||||
- ursula1
|
||||
command: nucypher ursula run --dev --federated-only --rest-host 172.28.1.5 --rest-port 11500 --teacher 172.28.1.1:11500
|
||||
networks:
|
||||
nucypher_net:
|
||||
ipv4_address: 172.28.1.5
|
||||
container_name: ursula5
|
||||
ursula6:
|
||||
volumes:
|
||||
- ../..:/code
|
||||
ports:
|
||||
- 11500
|
||||
image: dev:nucypher
|
||||
depends_on:
|
||||
- ursula1
|
||||
command: nucypher ursula run --dev --federated-only --rest-host 172.28.1.6 --rest-port 11500 --teacher 172.28.1.1:11500
|
||||
networks:
|
||||
nucypher_net:
|
||||
ipv4_address: 172.28.1.6
|
||||
container_name: ursula6
|
||||
ursula7:
|
||||
volumes:
|
||||
- ../..:/code
|
||||
ports:
|
||||
- 11500
|
||||
image: dev:nucypher
|
||||
depends_on:
|
||||
- ursula1
|
||||
command: nucypher ursula run --dev --federated-only --rest-host 172.28.1.7 --rest-port 11500 --teacher 172.28.1.1:11500
|
||||
networks:
|
||||
nucypher_net:
|
||||
ipv4_address: 172.28.1.7
|
||||
container_name: ursula7
|
||||
ursula8:
|
||||
volumes:
|
||||
- ../..:/code
|
||||
ports:
|
||||
- 11500
|
||||
image: dev:nucypher
|
||||
depends_on:
|
||||
- ursula1
|
||||
command: nucypher ursula run --dev --federated-only --rest-host 172.28.1.8 --rest-port 11500 --teacher 172.28.1.1:11500
|
||||
networks:
|
||||
nucypher_net:
|
||||
ipv4_address: 172.28.1.8
|
||||
container_name: ursula8
|
||||
|
||||
networks:
|
||||
nucypher_net:
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 172.28.0.0/16
|
|
@ -1,35 +0,0 @@
|
|||
FROM nucypher/rust-python:3.9.9
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
ENV PYTHONPATH /code
|
||||
|
||||
# Update
|
||||
RUN apt-get update -y && apt-get upgrade -y && apt-get install patch gcc libffi-dev wget git -y
|
||||
|
||||
# make an install directory
|
||||
RUN mkdir /install
|
||||
WORKDIR /install
|
||||
|
||||
# copy only the exact files needed for install into the container
|
||||
COPY ./nucypher/__about__.py /install/nucypher/
|
||||
COPY README.md /install
|
||||
COPY setup.py /install
|
||||
COPY ./nucypher/blockchain/eth/sol/__conf__.py /install/nucypher/blockchain/eth/sol/__conf__.py
|
||||
COPY scripts/installation/install_solc.py /install/scripts/installation/
|
||||
COPY dev-requirements.txt /install
|
||||
COPY requirements.txt /install
|
||||
COPY docs-requirements.txt /install
|
||||
COPY dev/docker/scripts/install/entrypoint.sh /install
|
||||
|
||||
# install reqs and solc
|
||||
RUN pip install --upgrade pip
|
||||
RUN pip3 install .[dev] --src /usr/local/src
|
||||
RUN pip3 install ipdb
|
||||
|
||||
# puts the nucypher executable in bin path
|
||||
RUN python3 /install/setup.py develop
|
||||
|
||||
# now install solc
|
||||
RUN python3 /install/scripts/installation/install_solc.py
|
||||
|
||||
# this gets called after volumes are mounted and so can modify the local disk
|
||||
CMD ["/install/entrypoint.sh"]
|
|
@ -1,38 +0,0 @@
|
|||
### Developing with Docker
|
||||
|
||||
The intention of the Docker configurations in this directory is to enable anyone to develop and test NuCypher on all major operating systems with minimal prerequisites and installation hassle.
|
||||
|
||||
#### quickstart
|
||||
|
||||
* install [Docker](https://docs.docker.com/install/)
|
||||
* install [Docker Compose](https://docs.docker.com/compose/install/)
|
||||
* cd to dev/docker (where this README is located)
|
||||
* `docker-compose up --build` **this must be done once to complete install**
|
||||
|
||||
|
||||
Then you can do things like:
|
||||
* run the tests:
|
||||
`docker-compose run nucypher-dev pytest`
|
||||
* start up an ursula:
|
||||
`docker-compose run nucypher-dev nucypher ursula run --dev --federated-only`
|
||||
* open a shell:
|
||||
`docker-compose run nucypher-dev bash`
|
||||
|
||||
* try some of the scripts in `dev/docker/scripts/`
|
||||
|
||||
**tested on (Ubuntu 16, MacOS 10.14, Windows 10)*
|
||||
|
||||
From there you can develop, modify code, test as normal.
|
||||
|
||||
### other cases
|
||||
|
||||
* run a network of 8 independent Ursulas
|
||||
`docker-compose -f 8-federated-ursulas.yml up`
|
||||
* get the local ports these ursulas will be exposed on
|
||||
`docker ps`
|
||||
* to stop them...
|
||||
`docker-compose -f 8-federated-ursulas.yml stop`
|
||||
|
||||
## Pycharm (pro version only)
|
||||
* You can configure pycharm to use the python interpreter inside docker.
|
||||
* docs for this are [here](https://www.jetbrains.com/help/pycharm/using-docker-compose-as-a-remote-interpreter.html#docker-compose-remote)
|
|
@ -1,16 +0,0 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
nucypher-dev:
|
||||
volumes:
|
||||
- ../..:/code
|
||||
ports:
|
||||
- 10151:10151
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: dev/docker/Dockerfile
|
||||
image: dev:nucypher
|
||||
container_name: nucypher-dev
|
||||
working_dir: /code
|
||||
environment:
|
||||
- PYTHONBREAKPOINT=ipdb.set_trace
|
|
@ -1,10 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# runs inside docker container with access to local volume.
|
||||
# needed for local development, creates nucypher.egg-info on local disk
|
||||
# if it doesn't exist.
|
||||
|
||||
if [ ! -e /code/nucypher.egg-info ]; then
|
||||
echo "First time install..."
|
||||
python setup.py develop
|
||||
fi
|
|
@ -1 +0,0 @@
|
|||
docker-compose run nucypher-dev bash
|
|
@ -1,2 +0,0 @@
|
|||
#!/bin/bash
|
||||
docker-compose run nucypher-dev bash
|
|
@ -1 +0,0 @@
|
|||
docker-compose run nucypher-dev pytest $args
|
|
@ -1,3 +0,0 @@
|
|||
#!/bin/bash
|
||||
args="$@"
|
||||
docker-compose run nucypher-dev pytest $args
|
|
@ -1 +0,0 @@
|
|||
docker-compose run nucypher-dev nucypher ursula run --dev --federated-only
|
|
@ -1,2 +0,0 @@
|
|||
#!/bin/bash
|
||||
docker-compose run nucypher-dev nucypher ursula run --dev --federated-only
|
|
@ -1,94 +0,0 @@
|
|||
Local Development Fleet Testing
|
||||
===============================
|
||||
|
||||
.. image:: https://lh3.googleusercontent.com/u7OEMBBCZjPEZunlVJFC5kR7_2k2FEJWnkzQEB_P0JW-28wtmhFJbE_7M5Ludcuh9yJKXpM8ENKV3QXT4xq3ZGLbzGQMxSm6emo_rR0vLJBnXy0-LiwXPExIDE9F0bSbPV-27bKSS5Rohyl5magLvmFvYRZr9w7MUnoGifhLma0EpQBsRpiTJRVat8ceoxj-7xN3SA9_7BmvuzCbs6xj4KjMAzjkEEaW4t52KSmMeP3X_dc6GbCkIdo1t13Vg09bC5k1kyAYStrbgXx2wWiA5p3N_9TISWgTez4A2Wn1f36DB8V-sOCp5w51u9sUWjGtXZCWsFuUWtB7e3Far2SAnaOYfFNmf4cn0q81R9u5YannkZberqPT9MEhhJA7PRbB1NRRI4a5N_406NoyQlSZHXweC-KQ74Vn147BmJ3UeZETKILCUGk8OpD_qUZ89Rz3R1HUoSpvO9fDIHeZbcB-KXE-wCIRXynMgOunQWP5vy_nZj8mMeOIzlMxorC2uUotToNfjZFPRbMPflz_z-5jE6aYIWf7d8OOgUbOKp_Rw9dJDpZYJAIfwVglYPYMQUyRkkpNzApS6QJCpGtOh_c-b5Kc1mFUpyD-BO3KLHKorNdH1Pnq15D1rLZ8JQ-WjsGDkMEUsndLQt8giYU5hY5NQGg8wMN8LduFZlfi0uRHEc9LiiBmCJCtZ6Fcvltk1WAhhf0k5gpAUwKIogko9w=w1308-h982-no
|
||||
:target: https://pypi.org/project/nucypher/
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
To aid in application development, a network of federated Ursulas can be run locally. These Ursulas do not utilize
|
||||
blockchain functionality, but afford the same cryptographic capabilities of the public PRE nodes on the Threshold Network.
|
||||
|
||||
.. note::
|
||||
|
||||
Currently only "Federated Only" mode is supported for local fleets
|
||||
|
||||
All Demo Ursulas:
|
||||
* Run on ``localhost``
|
||||
* In ``--federated-only`` mode
|
||||
* On the ``TEMPORARY_DOMAIN`` (implied by ``--dev``)
|
||||
* Using temporary resources (files, database, etc.)
|
||||
|
||||
|
||||
Running a Local Fleet
|
||||
---------------------
|
||||
|
||||
1. Install NuCypher
|
||||
|
||||
Acquire the NuCypher application code and install the dependencies.
|
||||
For a full installation guide see the :doc:`/references/installation`.
|
||||
|
||||
2. Run a Lonely Ursula
|
||||
|
||||
The first step is to launch the first Ursula on the network by running:
|
||||
|
||||
|
||||
.. code::
|
||||
|
||||
$ cd scripts/local_fleet
|
||||
$ python run_lonely_ursula.py
|
||||
|
||||
This will start an Ursula node:
|
||||
* With seednode discovery disabled
|
||||
* On port ``11500``
|
||||
|
||||
|
||||
3. Run a Local Fleet of Ursulas
|
||||
|
||||
Next, launch subsequent Ursulas in another terminal, informing them of the first Ursula:
|
||||
|
||||
|
||||
.. code::
|
||||
|
||||
$ python run_local_ursula_fleet.py
|
||||
|
||||
This will run 5 temporary Ursulas that:
|
||||
* All specify the lonely Ursula as a teacher
|
||||
* Run on ports ``11501`` through ``11506``
|
||||
|
||||
|
||||
4. Run an Entry-Point Ursula (Optional)
|
||||
|
||||
While the local fleet is running, you may want an entry-point to introspect the code in a debugger.
|
||||
For this we provide the optional script ``run_single_ursula.py`` for your convenience.
|
||||
|
||||
|
||||
.. code::
|
||||
|
||||
$ python run_single_ursula.py
|
||||
|
||||
This will run a single temporary Ursula:
|
||||
|
||||
* That specifies a random fleet node as a teacher
|
||||
* On a random available port
|
||||
|
||||
|
||||
Connecting to the Local Fleet
|
||||
------------------------------
|
||||
|
||||
Alternately, you can connect any node run from the CLI by specifying one of the nodes
|
||||
in the local fleet as a teacher, the same network domain, and the same operating mode.
|
||||
By default, nodes started with the ``--dev`` flag run on a dedicated domain (``TEMPORARY_DOMAIN``) and
|
||||
on a different port than the production default port (``9151``).
|
||||
Local fleet Ursulas range from ports ``11500`` to ``11506`` by default.
|
||||
|
||||
Here is an example of connecting to a node in the local development fleet:
|
||||
|
||||
.. code::
|
||||
|
||||
nucypher ursula run --dev --teacher localhost:11501
|
||||
|
||||
|
||||
.. note::
|
||||
The local development fleet is an *example* meant to demonstrate how to design and use your own local fleet.
|
|
@ -80,39 +80,6 @@ Alternately, you can install the development dependencies with pip:
|
|||
$ ./scripts/installation/install_solc.py
|
||||
|
||||
|
||||
Development Docker Installation
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The intention of the Docker configurations in this directory is to enable anyone to develop and test
|
||||
NuCypher on all major operating systems with minimal prerequisites and installation hassle (tested on Ubuntu 16, MacOS 10.14, Windows 10).
|
||||
|
||||
Standard Docker Installation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
#. Install `Docker <https://docs.docker.com/install/>`_
|
||||
#. Install `Docker Compose <https://docs.docker.com/compose/install/>`_
|
||||
#. ``cd`` to ``dev/docker``
|
||||
#. Run ``docker-compose up --build`` **this must be done once to complete install**
|
||||
|
||||
Running NuCypher
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Then you can do things like:
|
||||
|
||||
* Run the tests: ``docker-compose run nucypher-dev pytest tests/unit``
|
||||
* Start up an Ursula: ``docker-compose run nucypher-dev nucypher ursula run --dev --federated-only``
|
||||
* Open a shell: ``docker-compose run nucypher-dev bash``
|
||||
* Try some of the scripts in ``dev/docker/scripts/``
|
||||
|
||||
From there you can develop, modify code, test as normal.
|
||||
|
||||
Other cases:
|
||||
|
||||
* Run a network of 8 independent Ursulas: ``docker-compose -f 8-federated-ursulas.yml up``
|
||||
* Get the local ports these ursulas will be exposed on: ``docker ps``
|
||||
* To stop them... ``docker-compose -f 8-federated-ursulas.yml stop``
|
||||
|
||||
|
||||
Running the Tests
|
||||
-----------------
|
||||
|
||||
|
@ -127,13 +94,12 @@ There are several test implementations in ``nucypher``, however, the vast majori
|
|||
of test are written for execution with ``pytest``.
|
||||
For more details see the `Pytest Documentation`_.
|
||||
|
||||
|
||||
To run the tests:
|
||||
To run the tests, use the following commands:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
(nucypher)$ pytest -s
|
||||
|
||||
(nucypher)$ pytest -s tests/unit
|
||||
(nucypher)$ pytest -s tests/integration
|
||||
|
||||
Optionally, to run the full, slow, verbose test suite run:
|
||||
|
||||
|
@ -387,4 +353,4 @@ For example, for a new ``patch`` release, we would do:
|
|||
(nucypher)$ make release bump=patch
|
||||
|
||||
3. The previous step triggers the publication webhooks on CircleCI.
|
||||
Monitor the triggered deployment build for manual approval.
|
||||
Monitor the triggered deployment build for manual approval.
|
||||
|
|
|
@ -3,11 +3,6 @@
|
|||
This illustrates Alice sharing data with Bob over the Threshold Network using proxy re-encryption (PRE),
|
||||
without revealing private keys to intermediary entities. For more detailed information see the [official documentation](https://docs.nucypher.com/en/latest/).
|
||||
|
||||
There are two version of the example, one using the decentralized network (ethereum/polygon),
|
||||
and a federated example using a local network.
|
||||
|
||||
### Decentralized Network Demo
|
||||
|
||||
First, configure the demo by making exporting environment variables
|
||||
with your provider and wallet details.
|
||||
|
||||
|
|
|
@ -1,160 +0,0 @@
|
|||
"""
|
||||
This file is part of nucypher.
|
||||
|
||||
nucypher is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
nucypher 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
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import datetime
|
||||
import maya
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from nucypher.characters.lawful import Alice, Bob, Ursula
|
||||
from nucypher.characters.lawful import Enrico as Enrico
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN
|
||||
from nucypher.crypto.powers import SigningPower, DecryptingPower
|
||||
from nucypher.utilities.logging import GlobalLoggerSettings
|
||||
|
||||
|
||||
######################
|
||||
# Boring setup stuff #
|
||||
######################
|
||||
|
||||
BOOK_PATH = Path(os.getenv('FINNEGANS_WAKE_PATH') or 'finnegans-wake-excerpt.txt')
|
||||
|
||||
# Twisted Logger
|
||||
GlobalLoggerSettings.set_log_level(log_level_name='info')
|
||||
GlobalLoggerSettings.start_console_logging()
|
||||
|
||||
# if your ursulas are NOT running on your current host,
|
||||
# run like this: python finnegans-wake-demo.py 172.28.1.3:11500
|
||||
# otherwise the default will be fine.
|
||||
|
||||
try:
|
||||
SEEDNODE_URI = sys.argv[1]
|
||||
except IndexError:
|
||||
SEEDNODE_URI = "localhost:11500"
|
||||
|
||||
##############################################
|
||||
# Ursula, the Untrusted Re-Encryption Proxy #
|
||||
##############################################
|
||||
ursula = Ursula.from_seed_and_stake_info(seed_uri=SEEDNODE_URI, federated_only=True)
|
||||
|
||||
# Here are our Policy details.
|
||||
policy_end_datetime = maya.now() + datetime.timedelta(days=1)
|
||||
threshold, shares = 2, 3
|
||||
label = b"secret/files/and/stuff"
|
||||
|
||||
|
||||
#####################
|
||||
# Bob the BUIDLer ##
|
||||
#####################
|
||||
|
||||
# First there was Bob.
|
||||
bob = Bob(federated_only=True, domain=TEMPORARY_DOMAIN, known_nodes=[ursula])
|
||||
|
||||
# Bob gives his public keys to alice.
|
||||
verifying_key = bob.public_keys(SigningPower)
|
||||
encrypting_key = bob.public_keys(DecryptingPower)
|
||||
|
||||
######################################
|
||||
# Alice, the Authority of the Policy #
|
||||
######################################
|
||||
|
||||
alice = Alice(federated_only=True, domain=TEMPORARY_DOMAIN, known_nodes=[ursula])
|
||||
|
||||
|
||||
# Start node discovery and wait until 8 nodes are known in case
|
||||
# the fleet isn't fully spun up yet, as sometimes happens on CI.
|
||||
alice.start_learning_loop(now=True)
|
||||
alice.block_until_number_of_known_nodes_is(8, timeout=30, learn_on_this_thread=True)
|
||||
|
||||
# Alice can get the public key even before creating the policy.
|
||||
# From this moment on, any Data Source that knows the public key
|
||||
# can encrypt data originally intended for Alice, but that can be shared with
|
||||
# any Bob that Alice grants access.
|
||||
policy_public_key = alice.get_policy_encrypting_key_from_label(label)
|
||||
|
||||
# Alice grant access to Bob. She already knows Bob's public keys from a side-channel.
|
||||
remote_bob = Bob.from_public_keys(encrypting_key=encrypting_key, verifying_key=verifying_key)
|
||||
policy = alice.grant(remote_bob, label, threshold=threshold, shares=shares, expiration=policy_end_datetime)
|
||||
|
||||
assert policy.public_key == policy_public_key
|
||||
|
||||
# Alice puts her public key somewhere for Bob to find later...
|
||||
alice_verifying_key = alice.stamp.as_umbral_pubkey()
|
||||
|
||||
# ...and then disappears from the internet.
|
||||
#
|
||||
# Note that local characters (alice and bob), as opposed to objects representing
|
||||
# remote characters constructed from public data (remote_alice and remote_bob)
|
||||
# run a learning loop in a background thread and need to be stopped explicitly.
|
||||
alice.disenchant()
|
||||
del alice
|
||||
|
||||
#####################
|
||||
# some time passes. #
|
||||
# ... #
|
||||
# #
|
||||
# ... #
|
||||
# And now for Bob. #
|
||||
#####################
|
||||
|
||||
#####################
|
||||
# Bob the BUIDLer ##
|
||||
#####################
|
||||
|
||||
# Now let's show how Enrico the Encryptor
|
||||
# can share data with the members of this Policy and then how Bob retrieves it.
|
||||
# In order to avoid re-encrypting the entire book in this demo, we only read some lines.
|
||||
with open(BOOK_PATH, 'rb') as file:
|
||||
finnegans_wake = file.readlines()
|
||||
|
||||
print()
|
||||
print("**************James Joyce's Finnegan's Wake (Excerpt)**************")
|
||||
print()
|
||||
print("---------------------------------------------------------")
|
||||
|
||||
for counter, plaintext in enumerate(finnegans_wake):
|
||||
|
||||
#########################
|
||||
# Enrico, the Encryptor #
|
||||
#########################
|
||||
|
||||
enrico = Enrico(policy_encrypting_key=policy_public_key)
|
||||
|
||||
# In this case, the plaintext is a
|
||||
# single passage from James Joyce's Finnegan's Wake.
|
||||
# The matter of whether encryption makes the passage more or less readable
|
||||
# is left to the reader to determine.
|
||||
single_passage_message_kit = enrico.encrypt_message(plaintext)
|
||||
data_source_public_key = enrico.stamp.as_umbral_pubkey()
|
||||
del enrico
|
||||
|
||||
###############
|
||||
# Back to Bob #
|
||||
###############
|
||||
|
||||
# Now Bob can retrieve the original message.
|
||||
delivered_cleartexts = bob.retrieve_and_decrypt([single_passage_message_kit],
|
||||
alice_verifying_key=alice_verifying_key,
|
||||
encrypted_treasure_map=policy.treasure_map)
|
||||
|
||||
# We show that indeed this is the passage originally encrypted by Enrico.
|
||||
assert plaintext == delivered_cleartexts[0]
|
||||
print("Retrieved: {}".format(delivered_cleartexts[0]))
|
||||
|
||||
bob.disenchant()
|
|
@ -1,21 +1,3 @@
|
|||
"""
|
||||
This file is part of nucypher.
|
||||
|
||||
nucypher is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
nucypher 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
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import datetime
|
||||
import os
|
||||
from getpass import getpass
|
||||
|
@ -26,7 +8,7 @@ import maya
|
|||
from nucypher.blockchain.eth.signers.base import Signer
|
||||
from nucypher.characters.lawful import Alice, Bob
|
||||
from nucypher.characters.lawful import Enrico as Enrico
|
||||
from nucypher.crypto.powers import SigningPower, DecryptingPower
|
||||
from nucypher.crypto.powers import DecryptingPower, SigningPower
|
||||
from nucypher.policy.payment import SubscriptionManagerPayment
|
||||
from nucypher.utilities.ethereum import connect_web3_provider
|
||||
from nucypher.utilities.logging import GlobalLoggerSettings
|
||||
|
|
|
@ -25,7 +25,7 @@ from pathlib import Path
|
|||
import maya
|
||||
|
||||
from nucypher.blockchain.eth.signers import Signer
|
||||
from nucypher.characters.lawful import Bob, Alice
|
||||
from nucypher.characters.lawful import Alice, Bob
|
||||
from nucypher.policy.payment import SubscriptionManagerPayment
|
||||
from nucypher.utilities.ethereum import connect_web3_provider
|
||||
from nucypher.utilities.logging import GlobalLoggerSettings
|
||||
|
@ -116,6 +116,7 @@ print("The policy public key for "
|
|||
# In this example, we create a local file with encrypted data, containing
|
||||
# heart rate measurements from a heart monitor
|
||||
import heart_monitor
|
||||
|
||||
heart_monitor.generate_heart_rate_samples(policy_pubkey,
|
||||
samples=50,
|
||||
save_as_file=True)
|
||||
|
@ -125,12 +126,13 @@ heart_monitor.generate_heart_rate_samples(policy_pubkey,
|
|||
# To do so, she needs the public key of the recipient.
|
||||
# In this example, we generate it on the fly (for demonstration purposes)
|
||||
from doctor_keys import get_doctor_pubkeys
|
||||
|
||||
doctor_pubkeys = get_doctor_pubkeys()
|
||||
|
||||
# We create a view of the Bob who's going to be granted access.
|
||||
doctor_strange = Bob.from_public_keys(verifying_key=doctor_pubkeys['sig'],
|
||||
encrypting_key=doctor_pubkeys['enc'],
|
||||
federated_only=True)
|
||||
doctor_strange = Bob.from_public_keys(
|
||||
verifying_key=doctor_pubkeys["sig"], encrypting_key=doctor_pubkeys["enc"]
|
||||
)
|
||||
|
||||
# Here are our remaining Policy details, such as:
|
||||
# - Policy expiration date
|
||||
|
|
|
@ -1,21 +1,3 @@
|
|||
"""
|
||||
This file is part of nucypher.
|
||||
|
||||
nucypher is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
nucypher 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
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import base64
|
||||
import json
|
||||
import shutil
|
||||
|
@ -23,7 +5,7 @@ from timeit import default_timer as timer
|
|||
|
||||
import maya
|
||||
import msgpack
|
||||
from nucypher_core import MessageKit, EncryptedTreasureMap
|
||||
from nucypher_core import EncryptedTreasureMap, MessageKit
|
||||
from nucypher_core.umbral import PublicKey
|
||||
|
||||
from nucypher.characters.lawful import Bob
|
||||
|
|
|
@ -1,24 +1,7 @@
|
|||
"""
|
||||
This file is part of nucypher.
|
||||
|
||||
nucypher is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
nucypher 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
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from nucypher_core.umbral import SecretKey, PublicKey
|
||||
from nucypher_core.umbral import PublicKey, SecretKey
|
||||
|
||||
DOCTOR_PUBLIC_JSON = Path('doctor.public.json')
|
||||
DOCTOR_PRIVATE_JSON = Path('doctor.private.json')
|
||||
|
|
|
@ -1,27 +1,10 @@
|
|||
"""
|
||||
This file is part of nucypher.
|
||||
|
||||
nucypher is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
nucypher 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
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import random
|
||||
import time
|
||||
|
||||
import msgpack
|
||||
|
||||
from nucypher.characters.lawful import Enrico
|
||||
|
||||
|
||||
HEART_DATA_FILENAME = 'heart_data.msgpack'
|
||||
|
||||
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
"""
|
||||
This file is part of nucypher.
|
||||
|
||||
nucypher is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
nucypher 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
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
|
||||
from twisted.internet import reactor
|
||||
from contextlib import suppress
|
||||
|
||||
from nucypher.characters.lawful import Ursula
|
||||
from nucypher.config.constants import APP_DIR, TEMPORARY_DOMAIN
|
||||
from nucypher.utilities.networking import LOOPBACK_ADDRESS
|
||||
|
||||
FLEET_POPULATION = 12
|
||||
DEMO_NODE_STARTING_PORT = 11500
|
||||
USER_CACHE = Path(APP_DIR.user_cache_dir)
|
||||
|
||||
ursula_maker = partial(Ursula, rest_host=LOOPBACK_ADDRESS,
|
||||
federated_only=True,
|
||||
domain=TEMPORARY_DOMAIN)
|
||||
|
||||
|
||||
def spin_up_federated_ursulas(quantity: int = FLEET_POPULATION):
|
||||
# Ports
|
||||
starting_port = DEMO_NODE_STARTING_PORT
|
||||
ports = list(map(str, range(starting_port, starting_port + quantity)))
|
||||
ursulas = []
|
||||
|
||||
sage = ursula_maker(rest_port=ports[0])
|
||||
|
||||
ursulas.append(sage)
|
||||
for index, port in enumerate(ports[1:]):
|
||||
u = ursula_maker(
|
||||
rest_port=port,
|
||||
seed_nodes=[sage.seed_node_metadata()],
|
||||
start_learning_now=True,
|
||||
)
|
||||
ursulas.append(u)
|
||||
|
||||
for u in ursulas:
|
||||
deployer = u.get_deployer()
|
||||
deployer.addServices()
|
||||
deployer.catalogServers(deployer.hendrix)
|
||||
deployer.start()
|
||||
print(f"{u}: {deployer._listening_message()}")
|
||||
|
||||
reactor.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
spin_up_federated_ursulas()
|
|
@ -0,0 +1 @@
|
|||
Deprecated "federated mode" ursulas and the --federated-only launch flag.
|
|
@ -1,10 +1,6 @@
|
|||
|
||||
|
||||
|
||||
import json
|
||||
from decimal import Decimal
|
||||
from typing import Optional, Tuple
|
||||
from typing import Union
|
||||
from typing import Optional, Tuple, Union
|
||||
|
||||
import maya
|
||||
import time
|
||||
|
@ -20,21 +16,24 @@ from nucypher.blockchain.eth.agents import (
|
|||
AdjudicatorAgent,
|
||||
ContractAgency,
|
||||
NucypherTokenAgent,
|
||||
PREApplicationAgent
|
||||
PREApplicationAgent,
|
||||
)
|
||||
from nucypher.blockchain.eth.constants import NULL_ADDRESS
|
||||
from nucypher.blockchain.eth.decorators import save_receipt, validate_checksum_address
|
||||
from nucypher.blockchain.eth.deployers import (
|
||||
AdjudicatorDeployer,
|
||||
BaseContractDeployer,
|
||||
NucypherTokenDeployer,
|
||||
PREApplicationDeployer,
|
||||
SubscriptionManagerDeployer, AdjudicatorDeployer
|
||||
SubscriptionManagerDeployer,
|
||||
)
|
||||
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
|
||||
from nucypher.blockchain.eth.registry import BaseContractRegistry
|
||||
from nucypher.blockchain.eth.signers import Signer
|
||||
from nucypher.blockchain.eth.token import NU, WorkTracker
|
||||
from nucypher.config.constants import DEFAULT_CONFIG_ROOT
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
from nucypher.crypto.powers import CryptoPower, TransactingPower
|
||||
from nucypher.network.trackers import OperatorBondedTracker
|
||||
from nucypher.policy.payment import ContractPayment
|
||||
from nucypher.utilities.emitters import StdoutEmitter
|
||||
from nucypher.utilities.logging import Logger
|
||||
|
@ -288,14 +287,46 @@ class Operator(BaseActor):
|
|||
class OperatorError(BaseActor.ActorError):
|
||||
pass
|
||||
|
||||
def __init__(self,
|
||||
is_me: bool,
|
||||
payment_method: ContractPayment,
|
||||
work_tracker: WorkTracker = None,
|
||||
operator_address: ChecksumAddress = None,
|
||||
*args, **kwargs):
|
||||
def __init__(
|
||||
self,
|
||||
is_me: bool,
|
||||
eth_provider_uri: str,
|
||||
payment_method: ContractPayment,
|
||||
work_tracker: Optional[WorkTracker] = None,
|
||||
operator_address: Optional[ChecksumAddress] = None,
|
||||
signer: Signer = None,
|
||||
crypto_power: CryptoPower = None,
|
||||
client_password: str = None,
|
||||
transacting_power: TransactingPower = None,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
|
||||
# Falsy values may be passed down from the superclass
|
||||
if not eth_provider_uri:
|
||||
raise ValueError("ETH Provider URI is required to init a local character.")
|
||||
if not payment_method:
|
||||
raise ValueError("Payment method is required to init a local character.")
|
||||
|
||||
if not transacting_power:
|
||||
transacting_power = TransactingPower(
|
||||
account=operator_address,
|
||||
password=client_password,
|
||||
signer=signer,
|
||||
cache=True,
|
||||
)
|
||||
|
||||
# We pass the newly instantiated TransactingPower into consume_power_up here, even though it's accessible
|
||||
# on the instance itself (being composed in the __init__ of the base class, which we will call shortly)
|
||||
# because, given the need for initialization context, it's far less melodramatic
|
||||
# to do it here, and it's still available via the public crypto powers API.
|
||||
crypto_power.consume_power_up(transacting_power)
|
||||
|
||||
self.payment_method = payment_method
|
||||
self._operator_bonded_tracker = OperatorBondedTracker(ursula=self)
|
||||
|
||||
super().__init__(transacting_power=transacting_power, *args, **kwargs)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
self.log = Logger("worker")
|
||||
self.is_me = is_me
|
||||
self.__operator_address = operator_address
|
||||
|
@ -382,7 +413,7 @@ class Operator(BaseActor):
|
|||
return func
|
||||
|
||||
|
||||
class BlockchainPolicyAuthor(NucypherTokenActor):
|
||||
class PolicyAuthor(NucypherTokenActor):
|
||||
"""Alice base class for blockchain operations, mocking up new policies!"""
|
||||
|
||||
def __init__(self, eth_provider_uri: str, *args, **kwargs):
|
||||
|
@ -395,8 +426,9 @@ class BlockchainPolicyAuthor(NucypherTokenActor):
|
|||
|
||||
def create_policy(self, *args, **kwargs):
|
||||
"""Hence the name, a BlockchainPolicyAuthor can create a BlockchainPolicy with themself as the author."""
|
||||
from nucypher.policy.policies import BlockchainPolicy
|
||||
blockchain_policy = BlockchainPolicy(publisher=self, *args, **kwargs)
|
||||
from nucypher.policy.policies import Policy
|
||||
|
||||
blockchain_policy = Policy(publisher=self, *args, **kwargs)
|
||||
return blockchain_policy
|
||||
|
||||
|
||||
|
|
|
@ -11,14 +11,17 @@ from eth_account.messages import encode_defunct
|
|||
from eth_typing.evm import BlockNumber, ChecksumAddress
|
||||
from eth_utils import to_canonical_address, to_checksum_address
|
||||
from web3 import Web3
|
||||
from web3.contract import Contract
|
||||
from web3.types import Wei, TxReceipt
|
||||
from web3._utils.threads import Timeout
|
||||
from web3.contract import Contract
|
||||
from web3.exceptions import TimeExhausted, TransactionNotFound
|
||||
from web3.types import TxReceipt, Wei
|
||||
|
||||
from nucypher.blockchain.eth.constants import AVERAGE_BLOCK_TIME_IN_SECONDS
|
||||
from nucypher.blockchain.middleware.retry import RetryRequestMiddleware, AlchemyRetryRequestMiddleware, \
|
||||
InfuraRetryRequestMiddleware
|
||||
from nucypher.blockchain.middleware.retry import (
|
||||
AlchemyRetryRequestMiddleware,
|
||||
InfuraRetryRequestMiddleware,
|
||||
RetryRequestMiddleware,
|
||||
)
|
||||
from nucypher.utilities.logging import Logger
|
||||
|
||||
UNKNOWN_DEVELOPMENT_CHAIN_ID.bool_value(True)
|
||||
|
|
|
@ -1,19 +1,8 @@
|
|||
|
||||
|
||||
|
||||
from contextlib import suppress
|
||||
from pathlib import Path
|
||||
from typing import ClassVar, Dict, List, Optional
|
||||
|
||||
from constant_sorrow.constants import (
|
||||
NO_BLOCKCHAIN_CONNECTION,
|
||||
NO_NICKNAME,
|
||||
NO_SIGNING_POWER,
|
||||
STRANGER,
|
||||
)
|
||||
from eth_keys import KeyAPI as EthKeyAPI
|
||||
from constant_sorrow.constants import NO_NICKNAME, NO_SIGNING_POWER, STRANGER
|
||||
from eth_utils import to_canonical_address
|
||||
from nucypher_core import MessageKit
|
||||
from nucypher_core.umbral import PublicKey
|
||||
|
||||
from nucypher.acumen.nicknames import Nickname
|
||||
|
@ -22,6 +11,7 @@ from nucypher.blockchain.eth.registry import (
|
|||
InMemoryContractRegistry,
|
||||
)
|
||||
from nucypher.blockchain.eth.signers.base import Signer
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN
|
||||
from nucypher.crypto.keystore import Keystore
|
||||
from nucypher.crypto.powers import (
|
||||
CryptoPower,
|
||||
|
@ -42,22 +32,23 @@ class Character(Learner):
|
|||
_default_crypto_powerups = None
|
||||
_stamp = None
|
||||
|
||||
def __init__(self,
|
||||
domain: str = None,
|
||||
known_node_class: object = None,
|
||||
is_me: bool = True,
|
||||
federated_only: bool = False,
|
||||
checksum_address: str = None,
|
||||
network_middleware: RestMiddleware = None,
|
||||
keystore: Keystore = None,
|
||||
crypto_power: CryptoPower = None,
|
||||
crypto_power_ups: List[CryptoPowerUp] = None,
|
||||
eth_provider_uri: str = None,
|
||||
signer: Signer = None,
|
||||
registry: BaseContractRegistry = None,
|
||||
include_self_in_the_state: bool = False,
|
||||
*args, **kwargs
|
||||
) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
domain: str,
|
||||
eth_provider_uri: str = None,
|
||||
known_node_class: object = None,
|
||||
is_me: bool = True,
|
||||
checksum_address: str = None,
|
||||
network_middleware: RestMiddleware = None,
|
||||
keystore: Keystore = None,
|
||||
crypto_power: CryptoPower = None,
|
||||
crypto_power_ups: List[CryptoPowerUp] = None,
|
||||
signer: Signer = None,
|
||||
registry: BaseContractRegistry = None,
|
||||
include_self_in_the_state: bool = False,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
|
||||
"""
|
||||
|
||||
|
@ -87,26 +78,6 @@ class Character(Learner):
|
|||
|
||||
"""
|
||||
|
||||
#
|
||||
# Prologue of the federation
|
||||
#
|
||||
|
||||
# FIXME: excuse me... can I speak to the manager?
|
||||
if is_me:
|
||||
# If this is a federated-is_me-character, assume everyone else is too.
|
||||
self._set_known_node_class(known_node_class, federated_only)
|
||||
else:
|
||||
# What an awful hack. The last convulsions of #466. # TODO: Anything else.
|
||||
with suppress(AttributeError):
|
||||
federated_only = known_node_class._federated_only_instances
|
||||
|
||||
if federated_only:
|
||||
if registry or eth_provider_uri:
|
||||
raise ValueError(f"Cannot init federated-only character with {registry or eth_provider_uri}.")
|
||||
self.federated_only: bool = federated_only
|
||||
|
||||
##########################################
|
||||
|
||||
#
|
||||
# Keys & Powers
|
||||
#
|
||||
|
@ -130,7 +101,7 @@ class Character(Learner):
|
|||
self._crypto_power = CryptoPower(power_ups=self._default_crypto_powerups)
|
||||
|
||||
#
|
||||
# Self
|
||||
# Local
|
||||
#
|
||||
|
||||
if is_me:
|
||||
|
@ -138,17 +109,16 @@ class Character(Learner):
|
|||
# Signing Power
|
||||
self.signer = signer
|
||||
try:
|
||||
signing_power = self._crypto_power.power_ups(SigningPower) # type: SigningPower
|
||||
self._stamp = signing_power.get_signature_stamp() # type: SignatureStamp
|
||||
signing_power: SigningPower = self._crypto_power.power_ups(SigningPower)
|
||||
self._stamp: SignatureStamp = signing_power.get_signature_stamp()
|
||||
except NoSigningPower:
|
||||
self._stamp = NO_SIGNING_POWER
|
||||
|
||||
# Blockchainy
|
||||
if not self.federated_only:
|
||||
self.eth_provider_uri = eth_provider_uri
|
||||
self.registry = registry or InMemoryContractRegistry.from_latest_publication(network=domain) # See #1580
|
||||
else:
|
||||
self.registry = NO_BLOCKCHAIN_CONNECTION.bool_value(False)
|
||||
self.eth_provider_uri = eth_provider_uri
|
||||
self.registry = (
|
||||
registry
|
||||
or InMemoryContractRegistry.from_latest_publication(network=domain)
|
||||
) # See #1580
|
||||
|
||||
# REST
|
||||
self.network_middleware = network_middleware or RestMiddleware(registry=self.registry,
|
||||
|
@ -162,22 +132,10 @@ class Character(Learner):
|
|||
include_self_in_the_state=include_self_in_the_state,
|
||||
*args, **kwargs)
|
||||
|
||||
if self.federated_only:
|
||||
try:
|
||||
derived_federated_address = self.derive_federated_address()
|
||||
except NoSigningPower:
|
||||
# TODO: Why allow such a character (without signing power) to be created at all?
|
||||
derived_federated_address = NO_SIGNING_POWER.bool_value(False)
|
||||
|
||||
if checksum_address and (checksum_address != derived_federated_address):
|
||||
raise ValueError(f"Provided checksum address {checksum_address} "
|
||||
f"does not match federated character's verifying key {derived_federated_address}")
|
||||
checksum_address = derived_federated_address
|
||||
|
||||
self.checksum_address = checksum_address
|
||||
|
||||
#
|
||||
# Stranger
|
||||
# Peer
|
||||
#
|
||||
|
||||
else:
|
||||
|
@ -214,23 +172,17 @@ class Character(Learner):
|
|||
return r
|
||||
|
||||
def __setup_nickname(self, is_me: bool):
|
||||
if not self.checksum_address and not self.federated_only and not is_me:
|
||||
if not self.checksum_address and not is_me:
|
||||
# Sometimes we don't care about the nickname. For example, if Alice is granting to Bob, she usually
|
||||
# doesn't know or care about his wallet. Maybe this needs to change?
|
||||
# Currently, if this is a stranger and there's no blockchain connection, we assign NO_NICKNAME:
|
||||
self.nickname = NO_NICKNAME
|
||||
else:
|
||||
try:
|
||||
if not self.checksum_address:
|
||||
self.nickname = NO_NICKNAME
|
||||
else:
|
||||
# This can call _set_checksum_address.
|
||||
self.nickname = Nickname.from_seed(self.checksum_address)
|
||||
except SigningPower.not_found_error:
|
||||
if self.federated_only:
|
||||
self.nickname = NO_NICKNAME
|
||||
else:
|
||||
raise
|
||||
if not self.checksum_address:
|
||||
self.nickname = NO_NICKNAME
|
||||
else:
|
||||
# This can call _set_checksum_address.
|
||||
self.nickname = Nickname.from_seed(self.checksum_address)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -248,7 +200,7 @@ class Character(Learner):
|
|||
@property
|
||||
def canonical_address(self):
|
||||
# TODO: This is wasteful. #1995
|
||||
return to_canonical_address(self.checksum_address)
|
||||
return to_canonical_address(str(self.checksum_address))
|
||||
|
||||
@classmethod
|
||||
def from_config(cls, config, **overrides) -> 'Character':
|
||||
|
@ -291,16 +243,22 @@ class Character(Learner):
|
|||
|
||||
crypto_power.consume_power_up(power_up(public_key=umbral_key))
|
||||
|
||||
return cls(is_me=False, crypto_power=crypto_power, *args, **kwargs)
|
||||
return cls(
|
||||
is_me=False,
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
crypto_power=crypto_power,
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def _set_known_node_class(self, known_node_class, federated_only):
|
||||
def _set_known_node_class(self, known_node_class):
|
||||
"""
|
||||
Once in a while, in tests or demos, we init a plain Character who doesn't already know about its node class.
|
||||
"""
|
||||
if not known_node_class:
|
||||
# Once in a while, in tests or demos, we init a plain Character who doesn't already know about its node class.
|
||||
from nucypher.characters.lawful import Ursula
|
||||
known_node_class = Ursula
|
||||
self.known_node_class = known_node_class
|
||||
# If we're federated only, we assume that all other nodes in our domain are as well.
|
||||
known_node_class.set_federated_mode(federated_only)
|
||||
|
||||
# TODO: Unused
|
||||
def store_metadata(self, filepath: Path) -> Path:
|
||||
|
@ -312,28 +270,6 @@ class Character(Learner):
|
|||
|
||||
return self.node_storage.store_node_metadata(node=self, filepath=filepath)
|
||||
|
||||
def encrypt_for(self,
|
||||
recipient: 'Character',
|
||||
plaintext: bytes,
|
||||
) -> MessageKit:
|
||||
"""
|
||||
Encrypts plaintext for recipient actor. Optionally signs the message as well.
|
||||
|
||||
:param recipient: The character whose public key will be used to encrypt
|
||||
cleartext.
|
||||
:param plaintext: The secret to be encrypted.
|
||||
:param sign_plaintext: the cleartext is signed if this is
|
||||
True, Otherwise, the resulting ciphertext is signed.
|
||||
|
||||
:return: the message kit.
|
||||
"""
|
||||
|
||||
# TODO: who even uses this method except for tests?
|
||||
|
||||
message_kit = MessageKit(policy_encrypting_key=recipient.public_keys(DecryptingPower),
|
||||
plaintext=plaintext)
|
||||
return message_kit
|
||||
|
||||
def public_keys(self, power_up_class: ClassVar):
|
||||
"""
|
||||
Pass a power_up_class, get the public material for this Character which corresponds to that
|
||||
|
@ -345,15 +281,6 @@ class Character(Learner):
|
|||
power_up = self._crypto_power.power_ups(power_up_class)
|
||||
return power_up.public_key()
|
||||
|
||||
def derive_federated_address(self):
|
||||
if self.federated_only:
|
||||
verifying_key = self.public_keys(SigningPower)
|
||||
verifying_key_as_eth_key = EthKeyAPI.PublicKey.from_compressed_bytes(bytes(verifying_key))
|
||||
federated_address = verifying_key_as_eth_key.to_checksum_address()
|
||||
else:
|
||||
raise RuntimeError('Federated address can only be derived for federated characters.')
|
||||
return federated_address
|
||||
|
||||
def disenchant(self):
|
||||
self.log.debug(f"Disenchanting {self}")
|
||||
Learner.stop_learning_loop(self)
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
|
||||
|
||||
|
||||
import contextlib
|
||||
import json
|
||||
import time
|
||||
|
@ -52,7 +49,7 @@ from web3.types import TxReceipt
|
|||
import nucypher
|
||||
from nucypher.acumen.nicknames import Nickname
|
||||
from nucypher.acumen.perception import ArchivedFleetState, RemoteUrsulaStatus
|
||||
from nucypher.blockchain.eth.actors import BlockchainPolicyAuthor, Operator
|
||||
from nucypher.blockchain.eth.actors import Operator, PolicyAuthor
|
||||
from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent
|
||||
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
|
||||
from nucypher.blockchain.eth.registry import BaseContractRegistry
|
||||
|
@ -84,45 +81,40 @@ from nucypher.network.trackers import AvailabilityTracker, OperatorBondedTracker
|
|||
from nucypher.policy.conditions.types import LingoList
|
||||
from nucypher.policy.conditions.utils import validate_condition_lingo
|
||||
from nucypher.policy.kits import PolicyMessageKit
|
||||
from nucypher.policy.payment import FreeReencryptions, PaymentMethod
|
||||
from nucypher.policy.policies import BlockchainPolicy, FederatedPolicy, Policy
|
||||
from nucypher.policy.payment import ContractPayment, PaymentMethod
|
||||
from nucypher.policy.policies import Policy
|
||||
from nucypher.utilities.emitters import StdoutEmitter
|
||||
from nucypher.utilities.logging import Logger
|
||||
from nucypher.utilities.networking import validate_operator_ip
|
||||
|
||||
|
||||
class Alice(Character, BlockchainPolicyAuthor):
|
||||
class Alice(Character, PolicyAuthor):
|
||||
banner = ALICE_BANNER
|
||||
_default_crypto_powerups = [SigningPower, DecryptingPower, DelegatingPower]
|
||||
|
||||
def __init__(self,
|
||||
|
||||
# Mode
|
||||
is_me: bool = True,
|
||||
federated_only: bool = False,
|
||||
eth_provider_uri: str = None,
|
||||
signer=None,
|
||||
|
||||
# Ownership
|
||||
checksum_address: str = None,
|
||||
|
||||
# M of N
|
||||
threshold: Optional[int] = None,
|
||||
shares: Optional[int] = None,
|
||||
|
||||
# Policy Value
|
||||
rate: int = None,
|
||||
duration: int = None,
|
||||
payment_method: PaymentMethod = None,
|
||||
|
||||
# Policy Storage
|
||||
store_policy_credentials: bool = None,
|
||||
|
||||
# Middleware
|
||||
timeout: int = 10, # seconds # TODO: configure NRN
|
||||
network_middleware: RestMiddleware = None,
|
||||
|
||||
*args, **kwargs) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
# Mode
|
||||
is_me: bool = True,
|
||||
eth_provider_uri: str = None,
|
||||
signer=None,
|
||||
# Ownership
|
||||
checksum_address: Optional[ChecksumAddress] = None,
|
||||
# M of N
|
||||
threshold: Optional[int] = None,
|
||||
shares: Optional[int] = None,
|
||||
# Policy Value
|
||||
rate: int = None,
|
||||
duration: int = None,
|
||||
payment_method: PaymentMethod = None,
|
||||
# Policy Storage
|
||||
store_policy_credentials: bool = None,
|
||||
# Middleware
|
||||
timeout: int = 10, # seconds # TODO: configure NRN
|
||||
network_middleware: RestMiddleware = None,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
|
||||
#
|
||||
# Fallback Policy Values
|
||||
|
@ -143,33 +135,28 @@ class Alice(Character, BlockchainPolicyAuthor):
|
|||
Character.__init__(self,
|
||||
known_node_class=Ursula,
|
||||
is_me=is_me,
|
||||
federated_only=federated_only,
|
||||
eth_provider_uri=eth_provider_uri,
|
||||
checksum_address=checksum_address,
|
||||
network_middleware=network_middleware,
|
||||
*args, **kwargs)
|
||||
|
||||
if is_me and not federated_only: # TODO: #289
|
||||
if not eth_provider_uri:
|
||||
raise ValueError('ETH Provider URI is required to init a decentralized character.')
|
||||
|
||||
if is_me: # TODO: #289
|
||||
blockchain = BlockchainInterfaceFactory.get_interface(eth_provider_uri=self.eth_provider_uri)
|
||||
signer = signer or Web3Signer(blockchain.client) # fallback to web3 provider by default for Alice.
|
||||
self.transacting_power = TransactingPower(account=self.checksum_address, signer=signer)
|
||||
self.transacting_power = TransactingPower(account=checksum_address, signer=signer)
|
||||
self._crypto_power.consume_power_up(self.transacting_power)
|
||||
BlockchainPolicyAuthor.__init__(self,
|
||||
domain=self.domain,
|
||||
transacting_power=self.transacting_power,
|
||||
registry=self.registry,
|
||||
eth_provider_uri=eth_provider_uri)
|
||||
PolicyAuthor.__init__(
|
||||
self,
|
||||
domain=self.domain,
|
||||
transacting_power=self.transacting_power,
|
||||
registry=self.registry,
|
||||
eth_provider_uri=eth_provider_uri,
|
||||
)
|
||||
|
||||
self.log = Logger(self.__class__.__name__)
|
||||
if is_me:
|
||||
|
||||
# Policy Payment
|
||||
if federated_only and not payment_method:
|
||||
# Federated payments are free by default.
|
||||
payment_method = FreeReencryptions()
|
||||
if not payment_method:
|
||||
raise ValueError('payment_method is a required argument for a local Alice.')
|
||||
self.payment_method = payment_method
|
||||
|
@ -238,14 +225,9 @@ class Alice(Character, BlockchainPolicyAuthor):
|
|||
public_key=public_key,
|
||||
**policy_params)
|
||||
|
||||
if self.federated_only:
|
||||
# Use known nodes
|
||||
policy = FederatedPolicy(publisher=self, **payload)
|
||||
else:
|
||||
# Sample from blockchain
|
||||
payload.update(**policy_params)
|
||||
policy = BlockchainPolicy(publisher=self, **payload)
|
||||
|
||||
# Sample from blockchain
|
||||
payload.update(**policy_params)
|
||||
policy = Policy(publisher=self, **payload)
|
||||
return policy
|
||||
|
||||
def generate_policy_parameters(self,
|
||||
|
@ -331,18 +313,6 @@ class Alice(Character, BlockchainPolicyAuthor):
|
|||
# Users may decide to inject some market strategies here.
|
||||
#
|
||||
|
||||
# If we're federated only, we need to block to make sure we have enough nodes.
|
||||
if self.federated_only and len(self.known_nodes) < policy.shares:
|
||||
good_to_go = self.block_until_number_of_known_nodes_is(number_of_nodes_to_know=policy.shares,
|
||||
learn_on_this_thread=True,
|
||||
timeout=timeout)
|
||||
if not good_to_go:
|
||||
raise ValueError(
|
||||
"To make a Policy in federated mode, you need to know about "
|
||||
"all the Ursulas you need (in this case, {}); there's no other way to "
|
||||
"know which nodes to use. Either pass them here or when you make the Policy, "
|
||||
"or run the learning loop on a network with enough Ursulas.".format(policy.shares))
|
||||
|
||||
self.log.debug(f"Enacting {policy} ... ")
|
||||
enacted_policy = policy.enact(network_middleware=self.network_middleware, ursulas=ursulas)
|
||||
|
||||
|
@ -356,7 +326,7 @@ class Alice(Character, BlockchainPolicyAuthor):
|
|||
|
||||
def revoke(self,
|
||||
policy: Policy,
|
||||
onchain: bool = True, # forced to False for federated mode
|
||||
onchain: bool = True,
|
||||
offchain: bool = True
|
||||
) -> Tuple[TxReceipt, Dict[ChecksumAddress, Tuple['Revocation', Exception]]]:
|
||||
|
||||
|
@ -365,7 +335,7 @@ class Alice(Character, BlockchainPolicyAuthor):
|
|||
|
||||
receipt, failed = dict(), dict()
|
||||
|
||||
if onchain and (not self.federated_only):
|
||||
if onchain:
|
||||
pass
|
||||
# TODO: Decouple onchain revocation from SubscriptionManager or deprecate.
|
||||
# receipt = self.policy_agent.revoke_policy(policy_id=bytes(policy.hrac),
|
||||
|
@ -566,50 +536,44 @@ class Ursula(Teacher, Character, Operator):
|
|||
class NotFound(Exception):
|
||||
pass
|
||||
|
||||
def __init__(self,
|
||||
def __init__(
|
||||
self,
|
||||
# Ursula
|
||||
rest_host: str,
|
||||
rest_port: int,
|
||||
domain: str,
|
||||
is_me: bool = True,
|
||||
certificate: Optional[Certificate] = None,
|
||||
certificate_filepath: Optional[Path] = None,
|
||||
availability_check: bool = False, # TODO: Remove from init
|
||||
metadata: Optional[NodeMetadata] = None,
|
||||
# Blockchain
|
||||
checksum_address: Optional[ChecksumAddress] = None,
|
||||
operator_address: Optional[ChecksumAddress] = None,
|
||||
client_password: Optional[str] = None,
|
||||
operator_signature_from_metadata=NOT_SIGNED,
|
||||
eth_provider_uri: Optional[str] = None,
|
||||
payment_method: Optional[Union[PaymentMethod, ContractPayment]] = None,
|
||||
# Character
|
||||
abort_on_learning_error: bool = False,
|
||||
crypto_power=None,
|
||||
known_nodes: Iterable[Teacher] = None,
|
||||
**character_kwargs,
|
||||
):
|
||||
|
||||
# Ursula
|
||||
rest_host: str,
|
||||
rest_port: int,
|
||||
domain: str,
|
||||
is_me: bool = True,
|
||||
|
||||
certificate: Certificate = None,
|
||||
certificate_filepath: Optional[Path] = None,
|
||||
|
||||
availability_check: bool = False, # TODO: Remove from init
|
||||
metadata: Optional[NodeMetadata] = None,
|
||||
|
||||
# Blockchain
|
||||
checksum_address: ChecksumAddress = None,
|
||||
operator_address: ChecksumAddress = None, # TODO: deprecate, and rename to "checksum_address"
|
||||
client_password: str = None,
|
||||
operator_signature_from_metadata=NOT_SIGNED,
|
||||
|
||||
eth_provider_uri: str = None,
|
||||
payment_method: PaymentMethod = None,
|
||||
|
||||
# Character
|
||||
abort_on_learning_error: bool = False,
|
||||
federated_only: bool = False,
|
||||
crypto_power=None,
|
||||
known_nodes: Iterable[Teacher] = None,
|
||||
|
||||
**character_kwargs
|
||||
):
|
||||
|
||||
Character.__init__(self,
|
||||
is_me=is_me,
|
||||
checksum_address=checksum_address,
|
||||
federated_only=federated_only,
|
||||
crypto_power=crypto_power,
|
||||
abort_on_learning_error=abort_on_learning_error,
|
||||
known_nodes=known_nodes,
|
||||
domain=domain,
|
||||
known_node_class=Ursula,
|
||||
include_self_in_the_state=True,
|
||||
eth_provider_uri=eth_provider_uri,
|
||||
**character_kwargs)
|
||||
Character.__init__(
|
||||
self,
|
||||
is_me=is_me,
|
||||
checksum_address=checksum_address,
|
||||
crypto_power=crypto_power,
|
||||
abort_on_learning_error=abort_on_learning_error,
|
||||
known_nodes=known_nodes,
|
||||
domain=domain,
|
||||
known_node_class=Ursula,
|
||||
include_self_in_the_state=True,
|
||||
eth_provider_uri=eth_provider_uri,
|
||||
**character_kwargs,
|
||||
)
|
||||
|
||||
if is_me:
|
||||
|
||||
|
@ -617,64 +581,43 @@ class Ursula(Teacher, Character, Operator):
|
|||
raise ValueError("A local node must generate its own metadata.")
|
||||
self._metadata = None
|
||||
|
||||
# Operating Mode
|
||||
self.known_node_class.set_federated_mode(federated_only)
|
||||
|
||||
# Health Checks
|
||||
self._availability_check = availability_check
|
||||
self._availability_tracker = AvailabilityTracker(ursula=self)
|
||||
if not federated_only:
|
||||
self._operator_bonded_tracker = OperatorBondedTracker(ursula=self)
|
||||
|
||||
# Policy Payment
|
||||
if federated_only and not payment_method:
|
||||
# Federated payments are free by default.
|
||||
payment_method = FreeReencryptions()
|
||||
try:
|
||||
payment_method: ContractPayment
|
||||
Operator.__init__(
|
||||
self,
|
||||
is_me=is_me,
|
||||
domain=self.domain,
|
||||
registry=self.registry,
|
||||
signer=self.signer,
|
||||
crypto_power=self._crypto_power,
|
||||
operator_address=operator_address,
|
||||
eth_provider_uri=eth_provider_uri,
|
||||
payment_method=payment_method,
|
||||
client_password=client_password,
|
||||
)
|
||||
except Exception:
|
||||
# TODO: Do not announce self to "other nodes" until this init is finished.
|
||||
# It's not possible to finish constructing this node.
|
||||
self.stop(halt_reactor=False)
|
||||
raise
|
||||
|
||||
# Decentralized Operator
|
||||
if not federated_only:
|
||||
if not eth_provider_uri:
|
||||
raise ValueError('ETH Provider URI is required to init a decentralized character.')
|
||||
if not payment_method:
|
||||
raise ValueError('Payment method is required to init a decentralized character.')
|
||||
|
||||
# TODO: Move to method
|
||||
# Prepare a TransactingPower from worker node's transacting keys
|
||||
transacting_power = TransactingPower(account=operator_address,
|
||||
password=client_password,
|
||||
signer=self.signer,
|
||||
cache=True)
|
||||
self.transacting_power = transacting_power
|
||||
self._crypto_power.consume_power_up(transacting_power)
|
||||
|
||||
# Use this power to substantiate the stamp
|
||||
self.__substantiate_stamp()
|
||||
|
||||
try:
|
||||
Operator.__init__(self,
|
||||
is_me=is_me,
|
||||
domain=self.domain,
|
||||
transacting_power=self.transacting_power,
|
||||
registry=self.registry,
|
||||
operator_address=operator_address,
|
||||
payment_method=payment_method)
|
||||
except (Exception, self.OperatorError):
|
||||
# TODO: Do not announce self to "other nodes" until this init is finished.
|
||||
# It's not possible to finish constructing this node.
|
||||
self.stop(halt_reactor=False)
|
||||
raise
|
||||
|
||||
# Payment Method
|
||||
# TODO: What value is acceptable here for a remote node?
|
||||
# TODO: Include accepted payment method announcements in metadata?
|
||||
self.payment_method = payment_method
|
||||
# Use this power to substantiate the stamp
|
||||
self._substantiate_stamp()
|
||||
|
||||
# Server
|
||||
self.rest_server = self._make_local_server(host=rest_host, port=rest_port)
|
||||
|
||||
# Self-signed TLS certificate of self for Teacher.__init__
|
||||
certificate_filepath = self._crypto_power.power_ups(TLSHostingPower).keypair.certificate_filepath
|
||||
certificate = self._crypto_power.power_ups(TLSHostingPower).keypair.certificate
|
||||
certificate_filepath = self._crypto_power.power_ups(
|
||||
TLSHostingPower
|
||||
).keypair.certificate_filepath
|
||||
certificate = self._crypto_power.power_ups(
|
||||
TLSHostingPower
|
||||
).keypair.certificate
|
||||
|
||||
# Only *YOU* can prevent forest fires
|
||||
self.revoked_policies: Set[bytes] = set()
|
||||
|
@ -685,43 +628,22 @@ class Ursula(Teacher, Character, Operator):
|
|||
self.log.info(self.banner.format(self.nickname))
|
||||
|
||||
else:
|
||||
# Stranger HTTP Server
|
||||
# Peer HTTP Server
|
||||
# TODO: Use InterfaceInfo only
|
||||
self.rest_server = ProxyRESTServer(rest_host=rest_host, rest_port=rest_port)
|
||||
self._metadata = metadata
|
||||
self.__operator_address = None
|
||||
|
||||
# Teacher (All Modes)
|
||||
Teacher.__init__(self,
|
||||
domain=domain,
|
||||
certificate=certificate,
|
||||
certificate_filepath=certificate_filepath)
|
||||
Teacher.__init__(
|
||||
self,
|
||||
domain=domain,
|
||||
certificate=certificate,
|
||||
certificate_filepath=certificate_filepath,
|
||||
)
|
||||
|
||||
def __get_hosting_power(self, host: str) -> TLSHostingPower:
|
||||
try:
|
||||
# Pre-existing or injected power
|
||||
tls_hosting_power = self._crypto_power.power_ups(TLSHostingPower)
|
||||
except TLSHostingPower.not_found_error:
|
||||
if self.keystore:
|
||||
# Derive TLS private key from seed
|
||||
tls_hosting_power = self.keystore.derive_crypto_power(TLSHostingPower, host=host)
|
||||
else:
|
||||
# Generate ephemeral private key ("Dev Mode")
|
||||
tls_hosting_keypair = HostingKeypair(host=host, generate_certificate=True)
|
||||
tls_hosting_power = TLSHostingPower(keypair=tls_hosting_keypair, host=host)
|
||||
self._crypto_power.consume_power_up(tls_hosting_power) # Consume!
|
||||
return tls_hosting_power
|
||||
|
||||
def _make_local_server(self, host, port) -> ProxyRESTServer:
|
||||
rest_app = make_rest_app(this_node=self)
|
||||
rest_server = ProxyRESTServer(rest_host=host,
|
||||
rest_port=port,
|
||||
rest_app=rest_app,
|
||||
hosting_power=self.__get_hosting_power(host=host))
|
||||
return rest_server
|
||||
|
||||
def __substantiate_stamp(self):
|
||||
transacting_power = self._crypto_power.power_ups(TransactingPower)
|
||||
def _substantiate_stamp(self):
|
||||
transacting_power = self.transacting_power
|
||||
signature = transacting_power.sign_message(message=bytes(self.stamp))
|
||||
self.__operator_signature = signature
|
||||
self.__operator_address = transacting_power.account
|
||||
|
@ -734,49 +656,76 @@ class Ursula(Teacher, Character, Operator):
|
|||
|
||||
@property
|
||||
def operator_address(self):
|
||||
if not self.federated_only:
|
||||
# TODO (#2875): The reason for the fork here is the difference in available information
|
||||
# for local and remote nodes.
|
||||
# The local node knows its operator address, but doesn't yet know the staker address.
|
||||
# For the remote node, we know its staker address (from the metadata),
|
||||
# but don't know the worker address.
|
||||
# Can this be resolved more elegantly?
|
||||
if getattr(self, 'is_me', False):
|
||||
return self._local_operator_address()
|
||||
else:
|
||||
if not self.__operator_address:
|
||||
operator_address = to_checksum_address(self.metadata().payload.derive_operator_address())
|
||||
self.__operator_address = operator_address
|
||||
return self.__operator_address
|
||||
# TODO (#2875): The reason for the fork here is the difference in available information
|
||||
# for local and remote nodes.
|
||||
# The local node knows its operator address, but doesn't yet know the staker address.
|
||||
# For the remote node, we know its staker address (from the metadata),
|
||||
# but don't know the worker address.
|
||||
# Can this be resolved more elegantly?
|
||||
if getattr(self, "is_me", False):
|
||||
return self._local_operator_address()
|
||||
else:
|
||||
raise RuntimeError("Federated nodes do not have an operator address")
|
||||
if not self.__operator_address:
|
||||
address = self.metadata().payload.derive_operator_address()
|
||||
operator_address = to_checksum_address(bytes(address))
|
||||
self.__operator_address = operator_address
|
||||
return self.__operator_address
|
||||
|
||||
def __get_hosting_power(self, host: str) -> TLSHostingPower:
|
||||
try:
|
||||
# Pre-existing or injected power
|
||||
tls_hosting_power = self._crypto_power.power_ups(TLSHostingPower)
|
||||
except TLSHostingPower.not_found_error:
|
||||
if self.keystore:
|
||||
# Derive TLS private key from seed
|
||||
tls_hosting_power = self.keystore.derive_crypto_power(
|
||||
TLSHostingPower, host=host
|
||||
)
|
||||
else:
|
||||
# Generate ephemeral private key ("Dev Mode")
|
||||
tls_hosting_keypair = HostingKeypair(
|
||||
host=host, generate_certificate=True
|
||||
)
|
||||
tls_hosting_power = TLSHostingPower(
|
||||
keypair=tls_hosting_keypair, host=host
|
||||
)
|
||||
self._crypto_power.consume_power_up(tls_hosting_power) # Consume!
|
||||
return tls_hosting_power
|
||||
|
||||
def _make_local_server(self, host, port) -> ProxyRESTServer:
|
||||
rest_app = make_rest_app(this_node=self)
|
||||
rest_server = ProxyRESTServer(
|
||||
rest_host=host,
|
||||
rest_port=port,
|
||||
rest_app=rest_app,
|
||||
hosting_power=self.__get_hosting_power(host=host),
|
||||
)
|
||||
return rest_server
|
||||
|
||||
def __preflight(self) -> None:
|
||||
"""Called immediately before running services
|
||||
If an exception is raised, Ursula startup will be interrupted.
|
||||
|
||||
"""
|
||||
"""Called immediately before running services.
|
||||
If an exception is raised, Ursula startup will be interrupted."""
|
||||
validate_operator_ip(ip=self.rest_interface.host)
|
||||
|
||||
def run(self,
|
||||
emitter: StdoutEmitter = None,
|
||||
discovery: bool = True, # TODO: see below
|
||||
availability: bool = False,
|
||||
worker: bool = True,
|
||||
hendrix: bool = True,
|
||||
start_reactor: bool = True,
|
||||
prometheus_config: 'PrometheusMetricsConfig' = None,
|
||||
preflight: bool = True,
|
||||
block_until_ready: bool = True,
|
||||
eager: bool = False
|
||||
) -> None:
|
||||
def run(
|
||||
self,
|
||||
emitter: StdoutEmitter = None,
|
||||
discovery: bool = True, # TODO: see below
|
||||
availability: bool = False,
|
||||
worker: bool = True,
|
||||
hendrix: bool = True,
|
||||
start_reactor: bool = True,
|
||||
prometheus_config: "PrometheusMetricsConfig" = None,
|
||||
preflight: bool = True,
|
||||
block_until_ready: bool = True,
|
||||
eager: bool = False,
|
||||
) -> None:
|
||||
|
||||
"""Schedule and start select ursula services, then optionally start the reactor."""
|
||||
|
||||
# Connect to Provider
|
||||
if not self.federated_only:
|
||||
if not BlockchainInterfaceFactory.is_interface_initialized(eth_provider_uri=self.eth_provider_uri):
|
||||
BlockchainInterfaceFactory.initialize_interface(eth_provider_uri=self.eth_provider_uri)
|
||||
if not BlockchainInterfaceFactory.is_interface_initialized(eth_provider_uri=self.eth_provider_uri):
|
||||
BlockchainInterfaceFactory.initialize_interface(eth_provider_uri=self.eth_provider_uri)
|
||||
|
||||
if preflight:
|
||||
self.__preflight()
|
||||
|
@ -798,7 +747,7 @@ class Ursula(Teacher, Character, Operator):
|
|||
if emitter:
|
||||
emitter.message(f"✓ Availability Checks", color='green')
|
||||
|
||||
if worker and not self.federated_only:
|
||||
if worker:
|
||||
if block_until_ready:
|
||||
# Sets (staker's) checksum address; Prevent worker startup before bonding
|
||||
self.block_until_ready()
|
||||
|
@ -810,17 +759,16 @@ class Ursula(Teacher, Character, Operator):
|
|||
else:
|
||||
message = "✓ Operator already confirmed. Not starting worktracker."
|
||||
if emitter:
|
||||
emitter.message(message, color='green')
|
||||
emitter.message(message, color="green")
|
||||
|
||||
#
|
||||
# Non-order dependant services
|
||||
#
|
||||
|
||||
# Continuous bonded check now that Ursula is all ready to run
|
||||
if not self.federated_only:
|
||||
self._operator_bonded_tracker.start(now=eager)
|
||||
if emitter:
|
||||
emitter.message(f"✓ Start Operator Bonded Tracker", color='green')
|
||||
self._operator_bonded_tracker.start(now=eager)
|
||||
if emitter:
|
||||
emitter.message(f"✓ Start Operator Bonded Tracker", color="green")
|
||||
|
||||
if prometheus_config:
|
||||
# Locally scoped to prevent import without prometheus explicitly installed
|
||||
|
@ -828,11 +776,13 @@ class Ursula(Teacher, Character, Operator):
|
|||
|
||||
start_prometheus_exporter(ursula=self, prometheus_config=prometheus_config)
|
||||
if emitter:
|
||||
emitter.message(f"✓ Prometheus Exporter", color='green')
|
||||
emitter.message(f"✓ Prometheus Exporter", color="green")
|
||||
|
||||
if hendrix:
|
||||
if emitter:
|
||||
emitter.message(f"✓ Rest Server https://{self.rest_interface}", color='green')
|
||||
emitter.message(
|
||||
f"✓ Rest Server https://{self.rest_interface}", color="green"
|
||||
)
|
||||
|
||||
deployer = self.get_deployer()
|
||||
deployer.addServices()
|
||||
|
@ -865,9 +815,8 @@ class Ursula(Teacher, Character, Operator):
|
|||
with contextlib.suppress(AttributeError): # TODO: Is this acceptable here, what are alternatives?
|
||||
self._availability_tracker.stop()
|
||||
self.stop_learning_loop()
|
||||
if not self.federated_only:
|
||||
self.work_tracker.stop()
|
||||
self._operator_bonded_tracker.stop()
|
||||
self.work_tracker.stop()
|
||||
self._operator_bonded_tracker.stop()
|
||||
if halt_reactor:
|
||||
reactor.stop()
|
||||
|
||||
|
@ -909,10 +858,7 @@ class Ursula(Teacher, Character, Operator):
|
|||
# so we can cache the result of this method.
|
||||
# TODO: should this be a method of Teacher?
|
||||
timestamp = maya.now()
|
||||
if self.federated_only:
|
||||
operator_signature = None
|
||||
else:
|
||||
operator_signature = self.operator_signature
|
||||
operator_signature = self.operator_signature
|
||||
payload = NodeMetadataPayload(staking_provider_address=Address(self.canonical_address),
|
||||
domain=self.domain,
|
||||
timestamp_epoch=timestamp.epoch,
|
||||
|
@ -921,8 +867,7 @@ class Ursula(Teacher, Character, Operator):
|
|||
encrypting_key=self.public_keys(DecryptingPower),
|
||||
certificate_der=self.certificate.public_bytes(Encoding.DER),
|
||||
host=self.rest_interface.host,
|
||||
port=self.rest_interface.port,
|
||||
)
|
||||
port=self.rest_interface.port)
|
||||
return NodeMetadata(signer=self.stamp.as_umbral_signer(),
|
||||
payload=payload)
|
||||
|
||||
|
@ -959,8 +904,6 @@ class Ursula(Teacher, Character, Operator):
|
|||
"""
|
||||
Essentially another deserialization method, but this one doesn't reconstruct a complete
|
||||
node from bytes; instead it's just enough to connect to and verify a node.
|
||||
|
||||
NOTE: This is a federated only method.
|
||||
"""
|
||||
seed_uri = f'{seednode_metadata.checksum_address}@{seednode_metadata.rest_host}:{seednode_metadata.rest_port}'
|
||||
return cls.from_seed_and_stake_info(seed_uri=seed_uri, *args, **kwargs)
|
||||
|
@ -979,7 +922,6 @@ class Ursula(Teacher, Character, Operator):
|
|||
|
||||
@classmethod
|
||||
def from_teacher_uri(cls,
|
||||
federated_only: bool,
|
||||
teacher_uri: str,
|
||||
min_stake: int,
|
||||
network_middleware: RestMiddleware = None,
|
||||
|
@ -994,7 +936,6 @@ class Ursula(Teacher, Character, Operator):
|
|||
|
||||
try:
|
||||
teacher = cls.from_seed_and_stake_info(seed_uri=teacher_uri,
|
||||
federated_only=federated_only,
|
||||
minimum_stake=min_stake,
|
||||
network_middleware=network_middleware,
|
||||
registry=registry)
|
||||
|
@ -1013,7 +954,6 @@ class Ursula(Teacher, Character, Operator):
|
|||
@classmethod
|
||||
def from_seed_and_stake_info(cls,
|
||||
seed_uri: str,
|
||||
federated_only: bool = False,
|
||||
minimum_stake: int = 0,
|
||||
registry: BaseContractRegistry = None,
|
||||
network_middleware: RestMiddleware = None,
|
||||
|
@ -1042,7 +982,7 @@ class Ursula(Teacher, Character, Operator):
|
|||
)
|
||||
|
||||
# Check the node's stake (optional)
|
||||
if minimum_stake > 0 and staking_provider_address and not federated_only:
|
||||
if minimum_stake > 0 and staking_provider_address:
|
||||
application_agent = ContractAgency.get_agent(PREApplicationAgent, registry=registry)
|
||||
seednode_stake = application_agent.get_authorized_stake(staking_provider=staking_provider_address)
|
||||
if seednode_stake < minimum_stake:
|
||||
|
@ -1051,12 +991,8 @@ class Ursula(Teacher, Character, Operator):
|
|||
return potential_seed_node
|
||||
|
||||
@classmethod
|
||||
def from_storage(cls,
|
||||
node_storage: NodeStorage,
|
||||
checksum_adress: str,
|
||||
federated_only: bool = False) -> 'Ursula':
|
||||
return node_storage.get(checksum_address=checksum_adress,
|
||||
federated_only=federated_only)
|
||||
def from_storage(cls, node_storage: NodeStorage, checksum_adress: str) -> 'Ursula':
|
||||
return node_storage.get(checksum_address=checksum_adress)
|
||||
|
||||
#
|
||||
# Properties
|
||||
|
@ -1113,10 +1049,7 @@ class Ursula(Teacher, Character, Operator):
|
|||
else:
|
||||
known_nodes_info = None
|
||||
|
||||
if not self.federated_only:
|
||||
balance_eth = float(self.eth_balance)
|
||||
else:
|
||||
balance_eth = None
|
||||
balance_eth = float(self.eth_balance)
|
||||
|
||||
return LocalUrsulaStatus(nickname=self.nickname,
|
||||
staker_address=self.checksum_address,
|
||||
|
@ -1164,27 +1097,16 @@ class LocalUrsulaStatus(NamedTuple):
|
|||
)
|
||||
|
||||
|
||||
class Enrico(Character):
|
||||
"""A Character that represents a Data Source that encrypts data for some policy's public key"""
|
||||
class Enrico:
|
||||
"""A data source that encrypts data for some policy's public key"""
|
||||
|
||||
banner = ENRICO_BANNER
|
||||
_default_crypto_powerups = [SigningPower]
|
||||
|
||||
def __init__(self,
|
||||
is_me: bool = True,
|
||||
policy_encrypting_key: Optional[PublicKey] = None,
|
||||
*args, **kwargs):
|
||||
|
||||
def __init__(self, policy_encrypting_key: PublicKey):
|
||||
self.signing_power = SigningPower()
|
||||
self._policy_pubkey = policy_encrypting_key
|
||||
|
||||
# Enrico never uses the blockchain (hence federated_only)
|
||||
kwargs['federated_only'] = True
|
||||
kwargs['known_node_class'] = None
|
||||
super().__init__(is_me=is_me, *args, **kwargs)
|
||||
|
||||
self.log = Logger(f'{self.__class__.__name__}-{bytes(self.public_keys(SigningPower)).hex()[:6]}')
|
||||
if is_me:
|
||||
self.log.info(self.banner.format(policy_encrypting_key))
|
||||
self.log = Logger(f'{self.__class__.__name__}-{bytes(self.signing_power.public_key()).hex()[:6]}')
|
||||
self.log.info(self.banner.format(policy_encrypting_key))
|
||||
|
||||
def encrypt_message(
|
||||
self, plaintext: bytes, conditions: Optional[LingoList] = None
|
||||
|
@ -1214,6 +1136,3 @@ class Enrico(Character):
|
|||
if not self._policy_pubkey:
|
||||
raise TypeError("This Enrico doesn't know which policy encrypting key he used. Oh well.")
|
||||
return self._policy_pubkey
|
||||
|
||||
def _set_known_node_class(self, *args, **kwargs):
|
||||
"""Enrico doesn't init nodes, so it doesn't care what class they are."""
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
|
||||
|
||||
|
||||
import os
|
||||
|
||||
import click
|
||||
|
@ -12,12 +9,12 @@ from nucypher.cli.literature import (
|
|||
COLLECT_NUCYPHER_PASSWORD,
|
||||
DECRYPTING_CHARACTER_KEYSTORE,
|
||||
GENERIC_PASSWORD_PROMPT,
|
||||
PASSWORD_COLLECTION_NOTICE
|
||||
PASSWORD_COLLECTION_NOTICE,
|
||||
)
|
||||
from nucypher.config.base import CharacterConfiguration
|
||||
from nucypher.config.constants import NUCYPHER_ENVVAR_KEYSTORE_PASSWORD
|
||||
from nucypher.crypto.keystore import _WORD_COUNT, Keystore
|
||||
from nucypher.utilities.emitters import StdoutEmitter
|
||||
from nucypher.crypto.keystore import Keystore, _WORD_COUNT
|
||||
|
||||
|
||||
def get_password_from_prompt(prompt: str = GENERIC_PASSWORD_PROMPT, envvar: str = None, confirm: bool = False) -> str:
|
||||
|
@ -48,9 +45,9 @@ def unlock_signer_account(config: CharacterConfiguration, json_ipc: bool) -> Non
|
|||
else:
|
||||
account = config.checksum_address
|
||||
|
||||
eth_password_is_needed = all((not config.federated_only,
|
||||
not config.signer.is_device(account=account),
|
||||
not config.dev_mode))
|
||||
eth_password_is_needed = (
|
||||
not config.signer.is_device(account=account) and not config.dev_mode
|
||||
)
|
||||
|
||||
__password = None
|
||||
if eth_password_is_needed:
|
||||
|
|
|
@ -5,29 +5,33 @@ import glob
|
|||
import json
|
||||
from json.decoder import JSONDecodeError
|
||||
from pathlib import Path
|
||||
from typing import Optional, Type, List
|
||||
from typing import List, Optional, Type
|
||||
|
||||
import click
|
||||
|
||||
from nucypher.characters.lawful import Ursula
|
||||
from nucypher.cli.actions.confirm import confirm_destroy_configuration
|
||||
from nucypher.cli.literature import (
|
||||
COLLECT_URSULA_IPV4_ADDRESS,
|
||||
CONFIRM_FORGET_NODES,
|
||||
CONFIRM_URSULA_IPV4_ADDRESS,
|
||||
INVALID_CONFIGURATION_FILE_WARNING,
|
||||
INVALID_JSON_IN_CONFIGURATION_WARNING,
|
||||
MISSING_CONFIGURATION_FILE,
|
||||
SUCCESSFUL_DESTRUCTION,
|
||||
SUCCESSFUL_FORGET_NODES,
|
||||
SUCCESSFUL_UPDATE_CONFIGURATION_VALUES,
|
||||
COLLECT_URSULA_IPV4_ADDRESS,
|
||||
CONFIRM_URSULA_IPV4_ADDRESS
|
||||
)
|
||||
from nucypher.cli.types import OPERATOR_IP
|
||||
from nucypher.config.base import CharacterConfiguration
|
||||
from nucypher.config.constants import DEFAULT_CONFIG_ROOT
|
||||
from nucypher.utilities.emitters import StdoutEmitter
|
||||
from nucypher.utilities.networking import InvalidOperatorIP, validate_operator_ip
|
||||
from nucypher.utilities.networking import determine_external_ip_address, UnknownIPAddress
|
||||
from nucypher.utilities.networking import (
|
||||
InvalidOperatorIP,
|
||||
UnknownIPAddress,
|
||||
determine_external_ip_address,
|
||||
validate_operator_ip,
|
||||
)
|
||||
|
||||
|
||||
def forget(emitter: StdoutEmitter, configuration: CharacterConfiguration) -> None:
|
||||
|
@ -138,6 +142,8 @@ def collect_operator_ip_address(emitter: StdoutEmitter, network: str, force: boo
|
|||
if not force:
|
||||
if not click.confirm(CONFIRM_URSULA_IPV4_ADDRESS.format(rest_host=ip)):
|
||||
ip = click.prompt(COLLECT_URSULA_IPV4_ADDRESS, type=OPERATOR_IP)
|
||||
else:
|
||||
emitter.message(f"Using auto-detected IP address {ip}")
|
||||
|
||||
validate_operator_ip(ip=ip)
|
||||
return ip
|
||||
|
|
|
@ -1,26 +1,36 @@
|
|||
|
||||
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
|
||||
from nucypher.blockchain.eth.networks import NetworksInventory
|
||||
from nucypher.cli.actions.auth import get_client_password, get_nucypher_password, recover_keystore
|
||||
from nucypher.cli.actions.configure import (
|
||||
destroy_configuration,
|
||||
handle_missing_configuration_file,
|
||||
get_or_update_configuration,
|
||||
collect_operator_ip_address
|
||||
from nucypher.cli.actions.auth import (
|
||||
get_client_password,
|
||||
get_nucypher_password,
|
||||
recover_keystore,
|
||||
)
|
||||
from nucypher.cli.actions.configure import (
|
||||
collect_operator_ip_address,
|
||||
destroy_configuration,
|
||||
)
|
||||
from nucypher.cli.actions.configure import forget as forget_nodes
|
||||
from nucypher.cli.actions.configure import (
|
||||
get_or_update_configuration,
|
||||
handle_missing_configuration_file,
|
||||
perform_startup_ip_check,
|
||||
)
|
||||
from nucypher.cli.actions.select import (
|
||||
select_client_account,
|
||||
select_config_file,
|
||||
select_network,
|
||||
)
|
||||
from nucypher.cli.actions.configure import forget as forget_nodes, perform_startup_ip_check
|
||||
from nucypher.cli.actions.select import select_client_account, select_config_file, select_network
|
||||
from nucypher.cli.commands.deploy import option_gas_strategy
|
||||
from nucypher.cli.config import group_general_config
|
||||
from nucypher.cli.literature import (
|
||||
DEVELOPMENT_MODE_WARNING,
|
||||
FORCE_MODE_WARNING,
|
||||
SUCCESSFUL_MANUALLY_SAVE_METADATA
|
||||
SELECT_OPERATOR_ACCOUNT,
|
||||
SELECT_PAYMENT_NETWORK,
|
||||
SUCCESSFUL_MANUALLY_SAVE_METADATA,
|
||||
)
|
||||
from nucypher.cli.options import (
|
||||
group_options,
|
||||
|
@ -28,23 +38,22 @@ from nucypher.cli.options import (
|
|||
option_config_root,
|
||||
option_dev,
|
||||
option_dry_run,
|
||||
option_federated_only,
|
||||
option_eth_provider_uri,
|
||||
option_force,
|
||||
option_key_material,
|
||||
option_light,
|
||||
option_lonely,
|
||||
option_max_gas_price,
|
||||
option_min_stake,
|
||||
option_network,
|
||||
option_payment_method,
|
||||
option_payment_network,
|
||||
option_payment_provider,
|
||||
option_poa,
|
||||
option_eth_provider_uri,
|
||||
option_policy_registry_filepath,
|
||||
option_registry_filepath,
|
||||
option_signer_uri,
|
||||
option_teacher_uri,
|
||||
option_lonely,
|
||||
option_max_gas_price,
|
||||
option_key_material,
|
||||
option_payment_provider,
|
||||
option_payment_method,
|
||||
option_payment_network,
|
||||
option_policy_registry_filepath
|
||||
)
|
||||
from nucypher.cli.painting.help import paint_new_installation_help
|
||||
from nucypher.cli.types import EIP55_CHECKSUM_ADDRESS, NETWORK_PORT, OPERATOR_IP
|
||||
|
@ -52,47 +61,40 @@ from nucypher.cli.utils import make_cli_character, setup_emitter
|
|||
from nucypher.config.characters import UrsulaConfiguration
|
||||
from nucypher.config.constants import (
|
||||
NUCYPHER_ENVVAR_OPERATOR_ETH_PASSWORD,
|
||||
TEMPORARY_DOMAIN
|
||||
TEMPORARY_DOMAIN,
|
||||
)
|
||||
from nucypher.crypto.keystore import Keystore
|
||||
|
||||
|
||||
|
||||
class UrsulaConfigOptions:
|
||||
|
||||
__option_name__ = 'config_options'
|
||||
__option_name__ = "config_options"
|
||||
|
||||
def __init__(self,
|
||||
eth_provider_uri: str,
|
||||
operator_address: str,
|
||||
federated_only: bool,
|
||||
rest_host: str,
|
||||
rest_port: int,
|
||||
network: str,
|
||||
registry_filepath: Path,
|
||||
policy_registry_filepath: Path,
|
||||
dev: bool,
|
||||
poa: bool,
|
||||
light: bool,
|
||||
gas_strategy: str,
|
||||
max_gas_price: int, # gwei
|
||||
signer_uri: str,
|
||||
availability_check: bool,
|
||||
lonely: bool,
|
||||
payment_method: str,
|
||||
payment_provider: str,
|
||||
payment_network: str
|
||||
):
|
||||
|
||||
if federated_only:
|
||||
if registry_filepath or policy_registry_filepath:
|
||||
raise click.BadOptionUsage(option_name="--registry-filepath",
|
||||
message=click.style("--registry-filepath and --policy-registry-filepath cannot be used in federated mode.", fg="red"))
|
||||
def __init__(
|
||||
self,
|
||||
eth_provider_uri: str,
|
||||
operator_address: str,
|
||||
rest_host: str,
|
||||
rest_port: int,
|
||||
network: str,
|
||||
registry_filepath: Path,
|
||||
policy_registry_filepath: Path,
|
||||
dev: bool,
|
||||
poa: bool,
|
||||
light: bool,
|
||||
gas_strategy: str,
|
||||
max_gas_price: int, # gwei
|
||||
signer_uri: str,
|
||||
availability_check: bool,
|
||||
lonely: bool,
|
||||
payment_method: str,
|
||||
payment_provider: str,
|
||||
payment_network: str,
|
||||
):
|
||||
|
||||
self.eth_provider_uri = eth_provider_uri
|
||||
self.signer_uri = signer_uri
|
||||
self.operator_address = operator_address
|
||||
self.federated_only = federated_only
|
||||
self.rest_host = rest_host
|
||||
self.rest_port = rest_port # FIXME: not used in generate()
|
||||
self.domain = network
|
||||
|
@ -123,14 +125,13 @@ class UrsulaConfigOptions:
|
|||
signer_uri=self.signer_uri,
|
||||
gas_strategy=self.gas_strategy,
|
||||
max_gas_price=self.max_gas_price,
|
||||
checksum_address=self.operator_address,
|
||||
federated_only=self.federated_only,
|
||||
operator_address=self.operator_address,
|
||||
rest_host=self.rest_host,
|
||||
rest_port=self.rest_port,
|
||||
availability_check=self.availability_check,
|
||||
payment_method=self.payment_method,
|
||||
payment_provider=self.payment_provider,
|
||||
payment_network=self.payment_network
|
||||
payment_network=self.payment_network,
|
||||
)
|
||||
else:
|
||||
if not config_file:
|
||||
|
@ -152,11 +153,10 @@ class UrsulaConfigOptions:
|
|||
rest_port=self.rest_port,
|
||||
poa=self.poa,
|
||||
light=self.light,
|
||||
federated_only=self.federated_only,
|
||||
availability_check=self.availability_check,
|
||||
payment_method=self.payment_method,
|
||||
payment_provider=self.payment_provider,
|
||||
payment_network=self.payment_network
|
||||
payment_network=self.payment_network,
|
||||
)
|
||||
except FileNotFoundError:
|
||||
return handle_missing_configuration_file(character_config_class=UrsulaConfiguration, config_file=config_file)
|
||||
|
@ -168,60 +168,64 @@ class UrsulaConfigOptions:
|
|||
def generate_config(self, emitter, config_root, force, key_material):
|
||||
|
||||
if self.dev:
|
||||
raise RuntimeError('Persistent configurations cannot be created in development mode.')
|
||||
raise RuntimeError(
|
||||
"Persistent configurations cannot be created in development mode."
|
||||
)
|
||||
|
||||
if (not self.operator_address) and not self.federated_only:
|
||||
prompt = "Select operator account"
|
||||
self.operator_address = select_client_account(emitter=emitter,
|
||||
prompt=prompt,
|
||||
eth_provider_uri=self.eth_provider_uri,
|
||||
signer_uri=self.signer_uri)
|
||||
if not self.operator_address:
|
||||
prompt = SELECT_OPERATOR_ACCOUNT
|
||||
self.operator_address = select_client_account(
|
||||
emitter=emitter,
|
||||
prompt=prompt,
|
||||
eth_provider_uri=self.eth_provider_uri,
|
||||
signer_uri=self.signer_uri,
|
||||
)
|
||||
|
||||
# Resolve rest host
|
||||
if not self.rest_host:
|
||||
self.rest_host = collect_operator_ip_address(emitter, network=self.domain, force=force)
|
||||
|
||||
return UrsulaConfiguration.generate(password=get_nucypher_password(emitter=emitter, confirm=True),
|
||||
key_material=bytes.fromhex(key_material) if key_material else None,
|
||||
config_root=config_root,
|
||||
rest_host=self.rest_host,
|
||||
rest_port=self.rest_port,
|
||||
domain=self.domain,
|
||||
federated_only=self.federated_only,
|
||||
operator_address=self.operator_address,
|
||||
registry_filepath=self.registry_filepath,
|
||||
policy_registry_filepath=self.policy_registry_filepath,
|
||||
eth_provider_uri=self.eth_provider_uri,
|
||||
signer_uri=self.signer_uri,
|
||||
gas_strategy=self.gas_strategy,
|
||||
max_gas_price=self.max_gas_price,
|
||||
poa=self.poa,
|
||||
light=self.light,
|
||||
availability_check=self.availability_check,
|
||||
payment_method=self.payment_method,
|
||||
payment_provider=self.payment_provider,
|
||||
payment_network=self.payment_network
|
||||
)
|
||||
return UrsulaConfiguration.generate(
|
||||
password=get_nucypher_password(emitter=emitter, confirm=True),
|
||||
key_material=bytes.fromhex(key_material) if key_material else None,
|
||||
config_root=config_root,
|
||||
rest_host=self.rest_host,
|
||||
rest_port=self.rest_port,
|
||||
domain=self.domain,
|
||||
operator_address=self.operator_address,
|
||||
registry_filepath=self.registry_filepath,
|
||||
policy_registry_filepath=self.policy_registry_filepath,
|
||||
eth_provider_uri=self.eth_provider_uri,
|
||||
signer_uri=self.signer_uri,
|
||||
gas_strategy=self.gas_strategy,
|
||||
max_gas_price=self.max_gas_price,
|
||||
poa=self.poa,
|
||||
light=self.light,
|
||||
availability_check=self.availability_check,
|
||||
payment_method=self.payment_method,
|
||||
payment_provider=self.payment_provider,
|
||||
payment_network=self.payment_network,
|
||||
)
|
||||
|
||||
def get_updates(self) -> dict:
|
||||
payload = dict(rest_host=self.rest_host,
|
||||
rest_port=self.rest_port,
|
||||
domain=self.domain,
|
||||
federated_only=self.federated_only,
|
||||
operator_address=self.operator_address,
|
||||
registry_filepath=self.registry_filepath,
|
||||
policy_registry_filepath=self.policy_registry_filepath,
|
||||
eth_provider_uri=self.eth_provider_uri,
|
||||
signer_uri=self.signer_uri,
|
||||
gas_strategy=self.gas_strategy,
|
||||
max_gas_price=self.max_gas_price,
|
||||
poa=self.poa,
|
||||
light=self.light,
|
||||
availability_check=self.availability_check,
|
||||
payment_method=self.payment_method,
|
||||
payment_provider=self.payment_provider,
|
||||
payment_network=self.payment_network
|
||||
)
|
||||
payload = dict(
|
||||
rest_host=self.rest_host,
|
||||
rest_port=self.rest_port,
|
||||
domain=self.domain,
|
||||
operator_address=self.operator_address,
|
||||
registry_filepath=self.registry_filepath,
|
||||
policy_registry_filepath=self.policy_registry_filepath,
|
||||
eth_provider_uri=self.eth_provider_uri,
|
||||
signer_uri=self.signer_uri,
|
||||
gas_strategy=self.gas_strategy,
|
||||
max_gas_price=self.max_gas_price,
|
||||
poa=self.poa,
|
||||
light=self.light,
|
||||
availability_check=self.availability_check,
|
||||
payment_method=self.payment_method,
|
||||
payment_provider=self.payment_provider,
|
||||
payment_network=self.payment_network,
|
||||
)
|
||||
# Depends on defaults being set on Configuration classes, filtrates None values
|
||||
updates = {k: v for k, v in payload.items() if v is not None}
|
||||
return updates
|
||||
|
@ -234,10 +238,21 @@ group_config_options = group_options(
|
|||
signer_uri=option_signer_uri,
|
||||
gas_strategy=option_gas_strategy,
|
||||
max_gas_price=option_max_gas_price,
|
||||
operator_address=click.option('--operator-address', help="Run with the specified operator address", type=EIP55_CHECKSUM_ADDRESS),
|
||||
federated_only=option_federated_only,
|
||||
rest_host=click.option('--rest-host', help="The host IP address to run Ursula network services on", type=OPERATOR_IP),
|
||||
rest_port=click.option('--rest-port', help="The host port to run Ursula network services on", type=NETWORK_PORT),
|
||||
operator_address=click.option(
|
||||
"--operator-address",
|
||||
help="Run with the specified operator address",
|
||||
type=EIP55_CHECKSUM_ADDRESS,
|
||||
),
|
||||
rest_host=click.option(
|
||||
"--rest-host",
|
||||
help="The host IP address to run Ursula network services on",
|
||||
type=OPERATOR_IP,
|
||||
),
|
||||
rest_port=click.option(
|
||||
"--rest-port",
|
||||
help="The host port to run Ursula network services on",
|
||||
type=NETWORK_PORT,
|
||||
),
|
||||
network=option_network(),
|
||||
registry_filepath=option_registry_filepath,
|
||||
policy_registry_filepath=option_policy_registry_filepath,
|
||||
|
@ -263,13 +278,13 @@ class UrsulaCharacterOptions:
|
|||
|
||||
def create_character(self, emitter, config_file, json_ipc, load_seednodes=True):
|
||||
ursula_config = self.config_options.create_config(emitter, config_file)
|
||||
password_required = all((not ursula_config.federated_only,
|
||||
not self.config_options.dev,
|
||||
not json_ipc))
|
||||
password_required = all((not self.config_options.dev, not json_ipc))
|
||||
__password = None
|
||||
if password_required:
|
||||
__password = get_client_password(checksum_address=ursula_config.operator_address,
|
||||
envvar=NUCYPHER_ENVVAR_OPERATOR_ETH_PASSWORD)
|
||||
__password = get_client_password(
|
||||
checksum_address=ursula_config.operator_address,
|
||||
envvar=NUCYPHER_ENVVAR_OPERATOR_ETH_PASSWORD,
|
||||
)
|
||||
|
||||
try:
|
||||
URSULA = make_cli_character(character_config=ursula_config,
|
||||
|
@ -315,20 +330,39 @@ def init(general_config, config_options, force, config_root, key_material):
|
|||
_pre_launch_warnings(emitter, dev=None, force=force)
|
||||
if not config_root:
|
||||
config_root = general_config.config_root
|
||||
if not config_options.federated_only and not config_options.eth_provider_uri:
|
||||
raise click.BadOptionUsage('--eth-provider', message=click.style("--eth-provider is required to initialize a new ursula.", fg="red"))
|
||||
if not config_options.federated_only and not config_options.payment_provider:
|
||||
raise click.BadOptionUsage('--payment-provider', message=click.style("--payment-provider is required to initialize a new ursula.", fg="red"))
|
||||
if not config_options.federated_only and not config_options.domain:
|
||||
config_options.domain = select_network(emitter, message="Select Staking Network", network_type=NetworksInventory.ETH)
|
||||
if not config_options.federated_only and not config_options.payment_network:
|
||||
config_options.payment_network = select_network(emitter, message="Select Payment Network", network_type=NetworksInventory.POLYGON)
|
||||
ursula_config = config_options.generate_config(emitter=emitter,
|
||||
config_root=config_root,
|
||||
force=force,
|
||||
key_material=key_material)
|
||||
if not config_options.eth_provider_uri:
|
||||
raise click.BadOptionUsage(
|
||||
"--eth-provider",
|
||||
message=click.style(
|
||||
"--eth-provider is required to initialize a new ursula.", fg="red"
|
||||
),
|
||||
)
|
||||
if not config_options.payment_provider:
|
||||
raise click.BadOptionUsage(
|
||||
"--payment-provider",
|
||||
message=click.style(
|
||||
"--payment-provider is required to initialize a new ursula.", fg="red"
|
||||
),
|
||||
)
|
||||
if not config_options.domain:
|
||||
config_options.domain = select_network(
|
||||
emitter,
|
||||
message="Select Staking Network",
|
||||
network_type=NetworksInventory.ETH,
|
||||
)
|
||||
if not config_options.payment_network:
|
||||
config_options.payment_network = select_network(
|
||||
emitter,
|
||||
message=SELECT_PAYMENT_NETWORK,
|
||||
network_type=NetworksInventory.POLYGON,
|
||||
)
|
||||
ursula_config = config_options.generate_config(
|
||||
emitter=emitter, config_root=config_root, force=force, key_material=key_material
|
||||
)
|
||||
filepath = ursula_config.to_configuration_file()
|
||||
paint_new_installation_help(emitter, new_configuration=ursula_config, filepath=filepath)
|
||||
paint_new_installation_help(
|
||||
emitter, new_configuration=ursula_config, filepath=filepath
|
||||
)
|
||||
|
||||
|
||||
@ursula.command()
|
||||
|
@ -393,18 +427,21 @@ def run(general_config, character_options, config_file, dry_run, prometheus, met
|
|||
|
||||
_pre_launch_warnings(emitter, dev=dev_mode, force=None)
|
||||
|
||||
prometheus_config: 'PrometheusMetricsConfig' = None
|
||||
prometheus_config: "PrometheusMetricsConfig" = None
|
||||
if prometheus and not dev_mode:
|
||||
# Locally scoped to prevent import without prometheus explicitly installed
|
||||
from nucypher.utilities.prometheus.metrics import PrometheusMetricsConfig
|
||||
prometheus_config = PrometheusMetricsConfig(port=metrics_port,
|
||||
metrics_prefix=metrics_prefix,
|
||||
listen_address=metrics_listen_address,
|
||||
collection_interval=metrics_interval)
|
||||
|
||||
ursula_config, URSULA = character_options.create_character(emitter=emitter,
|
||||
config_file=config_file,
|
||||
json_ipc=general_config.json_ipc)
|
||||
prometheus_config = PrometheusMetricsConfig(
|
||||
port=metrics_port,
|
||||
metrics_prefix=metrics_prefix,
|
||||
listen_address=metrics_listen_address,
|
||||
collection_interval=metrics_interval,
|
||||
)
|
||||
|
||||
ursula_config, URSULA = character_options.create_character(
|
||||
emitter=emitter, config_file=config_file, json_ipc=general_config.json_ipc
|
||||
)
|
||||
|
||||
if ip_checkup and not (dev_mode or lonely):
|
||||
# Always skip startup IP checks for dev and lonely modes.
|
||||
|
|
|
@ -24,8 +24,6 @@ PRODUCTION_REGISTRY_ADVISORY = "Using latest published registry from {source}"
|
|||
|
||||
LOCAL_REGISTRY_ADVISORY = "Configured to registry filepath {registry_filepath}"
|
||||
|
||||
FEDERATED_WARNING = "WARNING: Running in Federated mode"
|
||||
|
||||
PERIOD_ADVANCED_WARNING = "Current period advanced before the action could be completed. Please try again."
|
||||
|
||||
#
|
||||
|
@ -92,6 +90,8 @@ nucypher {init_command}
|
|||
|
||||
SELECT_NETWORK = "Select Network"
|
||||
|
||||
SELECT_PAYMENT_NETWORK = "Select Payment Network"
|
||||
|
||||
NO_CONFIGURATIONS_ON_DISK = "No {name} configurations found. Run 'nucypher {command} init' then try again."
|
||||
|
||||
SUCCESSFUL_UPDATE_CONFIGURATION_VALUES = "Updated configuration values: {fields}"
|
||||
|
@ -141,13 +141,18 @@ GENERIC_PASSWORD_PROMPT = "Enter password"
|
|||
|
||||
DECRYPTING_CHARACTER_KEYSTORE = 'Authenticating {name}'
|
||||
|
||||
REPEAT_FOR_CONFIRMATION = "Repeat for confirmation:"
|
||||
|
||||
|
||||
#
|
||||
# Networking
|
||||
#
|
||||
|
||||
CONFIRM_IPV4_ADDRESS_QUESTION = "Is this the public-facing address of Ursula?"
|
||||
|
||||
CONFIRM_URSULA_IPV4_ADDRESS = "Detected IPv4 address ({rest_host}) - Is this the public-facing address of Ursula?"
|
||||
CONFIRM_URSULA_IPV4_ADDRESS = (
|
||||
"Detected IPv4 address ({rest_host}) - " + CONFIRM_IPV4_ADDRESS_QUESTION
|
||||
)
|
||||
|
||||
COLLECT_URSULA_IPV4_ADDRESS = "Enter Ursula's public-facing IPv4 address"
|
||||
|
||||
|
@ -250,6 +255,8 @@ SUCCESSFUL_MANUALLY_SAVE_METADATA = "Successfully saved node metadata to {metada
|
|||
|
||||
STAKING_PROVIDER_UNAUTHORIZED = '{provider} is not authorized.'
|
||||
|
||||
SELECT_OPERATOR_ACCOUNT = "Select operator account"
|
||||
|
||||
CONFIRM_BONDING = 'Are you sure you want to bond staking provider {provider} to operator {operator}?'
|
||||
|
||||
BONDING_TIME = 'Bonding/Unbonding not permitted until {date}.'
|
||||
|
|
|
@ -13,11 +13,12 @@ from nucypher.cli.types import (
|
|||
EIP55_CHECKSUM_ADDRESS,
|
||||
EXISTING_READABLE_FILE,
|
||||
GWEI,
|
||||
MIN_AUTHORIZATION,
|
||||
NETWORK_PORT,
|
||||
NuCypherNetworkName,
|
||||
WEI,
|
||||
PAYMENT_METHOD_CHOICES,
|
||||
STAKED_TOKENS_RANGE,
|
||||
MIN_AUTHORIZATION, PAYMENT_METHOD_CHOICES
|
||||
WEI,
|
||||
NuCypherNetworkName,
|
||||
)
|
||||
from nucypher.utilities.logging import Logger
|
||||
|
||||
|
@ -29,7 +30,6 @@ option_dev = click.option('--dev', '-d', help="Enable development mode", is_flag
|
|||
option_dry_run = click.option('--dry-run', '-x', help="Execute normally without actually starting the node", is_flag=True)
|
||||
option_etherscan = click.option('--etherscan/--no-etherscan', help="Enable/disable viewing TX in Etherscan")
|
||||
option_event_name = click.option('--event-name', help="Specify an event by name", type=click.STRING)
|
||||
option_federated_only = click.option('--federated-only/--decentralized', '-F', help="Connect only to federated nodes", is_flag=True, default=None)
|
||||
option_force = click.option('--force', help="Don't ask for confirmation", is_flag=True)
|
||||
option_gas_strategy = click.option('--gas-strategy', help="Operate with a specified gas price strategy", type=click.STRING) # TODO: GAS_STRATEGY_CHOICES
|
||||
option_key_material = click.option('--key-material', help="A pre-secured hex-encoded secret to use for private key derivations", type=click.STRING)
|
||||
|
|
|
@ -32,16 +32,13 @@ def paint_node_status(emitter, ursula, start_time):
|
|||
'Fleet State.......... {}'.format(fleet_state),
|
||||
'Learning Status ..... {}'.format(learning_status),
|
||||
'Learning Round ...... Round #{}'.format(ursula._learning_round),
|
||||
'Operating Mode ...... {}'.format('Federated' if ursula.federated_only else 'Decentralized'),
|
||||
'Rest Interface ...... {}'.format(ursula.rest_url()),
|
||||
'Node Storage Type ... {}'.format(ursula.node_storage._name.capitalize()),
|
||||
'Known Nodes ......... {}'.format(len(ursula.known_nodes)),
|
||||
teacher]
|
||||
|
||||
if not ursula.federated_only:
|
||||
operator_address = 'Operator Address ...... {}'.format(ursula.operator_address)
|
||||
current_period = f'Current Period ...... {ursula.application_agent.get_current_period()}'
|
||||
stats.extend([current_period, operator_address])
|
||||
operator_address = 'Operator Address ...... {}'.format(ursula.operator_address)
|
||||
stats.extend([operator_address])
|
||||
|
||||
if ursula._availability_tracker:
|
||||
if ursula._availability_tracker.running:
|
||||
|
@ -57,13 +54,8 @@ def paint_node_status(emitter, ursula, start_time):
|
|||
def paint_known_nodes(emitter, ursula) -> None:
|
||||
# Gather Data
|
||||
known_nodes = ursula.known_nodes
|
||||
number_of_known_nodes = len(ursula.node_storage.all(federated_only=ursula.federated_only))
|
||||
seen_nodes = len(ursula.node_storage.all(federated_only=ursula.federated_only, certificates_only=True))
|
||||
|
||||
# Operating Mode
|
||||
federated_only = ursula.federated_only
|
||||
if federated_only:
|
||||
emitter.echo("Configured in Federated Only mode", color='green')
|
||||
number_of_known_nodes = len(ursula.node_storage.all())
|
||||
seen_nodes = len(ursula.node_storage.all(certificates_only=True))
|
||||
|
||||
# Heading
|
||||
label = "Known Nodes (connected {} / seen {})".format(number_of_known_nodes, seen_nodes)
|
||||
|
|
|
@ -14,31 +14,30 @@ from nucypher.blockchain.eth.events import EventRecord
|
|||
from nucypher.blockchain.eth.interfaces import (
|
||||
BlockchainDeployerInterface,
|
||||
BlockchainInterface,
|
||||
BlockchainInterfaceFactory
|
||||
BlockchainInterfaceFactory,
|
||||
)
|
||||
from nucypher.blockchain.eth.registry import (
|
||||
BaseContractRegistry,
|
||||
InMemoryContractRegistry,
|
||||
LocalContractRegistry
|
||||
LocalContractRegistry,
|
||||
)
|
||||
from nucypher.characters.base import Character
|
||||
from nucypher.utilities.emitters import StdoutEmitter
|
||||
from nucypher.cli.actions.auth import (
|
||||
get_nucypher_password,
|
||||
unlock_nucypher_keystore,
|
||||
unlock_signer_account
|
||||
unlock_signer_account,
|
||||
)
|
||||
from nucypher.cli.literature import (
|
||||
CONFIRM_OVERWRITE_EVENTS_CSV_FILE,
|
||||
CONNECTING_TO_BLOCKCHAIN,
|
||||
ETHERSCAN_FLAG_DISABLED_WARNING,
|
||||
ETHERSCAN_FLAG_ENABLED_WARNING,
|
||||
FEDERATED_WARNING,
|
||||
LOCAL_REGISTRY_ADVISORY,
|
||||
NO_HARDWARE_WALLET_WARNING,
|
||||
PRODUCTION_REGISTRY_ADVISORY,
|
||||
CONFIRM_OVERWRITE_EVENTS_CSV_FILE
|
||||
)
|
||||
from nucypher.config.constants import DEFAULT_CONFIG_ROOT
|
||||
from nucypher.utilities.emitters import StdoutEmitter
|
||||
from nucypher.utilities.events import write_events_to_csv_file
|
||||
|
||||
|
||||
|
@ -86,7 +85,6 @@ def make_cli_character(character_config,
|
|||
maybe_sage_node = character_config.known_node_class.from_teacher_uri(
|
||||
teacher_uri=teacher_uri,
|
||||
min_stake=min_stake,
|
||||
federated_only=character_config.federated_only,
|
||||
network_middleware=character_config.network_middleware,
|
||||
registry=character_config.registry
|
||||
)
|
||||
|
@ -100,10 +98,6 @@ def make_cli_character(character_config,
|
|||
# Post-Init
|
||||
#
|
||||
|
||||
# Federated
|
||||
if character_config.federated_only:
|
||||
emitter.message(FEDERATED_WARNING, color='yellow')
|
||||
|
||||
emitter.message(f"Loaded {CHARACTER.__class__.__name__} ({CHARACTER.domain})", color='green')
|
||||
return CHARACTER
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ from constant_sorrow.constants import (
|
|||
NO_BLOCKCHAIN_CONNECTION,
|
||||
NO_KEYSTORE_ATTACHED,
|
||||
UNINITIALIZED_CONFIGURATION,
|
||||
UNKNOWN_VERSION
|
||||
UNKNOWN_VERSION,
|
||||
)
|
||||
from eth_utils.address import is_checksum_address
|
||||
|
||||
|
@ -24,7 +24,7 @@ from nucypher.blockchain.eth.networks import NetworksInventory
|
|||
from nucypher.blockchain.eth.registry import (
|
||||
BaseContractRegistry,
|
||||
InMemoryContractRegistry,
|
||||
LocalContractRegistry
|
||||
LocalContractRegistry,
|
||||
)
|
||||
from nucypher.blockchain.eth.signers import Signer
|
||||
from nucypher.characters.lawful import Ursula
|
||||
|
@ -32,7 +32,7 @@ from nucypher.config import constants
|
|||
from nucypher.config.storages import (
|
||||
ForgetfulNodeStorage,
|
||||
LocalFileBasedNodeStorage,
|
||||
NodeStorage
|
||||
NodeStorage,
|
||||
)
|
||||
from nucypher.config.util import cast_paths_from
|
||||
from nucypher.crypto.keystore import Keystore
|
||||
|
@ -326,80 +326,67 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
# Payments
|
||||
DEFAULT_PAYMENT_METHOD = 'SubscriptionManager'
|
||||
DEFAULT_PAYMENT_NETWORK = 'polygon'
|
||||
DEFAULT_FEDERATED_PAYMENT_METHOD = 'Free'
|
||||
|
||||
# Fields specified here are *not* passed into the Character's constructor
|
||||
# and can be understood as configuration fields only.
|
||||
_CONFIG_FIELDS = ('config_root',
|
||||
'poa',
|
||||
'light',
|
||||
'registry_filepath',
|
||||
'gas_strategy',
|
||||
'max_gas_price', # gwei
|
||||
'signer_uri',
|
||||
'keystore_path',
|
||||
'payment_provider',
|
||||
'payment_network'
|
||||
)
|
||||
_CONFIG_FIELDS = (
|
||||
"config_root",
|
||||
"poa",
|
||||
"light",
|
||||
"registry_filepath",
|
||||
"gas_strategy",
|
||||
"max_gas_price", # gwei
|
||||
"signer_uri",
|
||||
"keystore_path",
|
||||
"payment_provider",
|
||||
"payment_network",
|
||||
)
|
||||
|
||||
def __init__(self,
|
||||
|
||||
# Base
|
||||
emitter=None,
|
||||
config_root: Optional[Path] = None,
|
||||
filepath: Optional[Path] = None,
|
||||
|
||||
# Mode
|
||||
dev_mode: bool = False,
|
||||
federated_only: bool = False,
|
||||
|
||||
# Identity
|
||||
checksum_address: str = None,
|
||||
crypto_power: CryptoPower = None,
|
||||
|
||||
# Keystore
|
||||
keystore: Keystore = None,
|
||||
keystore_path: Optional[Path] = None,
|
||||
|
||||
# Learner
|
||||
learn_on_same_thread: bool = False,
|
||||
abort_on_learning_error: bool = False,
|
||||
start_learning_now: bool = True,
|
||||
|
||||
# Network
|
||||
domain: str = DEFAULT_DOMAIN,
|
||||
network_middleware: RestMiddleware = None,
|
||||
lonely: bool = False,
|
||||
|
||||
# Node Storage
|
||||
known_nodes: set = None,
|
||||
node_storage: NodeStorage = None,
|
||||
reload_metadata: bool = True,
|
||||
save_metadata: bool = True,
|
||||
|
||||
# Blockchain
|
||||
poa: bool = None,
|
||||
light: bool = False,
|
||||
eth_provider_uri: str = None,
|
||||
gas_strategy: Union[Callable, str] = DEFAULT_GAS_STRATEGY,
|
||||
max_gas_price: Optional[int] = None,
|
||||
signer_uri: str = None,
|
||||
|
||||
# Payments
|
||||
# TODO: Resolve code prefixing below, possibly with the use of nested configuration fields
|
||||
payment_method: str = None,
|
||||
payment_provider: str = None,
|
||||
payment_network: str = None,
|
||||
|
||||
# Registries
|
||||
registry: BaseContractRegistry = None,
|
||||
registry_filepath: Optional[Path] = None,
|
||||
policy_registry: BaseContractRegistry = None,
|
||||
policy_registry_filepath: Optional[Path] = None,
|
||||
|
||||
# Deployed Operators
|
||||
worker_data: dict = None
|
||||
):
|
||||
def __init__(
|
||||
self,
|
||||
# Base
|
||||
emitter=None,
|
||||
config_root: Optional[Path] = None,
|
||||
filepath: Optional[Path] = None,
|
||||
# Mode
|
||||
dev_mode: bool = False,
|
||||
# Identity
|
||||
checksum_address: Optional[str] = None,
|
||||
crypto_power: Optional[CryptoPower] = None,
|
||||
# Keystore
|
||||
keystore: Optional[Keystore] = None,
|
||||
keystore_path: Optional[Path] = None,
|
||||
# Learner
|
||||
learn_on_same_thread: bool = False,
|
||||
abort_on_learning_error: bool = False,
|
||||
start_learning_now: bool = True,
|
||||
# Network
|
||||
domain: str = DEFAULT_DOMAIN,
|
||||
network_middleware: Optional[RestMiddleware] = None,
|
||||
lonely: bool = False,
|
||||
# Node Storage
|
||||
known_nodes: Optional[set] = None,
|
||||
node_storage: Optional[NodeStorage] = None,
|
||||
reload_metadata: bool = True,
|
||||
save_metadata: bool = True,
|
||||
# Blockchain
|
||||
poa: Optional[bool] = None,
|
||||
light: bool = False,
|
||||
eth_provider_uri: Optional[str] = None,
|
||||
gas_strategy: Union[Callable, str] = DEFAULT_GAS_STRATEGY,
|
||||
max_gas_price: Optional[int] = None,
|
||||
signer_uri: Optional[str] = None,
|
||||
# Payments
|
||||
# TODO: Resolve code prefixing below, possibly with the use of nested configuration fields
|
||||
payment_method: Optional[str] = None,
|
||||
payment_provider: Optional[str] = None,
|
||||
payment_network: Optional[str] = None,
|
||||
# Registries
|
||||
registry: Optional[BaseContractRegistry] = None,
|
||||
registry_filepath: Optional[Path] = None,
|
||||
policy_registry: Optional[BaseContractRegistry] = None,
|
||||
policy_registry_filepath: Optional[Path] = None,
|
||||
):
|
||||
|
||||
self.log = Logger(self.__class__.__name__)
|
||||
|
||||
|
@ -439,7 +426,6 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
self.signer_uri = signer_uri or None
|
||||
|
||||
# Learner
|
||||
self.federated_only = federated_only
|
||||
self.domain = domain
|
||||
self.learn_on_same_thread = learn_on_same_thread
|
||||
self.abort_on_learning_error = abort_on_learning_error
|
||||
|
@ -454,96 +440,77 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
self.config_file_location = filepath or UNINITIALIZED_CONFIGURATION
|
||||
self.config_root = UNINITIALIZED_CONFIGURATION
|
||||
|
||||
# Deployed Operators
|
||||
self.worker_data = worker_data
|
||||
|
||||
#
|
||||
# Federated vs. Blockchain arguments consistency
|
||||
#
|
||||
|
||||
#
|
||||
# Federated
|
||||
#
|
||||
|
||||
if self.federated_only:
|
||||
# Check for incompatible values
|
||||
blockchain_args = {'filepath': registry_filepath,
|
||||
'poa': poa,
|
||||
'eth_provider_uri': eth_provider_uri,
|
||||
'payment_provider': payment_provider,
|
||||
'gas_strategy': gas_strategy,
|
||||
'max_gas_price': max_gas_price}
|
||||
if any(blockchain_args.values()):
|
||||
bad_args = ", ".join(f"{arg}={val}" for arg, val in blockchain_args.items() if val)
|
||||
self.log.warn(f"Arguments {bad_args} are incompatible with federated_only. "
|
||||
f"Overridden with a sane default.")
|
||||
|
||||
# Clear decentralized attributes to ensure consistency with a
|
||||
# federated configuration.
|
||||
self.poa = False
|
||||
self.is_light = False
|
||||
self.eth_provider_uri = None
|
||||
self.registry_filepath = None
|
||||
self.policy_registry_filepath = None
|
||||
self.gas_strategy = None
|
||||
self.max_gas_price = None
|
||||
|
||||
# Federated Payments
|
||||
self.payment_method = payment_method or self.DEFAULT_FEDERATED_PAYMENT_METHOD
|
||||
self.payment_network = payment_network
|
||||
self.payment_provider = payment_provider
|
||||
|
||||
#
|
||||
# Decentralized
|
||||
#
|
||||
|
||||
self.gas_strategy = gas_strategy
|
||||
self.max_gas_price = max_gas_price # gwei
|
||||
is_initialized = BlockchainInterfaceFactory.is_interface_initialized(
|
||||
eth_provider_uri=self.eth_provider_uri
|
||||
)
|
||||
if not is_initialized and eth_provider_uri:
|
||||
BlockchainInterfaceFactory.initialize_interface(
|
||||
eth_provider_uri=self.eth_provider_uri,
|
||||
poa=self.poa,
|
||||
light=self.is_light,
|
||||
emitter=emitter,
|
||||
gas_strategy=self.gas_strategy,
|
||||
max_gas_price=self.max_gas_price,
|
||||
)
|
||||
else:
|
||||
self.gas_strategy = gas_strategy
|
||||
self.max_gas_price = max_gas_price # gwei
|
||||
is_initialized = BlockchainInterfaceFactory.is_interface_initialized(eth_provider_uri=self.eth_provider_uri)
|
||||
if not is_initialized and eth_provider_uri:
|
||||
BlockchainInterfaceFactory.initialize_interface(eth_provider_uri=self.eth_provider_uri,
|
||||
poa=self.poa,
|
||||
light=self.is_light,
|
||||
emitter=emitter,
|
||||
gas_strategy=self.gas_strategy,
|
||||
max_gas_price=self.max_gas_price)
|
||||
self.log.warn(
|
||||
f"Using existing blockchain interface connection ({self.eth_provider_uri})."
|
||||
)
|
||||
|
||||
if not self.registry:
|
||||
# TODO: These two code blocks are untested.
|
||||
if (
|
||||
not self.registry_filepath
|
||||
): # TODO: Registry URI (goerli://speedynet.json) :-)
|
||||
self.log.info(f"Fetching latest registry from source.")
|
||||
self.registry = InMemoryContractRegistry.from_latest_publication(
|
||||
network=self.domain
|
||||
)
|
||||
else:
|
||||
self.log.warn(f"Using existing blockchain interface connection ({self.eth_provider_uri}).")
|
||||
self.registry = LocalContractRegistry(filepath=self.registry_filepath)
|
||||
self.log.info(f"Using local registry ({self.registry}).")
|
||||
|
||||
if not self.registry:
|
||||
# TODO: These two code blocks are untested.
|
||||
if not self.registry_filepath: # TODO: Registry URI (goerli://speedynet.json) :-)
|
||||
self.log.info(f"Fetching latest registry from source.")
|
||||
self.registry = InMemoryContractRegistry.from_latest_publication(network=self.domain)
|
||||
self.testnet = self.domain != NetworksInventory.MAINNET
|
||||
self.signer = Signer.from_signer_uri(self.signer_uri, testnet=self.testnet)
|
||||
|
||||
#
|
||||
# Onchain Payments & Policies
|
||||
#
|
||||
|
||||
# FIXME: Enforce this for Ursula/Alice but not Bob?
|
||||
from nucypher.config.characters import BobConfiguration
|
||||
|
||||
if not isinstance(self, BobConfiguration):
|
||||
# if not payment_provider:
|
||||
# raise self.ConfigurationError("payment provider is required.")
|
||||
self.payment_method = payment_method or self.DEFAULT_PAYMENT_METHOD
|
||||
self.payment_network = payment_network or self.DEFAULT_PAYMENT_NETWORK
|
||||
self.payment_provider = payment_provider or (
|
||||
self.eth_provider_uri or None
|
||||
) # default to L1 payments
|
||||
|
||||
# TODO: Dedupe
|
||||
if not self.policy_registry:
|
||||
if not self.policy_registry_filepath:
|
||||
self.log.info(f"Fetching latest policy registry from source.")
|
||||
self.policy_registry = (
|
||||
InMemoryContractRegistry.from_latest_publication(
|
||||
network=self.payment_network
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.registry = LocalContractRegistry(filepath=self.registry_filepath)
|
||||
self.log.info(f"Using local registry ({self.registry}).")
|
||||
|
||||
self.testnet = self.domain != NetworksInventory.MAINNET
|
||||
self.signer = Signer.from_signer_uri(self.signer_uri, testnet=self.testnet)
|
||||
|
||||
#
|
||||
# Onchain Payments & Policies
|
||||
#
|
||||
|
||||
# FIXME: Enforce this for Ursula/Alice but not Bob?
|
||||
from nucypher.config.characters import BobConfiguration
|
||||
if not isinstance(self, BobConfiguration):
|
||||
# if not payment_provider:
|
||||
# raise self.ConfigurationError("payment provider is required.")
|
||||
self.payment_method = payment_method or self.DEFAULT_PAYMENT_METHOD
|
||||
self.payment_network = payment_network or self.DEFAULT_PAYMENT_NETWORK
|
||||
self.payment_provider = payment_provider or (self.eth_provider_uri or None) # default to L1 payments
|
||||
|
||||
# TODO: Dedupe
|
||||
if not self.policy_registry:
|
||||
if not self.policy_registry_filepath:
|
||||
self.log.info(f"Fetching latest policy registry from source.")
|
||||
self.policy_registry = InMemoryContractRegistry.from_latest_publication(network=self.payment_network)
|
||||
else:
|
||||
self.policy_registry = LocalContractRegistry(filepath=self.policy_registry_filepath)
|
||||
self.log.info(f"Using local policy registry ({self.policy_registry}).")
|
||||
self.policy_registry = LocalContractRegistry(
|
||||
filepath=self.policy_registry_filepath
|
||||
)
|
||||
self.log.info(
|
||||
f"Using local policy registry ({self.policy_registry})."
|
||||
)
|
||||
|
||||
if dev_mode:
|
||||
self.__temp_dir = UNINITIALIZED_CONFIGURATION
|
||||
|
@ -621,21 +588,19 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
return self.__dev_mode
|
||||
|
||||
def _setup_node_storage(self, node_storage=None) -> None:
|
||||
# TODO: Disables node metadata persistence..
|
||||
# TODO: Disables node metadata persistence
|
||||
# if self.dev_mode:
|
||||
# node_storage = ForgetfulNodeStorage(registry=self.registry, federated_only=self.federated_only)
|
||||
# node_storage = ForgetfulNodeStorage(registry=self.registry)
|
||||
|
||||
# TODO: Forcibly clears the filesystem of any stored node metadata and certificates...
|
||||
local_node_storage = LocalFileBasedNodeStorage(
|
||||
registry=self.registry,
|
||||
config_root=self.config_root,
|
||||
federated_only=self.federated_only
|
||||
registry=self.registry, config_root=self.config_root
|
||||
)
|
||||
local_node_storage.clear()
|
||||
self.log.info(f'Cleared peer metadata from {local_node_storage.root_dir}')
|
||||
|
||||
# TODO: Always sets up nodes for in-memory node metadata storage
|
||||
node_storage = ForgetfulNodeStorage(registry=self.registry, federated_only=self.federated_only)
|
||||
node_storage = ForgetfulNodeStorage(registry=self.registry)
|
||||
self.node_storage = node_storage
|
||||
|
||||
def forget_nodes(self) -> None:
|
||||
|
@ -667,9 +632,8 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
Warning: This method allows mutation and may result in an inconsistent configuration.
|
||||
"""
|
||||
payload = cls._read_configuration_file(filepath=filepath)
|
||||
node_storage = cls.load_node_storage(storage_payload=payload['node_storage'],
|
||||
federated_only=payload['federated_only'])
|
||||
max_gas_price = payload.get('max_gas_price') # gwei
|
||||
node_storage = cls.load_node_storage(storage_payload=payload["node_storage"])
|
||||
max_gas_price = payload.get("max_gas_price") # gwei
|
||||
if max_gas_price:
|
||||
max_gas_price = Decimal(max_gas_price)
|
||||
|
||||
|
@ -705,8 +669,6 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
for field, path in filepaths.items():
|
||||
if path and not path.exists():
|
||||
message = 'Missing configuration file or directory: {}.'
|
||||
if 'registry' in path:
|
||||
message += ' Did you mean to pass --federated-only?'
|
||||
raise CharacterConfiguration.InvalidConfiguration(message.format(path))
|
||||
return True
|
||||
|
||||
|
@ -716,7 +678,6 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
payload = dict(
|
||||
|
||||
# Identity
|
||||
federated_only=self.federated_only,
|
||||
checksum_address=self.checksum_address,
|
||||
keystore_path=keystore_path,
|
||||
|
||||
|
@ -731,20 +692,23 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
)
|
||||
|
||||
# Optional values (mode)
|
||||
if not self.federated_only:
|
||||
if self.eth_provider_uri:
|
||||
if not self.signer_uri:
|
||||
self.signer_uri = self.eth_provider_uri
|
||||
payload.update(dict(eth_provider_uri=self.eth_provider_uri,
|
||||
poa=self.poa,
|
||||
light=self.is_light,
|
||||
signer_uri=self.signer_uri))
|
||||
if self.registry_filepath:
|
||||
payload.update(dict(registry_filepath=self.registry_filepath))
|
||||
if self.eth_provider_uri:
|
||||
if not self.signer_uri:
|
||||
self.signer_uri = self.eth_provider_uri
|
||||
payload.update(
|
||||
dict(
|
||||
eth_provider_uri=self.eth_provider_uri,
|
||||
poa=self.poa,
|
||||
light=self.is_light,
|
||||
signer_uri=self.signer_uri,
|
||||
)
|
||||
)
|
||||
if self.registry_filepath:
|
||||
payload.update(dict(registry_filepath=self.registry_filepath))
|
||||
|
||||
# Gas Price
|
||||
__max_price = str(self.max_gas_price) if self.max_gas_price else None
|
||||
payload.update(dict(gas_strategy=self.gas_strategy, max_gas_price=__max_price))
|
||||
# Gas Price
|
||||
__max_price = str(self.max_gas_price) if self.max_gas_price else None
|
||||
payload.update(dict(gas_strategy=self.gas_strategy, max_gas_price=__max_price))
|
||||
|
||||
# Merge with base payload
|
||||
base_payload = super().static_payload()
|
||||
|
@ -759,15 +723,16 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
These values are used to init a character instance but are *not*
|
||||
saved to the JSON configuration.
|
||||
"""
|
||||
payload = dict()
|
||||
if not self.federated_only:
|
||||
payload.update(dict(registry=self.registry, signer=self.signer))
|
||||
|
||||
payload.update(dict(network_middleware=self.network_middleware or self.DEFAULT_NETWORK_MIDDLEWARE(),
|
||||
known_nodes=self.known_nodes,
|
||||
node_storage=self.node_storage,
|
||||
keystore=self.keystore,
|
||||
crypto_power_ups=self.derive_node_power_ups()))
|
||||
payload = dict(
|
||||
registry=self.registry,
|
||||
signer=self.signer,
|
||||
network_middleware=self.network_middleware
|
||||
or self.DEFAULT_NETWORK_MIDDLEWARE(),
|
||||
known_nodes=self.known_nodes,
|
||||
node_storage=self.node_storage,
|
||||
keystore=self.keystore,
|
||||
crypto_power_ups=self.derive_node_power_ups(),
|
||||
)
|
||||
|
||||
return payload
|
||||
|
||||
|
@ -851,20 +816,22 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
return self.keystore
|
||||
|
||||
@classmethod
|
||||
def load_node_storage(cls, storage_payload: dict, federated_only: bool):
|
||||
def load_node_storage(cls, storage_payload: dict):
|
||||
from nucypher.config.storages import NodeStorage
|
||||
node_storage_subclasses = {storage._name: storage for storage in NodeStorage.__subclasses__()}
|
||||
storage_type = storage_payload[NodeStorage._TYPE_LABEL]
|
||||
storage_class = node_storage_subclasses[storage_type]
|
||||
node_storage = storage_class.from_payload(payload=storage_payload, federated_only=federated_only)
|
||||
node_storage = storage_class.from_payload(payload=storage_payload)
|
||||
return node_storage
|
||||
|
||||
def configure_payment_method(self):
|
||||
# TODO: finalize config fields
|
||||
#
|
||||
# Strategy-Based (current implementation, inflexible & hardcoded)
|
||||
# 'payment_strategy': 'SubscriptionManager'
|
||||
# 'payment_network': 'matic'
|
||||
# 'payment_provider': 'https:///matic.infura.io....'
|
||||
#
|
||||
# Contract-Targeted (alternative implementation, flexible & generic)
|
||||
# 'payment': {
|
||||
# 'contract': '0xdeadbeef'
|
||||
|
@ -872,11 +839,12 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
# 'function': 'isPolicyActive'
|
||||
# 'provider': 'https:///matic.infura.io....'
|
||||
# }
|
||||
#
|
||||
|
||||
try:
|
||||
payment_class = PAYMENT_METHODS[self.payment_method]
|
||||
except KeyError:
|
||||
raise KeyError(f'Unknown payment verifier "{self.payment_method}"')
|
||||
raise KeyError(f'Unknown payment method "{self.payment_method}"')
|
||||
|
||||
if payment_class.ONCHAIN:
|
||||
# on-chain payment strategies require a blockchain connection
|
||||
|
|
|
@ -3,17 +3,16 @@
|
|||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from typing import Dict, Optional
|
||||
|
||||
from constant_sorrow.constants import UNINITIALIZED_CONFIGURATION
|
||||
from cryptography.x509 import Certificate
|
||||
from eth_utils import is_checksum_address
|
||||
|
||||
from nucypher.config.base import CharacterConfiguration
|
||||
from nucypher.config.constants import (
|
||||
NUCYPHER_ENVVAR_OPERATOR_ETH_PASSWORD,
|
||||
NUCYPHER_ENVVAR_ALICE_ETH_PASSWORD,
|
||||
NUCYPHER_ENVVAR_BOB_ETH_PASSWORD
|
||||
NUCYPHER_ENVVAR_BOB_ETH_PASSWORD,
|
||||
NUCYPHER_ENVVAR_OPERATOR_ETH_PASSWORD,
|
||||
)
|
||||
from nucypher.utilities.networking import LOOPBACK_ADDRESS
|
||||
|
||||
|
@ -32,15 +31,18 @@ class UrsulaConfiguration(CharacterConfiguration):
|
|||
SIGNER_ENVVAR = NUCYPHER_ENVVAR_OPERATOR_ETH_PASSWORD
|
||||
MNEMONIC_KEYSTORE = True
|
||||
|
||||
def __init__(self,
|
||||
rest_host: str = None,
|
||||
operator_address: str = None,
|
||||
dev_mode: bool = False,
|
||||
keystore_path: Optional[Path] = None,
|
||||
rest_port: int = None,
|
||||
certificate: Certificate = None,
|
||||
availability_check: bool = None,
|
||||
*args, **kwargs) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
rest_host: Optional[str] = None,
|
||||
operator_address: Optional[str] = None,
|
||||
dev_mode: bool = False,
|
||||
keystore_path: Optional[Path] = None,
|
||||
rest_port: Optional[int] = None,
|
||||
certificate: Optional[Certificate] = None,
|
||||
availability_check: Optional[bool] = None,
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
|
||||
if dev_mode:
|
||||
rest_host = rest_host or self.DEFAULT_DEVELOPMENT_REST_HOST
|
||||
|
@ -48,7 +50,7 @@ class UrsulaConfiguration(CharacterConfiguration):
|
|||
rest_port = self.DEFAULT_DEVELOPMENT_REST_PORT
|
||||
else:
|
||||
if not rest_host:
|
||||
raise ValueError('rest_host is required for live workers.')
|
||||
raise ValueError("rest_host is required for live nodes.")
|
||||
if not rest_port:
|
||||
rest_port = self.DEFAULT_REST_PORT
|
||||
|
||||
|
@ -61,14 +63,8 @@ class UrsulaConfiguration(CharacterConfiguration):
|
|||
|
||||
@classmethod
|
||||
def checksum_address_from_filepath(cls, filepath: Path) -> str:
|
||||
"""
|
||||
Extracts worker address by "peeking" inside the ursula configuration file.
|
||||
"""
|
||||
"""Extracts worker address by "peeking" inside the ursula configuration file."""
|
||||
checksum_address = cls.peek(filepath=filepath, field='checksum_address')
|
||||
federated = bool(cls.peek(filepath=filepath, field='federated_only'))
|
||||
if not federated:
|
||||
checksum_address = cls.peek(filepath=filepath, field='operator_address')
|
||||
|
||||
if not is_checksum_address(checksum_address):
|
||||
raise RuntimeError(f"Invalid checksum address detected in configuration file at '{filepath}'.")
|
||||
return checksum_address
|
||||
|
@ -90,6 +86,7 @@ class UrsulaConfiguration(CharacterConfiguration):
|
|||
rest_port=self.rest_port,
|
||||
availability_check=self.availability_check,
|
||||
|
||||
# PRE Payments
|
||||
# TODO: Resolve variable prefixing below (uses nested configuration fields?)
|
||||
payment_method=self.payment_method,
|
||||
payment_provider=self.payment_provider,
|
||||
|
@ -108,7 +105,6 @@ class UrsulaConfiguration(CharacterConfiguration):
|
|||
|
||||
def produce(self, **overrides):
|
||||
"""Produce a new Ursula from configuration"""
|
||||
|
||||
merged_parameters = self.generate_parameters(**overrides)
|
||||
ursula = self.CHARACTER_CLASS(**merged_parameters)
|
||||
return ursula
|
||||
|
@ -133,33 +129,18 @@ class AliceConfiguration(CharacterConfiguration):
|
|||
# TODO: Best (Sane) Defaults
|
||||
DEFAULT_THRESHOLD = 2
|
||||
DEFAULT_SHARES = 3
|
||||
|
||||
DEFAULT_STORE_POLICIES = True
|
||||
DEFAULT_STORE_CARDS = True
|
||||
|
||||
SIGNER_ENVVAR = NUCYPHER_ENVVAR_ALICE_ETH_PASSWORD
|
||||
|
||||
_CONFIG_FIELDS = (
|
||||
*CharacterConfiguration._CONFIG_FIELDS,
|
||||
'store_policies',
|
||||
'store_cards',
|
||||
)
|
||||
_CONFIG_FIELDS = (*CharacterConfiguration._CONFIG_FIELDS,)
|
||||
|
||||
def __init__(self,
|
||||
threshold: int = None,
|
||||
shares: int = None,
|
||||
rate: int = None,
|
||||
duration: int = None,
|
||||
store_policies: bool = DEFAULT_STORE_POLICIES,
|
||||
store_cards: bool = DEFAULT_STORE_CARDS,
|
||||
*args, **kwargs):
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Storage
|
||||
self.store_policies = store_policies
|
||||
self.store_cards = store_cards
|
||||
|
||||
# Policy Value Defaults
|
||||
self.rate = rate
|
||||
self.duration = duration
|
||||
|
@ -170,17 +151,12 @@ class AliceConfiguration(CharacterConfiguration):
|
|||
payload = dict(
|
||||
threshold=self.threshold,
|
||||
shares=self.shares,
|
||||
store_policies=self.store_policies,
|
||||
store_cards=self.store_cards,
|
||||
payment_network=self.payment_network,
|
||||
payment_provider=self.payment_provider,
|
||||
payment_method=self.payment_method
|
||||
payment_method=self.payment_method,
|
||||
rate=self.rate,
|
||||
duration=self.duration,
|
||||
)
|
||||
if not self.federated_only:
|
||||
if self.rate:
|
||||
payload['rate'] = self.rate
|
||||
if self.duration:
|
||||
payload['duration'] = self.duration
|
||||
return {**super().static_payload(), **payload}
|
||||
|
||||
@property
|
||||
|
@ -194,27 +170,5 @@ class BobConfiguration(CharacterConfiguration):
|
|||
|
||||
CHARACTER_CLASS = Bob
|
||||
NAME = CHARACTER_CLASS.__name__.lower()
|
||||
DEFAULT_STORE_POLICIES = True
|
||||
DEFAULT_STORE_CARDS = True
|
||||
SIGNER_ENVVAR = NUCYPHER_ENVVAR_BOB_ETH_PASSWORD
|
||||
|
||||
_CONFIG_FIELDS = (
|
||||
*CharacterConfiguration._CONFIG_FIELDS,
|
||||
'store_policies',
|
||||
'store_cards'
|
||||
)
|
||||
|
||||
def __init__(self,
|
||||
store_policies: bool = DEFAULT_STORE_POLICIES,
|
||||
store_cards: bool = DEFAULT_STORE_CARDS,
|
||||
*args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.store_policies = store_policies
|
||||
self.store_cards = store_cards
|
||||
|
||||
def static_payload(self) -> dict:
|
||||
payload = dict(
|
||||
store_policies=self.store_policies,
|
||||
store_cards=self.store_cards
|
||||
)
|
||||
return {**super().static_payload(), **payload}
|
||||
_CONFIG_FIELDS = (*CharacterConfiguration._CONFIG_FIELDS,)
|
||||
|
|
|
@ -33,27 +33,22 @@ class NodeStorage(ABC):
|
|||
class UnknownNode(NodeStorageError):
|
||||
pass
|
||||
|
||||
def __init__(self,
|
||||
federated_only: bool = False, # TODO# 466
|
||||
character_class=None,
|
||||
registry: BaseContractRegistry = None,
|
||||
) -> None:
|
||||
def __init__(self, character_class=None, registry: BaseContractRegistry = None):
|
||||
|
||||
from nucypher.characters.lawful import Ursula
|
||||
|
||||
self.log = Logger(self.__class__.__name__)
|
||||
self.registry = registry
|
||||
self.federated_only = federated_only
|
||||
self.character_class = character_class or Ursula
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.get(checksum_address=item, federated_only=self.federated_only)
|
||||
return self.get(checksum_address=item)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
return self.store_node_metadata(node=value)
|
||||
|
||||
def __iter__(self):
|
||||
return self.all(federated_only=self.federated_only)
|
||||
return self.all()
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
|
@ -129,12 +124,12 @@ class NodeStorage(ABC):
|
|||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def all(self, federated_only: bool, certificates_only: bool = False) -> set:
|
||||
def all(self, certificates_only: bool = False) -> set:
|
||||
"""Return s set of all stored nodes"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def get(self, checksum_address: str, federated_only: bool):
|
||||
def get(self, checksum_address: str):
|
||||
"""Retrieve a single stored node"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
@ -162,12 +157,11 @@ class ForgetfulNodeStorage(NodeStorage):
|
|||
"""Human readable source string"""
|
||||
return self._name
|
||||
|
||||
def all(self, federated_only: bool, certificates_only: bool = False) -> set:
|
||||
def all(self, certificates_only: bool = False) -> set:
|
||||
return set(self.__certificates.values() if certificates_only else self.__metadata.values())
|
||||
|
||||
@validate_checksum_address
|
||||
def get(self,
|
||||
federated_only: bool,
|
||||
host: str = None,
|
||||
stamp: SignatureStamp = None,
|
||||
certificate_only: bool = False):
|
||||
|
@ -355,7 +349,7 @@ class LocalFileBasedNodeStorage(NodeStorage):
|
|||
#
|
||||
# API
|
||||
#
|
||||
def all(self, federated_only: bool, certificates_only: bool = False) -> Set[Union[Any, Certificate]]:
|
||||
def all(self, certificates_only: bool = False) -> Set[Union[Any, Certificate]]:
|
||||
filenames = list((self.certificates_dir if certificates_only else self.metadata_dir).iterdir())
|
||||
self.log.info("Found {} known node metadata files at {}".format(len(filenames), self.metadata_dir))
|
||||
|
||||
|
@ -383,7 +377,7 @@ class LocalFileBasedNodeStorage(NodeStorage):
|
|||
return known_nodes
|
||||
|
||||
@validate_checksum_address
|
||||
def get(self, stamp: Union[SignatureStamp, str], federated_only: bool, certificate_only: bool = False):
|
||||
def get(self, stamp: Union[SignatureStamp, str], certificate_only: bool = False):
|
||||
if certificate_only is True:
|
||||
certificate = self.__read_node_tls_certificate(stamp=stamp)
|
||||
return certificate
|
||||
|
|
|
@ -255,10 +255,7 @@ class Learner:
|
|||
|
||||
self.learning_deferred = Deferred()
|
||||
self.domain = domain
|
||||
if not self.federated_only:
|
||||
default_middleware = self.__DEFAULT_MIDDLEWARE_CLASS(registry=self.registry)
|
||||
else:
|
||||
default_middleware = self.__DEFAULT_MIDDLEWARE_CLASS()
|
||||
default_middleware = self.__DEFAULT_MIDDLEWARE_CLASS(registry=self.registry)
|
||||
self.network_middleware = network_middleware or default_middleware
|
||||
self.save_metadata = save_metadata
|
||||
self.start_learning_now = start_learning_now
|
||||
|
@ -275,7 +272,7 @@ class Learner:
|
|||
self._discovery_canceller = DiscoveryCanceller()
|
||||
|
||||
if not node_storage:
|
||||
node_storage = self.__DEFAULT_NODE_STORAGE(federated_only=self.federated_only)
|
||||
node_storage = self.__DEFAULT_NODE_STORAGE()
|
||||
self.node_storage = node_storage
|
||||
if save_metadata and node_storage is NO_STORAGE_AVAILABLE:
|
||||
raise ValueError("Cannot save nodes without a configured node storage")
|
||||
|
@ -302,7 +299,7 @@ class Learner:
|
|||
|
||||
if self._DEBUG_MODE:
|
||||
# Very slow, but provides useful info when trying to track down a stray Character.
|
||||
# Seems mostly useful for Bob or federated Ursulas, but perhaps useful for other Characters as well.
|
||||
# Seems mostly useful for Bob but perhaps useful for other Characters as well.
|
||||
|
||||
import inspect
|
||||
import os
|
||||
|
@ -344,7 +341,6 @@ class Learner:
|
|||
try:
|
||||
maybe_sage_node = self.node_class.from_teacher_uri(teacher_uri=uri,
|
||||
min_stake=0, # TODO: Where to get this?
|
||||
federated_only=self.federated_only,
|
||||
network_middleware=self.network_middleware,
|
||||
registry=self.registry)
|
||||
except Exception as e:
|
||||
|
@ -388,7 +384,7 @@ class Learner:
|
|||
return discovered
|
||||
|
||||
def read_nodes_from_storage(self) -> List:
|
||||
stored_nodes = self.node_storage.all(federated_only=self.federated_only) # TODO: #466
|
||||
stored_nodes = self.node_storage.all() # TODO: #466
|
||||
|
||||
restored_from_disk = []
|
||||
invalid_nodes = defaultdict(list)
|
||||
|
@ -447,7 +443,7 @@ class Learner:
|
|||
# Use this to control whether or not this node performs
|
||||
# blockchain calls to determine if stranger nodes are bonded.
|
||||
# Note: self.registry is composed on blockchainy character subclasses.
|
||||
registry = self.registry if self._verify_node_bonding else None # TODO: Federated mode?
|
||||
registry = self.registry if self._verify_node_bonding else None
|
||||
|
||||
try:
|
||||
node.verify_node(force=force_verification_recheck,
|
||||
|
@ -746,9 +742,7 @@ class Learner:
|
|||
def learn_from_teacher_node(self, eager=False, canceller=None):
|
||||
"""
|
||||
Sends a request to node_url to find out about known nodes.
|
||||
|
||||
TODO: Does this (and related methods) belong on FleetSensor for portability?
|
||||
|
||||
TODO: A lot of other code can be simplified if this is converted to async def. That's a project, though.
|
||||
"""
|
||||
remembered = []
|
||||
|
@ -976,9 +970,6 @@ class Teacher:
|
|||
class UnbondedOperator(InvalidNode):
|
||||
"""Raised when a node fails verification because it is not bonded to a Staker"""
|
||||
|
||||
class WrongMode(TypeError):
|
||||
"""Raised when a Character tries to use another Character as decentralized when the latter is federated_only."""
|
||||
|
||||
@classmethod
|
||||
def set_cert_storage_function(cls, node_storage_function: Callable):
|
||||
cls._cert_store_function = node_storage_function
|
||||
|
@ -987,10 +978,6 @@ class Teacher:
|
|||
"""This is the most mature form, so we do nothing."""
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def set_federated_mode(cls, federated_only: bool):
|
||||
cls._federated_only_instances = federated_only
|
||||
|
||||
#
|
||||
# Known Nodes
|
||||
#
|
||||
|
@ -1036,41 +1023,32 @@ class Teacher:
|
|||
return is_staking
|
||||
|
||||
def validate_operator(self, registry: BaseContractRegistry = None, eth_provider_uri: Optional[str] = None) -> None:
|
||||
# Try to derive the worker address if it hasn't been derived yet.
|
||||
try:
|
||||
# TODO: This is overtly implicit
|
||||
_operator_address = self.operator_address
|
||||
except Exception as e:
|
||||
raise self.InvalidOperatorSignature(str(e)) from e
|
||||
self.verified_stamp = True # TODO: Does this belong here?
|
||||
|
||||
# Federated
|
||||
if self.federated_only:
|
||||
message = "This node cannot be verified in this manner, " \
|
||||
"but is OK to use in federated mode if you " \
|
||||
"have reason to believe it is trustworthy."
|
||||
raise self.WrongMode(message)
|
||||
# On-chain staking check, if registry is present
|
||||
if registry:
|
||||
|
||||
# Decentralized
|
||||
else:
|
||||
|
||||
# Try to derive the worker address if it hasn't been derived yet.
|
||||
try:
|
||||
# TODO: This is overtly implicit
|
||||
_operator_address = self.operator_address
|
||||
except Exception as e:
|
||||
raise self.InvalidOperatorSignature(str(e)) from e
|
||||
self.verified_stamp = True # TODO: Does this belong here?
|
||||
|
||||
# On-chain staking check, if registry is present
|
||||
if registry:
|
||||
|
||||
if not self._operator_is_bonded(registry=registry): # <-- Blockchain CALL
|
||||
message = f"Operator {self.operator_address} is not bonded to staking provider {self.checksum_address}"
|
||||
self.log.debug(message)
|
||||
raise self.UnbondedOperator(message)
|
||||
|
||||
if self._staking_provider_is_really_staking(registry=registry, eth_provider_uri=eth_provider_uri): # <-- Blockchain CALL
|
||||
self.log.info(f'Verified operator {self}')
|
||||
self.verified_operator = True
|
||||
else:
|
||||
raise self.NotStaking(f"{self.checksum_address} is not staking")
|
||||
if not self._operator_is_bonded(registry=registry): # <-- Blockchain CALL
|
||||
message = f"Operator {self.operator_address} is not bonded to staking provider {self.checksum_address}"
|
||||
self.log.debug(message)
|
||||
raise self.UnbondedOperator(message)
|
||||
|
||||
if self._staking_provider_is_really_staking(
|
||||
registry=registry, eth_provider_uri=eth_provider_uri
|
||||
): # <-- Blockchain CALL
|
||||
self.log.info(f"Verified operator {self}")
|
||||
self.verified_operator = True
|
||||
else:
|
||||
self.log.info('No registry provided for staking verification.')
|
||||
raise self.NotStaking(f"{self.checksum_address} is not staking")
|
||||
|
||||
else:
|
||||
self.log.info("No registry provided for staking verification.")
|
||||
|
||||
def validate_metadata_signature(self) -> bool:
|
||||
"""Checks that the interface info is valid for this node's canonical address."""
|
||||
|
@ -1092,11 +1070,7 @@ class Teacher:
|
|||
return
|
||||
|
||||
# Offline check of valid stamp signature by worker
|
||||
try:
|
||||
self.validate_operator(registry=registry, eth_provider_uri=eth_provider_uri)
|
||||
except self.WrongMode:
|
||||
if bool(registry):
|
||||
raise
|
||||
self.validate_operator(registry=registry, eth_provider_uri=eth_provider_uri)
|
||||
|
||||
def verify_node(self,
|
||||
network_middleware_client,
|
||||
|
@ -1108,8 +1082,7 @@ class Teacher:
|
|||
"""
|
||||
Three things happening here:
|
||||
|
||||
* Verify that the stamp matches the address (raises InvalidNode is it's not valid,
|
||||
or WrongMode if it's a federated mode and being verified as a decentralized node)
|
||||
* Verify that the stamp matches the address
|
||||
|
||||
* Verify the interface signature (raises InvalidNode if not valid)
|
||||
|
||||
|
@ -1128,9 +1101,11 @@ class Teacher:
|
|||
if self.verified_node:
|
||||
return True
|
||||
|
||||
if not registry and not self.federated_only: # TODO: # 466
|
||||
self.log.debug("No registry provided for decentralized stranger node verification - "
|
||||
"on-chain Staking verification will not be performed.")
|
||||
if not registry: # TODO: # 466
|
||||
self.log.debug(
|
||||
"No registry provided for peer verification - "
|
||||
"on-chain stake verification will not be performed."
|
||||
)
|
||||
|
||||
# This is both the stamp's client signature and interface metadata check; May raise InvalidNode
|
||||
self.validate_metadata(registry=registry, eth_provider_uri=eth_provider_uri)
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
|
||||
from eth_utils import is_checksum_address
|
||||
from typing import Tuple
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from eth_typing import Address
|
||||
from eth_utils import is_checksum_address
|
||||
|
||||
from nucypher.utilities.networking import LOOPBACK_ADDRESS
|
||||
|
||||
|
||||
|
@ -9,17 +11,16 @@ class SuspiciousActivity(RuntimeError):
|
|||
"""raised when an action appears to amount to malicious conduct."""
|
||||
|
||||
|
||||
def parse_node_uri(uri: str):
|
||||
def parse_node_uri(uri: str, delimiter: str = "@") -> Tuple[str, int, Address]:
|
||||
from nucypher.config.characters import UrsulaConfiguration
|
||||
|
||||
if '@' in uri:
|
||||
checksum_address, uri = uri.split("@")
|
||||
checksum_address = None
|
||||
if delimiter in uri:
|
||||
checksum_address, uri = uri.split(delimiter)
|
||||
if checksum_address is None:
|
||||
raise ValueError(f"{uri} is not a valid Teacher URI - no checksum address.")
|
||||
if not is_checksum_address(checksum_address):
|
||||
raise ValueError("{} is not a valid checksum address.".format(checksum_address))
|
||||
else:
|
||||
checksum_address = None # federated
|
||||
|
||||
#############################################
|
||||
# Strange logic here to ensure https:// - possibly pursuant to https://bugs.python.org/msg179670
|
||||
|
|
|
@ -193,13 +193,12 @@ def _make_rest_app(this_node, log: Logger) -> Flask:
|
|||
return Response(message, status=HTTPStatus.BAD_REQUEST)
|
||||
|
||||
# Enforce Reencryption Conditions
|
||||
providers = this_node.condition_providers if not this_node.federated_only else dict()
|
||||
capsules_to_process = list()
|
||||
for capsule, condition_lingo in packets:
|
||||
if condition_lingo:
|
||||
error = evaluate_condition_lingo(
|
||||
lingo=condition_lingo,
|
||||
providers=providers,
|
||||
providers=this_node.condition_providers,
|
||||
context=context
|
||||
)
|
||||
if error:
|
||||
|
|
|
@ -119,7 +119,7 @@ def evaluate_condition_lingo(
|
|||
# TODO: Evaluate all conditions even if one fails and report the result
|
||||
"""
|
||||
|
||||
# Setup (don't use mutable defaults and support federated mode)
|
||||
# Setup (don't use mutable defaults)
|
||||
context = context or dict()
|
||||
providers = providers or dict()
|
||||
error = None
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional, NamedTuple, Dict
|
||||
from typing import Dict, NamedTuple, Optional
|
||||
|
||||
import maya
|
||||
from nucypher_core import ReencryptionRequest
|
||||
from web3.types import Wei, Timestamp, TxReceipt, ChecksumAddress
|
||||
from web3.types import ChecksumAddress, Timestamp, TxReceipt, Wei
|
||||
|
||||
from nucypher.blockchain.eth.agents import SubscriptionManagerAgent, ContractAgency
|
||||
from nucypher.blockchain.eth.registry import InMemoryContractRegistry, BaseContractRegistry
|
||||
from nucypher.policy.policies import BlockchainPolicy, Policy
|
||||
from nucypher.blockchain.eth.agents import ContractAgency, SubscriptionManagerAgent
|
||||
from nucypher.blockchain.eth.registry import (
|
||||
BaseContractRegistry,
|
||||
InMemoryContractRegistry,
|
||||
)
|
||||
from nucypher.policy.policies import Policy
|
||||
|
||||
|
||||
class PaymentMethod(ABC):
|
||||
|
@ -102,7 +103,7 @@ class SubscriptionManagerPayment(ContractPayment):
|
|||
result = self.agent.is_policy_active(policy_id=bytes(request.hrac))
|
||||
return result
|
||||
|
||||
def pay(self, policy: BlockchainPolicy) -> TxReceipt:
|
||||
def pay(self, policy: Policy) -> TxReceipt:
|
||||
"""Writes a new policy to the SubscriptionManager contract."""
|
||||
receipt = self.agent.create_policy(
|
||||
value=policy.value, # wei
|
||||
|
@ -169,7 +170,7 @@ class SubscriptionManagerPayment(ContractPayment):
|
|||
|
||||
|
||||
class FreeReencryptions(PaymentMethod):
|
||||
"""Useful for private federations and testing."""
|
||||
"""Useful for testing."""
|
||||
|
||||
ONCHAIN = False
|
||||
NAME = 'Free'
|
||||
|
|
|
@ -1,28 +1,19 @@
|
|||
|
||||
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Sequence, Optional, Iterable, List, Dict
|
||||
from typing import Dict, Iterable, List, Optional, Sequence
|
||||
|
||||
import maya
|
||||
from eth_typing.evm import ChecksumAddress
|
||||
from nucypher_core import Address, HRAC, TreasureMap
|
||||
from nucypher_core import HRAC, Address, TreasureMap
|
||||
from nucypher_core.umbral import PublicKey, VerifiedKeyFrag
|
||||
|
||||
from nucypher.crypto.powers import DecryptingPower
|
||||
from nucypher.network.middleware import RestMiddleware
|
||||
from nucypher.policy.reservoir import (
|
||||
make_federated_staker_reservoir,
|
||||
MergedReservoir,
|
||||
PrefetchStrategy,
|
||||
make_decentralized_staking_provider_reservoir
|
||||
)
|
||||
from nucypher.policy.reservoir import PrefetchStrategy, make_staking_provider_reservoir
|
||||
from nucypher.policy.revocation import RevocationKit
|
||||
from nucypher.utilities.concurrency import WorkerPool
|
||||
from nucypher.utilities.logging import Logger
|
||||
|
||||
|
||||
class Policy(ABC):
|
||||
class Policy:
|
||||
"""
|
||||
An edict by Alice, arranged with n Ursulas, to perform re-encryption for a specific Bob.
|
||||
"""
|
||||
|
@ -76,10 +67,13 @@ class Policy(ABC):
|
|||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}:{bytes(self.hrac).hex()[:6]}"
|
||||
|
||||
@abstractmethod
|
||||
def _make_reservoir(self, handpicked_addresses: Sequence[ChecksumAddress]) -> MergedReservoir:
|
||||
"""Builds a `MergedReservoir` to use for drawing addresses to send proposals to."""
|
||||
raise NotImplementedError
|
||||
def _make_reservoir(self, handpicked_addresses: List[ChecksumAddress]):
|
||||
"""Returns a reservoir of staking nodes to create a policy."""
|
||||
reservoir = make_staking_provider_reservoir(
|
||||
application_agent=self.publisher.application_agent,
|
||||
include_addresses=handpicked_addresses,
|
||||
)
|
||||
return reservoir
|
||||
|
||||
def _publish(self, ursulas: List['Ursula']) -> Dict:
|
||||
self.nodes = [ursula.checksum_address for ursula in ursulas]
|
||||
|
@ -182,23 +176,6 @@ class Policy(ABC):
|
|||
return enacted_policy
|
||||
|
||||
|
||||
class FederatedPolicy(Policy):
|
||||
|
||||
def _make_reservoir(self, handpicked_addresses: List[ChecksumAddress]):
|
||||
"""Returns a federated node reservoir for creating a federated policy."""
|
||||
return make_federated_staker_reservoir(known_nodes=self.publisher.known_nodes,
|
||||
include_addresses=handpicked_addresses)
|
||||
|
||||
|
||||
class BlockchainPolicy(Policy):
|
||||
|
||||
def _make_reservoir(self, handpicked_addresses: List[ChecksumAddress]):
|
||||
"""Returns a reservoir of staking nodes to create a decentralized policy."""
|
||||
reservoir = make_decentralized_staking_provider_reservoir(application_agent=self.publisher.application_agent,
|
||||
include_addresses=handpicked_addresses)
|
||||
return reservoir
|
||||
|
||||
|
||||
class EnactedPolicy:
|
||||
|
||||
def __init__(self,
|
||||
|
|
|
@ -1,36 +1,19 @@
|
|||
|
||||
|
||||
|
||||
from typing import Iterable, List, Optional
|
||||
|
||||
from eth_typing import ChecksumAddress
|
||||
|
||||
from nucypher.acumen.perception import FleetSensor
|
||||
from nucypher.blockchain.eth.agents import StakingProvidersReservoir, PREApplicationAgent
|
||||
from nucypher.blockchain.eth.agents import (
|
||||
PREApplicationAgent,
|
||||
StakingProvidersReservoir,
|
||||
)
|
||||
|
||||
|
||||
def make_federated_staker_reservoir(known_nodes: FleetSensor,
|
||||
exclude_addresses: Optional[Iterable[ChecksumAddress]] = None,
|
||||
include_addresses: Optional[Iterable[ChecksumAddress]] = None):
|
||||
"""Get a sampler object containing the federated stakers."""
|
||||
# needs to not include both exclude and include addresses
|
||||
# so that they aren't included in reservoir, include_address will be re-added to reservoir afterwards
|
||||
include_addresses = include_addresses or ()
|
||||
exclusion_set = set(include_addresses) | set(exclude_addresses or ())
|
||||
addresses = {}
|
||||
for ursula in known_nodes:
|
||||
if ursula.checksum_address in exclusion_set:
|
||||
continue
|
||||
addresses[ursula.checksum_address] = 1
|
||||
|
||||
# add include addresses
|
||||
return MergedReservoir(include_addresses, StakingProvidersReservoir(addresses))
|
||||
|
||||
|
||||
def make_decentralized_staking_provider_reservoir(application_agent: PREApplicationAgent,
|
||||
exclude_addresses: Optional[Iterable[ChecksumAddress]] = None,
|
||||
include_addresses: Optional[Iterable[ChecksumAddress]] = None,
|
||||
pagination_size: int = None):
|
||||
def make_staking_provider_reservoir(
|
||||
application_agent: PREApplicationAgent,
|
||||
exclude_addresses: Optional[Iterable[ChecksumAddress]] = None,
|
||||
include_addresses: Optional[Iterable[ChecksumAddress]] = None,
|
||||
pagination_size: Optional[int] = None,
|
||||
):
|
||||
"""Get a sampler object containing the currently registered staking providers."""
|
||||
|
||||
# needs to not include both exclude and include addresses
|
||||
|
|
|
@ -3,16 +3,16 @@
|
|||
|
||||
import random
|
||||
from ipaddress import ip_address
|
||||
from typing import Union, Optional
|
||||
from typing import Optional, Union
|
||||
|
||||
import requests
|
||||
from requests.exceptions import RequestException, HTTPError
|
||||
from requests.exceptions import HTTPError, RequestException
|
||||
|
||||
from nucypher.acumen.perception import FleetSensor
|
||||
from nucypher.blockchain.eth.registry import BaseContractRegistry
|
||||
from nucypher.config.storages import LocalFileBasedNodeStorage
|
||||
from nucypher.network.exceptions import NodeSeemsToBeDown
|
||||
from nucypher.network.middleware import RestMiddleware, NucypherMiddlewareClient
|
||||
from nucypher.network.middleware import NucypherMiddlewareClient, RestMiddleware
|
||||
from nucypher.utilities.logging import Logger
|
||||
|
||||
|
||||
|
@ -96,7 +96,6 @@ def _request_from_node(teacher,
|
|||
|
||||
|
||||
def get_external_ip_from_default_teacher(network: str,
|
||||
federated_only: bool = False,
|
||||
registry: Optional[BaseContractRegistry] = None,
|
||||
log: Logger = IP_DETECTION_LOGGER
|
||||
) -> Union[str, None]:
|
||||
|
@ -105,28 +104,21 @@ def get_external_ip_from_default_teacher(network: str,
|
|||
from nucypher.characters.lawful import Ursula
|
||||
from nucypher.network.nodes import TEACHER_NODES
|
||||
|
||||
if federated_only and registry:
|
||||
raise ValueError('Federated mode must not be true if registry is provided.')
|
||||
|
||||
base_error = 'Cannot determine IP using default teacher'
|
||||
|
||||
if network not in TEACHER_NODES:
|
||||
log.debug(f'{base_error}: Unknown network "{network}".')
|
||||
return
|
||||
|
||||
####
|
||||
# TODO: Clean this mess #1481 (Federated Mode)
|
||||
node_storage = LocalFileBasedNodeStorage(federated_only=federated_only)
|
||||
node_storage = LocalFileBasedNodeStorage()
|
||||
Ursula.set_cert_storage_function(node_storage.store_node_certificate)
|
||||
Ursula.set_federated_mode(federated_only)
|
||||
#####
|
||||
|
||||
external_ip = None
|
||||
for teacher_uri in TEACHER_NODES[network]:
|
||||
try:
|
||||
teacher = Ursula.from_teacher_uri(teacher_uri=teacher_uri,
|
||||
federated_only=federated_only,
|
||||
min_stake=0) # TODO: Handle customized min stake here.
|
||||
teacher = Ursula.from_teacher_uri(
|
||||
teacher_uri=teacher_uri, min_stake=0
|
||||
) # TODO: Handle customized min stake here.
|
||||
# TODO: Pass registry here to verify stake (not essential here since it's a hardcoded node)
|
||||
external_ip = _request_from_node(teacher=teacher)
|
||||
# Found a reachable teacher, return from loop
|
||||
|
|
|
@ -102,25 +102,19 @@ class UrsulaInfoMetricsCollector(BaseMetricsCollector):
|
|||
|
||||
def _collect_internal(self) -> None:
|
||||
# info
|
||||
base_payload = {
|
||||
payload = {
|
||||
"app_version": nucypher.__version__,
|
||||
"host": str(self.ursula.rest_interface),
|
||||
"domain": self.ursula.domain,
|
||||
"nickname": str(self.ursula.nickname),
|
||||
"nickname_icon": self.ursula.nickname.icon,
|
||||
"staking_provider_address": self.ursula.checksum_address,
|
||||
"operator_address": self.ursula.operator_address,
|
||||
}
|
||||
|
||||
self.metrics["learning_status"].state('running' if self.ursula._learning_task.running else 'stopped')
|
||||
self.metrics["known_nodes_gauge"].set(len(self.ursula.known_nodes))
|
||||
|
||||
if not self.ursula.federated_only:
|
||||
decentralized_payload = {
|
||||
"staking_provider_address": self.ursula.checksum_address,
|
||||
"operator_address": self.ursula.operator_address,
|
||||
}
|
||||
base_payload.update(decentralized_payload)
|
||||
|
||||
self.metrics["host_info"].info(base_payload)
|
||||
self.metrics["host_info"].info(payload)
|
||||
|
||||
|
||||
class BlockchainMetricsCollector(BaseMetricsCollector):
|
||||
|
|
|
@ -157,26 +157,27 @@ def create_metrics_collectors(ursula: "Ursula") -> List[MetricsCollector]:
|
|||
"""Create collectors used to obtain metrics."""
|
||||
collectors: List[MetricsCollector] = [UrsulaInfoMetricsCollector(ursula=ursula)]
|
||||
|
||||
if not ursula.federated_only:
|
||||
# Blockchain prometheus
|
||||
# TODO possible include information about payment
|
||||
collectors.append(BlockchainMetricsCollector(eth_provider_uri=ursula.eth_provider_uri))
|
||||
# Blockchain prometheus
|
||||
# TODO possible include information about payment
|
||||
collectors.append(
|
||||
BlockchainMetricsCollector(eth_provider_uri=ursula.eth_provider_uri)
|
||||
)
|
||||
|
||||
# Staking Provider prometheus
|
||||
collectors.append(
|
||||
StakingProviderMetricsCollector(
|
||||
staking_provider_address=ursula.checksum_address,
|
||||
contract_registry=ursula.registry,
|
||||
)
|
||||
# Staking Provider prometheus
|
||||
collectors.append(
|
||||
StakingProviderMetricsCollector(
|
||||
staking_provider_address=ursula.checksum_address,
|
||||
contract_registry=ursula.registry,
|
||||
)
|
||||
)
|
||||
|
||||
# Operator prometheus
|
||||
collectors.append(
|
||||
OperatorMetricsCollector(
|
||||
domain=ursula.domain,
|
||||
operator_address=ursula.operator_address,
|
||||
contract_registry=ursula.registry,
|
||||
)
|
||||
# Operator prometheus
|
||||
collectors.append(
|
||||
OperatorMetricsCollector(
|
||||
domain=ursula.domain,
|
||||
operator_address=ursula.operator_address,
|
||||
contract_registry=ursula.registry,
|
||||
)
|
||||
)
|
||||
|
||||
return collectors
|
||||
|
|
|
@ -1,157 +0,0 @@
|
|||
version: '3'
|
||||
|
||||
# USAGE
|
||||
# docker-compose run nucypher-ci-dev python finnegans-wake-demo.py 172.29.1.3:11500
|
||||
|
||||
services:
|
||||
nucypher-ci-dev:
|
||||
ports:
|
||||
- 11500
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: deploy/docker/Dockerfile
|
||||
image: ci:nucypher
|
||||
container_name: nucypher-ci-dev
|
||||
working_dir: /code/examples/
|
||||
networks:
|
||||
nucypher_ci_net:
|
||||
ipv4_address: 172.29.1.0
|
||||
environment:
|
||||
- FINNEGANS_WAKE_PATH=finnegans_wake_demo/finnegans-wake-excerpt.txt
|
||||
ciursula1:
|
||||
ports:
|
||||
- 11500
|
||||
image: ci:nucypher
|
||||
command: nucypher ursula run --dev --federated-only --rest-host 172.29.1.1 --rest-port 11500 --lonely --disable-availability-check --no-ip-checkup
|
||||
networks:
|
||||
nucypher_ci_net:
|
||||
ipv4_address: 172.29.1.1
|
||||
container_name: ciursula1
|
||||
ciursula2:
|
||||
ports:
|
||||
- 11500
|
||||
image: ci:nucypher
|
||||
depends_on:
|
||||
- ciursula1
|
||||
command: nucypher ursula run --dev --federated-only --rest-host 172.29.1.2 --rest-port 11500 --disable-availability-check --teacher 172.29.1.1:11500 --no-ip-checkup
|
||||
networks:
|
||||
nucypher_ci_net:
|
||||
ipv4_address: 172.29.1.2
|
||||
container_name: ciursula2
|
||||
ciursula3:
|
||||
ports:
|
||||
- 11500
|
||||
image: ci:nucypher
|
||||
depends_on:
|
||||
- ciursula1
|
||||
command: nucypher ursula run --dev --federated-only --rest-host 172.29.1.3 --rest-port 11500 --disable-availability-check --teacher 172.29.1.1:11500 --no-ip-checkup
|
||||
networks:
|
||||
nucypher_ci_net:
|
||||
ipv4_address: 172.29.1.3
|
||||
container_name: ciursula3
|
||||
ciursula4:
|
||||
ports:
|
||||
- 11500
|
||||
image: ci:nucypher
|
||||
depends_on:
|
||||
- ciursula1
|
||||
command: nucypher ursula run --dev --federated-only --rest-host 172.29.1.4 --rest-port 11500 --teacher 172.29.1.1:11500 --no-ip-checkup
|
||||
networks:
|
||||
nucypher_ci_net:
|
||||
ipv4_address: 172.29.1.4
|
||||
container_name: ciursula4
|
||||
ciursula5:
|
||||
ports:
|
||||
- 11500
|
||||
image: ci:nucypher
|
||||
depends_on:
|
||||
- ciursula1
|
||||
command: nucypher ursula run --dev --federated-only --rest-host 172.29.1.5 --rest-port 11500 --teacher 172.29.1.1:11500 --no-ip-checkup
|
||||
networks:
|
||||
nucypher_ci_net:
|
||||
ipv4_address: 172.29.1.5
|
||||
container_name: ciursula5
|
||||
ciursula6:
|
||||
ports:
|
||||
- 11500
|
||||
image: ci:nucypher
|
||||
depends_on:
|
||||
- ciursula1
|
||||
command: nucypher ursula run --dev --federated-only --rest-host 172.29.1.6 --rest-port 11500 --teacher 172.29.1.1:11500 --no-ip-checkup
|
||||
networks:
|
||||
nucypher_ci_net:
|
||||
ipv4_address: 172.29.1.6
|
||||
container_name: ciursula6
|
||||
ciursula7:
|
||||
ports:
|
||||
- 11500
|
||||
image: ci:nucypher
|
||||
depends_on:
|
||||
- ciursula1
|
||||
command: nucypher ursula run --dev --federated-only --rest-host 172.29.1.7 --rest-port 11500 --teacher 172.29.1.1:11500 --no-ip-checkup
|
||||
networks:
|
||||
nucypher_ci_net:
|
||||
ipv4_address: 172.29.1.7
|
||||
container_name: ciursula7
|
||||
ciursula8:
|
||||
ports:
|
||||
- 11500
|
||||
image: ci:nucypher
|
||||
depends_on:
|
||||
- ciursula1
|
||||
command: nucypher ursula run --dev --federated-only --rest-host 172.29.1.8 --rest-port 11500 --teacher 172.29.1.1:11500 --no-ip-checkup
|
||||
networks:
|
||||
nucypher_ci_net:
|
||||
ipv4_address: 172.29.1.8
|
||||
container_name: ciursula8
|
||||
ciursula9:
|
||||
ports:
|
||||
- 11500
|
||||
image: ci:nucypher
|
||||
depends_on:
|
||||
- ciursula1
|
||||
command: nucypher ursula run --dev --federated-only --rest-host 172.29.1.9 --rest-port 11500 --teacher 172.29.1.1:11500 --no-ip-checkup
|
||||
networks:
|
||||
nucypher_ci_net:
|
||||
ipv4_address: 172.29.1.9
|
||||
container_name: ciursula9
|
||||
ciursula10:
|
||||
ports:
|
||||
- 11500
|
||||
image: ci:nucypher
|
||||
depends_on:
|
||||
- ciursula1
|
||||
command: nucypher ursula run --dev --federated-only --rest-host 172.29.1.10 --rest-port 11500 --teacher 172.29.1.1:11500 --no-ip-checkup
|
||||
networks:
|
||||
nucypher_ci_net:
|
||||
ipv4_address: 172.29.1.10
|
||||
container_name: ciursula10
|
||||
ciursula11:
|
||||
ports:
|
||||
- 11500
|
||||
image: ci:nucypher
|
||||
depends_on:
|
||||
- ciursula1
|
||||
command: nucypher ursula run --dev --federated-only --rest-host 172.29.1.11 --rest-port 11500 --teacher 172.29.1.1:11500 --no-ip-checkup
|
||||
networks:
|
||||
nucypher_ci_net:
|
||||
ipv4_address: 172.29.1.11
|
||||
container_name: ciursula11
|
||||
ciursula12:
|
||||
ports:
|
||||
- 11500
|
||||
image: ci:nucypher
|
||||
depends_on:
|
||||
- ciursula11
|
||||
command: nucypher ursula run --dev --federated-only --rest-host 172.29.1.12 --rest-port 11500 --teacher 172.29.1.1:11500 --no-ip-checkup
|
||||
networks:
|
||||
nucypher_ci_net:
|
||||
ipv4_address: 172.29.1.12
|
||||
container_name: ciursula12
|
||||
|
||||
networks:
|
||||
nucypher_ci_net:
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 172.29.1.0/16
|
|
@ -1,8 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
LOGDIR=/tmp/ursulas-logs
|
||||
mkdir $LOGDIR
|
||||
docker exec ciursula1 cat /root/.cache/nucypher/log/nucypher.log > $LOGDIR/ursula-1.txt
|
||||
docker exec ciursula2 cat /root/.cache/nucypher/log/nucypher.log > $LOGDIR/ursula-2.txt
|
||||
docker exec ciursula3 cat /root/.cache/nucypher/log/nucypher.log > $LOGDIR/ursula-3.txt
|
||||
docker exec ciursula4 cat /root/.cache/nucypher/log/nucypher.log > $LOGDIR/ursula-4.txt
|
|
@ -1,6 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# runs in docker on ci
|
||||
|
||||
set -e
|
||||
python /code/examples/finnegans_wake_demo/finnegans-wake-demo-federated.py 172.29.1.3:11500
|
|
@ -1,30 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# runs in ci environment
|
||||
# runs finnegan's wake demo in a docker container
|
||||
|
||||
set -e
|
||||
echo "Starting Up Finnegans Wake Demo Test..."
|
||||
|
||||
# Move to demo directory
|
||||
cd "${0%/*}"
|
||||
echo "working in directory: $PWD"
|
||||
|
||||
# run some ursulas
|
||||
docker-compose up -d --build
|
||||
|
||||
# Wait to ensure Ursulas are up.
|
||||
echo "War... watisit good for?"
|
||||
sleep 3
|
||||
|
||||
|
||||
# Run demo
|
||||
echo "Starting Demo"
|
||||
echo "working in directory: $PWD"
|
||||
docker-compose run nucypher-ci-dev bash /code/scripts/ci/run_finnegans_wake.sh
|
||||
|
||||
# spit out logs
|
||||
./logOutput.sh
|
||||
|
||||
# tear it down
|
||||
docker-compose stop
|
|
@ -1,9 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Run Alicia
|
||||
echo "Starting Alicia..."
|
||||
python3 /code/examples/heartbeat_demo/alicia.py 172.29.1.3:11500
|
||||
|
||||
# Run Dr. Bob
|
||||
echo "Starting Bob..."
|
||||
python3 /code/examples/heartbeat_demo/doctor.py 172.29.1.3:11500
|
|
@ -1,25 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
cd "${0%/*}"
|
||||
echo "working in directory: $PWD"
|
||||
|
||||
set -e
|
||||
echo "Starting Up Heartbeat Demo Test..."
|
||||
|
||||
# run some ursulas
|
||||
docker-compose up -d
|
||||
|
||||
# Wait to ensure Ursulas are up.
|
||||
echo "War... watisit good for?"
|
||||
sleep 3
|
||||
|
||||
echo "running heartbeat demo"
|
||||
|
||||
# run alicia and bob all in one running of docker since we lack persistent disks in ci
|
||||
docker-compose run nucypher-ci-dev bash /code/scripts/ci/run_heartbeat_alicia_and_bob.sh
|
||||
|
||||
# spit out logs
|
||||
./logOutput.sh
|
||||
|
||||
# tear it down
|
||||
docker-compose stop
|
|
@ -1,16 +0,0 @@
|
|||
"""
|
||||
This file is part of nucypher.
|
||||
|
||||
nucypher is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
nucypher 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
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
|
@ -1,13 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
echo "Starting Up Finnegans Wake Demo Test..."
|
||||
|
||||
# Start local Ursula fleet
|
||||
"${0%/*}"/../local_fleet/run_local_fleet.sh
|
||||
|
||||
# Move to demo directory
|
||||
cd "${0%/*}"/../../examples/finnegans_wake_demo/
|
||||
|
||||
# Run demo
|
||||
echo "Starting Demo"
|
||||
python3 finnegans-wake-demo-federated.py
|
|
@ -1,19 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
echo "Starting Up Finnegans Wake Demo Test..."
|
||||
|
||||
COMPOSE_FILE="${0%/*}/../../dev/docker/8-federated-ursulas.yml"
|
||||
DEMO_DIR="/code/examples/finnegans_wake_demo/"
|
||||
|
||||
# run some ursulas
|
||||
docker-compose -f $COMPOSE_FILE up -d
|
||||
echo "Wait for Ursula learning to occur"
|
||||
sleep 5
|
||||
|
||||
# Run demo
|
||||
echo "Starting Demo"
|
||||
docker-compose -f $COMPOSE_FILE run -w $DEMO_DIR nucypher-dev python finnegans-wake-demo-federated.py 172.28.1.3:11500
|
||||
|
||||
# tear it down
|
||||
docker-compose -f $COMPOSE_FILE stop
|
|
@ -1,17 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
echo "Starting Up Heartbeat Demo Test..."
|
||||
|
||||
# Start Local Fleet
|
||||
"${0%/*}"/../local_fleet/run_local_fleet.sh
|
||||
|
||||
# Move to examples directory
|
||||
cd "${0%/*}"/../../examples/heartbeat_demo/
|
||||
|
||||
# Run Alicia
|
||||
echo "Starting Alicia..."
|
||||
python3 alicia.py
|
||||
|
||||
# Run Dr. Bob
|
||||
echo "Starting Bob..."
|
||||
python3 doctor.py
|
|
@ -1,23 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
echo "Starting Up Heartbeat Demo Test..."
|
||||
|
||||
COMPOSE_FILE="${0%/*}/../../dev/docker/8-federated-ursulas.yml"
|
||||
DEMO_DIR="/code/examples/heartbeat_demo/"
|
||||
|
||||
# run some ursulas
|
||||
docker-compose -f $COMPOSE_FILE up -d
|
||||
echo "Wait for Ursula learning to occur"
|
||||
sleep 5
|
||||
|
||||
# Run Alicia
|
||||
echo "Starting Alicia..."
|
||||
docker-compose -f $COMPOSE_FILE run -w $DEMO_DIR nucypher-dev python3 alicia.py 172.28.1.3:11500
|
||||
|
||||
# Run Dr. Bob
|
||||
echo "Starting Bob..."
|
||||
docker-compose -f $COMPOSE_FILE run -w $DEMO_DIR nucypher-dev python3 doctor.py 172.28.1.3:11500
|
||||
|
||||
# tear it down
|
||||
docker-compose -f $COMPOSE_FILE stop
|
|
@ -1,32 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
echo "Starting Local Development Fleet..."
|
||||
|
||||
# Boring Setup Stuff
|
||||
rm -r /tmp/ursulas-logs | true
|
||||
mkdir /tmp/ursulas-logs
|
||||
|
||||
# Set PATH
|
||||
export PATH=~/.local/bin:$PATH
|
||||
if [ -f ~/.bashrc ]; then
|
||||
source ~/.bashrc
|
||||
fi
|
||||
|
||||
# Disable logging
|
||||
export NUCYPHER_SENTRY_LOGS=0
|
||||
export NUCYPHER_FILE_LOGS=0
|
||||
|
||||
# Run Node #1 (Lonely Ursula)
|
||||
echo "Starting Lonely Ursula..."
|
||||
python3 "${0%/*}"/../local_fleet/run_lonely_ursula.py > /tmp/ursulas-logs/ursula-11500.txt 2>&1 &
|
||||
sleep 2
|
||||
|
||||
# Connect Node #2 to Lonely Ursula
|
||||
echo "Starting Ursula #2..."
|
||||
nucypher ursula run --debug --dev --federated-only --teacher localhost:11500 --rest-port 11501 > /tmp/ursulas-logs/ursula-11501.txt 2>&1 &
|
||||
sleep 1
|
||||
|
||||
# Connect Node #3 to the local Fleet
|
||||
echo "Starting Ursula #3..."
|
||||
nucypher ursula run --debug --dev --federated-only --teacher localhost:11500 --rest-port 11502 > /tmp/ursulas-logs/ursula-11502.txt 2>&1 &
|
|
@ -1,80 +0,0 @@
|
|||
"""
|
||||
This file is part of nucypher.
|
||||
|
||||
nucypher is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
nucypher 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
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
# WARNING This is not a mining script!
|
||||
# you will not perform any re-encryptions, and you will not get paid.
|
||||
# It might be (but might not be) useful for determining whether you have
|
||||
# the proper dependencies and configuration to run an actual mining node.
|
||||
|
||||
|
||||
import os
|
||||
|
||||
from twisted.internet import protocol
|
||||
from twisted.internet import reactor
|
||||
|
||||
|
||||
FLEET_POPULATION = 5
|
||||
DEMO_NODE_STARTING_PORT = 11501
|
||||
TEACHER_URI = f'127.0.0.1:11500'
|
||||
|
||||
|
||||
def spin_up_federated_ursulas(quantity: int = FLEET_POPULATION):
|
||||
|
||||
# Ports
|
||||
starting_port = DEMO_NODE_STARTING_PORT
|
||||
ports = list(map(str, range(starting_port, starting_port + quantity)))
|
||||
|
||||
ursula_processes = list()
|
||||
for index, port in enumerate(ports):
|
||||
|
||||
args = ['nucypher',
|
||||
'ursula', 'run',
|
||||
'--debug',
|
||||
'--rest-port', port,
|
||||
'--teacher', TEACHER_URI,
|
||||
'--federated-only',
|
||||
'--dev',
|
||||
]
|
||||
|
||||
env = {'PATH': os.environ['PATH'],
|
||||
'NUCYPHER_SENTRY_LOGS': '0',
|
||||
'NUCYPHER_FILE_LOGS': '0',
|
||||
'LC_ALL': 'en_US.UTF-8',
|
||||
'LANG': 'en_US.UTF-8'}
|
||||
|
||||
childFDs = {0: 0,
|
||||
1: 1,
|
||||
2: 2}
|
||||
|
||||
class UrsulaProcessProtocol(protocol.Protocol):
|
||||
|
||||
def __init__(self, command):
|
||||
self.command = command
|
||||
|
||||
def processEnded(self, reason, *args, **kwargs):
|
||||
print(reason.value)
|
||||
|
||||
processProtocol = UrsulaProcessProtocol(command=args)
|
||||
p = reactor.spawnProcess(processProtocol, 'nucypher', args, env=env, childFDs=childFDs)
|
||||
ursula_processes.append(p)
|
||||
|
||||
reactor.run() # GO!
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
spin_up_federated_ursulas()
|
|
@ -1,38 +0,0 @@
|
|||
"""
|
||||
This file is part of nucypher.
|
||||
|
||||
nucypher is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
nucypher 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
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
# WARNING This is not a mining script!
|
||||
# you will not perform any re-encryptions, and you will not get paid.
|
||||
# It might be (but might not be) useful for determining whether you have
|
||||
# the proper dependencies and configuration to run an actual mining node.
|
||||
|
||||
|
||||
from click.testing import CliRunner
|
||||
from nucypher.cli.main import nucypher_cli
|
||||
|
||||
|
||||
click_runner = CliRunner()
|
||||
|
||||
args = ['ursula', 'run',
|
||||
'--debug', # Non-Interactive + Verbose
|
||||
'--rest-port', 11500, # REST Server
|
||||
'--federated-only', # Operating Mode
|
||||
'--dev', # In-Memory
|
||||
'--lonely'] # Disable Seednode Learning
|
||||
|
||||
nucypher_cli.main(args=args, prog_name="nucypher-cli")
|
|
@ -1,49 +0,0 @@
|
|||
"""
|
||||
This file is part of nucypher.
|
||||
|
||||
nucypher is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
nucypher 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
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
# WARNING This is not a mining script!
|
||||
# you will not perform any re-encryptions, and you will not get paid.
|
||||
# It might be (but might not be) useful for determining whether you have
|
||||
# the proper dependencies and configuration to run an actual mining node.
|
||||
|
||||
|
||||
from click.testing import CliRunner
|
||||
|
||||
from nucypher.cli.main import nucypher_cli
|
||||
from nucypher.exceptions import DevelopmentInstallationRequired
|
||||
from nucypher.utilities.networking import LOOPBACK_ADDRESS
|
||||
|
||||
try:
|
||||
from tests.utils.ursula import select_test_port
|
||||
except ImportError:
|
||||
raise DevelopmentInstallationRequired(importable_name='tests.utils.ursula.select_test_port')
|
||||
|
||||
click_runner = CliRunner()
|
||||
|
||||
DEMO_NODE_PORT = select_test_port()
|
||||
DEMO_FLEET_STARTING_PORT = 11500
|
||||
|
||||
args = ['ursula', 'run',
|
||||
'--debug',
|
||||
'--federated-only',
|
||||
'--teacher', f'https://{LOOPBACK_ADDRESS}:{DEMO_FLEET_STARTING_PORT}',
|
||||
'--rest-port', DEMO_NODE_PORT,
|
||||
'--dev'
|
||||
]
|
||||
|
||||
nucypher_cli.main(args=args, prog_name="nucypher-cli")
|
|
@ -0,0 +1,51 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
import sys
|
||||
|
||||
BACKUP_SUFFIX = '.old'
|
||||
OLD_VERSION = 4
|
||||
NEW_VERSION = 5
|
||||
|
||||
|
||||
def configuration_v4_to_v5(filepath: str):
|
||||
"""Updates configuration file v3 to v4 by remapping 'domains' to 'domain'"""
|
||||
|
||||
# Read + deserialize
|
||||
with open(filepath, 'r') as file:
|
||||
contents = file.read()
|
||||
config = json.loads(contents)
|
||||
|
||||
try:
|
||||
existing_version = config['version']
|
||||
if existing_version != OLD_VERSION:
|
||||
raise RuntimeError(f'Existing configuration is not version {OLD_VERSION}; Got version {existing_version}')
|
||||
|
||||
# Make a copy of the original file
|
||||
backup_filepath = filepath+BACKUP_SUFFIX
|
||||
os.rename(filepath, backup_filepath)
|
||||
print(f'Backed up existing configuration to {backup_filepath}')
|
||||
|
||||
# Apply updates
|
||||
del config['federated_only'] # deprecated
|
||||
del config['checksum_address']
|
||||
config['version'] = NEW_VERSION
|
||||
|
||||
except KeyError:
|
||||
raise RuntimeError(f'Invalid {OLD_VERSION} configuration file.')
|
||||
|
||||
# Commit updates
|
||||
with open(filepath, 'w') as file:
|
||||
file.write(json.dumps(config, indent=4))
|
||||
print(f'OK! Migrated configuration file from v{OLD_VERSION} -> v{NEW_VERSION}.')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
_python, filepath = sys.argv
|
||||
except ValueError:
|
||||
raise ValueError('Invalid command: Provide a single configuration filepath.')
|
||||
configuration_v4_to_v5(filepath=filepath)
|
|
@ -16,7 +16,7 @@ from nucypher.blockchain.eth.signers.software import Web3Signer
|
|||
from nucypher.blockchain.eth.token import NU
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
from nucypher.utilities.logging import Logger
|
||||
from tests.utils.ursula import make_decentralized_ursulas, start_pytest_ursula_services
|
||||
from tests.utils.ursula import make_ursulas, start_pytest_ursula_services
|
||||
|
||||
logger = Logger("test-operator")
|
||||
|
||||
|
@ -35,7 +35,7 @@ def test_work_tracker(
|
|||
staker,
|
||||
agency,
|
||||
application_economics,
|
||||
ursula_decentralized_test_config,
|
||||
ursula_test_config,
|
||||
):
|
||||
|
||||
staker.initialize_stake(
|
||||
|
@ -59,8 +59,8 @@ def test_work_tracker(
|
|||
)
|
||||
|
||||
# Make the Worker
|
||||
ursula = make_decentralized_ursulas(
|
||||
ursula_config=ursula_decentralized_test_config,
|
||||
ursula = make_ursulas(
|
||||
ursula_config=ursula_test_config,
|
||||
staking_provider_addresses=[staker.checksum_address],
|
||||
operator_addresses=[worker_address],
|
||||
registry=test_registry,
|
||||
|
@ -190,7 +190,7 @@ def test_work_tracker(
|
|||
|
||||
|
||||
def test_ursula_operator_confirmation(
|
||||
ursula_decentralized_test_config,
|
||||
ursula_test_config,
|
||||
testerchain,
|
||||
threshold_staking,
|
||||
agency,
|
||||
|
@ -217,7 +217,7 @@ def test_ursula_operator_confirmation(
|
|||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# make an ursula.
|
||||
blockchain_ursula = ursula_decentralized_test_config.produce(
|
||||
blockchain_ursula = ursula_test_config.produce(
|
||||
operator_address=operator_address, rest_port=9151
|
||||
)
|
||||
|
||||
|
@ -252,7 +252,7 @@ def test_ursula_operator_confirmation(
|
|||
@pytest_twisted.inlineCallbacks
|
||||
def test_ursula_operator_confirmation_autopilot(
|
||||
mocker,
|
||||
ursula_decentralized_test_config,
|
||||
ursula_test_config,
|
||||
testerchain,
|
||||
threshold_staking,
|
||||
agency,
|
||||
|
@ -292,9 +292,7 @@ def test_ursula_operator_confirmation_autopilot(
|
|||
)
|
||||
|
||||
# Make the Operator
|
||||
ursula = ursula_decentralized_test_config.produce(
|
||||
operator_address=operator2, rest_port=9151
|
||||
)
|
||||
ursula = ursula_test_config.produce(operator_address=operator2, rest_port=9151)
|
||||
|
||||
ursula.run(
|
||||
preflight=False,
|
||||
|
|
|
@ -270,14 +270,15 @@ def test_erc721_evm_condition_balanceof_evaluation(
|
|||
|
||||
def test_subscription_manager_is_active_policy_condition_evaluation(
|
||||
testerchain,
|
||||
enacted_blockchain_policy,
|
||||
enacted_policy,
|
||||
subscription_manager_is_active_policy_condition,
|
||||
condition_providers
|
||||
):
|
||||
context = {
|
||||
":hrac": bytes(enacted_blockchain_policy.hrac)
|
||||
} # user-defined context var
|
||||
condition_result, call_result = subscription_manager_is_active_policy_condition.verify(
|
||||
context = {":hrac": bytes(enacted_policy.hrac)} # user-defined context var
|
||||
(
|
||||
condition_result,
|
||||
call_result,
|
||||
) = subscription_manager_is_active_policy_condition.verify(
|
||||
providers=condition_providers, **context
|
||||
)
|
||||
assert call_result
|
||||
|
@ -294,7 +295,7 @@ def test_subscription_manager_is_active_policy_condition_evaluation(
|
|||
|
||||
def test_subscription_manager_get_policy_policy_struct_condition_evaluation(
|
||||
testerchain,
|
||||
enacted_blockchain_policy,
|
||||
enacted_policy,
|
||||
subscription_manager_get_policy_zeroized_policy_struct_condition,
|
||||
condition_providers
|
||||
):
|
||||
|
@ -304,7 +305,7 @@ def test_subscription_manager_get_policy_policy_struct_condition_evaluation(
|
|||
NULL_ADDRESS, 0, 0, 0, NULL_ADDRESS,
|
||||
)
|
||||
context = {
|
||||
":hrac": bytes(enacted_blockchain_policy.hrac),
|
||||
":hrac": bytes(enacted_policy.hrac),
|
||||
":expectedPolicyStruct": zeroized_policy_struct,
|
||||
} # user-defined context vars
|
||||
condition_result, call_result = subscription_manager_get_policy_zeroized_policy_struct_condition.verify(
|
||||
|
@ -326,18 +327,18 @@ def test_subscription_manager_get_policy_policy_struct_condition_key_tuple_evalu
|
|||
testerchain,
|
||||
agency,
|
||||
test_registry,
|
||||
idle_blockchain_policy,
|
||||
enacted_blockchain_policy,
|
||||
idle_policy,
|
||||
enacted_policy,
|
||||
condition_providers,
|
||||
):
|
||||
# enacted policy created from idle policy
|
||||
size = len(idle_blockchain_policy.kfrags)
|
||||
start = idle_blockchain_policy.commencement
|
||||
end = idle_blockchain_policy.expiration
|
||||
sponsor = idle_blockchain_policy.publisher.checksum_address
|
||||
size = len(idle_policy.kfrags)
|
||||
start = idle_policy.commencement
|
||||
end = idle_policy.expiration
|
||||
sponsor = idle_policy.publisher.checksum_address
|
||||
|
||||
context = {
|
||||
":hrac": bytes(enacted_blockchain_policy.hrac),
|
||||
":hrac": bytes(enacted_policy.hrac),
|
||||
} # user-defined context vars
|
||||
subscription_manager = ContractAgency.get_agent(
|
||||
SubscriptionManagerAgent, registry=test_registry
|
||||
|
@ -446,14 +447,14 @@ def test_subscription_manager_get_policy_policy_struct_condition_index_and_value
|
|||
testerchain,
|
||||
agency,
|
||||
test_registry,
|
||||
idle_blockchain_policy,
|
||||
enacted_blockchain_policy,
|
||||
idle_policy,
|
||||
enacted_policy,
|
||||
condition_providers,
|
||||
):
|
||||
# enacted policy created from idle policy
|
||||
sponsor = idle_blockchain_policy.publisher.checksum_address
|
||||
sponsor = idle_policy.publisher.checksum_address
|
||||
context = {
|
||||
":hrac": bytes(enacted_blockchain_policy.hrac),
|
||||
":hrac": bytes(enacted_policy.hrac),
|
||||
":sponsor": sponsor,
|
||||
} # user-defined context vars
|
||||
subscription_manager = ContractAgency.get_agent(
|
||||
|
@ -511,32 +512,26 @@ def test_onchain_conditions_lingo_evaluation(
|
|||
assert result is True
|
||||
|
||||
|
||||
def test_single_retrieve_with_onchain_conditions(enacted_blockchain_policy, blockchain_bob, blockchain_ursulas):
|
||||
blockchain_bob.start_learning_loop()
|
||||
def test_single_retrieve_with_onchain_conditions(enacted_policy, bob, ursulas):
|
||||
bob.remember_node(ursulas[0])
|
||||
bob.start_learning_loop()
|
||||
conditions = [
|
||||
{'returnValueTest': {'value': '0', 'comparator': '>'}, 'method': 'timelock'},
|
||||
{'operator': 'and'},
|
||||
{"chain": TESTERCHAIN_CHAIN_ID,
|
||||
"method": "eth_getBalance",
|
||||
"parameters": [
|
||||
blockchain_bob.checksum_address,
|
||||
"latest"
|
||||
],
|
||||
"returnValueTest": {
|
||||
"comparator": ">=",
|
||||
"value": "10000000000000"
|
||||
}
|
||||
}
|
||||
{"returnValueTest": {"value": "0", "comparator": ">"}, "method": "timelock"},
|
||||
{"operator": "and"},
|
||||
{
|
||||
"chain": TESTERCHAIN_CHAIN_ID,
|
||||
"method": "eth_getBalance",
|
||||
"parameters": [bob.checksum_address, "latest"],
|
||||
"returnValueTest": {"comparator": ">=", "value": "10000000000000"},
|
||||
},
|
||||
]
|
||||
messages, message_kits = make_message_kits(
|
||||
enacted_blockchain_policy.public_key, conditions
|
||||
)
|
||||
messages, message_kits = make_message_kits(enacted_policy.public_key, conditions)
|
||||
policy_info_kwargs = dict(
|
||||
encrypted_treasure_map=enacted_blockchain_policy.treasure_map,
|
||||
alice_verifying_key=enacted_blockchain_policy.publisher_verifying_key,
|
||||
encrypted_treasure_map=enacted_policy.treasure_map,
|
||||
alice_verifying_key=enacted_policy.publisher_verifying_key,
|
||||
)
|
||||
|
||||
cleartexts = blockchain_bob.retrieve_and_decrypt(
|
||||
cleartexts = bob.retrieve_and_decrypt(
|
||||
message_kits=message_kits,
|
||||
**policy_info_kwargs,
|
||||
)
|
||||
|
|
|
@ -31,12 +31,14 @@ def check(policy, bob, ursulas):
|
|||
# TODO: try to decrypt?
|
||||
|
||||
|
||||
def test_decentralized_grant_subscription_manager(blockchain_alice, blockchain_bob, blockchain_ursulas):
|
||||
def test_grant_subscription_manager(alice, bob, ursulas):
|
||||
payment_method = SubscriptionManagerPayment(eth_provider=TEST_ETH_PROVIDER_URI, network=TEMPORARY_DOMAIN)
|
||||
blockchain_alice.payment_method = payment_method
|
||||
policy = blockchain_alice.grant(bob=blockchain_bob,
|
||||
label=os.urandom(16),
|
||||
threshold=2,
|
||||
shares=shares,
|
||||
expiration=policy_end_datetime)
|
||||
check(policy=policy, bob=blockchain_bob, ursulas=blockchain_ursulas)
|
||||
alice.payment_method = payment_method
|
||||
policy = alice.grant(
|
||||
bob=bob,
|
||||
label=os.urandom(16),
|
||||
threshold=2,
|
||||
shares=shares,
|
||||
expiration=policy_end_datetime,
|
||||
)
|
||||
check(policy=policy, bob=bob, ursulas=ursulas)
|
||||
|
|
|
@ -10,33 +10,33 @@ from nucypher.characters.lawful import Enrico, Ursula
|
|||
from nucypher.characters.unlawful import Amonia
|
||||
|
||||
|
||||
@pytest.mark.skip('FIXME - DISABLED FOR TDEC ADAPTATION DEVELOPMENT')
|
||||
def test_try_to_post_free_service_by_hacking_enact(blockchain_ursulas,
|
||||
blockchain_alice,
|
||||
blockchain_bob,
|
||||
agency,
|
||||
testerchain):
|
||||
@pytest.mark.skip("FIXME - DISABLED FOR TDEC ADAPTATION DEVELOPMENT")
|
||||
def test_try_to_post_free_service_by_hacking_enact(
|
||||
ursulas, alice, bob, agency, testerchain
|
||||
):
|
||||
"""
|
||||
This time we won't rely on the tabulation in Alice's enact() to catch the problem.
|
||||
"""
|
||||
amonia = Amonia.from_lawful_alice(blockchain_alice)
|
||||
amonia = Amonia.from_lawful_alice(alice)
|
||||
# Set up the policy details
|
||||
shares = 3
|
||||
policy_end_datetime = maya.now() + datetime.timedelta(days=35)
|
||||
label = b"another_path"
|
||||
|
||||
bupkiss_policy = amonia.circumvent_safegaurds_and_grant_without_paying(bob=blockchain_bob,
|
||||
label=label,
|
||||
threshold=2,
|
||||
shares=shares,
|
||||
expiration=policy_end_datetime)
|
||||
bupkiss_policy = amonia.circumvent_safegaurds_and_grant_without_paying(
|
||||
bob=bob, label=label, threshold=2, shares=shares, expiration=policy_end_datetime
|
||||
)
|
||||
|
||||
# Enrico becomes
|
||||
enrico = Enrico(policy_encrypting_key=bupkiss_policy.public_key)
|
||||
plaintext = b"A crafty campaign"
|
||||
message_kit = enrico.encrypt_message(plaintext)
|
||||
|
||||
with pytest.raises(Ursula.NotEnoughUrsulas): # Return a more descriptive request error?
|
||||
blockchain_bob.retrieve_and_decrypt([message_kit],
|
||||
alice_verifying_key=amonia.stamp.as_umbral_pubkey(),
|
||||
encrypted_treasure_map=bupkiss_policy.treasure_map)
|
||||
with pytest.raises(
|
||||
Ursula.NotEnoughUrsulas
|
||||
): # Return a more descriptive request error?
|
||||
bob.retrieve_and_decrypt(
|
||||
[message_kit],
|
||||
alice_verifying_key=amonia.stamp.as_umbral_pubkey(),
|
||||
encrypted_treasure_map=bupkiss_policy.treasure_map,
|
||||
)
|
||||
|
|
|
@ -6,8 +6,9 @@ from eth_utils import to_checksum_address
|
|||
|
||||
from nucypher.blockchain.eth.signers.software import Web3Signer
|
||||
from nucypher.characters.lawful import Character
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
from nucypher.crypto.utils import verify_eip_191
|
||||
from nucypher.crypto.powers import (TransactingPower)
|
||||
from tests.constants import INSECURE_DEVELOPMENT_PASSWORD, MOCK_ETH_PROVIDER_URI
|
||||
|
||||
|
||||
|
@ -15,10 +16,13 @@ def test_character_transacting_power_signing(testerchain, agency, test_registry)
|
|||
|
||||
# Pretend to be a character.
|
||||
eth_address = testerchain.etherbase_account
|
||||
signer = Character(is_me=True,
|
||||
eth_provider_uri=MOCK_ETH_PROVIDER_URI,
|
||||
registry=test_registry,
|
||||
checksum_address=eth_address)
|
||||
signer = Character(
|
||||
is_me=True,
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
eth_provider_uri=MOCK_ETH_PROVIDER_URI,
|
||||
registry=test_registry,
|
||||
checksum_address=eth_address,
|
||||
)
|
||||
|
||||
# Manually consume the power up
|
||||
transacting_power = TransactingPower(password=INSECURE_DEVELOPMENT_PASSWORD,
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import datetime
|
||||
|
||||
import maya
|
||||
|
@ -8,25 +7,29 @@ from eth_account._utils.signing import to_standard_signature_bytes
|
|||
from nucypher.characters.lawful import Enrico, Ursula
|
||||
from nucypher.characters.unlawful import Vladimir
|
||||
from nucypher.crypto.utils import verify_eip_191
|
||||
from nucypher.policy.policies import BlockchainPolicy
|
||||
from nucypher.policy.policies import Policy
|
||||
from tests.utils.middleware import NodeIsDownMiddleware
|
||||
from tests.utils.ursula import make_decentralized_ursulas
|
||||
from tests.utils.ursula import make_ursulas
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("blockchain_ursulas")
|
||||
def test_stakers_bond_to_ursulas(testerchain, test_registry, staking_providers, ursula_decentralized_test_config):
|
||||
ursulas = make_decentralized_ursulas(ursula_config=ursula_decentralized_test_config,
|
||||
staking_provider_addresses=testerchain.stake_providers_accounts,
|
||||
operator_addresses=testerchain.ursulas_accounts)
|
||||
@pytest.mark.usefixtures("ursulas")
|
||||
def test_stakers_bond_to_ursulas(
|
||||
testerchain, test_registry, staking_providers, ursula_test_config
|
||||
):
|
||||
nodes = make_ursulas(
|
||||
ursula_config=ursula_test_config,
|
||||
staking_provider_addresses=testerchain.stake_providers_accounts,
|
||||
operator_addresses=testerchain.ursulas_accounts,
|
||||
)
|
||||
|
||||
assert len(ursulas) == len(staking_providers)
|
||||
for ursula in ursulas:
|
||||
assert len(nodes) == len(staking_providers)
|
||||
for ursula in nodes:
|
||||
ursula.validate_operator(registry=test_registry)
|
||||
assert ursula.verified_operator
|
||||
|
||||
|
||||
def test_blockchain_ursula_substantiates_stamp(blockchain_ursulas):
|
||||
first_ursula = list(blockchain_ursulas)[0]
|
||||
def test_ursula_substantiates_stamp(ursulas):
|
||||
first_ursula = list(ursulas)[0]
|
||||
signature_as_bytes = first_ursula.operator_signature
|
||||
signature_as_bytes = to_standard_signature_bytes(signature_as_bytes)
|
||||
# `operator_address` was derived in nucypher_core, check it independently
|
||||
|
@ -35,8 +38,8 @@ def test_blockchain_ursula_substantiates_stamp(blockchain_ursulas):
|
|||
signature=signature_as_bytes)
|
||||
|
||||
|
||||
def test_blockchain_ursula_verifies_stamp(blockchain_ursulas):
|
||||
first_ursula = list(blockchain_ursulas)[0]
|
||||
def test_blockchain_ursula_verifies_stamp(ursulas):
|
||||
first_ursula = list(ursulas)[0]
|
||||
|
||||
# This Ursula does not yet have a verified stamp
|
||||
first_ursula.verified_stamp = False
|
||||
|
@ -52,8 +55,8 @@ def remote_vladimir(**kwds):
|
|||
return remote_vladimir
|
||||
|
||||
|
||||
def test_vladimir_cannot_verify_interface_with_ursulas_signing_key(blockchain_ursulas):
|
||||
his_target = list(blockchain_ursulas)[4]
|
||||
def test_vladimir_cannot_verify_interface_with_ursulas_signing_key(ursulas):
|
||||
his_target = list(ursulas)[4]
|
||||
|
||||
# Vladimir has his own ether address; he hopes to publish it along with Ursula's details
|
||||
# so that Alice (or whomever) pays him instead of Ursula, even though Ursula is providing the service.
|
||||
|
@ -77,12 +80,12 @@ def test_vladimir_cannot_verify_interface_with_ursulas_signing_key(blockchain_ur
|
|||
vladimir.validate_metadata()
|
||||
|
||||
|
||||
def test_vladimir_uses_his_own_signing_key(blockchain_alice, blockchain_ursulas, test_registry):
|
||||
def test_vladimir_uses_his_own_signing_key(alice, ursulas, test_registry):
|
||||
"""
|
||||
Similar to the attack above, but this time Vladimir makes his own interface signature
|
||||
using his own signing key, which he claims is Ursula's.
|
||||
"""
|
||||
his_target = list(blockchain_ursulas)[4]
|
||||
his_target = list(ursulas)[4]
|
||||
vladimir = remote_vladimir(target_ursula=his_target,
|
||||
sign_metadata=True)
|
||||
|
||||
|
@ -110,8 +113,8 @@ def test_vladimir_uses_his_own_signing_key(blockchain_alice, blockchain_ursulas,
|
|||
vladimir.validate_metadata(registry=test_registry)
|
||||
|
||||
|
||||
def test_vladimir_invalidity_without_stake(testerchain, blockchain_ursulas, blockchain_alice):
|
||||
his_target = list(blockchain_ursulas)[4]
|
||||
def test_vladimir_invalidity_without_stake(testerchain, ursulas, alice):
|
||||
his_target = list(ursulas)[4]
|
||||
|
||||
vladimir = remote_vladimir(target_ursula=his_target,
|
||||
substitute_verifying_key=True,
|
||||
|
@ -123,45 +126,51 @@ def test_vladimir_invalidity_without_stake(testerchain, blockchain_ursulas, bloc
|
|||
# But the actual handshake proves him wrong.
|
||||
message = "Wallet address swapped out. It appears that someone is trying to defraud this node."
|
||||
with pytest.raises(vladimir.InvalidNode, match=message):
|
||||
vladimir.verify_node(blockchain_alice.network_middleware.client)
|
||||
vladimir.verify_node(alice.network_middleware.client)
|
||||
|
||||
|
||||
# TODO: Change name of this file, extract this test
|
||||
def test_blockchain_ursulas_reencrypt(blockchain_ursulas, blockchain_alice, blockchain_bob, policy_value):
|
||||
def test_ursulas_reencrypt(ursulas, alice, bob, policy_value):
|
||||
label = b'bbo'
|
||||
|
||||
# TODO: Make sample selection buffer configurable - #1061
|
||||
threshold = shares = 10
|
||||
expiration = maya.now() + datetime.timedelta(days=35)
|
||||
|
||||
_policy = blockchain_alice.grant(bob=blockchain_bob,
|
||||
label=label,
|
||||
threshold=threshold,
|
||||
shares=shares,
|
||||
expiration=expiration,
|
||||
value=policy_value)
|
||||
_policy = alice.grant(
|
||||
bob=bob,
|
||||
label=label,
|
||||
threshold=threshold,
|
||||
shares=shares,
|
||||
expiration=expiration,
|
||||
value=policy_value,
|
||||
)
|
||||
|
||||
enrico = Enrico.from_alice(blockchain_alice, label)
|
||||
enrico = Enrico.from_alice(alice, label)
|
||||
|
||||
message = b"Oh, this isn't even BO. This is beyond BO. It's BBO."
|
||||
|
||||
message_kit = enrico.encrypt_message(message)
|
||||
|
||||
blockchain_bob.start_learning_loop(now=True)
|
||||
bob.start_learning_loop(now=True)
|
||||
|
||||
plaintexts = blockchain_bob.retrieve_and_decrypt([message_kit],
|
||||
encrypted_treasure_map=_policy.treasure_map,
|
||||
alice_verifying_key=blockchain_alice.stamp.as_umbral_pubkey())
|
||||
plaintexts = bob.retrieve_and_decrypt(
|
||||
[message_kit],
|
||||
encrypted_treasure_map=_policy.treasure_map,
|
||||
alice_verifying_key=alice.stamp.as_umbral_pubkey(),
|
||||
)
|
||||
assert plaintexts == [message]
|
||||
|
||||
# Let's consider also that a node may be down when granting
|
||||
blockchain_alice.network_middleware = NodeIsDownMiddleware()
|
||||
blockchain_alice.network_middleware.node_is_down(blockchain_ursulas[0])
|
||||
alice.network_middleware = NodeIsDownMiddleware()
|
||||
alice.network_middleware.node_is_down(ursulas[0])
|
||||
|
||||
with pytest.raises(BlockchainPolicy.NotEnoughUrsulas):
|
||||
_policy = blockchain_alice.grant(bob=blockchain_bob,
|
||||
label=b'another-label',
|
||||
threshold=threshold,
|
||||
shares=shares,
|
||||
expiration=expiration,
|
||||
value=policy_value)
|
||||
with pytest.raises(Policy.NotEnoughUrsulas):
|
||||
_policy = alice.grant(
|
||||
bob=bob,
|
||||
label=b"another-label",
|
||||
threshold=threshold,
|
||||
shares=shares,
|
||||
expiration=expiration,
|
||||
value=policy_value,
|
||||
)
|
||||
|
|
|
@ -8,8 +8,8 @@ import pytest
|
|||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def ursula(blockchain_ursulas):
|
||||
ursula = blockchain_ursulas.pop()
|
||||
def ursula(ursulas):
|
||||
ursula = ursulas[3]
|
||||
return ursula
|
||||
|
||||
|
||||
|
@ -30,7 +30,7 @@ def test_ursula_html_renders(ursula, client):
|
|||
|
||||
|
||||
@pytest.mark.parametrize('omit_known_nodes', [False, True])
|
||||
def test_decentralized_json_status_endpoint(ursula, client, omit_known_nodes):
|
||||
def test_json_status_endpoint(ursula, client, omit_known_nodes):
|
||||
omit_known_nodes_str = 'true' if omit_known_nodes else 'false'
|
||||
response = client.get(f'/status/?json=true&omit_known_nodes={omit_known_nodes_str}')
|
||||
assert response.status_code == 200
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
import pytest
|
||||
|
||||
from nucypher.blockchain.eth.clients import EthereumClient
|
||||
|
|
|
@ -8,15 +8,19 @@ import pytest
|
|||
|
||||
from nucypher.blockchain.eth.registry import InMemoryContractRegistry
|
||||
from nucypher.cli.main import nucypher_cli
|
||||
from nucypher.config.characters import UrsulaConfiguration, CharacterConfiguration
|
||||
from nucypher.config.constants import NUCYPHER_ENVVAR_KEYSTORE_PASSWORD, TEMPORARY_DOMAIN
|
||||
from nucypher.config.characters import CharacterConfiguration, UrsulaConfiguration
|
||||
from nucypher.config.constants import (
|
||||
NUCYPHER_ENVVAR_KEYSTORE_PASSWORD,
|
||||
TEMPORARY_DOMAIN,
|
||||
)
|
||||
from tests.constants import (
|
||||
FAKE_PASSWORD_CONFIRMED,
|
||||
INSECURE_DEVELOPMENT_PASSWORD,
|
||||
MOCK_CUSTOM_INSTALLATION_PATH,
|
||||
MOCK_ETH_PROVIDER_URI,
|
||||
MOCK_IP_ADDRESS,
|
||||
TEST_ETH_PROVIDER_URI,
|
||||
YES
|
||||
YES,
|
||||
)
|
||||
|
||||
CONFIG_CLASSES = (UrsulaConfiguration, )
|
||||
|
@ -25,15 +29,29 @@ CONFIG_CLASSES = (UrsulaConfiguration, )
|
|||
ENV = {NUCYPHER_ENVVAR_KEYSTORE_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD}
|
||||
|
||||
|
||||
@pytest.mark.parametrize('config_class', CONFIG_CLASSES)
|
||||
def test_initialize_via_cli(config_class, custom_filepath: Path, click_runner, monkeypatch):
|
||||
@pytest.mark.parametrize("config_class", CONFIG_CLASSES)
|
||||
def test_initialize_via_cli(
|
||||
config_class,
|
||||
custom_filepath: Path,
|
||||
click_runner,
|
||||
monkeypatch,
|
||||
test_registry_source_manager,
|
||||
):
|
||||
command = config_class.CHARACTER_CLASS.__name__.lower()
|
||||
|
||||
# Use a custom local filepath for configuration
|
||||
init_args = (command, 'init',
|
||||
'--network', TEMPORARY_DOMAIN,
|
||||
'--federated-only',
|
||||
'--config-root', str(custom_filepath.absolute()))
|
||||
init_args = (
|
||||
command,
|
||||
"init",
|
||||
"--network",
|
||||
TEMPORARY_DOMAIN,
|
||||
"--eth-provider",
|
||||
MOCK_ETH_PROVIDER_URI,
|
||||
"--payment-provider",
|
||||
TEST_ETH_PROVIDER_URI,
|
||||
"--config-root",
|
||||
str(custom_filepath.absolute()),
|
||||
)
|
||||
|
||||
if config_class == UrsulaConfiguration:
|
||||
init_args += ('--rest-host', MOCK_IP_ADDRESS)
|
||||
|
@ -82,14 +100,12 @@ def test_reconfigure_via_cli(click_runner, custom_filepath: Path, config_class,
|
|||
|
||||
# Read pre-edit state
|
||||
config = config_class.from_configuration_file(custom_config_filepath)
|
||||
assert config.federated_only
|
||||
assert config.eth_provider_uri != TEST_ETH_PROVIDER_URI
|
||||
del config
|
||||
|
||||
# Write
|
||||
view_args = (config_class.CHARACTER_CLASS.__name__.lower(), 'config',
|
||||
'--config-file', str(custom_config_filepath.absolute()),
|
||||
'--decentralized',
|
||||
'--eth-provider', TEST_ETH_PROVIDER_URI)
|
||||
result = click_runner.invoke(nucypher_cli, view_args, env=ENV)
|
||||
assert result.exit_code == 0
|
||||
|
@ -102,5 +118,4 @@ def test_reconfigure_via_cli(click_runner, custom_filepath: Path, config_class,
|
|||
assert str(custom_filepath) in result.output
|
||||
|
||||
# After editing the fields have been updated
|
||||
assert not config.federated_only
|
||||
assert config.eth_provider_uri == TEST_ETH_PROVIDER_URI
|
||||
|
|
|
@ -1,115 +0,0 @@
|
|||
|
||||
|
||||
|
||||
import csv
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from nucypher.blockchain.eth.agents import (
|
||||
AdjudicatorAgent,
|
||||
ContractAgency,
|
||||
NucypherTokenAgent,
|
||||
)
|
||||
from nucypher.blockchain.eth.signers.software import Web3Signer
|
||||
from nucypher.cli.commands.status import status
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
|
||||
|
||||
@pytest.mark.skip()
|
||||
def test_nucypher_status_network(click_runner, testerchain, agency_local_registry):
|
||||
|
||||
network_command = ('network',
|
||||
'--registry-filepath', str(agency_local_registry.filepath.absolute()),
|
||||
'--eth-provider', TEST_ETH_PROVIDER_URI,
|
||||
'--network', TEMPORARY_DOMAIN)
|
||||
|
||||
result = click_runner.invoke(status, network_command, catch_exceptions=False)
|
||||
assert result.exit_code == 0
|
||||
|
||||
token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=agency_local_registry)
|
||||
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=agency_local_registry)
|
||||
adjudicator_agent = ContractAgency.get_agent(AdjudicatorAgent, registry=agency_local_registry)
|
||||
|
||||
agents = (token_agent, staking_agent, adjudicator_agent)
|
||||
for agent in agents:
|
||||
contract_regex = f"^{agent.contract_name} \\.+ {agent.contract_address}"
|
||||
assert re.search(contract_regex, result.output, re.MULTILINE)
|
||||
|
||||
assert re.search(f"^Provider URI \\.+ {TEST_ETH_PROVIDER_URI}", result.output, re.MULTILINE)
|
||||
assert re.search(f"^Current Period \\.+ {staking_agent.get_current_period()}", result.output, re.MULTILINE)
|
||||
|
||||
|
||||
@pytest.mark.skip()
|
||||
def test_nucypher_status_events(click_runner, testerchain, agency_local_registry, staking_providers, temp_dir_path):
|
||||
# All workers make a commitment
|
||||
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=agency_local_registry)
|
||||
starting_block_number = testerchain.get_block_number()
|
||||
for ursula in testerchain.ursulas_accounts:
|
||||
tpower = TransactingPower(signer=Web3Signer(client=testerchain.client), account=ursula)
|
||||
staking_agent.commit_to_next_period(transacting_power=tpower, fire_and_forget=False)
|
||||
committed_period = staking_agent.get_current_period() + 1
|
||||
|
||||
testerchain.time_travel(periods=1)
|
||||
|
||||
# Check CommitmentMade events
|
||||
|
||||
#
|
||||
# CLI output
|
||||
#
|
||||
status_command = ('events',
|
||||
'--eth-provider', TEST_ETH_PROVIDER_URI,
|
||||
'--network', TEMPORARY_DOMAIN,
|
||||
'--event-name', 'CommitmentMade',
|
||||
'--contract-name', 'StakingEscrow',
|
||||
'--from-block', starting_block_number)
|
||||
result = click_runner.invoke(status, status_command, catch_exceptions=False)
|
||||
for staker in staking_providers:
|
||||
assert re.search(f'staker: {staker.checksum_address}, period: {committed_period}', result.output, re.MULTILINE)
|
||||
|
||||
# event filter output
|
||||
first_staker = staking_providers[0]
|
||||
filter_status_command = ('events',
|
||||
'--eth-provider', TEST_ETH_PROVIDER_URI,
|
||||
'--network', TEMPORARY_DOMAIN,
|
||||
'--event-name', 'CommitmentMade',
|
||||
'--contract-name', 'StakingEscrow',
|
||||
'--from-block', starting_block_number,
|
||||
'--event-filter', f'staker={first_staker.checksum_address}')
|
||||
result = click_runner.invoke(status, filter_status_command, catch_exceptions=False)
|
||||
assert re.search(f'staker: {first_staker.checksum_address}, period: {committed_period}', result.output, re.MULTILINE)
|
||||
for staker in staking_providers:
|
||||
if staker != first_staker:
|
||||
assert not re.search(f'staker: {staker.checksum_address}', result.output, re.MULTILINE), result.output
|
||||
|
||||
#
|
||||
# CSV output
|
||||
#
|
||||
csv_file = temp_dir_path / 'status_events_output.csv'
|
||||
csv_status_command = ('events',
|
||||
'--eth-provider', TEST_ETH_PROVIDER_URI,
|
||||
'--network', TEMPORARY_DOMAIN,
|
||||
'--event-name', 'CommitmentMade',
|
||||
'--contract-name', 'StakingEscrow',
|
||||
'--from-block', starting_block_number,
|
||||
'--event-filter', f'staker={first_staker.checksum_address}',
|
||||
'--csv-file', str(csv_file.absolute()))
|
||||
result = click_runner.invoke(status, csv_status_command, catch_exceptions=False)
|
||||
assert re.search(f'StakingEscrow::CommitmentMade events written to {csv_file}', result.output, re.MULTILINE), result.output
|
||||
assert csv_file.exists(), 'events output to csv file'
|
||||
with open(csv_file, mode='r') as f:
|
||||
csv_reader = csv.reader(f, delimiter=',')
|
||||
line_count = 0
|
||||
for row in csv_reader:
|
||||
if line_count == 0:
|
||||
assert ",".join(row) == 'event_name,block_number,unix_timestamp,date,staker,period,value' # specific to CommitmentMade
|
||||
else:
|
||||
row_data = f'{row}'
|
||||
assert row[0] == 'CommitmentMade', row_data
|
||||
# skip block_number, unix_timestamp, date
|
||||
assert row[4] == first_staker.checksum_address, row_data
|
||||
assert row[5] == f'{committed_period}', row_data
|
||||
# skip value
|
||||
line_count += 1
|
||||
assert line_count == 2, 'column names and single event row in csv file'
|
|
@ -0,0 +1,12 @@
|
|||
import pytest
|
||||
|
||||
from nucypher.blockchain.eth.clients import EthereumClient
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_funding_and_bonding(testerchain, agency, mocker):
|
||||
mocker.patch(
|
||||
"nucypher.blockchain.eth.actors.Operator.get_staking_provider_address",
|
||||
return_value=testerchain.stake_providers_accounts[0],
|
||||
)
|
||||
mocker.patch.object(EthereumClient, "get_balance", return_value=1)
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import time
|
||||
from unittest import mock
|
||||
|
||||
|
@ -7,6 +6,7 @@ import pytest_twisted as pt
|
|||
from twisted.internet import threads
|
||||
|
||||
from nucypher.blockchain.eth.actors import Operator
|
||||
from nucypher.blockchain.eth.clients import EthereumClient
|
||||
from nucypher.characters.base import Learner
|
||||
from nucypher.cli.literature import NO_CONFIGURATIONS_ON_DISK
|
||||
from nucypher.cli.main import nucypher_cli
|
||||
|
@ -15,14 +15,12 @@ from nucypher.config.constants import (
|
|||
NUCYPHER_ENVVAR_KEYSTORE_PASSWORD,
|
||||
TEMPORARY_DOMAIN,
|
||||
)
|
||||
from nucypher.utilities.networking import LOOPBACK_ADDRESS, UnknownIPAddress
|
||||
from nucypher.utilities.networking import LOOPBACK_ADDRESS
|
||||
from tests.constants import (
|
||||
FAKE_PASSWORD_CONFIRMED,
|
||||
INSECURE_DEVELOPMENT_PASSWORD,
|
||||
MOCK_IP_ADDRESS,
|
||||
TEST_ETH_PROVIDER_URI,
|
||||
TEST_POLYGON_PROVIDER_URI,
|
||||
YES_ENTER,
|
||||
)
|
||||
from tests.utils.ursula import select_test_port, start_pytest_ursula_services
|
||||
|
||||
|
@ -37,40 +35,16 @@ def test_missing_configuration_file(_default_filepath_mock, click_runner):
|
|||
command=configuration_type) in result.output
|
||||
|
||||
|
||||
def test_ursula_startup_ip_checkup(click_runner, mocker):
|
||||
target = 'nucypher.cli.actions.configure.determine_external_ip_address'
|
||||
|
||||
# Patch the get_external_ip call
|
||||
mocker.patch(target, return_value=MOCK_IP_ADDRESS)
|
||||
mocker.patch.object(UrsulaConfiguration, 'to_configuration_file', return_value=None)
|
||||
|
||||
args = ('ursula', 'init', '--federated-only', '--network', TEMPORARY_DOMAIN)
|
||||
user_input = YES_ENTER + FAKE_PASSWORD_CONFIRMED
|
||||
result = click_runner.invoke(nucypher_cli, args, catch_exceptions=False, input=user_input)
|
||||
assert result.exit_code == 0
|
||||
assert MOCK_IP_ADDRESS in result.output
|
||||
|
||||
args = ('ursula', 'init', '--federated-only', '--network', TEMPORARY_DOMAIN, '--force')
|
||||
result = click_runner.invoke(nucypher_cli, args, catch_exceptions=False, input=FAKE_PASSWORD_CONFIRMED)
|
||||
assert result.exit_code == 0
|
||||
|
||||
# Patch get_external_ip call to error output
|
||||
mocker.patch(target, side_effect=UnknownIPAddress)
|
||||
args = ('ursula', 'init', '--federated-only', '--network', TEMPORARY_DOMAIN, '--force')
|
||||
result = click_runner.invoke(nucypher_cli, args, catch_exceptions=True, input=FAKE_PASSWORD_CONFIRMED)
|
||||
assert result.exit_code == 1, result.output
|
||||
assert isinstance(result.exception, UnknownIPAddress)
|
||||
|
||||
|
||||
@pt.inlineCallbacks
|
||||
def test_ursula_run_with_prometheus_but_no_metrics_port(click_runner):
|
||||
args = ('ursula', 'run', # Stat Ursula Command
|
||||
'--debug', # Display log output; Do not attach console
|
||||
'--federated-only', # Operating Mode
|
||||
'--dev', # Run in development mode (local ephemeral node)
|
||||
'--dry-run', # Disable twisted reactor in subprocess
|
||||
'--lonely', # Do not load seednodes
|
||||
'--prometheus' # Specify collection of prometheus metrics
|
||||
'--prometheus', # Specify collection of prometheus metrics
|
||||
'--eth-provider', TEST_ETH_PROVIDER_URI,
|
||||
'--payment-provider', TEST_POLYGON_PROVIDER_URI
|
||||
)
|
||||
|
||||
result = yield threads.deferToThread(click_runner.invoke,
|
||||
|
@ -83,15 +57,19 @@ def test_ursula_run_with_prometheus_but_no_metrics_port(click_runner):
|
|||
|
||||
|
||||
@pt.inlineCallbacks
|
||||
def test_run_lone_federated_default_development_ursula(click_runner):
|
||||
def test_run_lone_default_development_ursula(click_runner, test_registry_source_manager, testerchain, agency, mock_funding_and_bonding):
|
||||
|
||||
deploy_port = select_test_port()
|
||||
args = ('ursula', 'run', # Stat Ursula Command
|
||||
'--debug', # Display log output; Do not attach console
|
||||
'--federated-only', # Operating Mode
|
||||
'--rest-port', deploy_port, # Network Port
|
||||
'--dev', # Run in development mode (ephemeral node)
|
||||
'--dry-run', # Disable twisted reactor in subprocess
|
||||
'--lonely' # Do not load seednodes
|
||||
'--lonely', # Do not load seednodes,
|
||||
'--operator-address', testerchain.etherbase_account,
|
||||
'--eth-provider', TEST_ETH_PROVIDER_URI,
|
||||
'--payment-provider', TEST_ETH_PROVIDER_URI,
|
||||
'--payment-network', TEMPORARY_DOMAIN,
|
||||
)
|
||||
|
||||
result = yield threads.deferToThread(click_runner.invoke,
|
||||
|
@ -109,10 +87,12 @@ def test_run_lone_federated_default_development_ursula(click_runner):
|
|||
|
||||
|
||||
@pt.inlineCallbacks
|
||||
def test_federated_ursula_learns_via_cli(click_runner, federated_ursulas):
|
||||
def test_ursula_learns_via_cli(
|
||||
click_runner, ursulas, testerchain, agency, mock_funding_and_bonding
|
||||
):
|
||||
# Establish a running Teacher Ursula
|
||||
|
||||
teacher = list(federated_ursulas)[0]
|
||||
teacher = list(ursulas)[0]
|
||||
teacher_uri = teacher.seed_node_metadata(as_teacher_uri=True)
|
||||
|
||||
deploy_port = select_test_port()
|
||||
|
@ -121,11 +101,14 @@ def test_federated_ursula_learns_via_cli(click_runner, federated_ursulas):
|
|||
i = start_pytest_ursula_services(ursula=teacher)
|
||||
args = ('ursula', 'run',
|
||||
'--debug', # Display log output; Do not attach console
|
||||
'--federated-only', # Operating Mode
|
||||
'--rest-port', deploy_port, # Network Port
|
||||
'--teacher', teacher_uri,
|
||||
'--dev', # Run in development mode (ephemeral node)
|
||||
'--dry-run' # Disable twisted reactor
|
||||
'--dry-run', # Disable twisted reactor
|
||||
'--operator-address', testerchain.etherbase_account,
|
||||
'--eth-provider', TEST_ETH_PROVIDER_URI,
|
||||
'--payment-provider', TEST_ETH_PROVIDER_URI,
|
||||
'--payment-network', TEMPORARY_DOMAIN
|
||||
)
|
||||
|
||||
return threads.deferToThread(click_runner.invoke,
|
||||
|
@ -149,15 +132,11 @@ def test_federated_ursula_learns_via_cli(click_runner, federated_ursulas):
|
|||
# Check that CLI Ursula reports that it remembers the teacher and saves the TLS certificate
|
||||
assert f"Saved TLS certificate for {LOOPBACK_ADDRESS}" in result.output
|
||||
|
||||
federated_ursulas.clear()
|
||||
|
||||
|
||||
@pt.inlineCallbacks
|
||||
def test_persistent_node_storage_integration(click_runner,
|
||||
custom_filepath,
|
||||
testerchain,
|
||||
blockchain_ursulas,
|
||||
agency_local_registry):
|
||||
def test_persistent_node_storage_integration(
|
||||
click_runner, custom_filepath, testerchain, ursulas, agency_local_registry
|
||||
):
|
||||
|
||||
alice, ursula, another_ursula, staking_provider, *all_yall = testerchain.unassigned_accounts
|
||||
filename = UrsulaConfiguration.generate_filename()
|
||||
|
@ -178,7 +157,7 @@ def test_persistent_node_storage_integration(click_runner,
|
|||
result = click_runner.invoke(nucypher_cli, init_args, catch_exceptions=False, env=envvars)
|
||||
assert result.exit_code == 0
|
||||
|
||||
teacher = blockchain_ursulas.pop()
|
||||
teacher = ursulas[-1]
|
||||
teacher_uri = teacher.rest_information()[0].uri
|
||||
|
||||
start_pytest_ursula_services(ursula=teacher)
|
||||
|
@ -215,47 +194,3 @@ def test_persistent_node_storage_integration(click_runner,
|
|||
input=user_input,
|
||||
env=envvars)
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
def test_ursula_run_ip_checkup(testerchain, custom_filepath, click_runner, mocker, blockchain_ursulas, monkeypatch):
|
||||
|
||||
# Mock IP determination
|
||||
target = 'nucypher.cli.actions.configure.determine_external_ip_address'
|
||||
mocker.patch(target, return_value=MOCK_IP_ADDRESS)
|
||||
|
||||
# Mock Teacher Resolution
|
||||
from nucypher.characters.lawful import Ursula
|
||||
teacher = blockchain_ursulas.pop()
|
||||
mocker.patch.object(Ursula, 'from_teacher_uri', return_value=teacher)
|
||||
|
||||
# Mock worker qualification
|
||||
staking_provider = blockchain_ursulas.pop()
|
||||
|
||||
def set_staking_provider_address(operator, *args, **kwargs):
|
||||
operator.checksum_address = staking_provider.checksum_address
|
||||
return True
|
||||
monkeypatch.setattr(Operator, 'block_until_ready', set_staking_provider_address)
|
||||
|
||||
# Setup
|
||||
teacher = blockchain_ursulas.pop()
|
||||
filename = UrsulaConfiguration.generate_filename()
|
||||
another_ursula_configuration_file_location = custom_filepath / filename
|
||||
|
||||
# manual teacher
|
||||
run_args = ('ursula', 'run',
|
||||
'--dry-run',
|
||||
'--debug',
|
||||
'--config-file', str(another_ursula_configuration_file_location.absolute()),
|
||||
'--teacher', teacher.rest_url())
|
||||
result = click_runner.invoke(nucypher_cli, run_args, catch_exceptions=False, input=FAKE_PASSWORD_CONFIRMED)
|
||||
assert result.exit_code == 0, result.output
|
||||
|
||||
# default teacher
|
||||
run_args = ('ursula', 'run',
|
||||
'--dry-run',
|
||||
'--debug',
|
||||
'--config-file', str(another_ursula_configuration_file_location.absolute()))
|
||||
result = click_runner.invoke(nucypher_cli, run_args, catch_exceptions=False, input=FAKE_PASSWORD_CONFIRMED)
|
||||
assert result.exit_code == 0, result.output
|
||||
|
||||
blockchain_ursulas.clear()
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
from nucypher.blockchain.eth.actors import Operator
|
||||
from nucypher.cli.commands import ursula
|
||||
from nucypher.cli.main import nucypher_cli
|
||||
from nucypher.config.characters import UrsulaConfiguration
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN
|
||||
from nucypher.utilities.networking import UnknownIPAddress
|
||||
from tests.constants import (
|
||||
FAKE_PASSWORD_CONFIRMED,
|
||||
INSECURE_DEVELOPMENT_PASSWORD,
|
||||
MOCK_IP_ADDRESS,
|
||||
TEST_ETH_PROVIDER_URI,
|
||||
TEST_POLYGON_PROVIDER_URI,
|
||||
YES_ENTER,
|
||||
)
|
||||
|
||||
|
||||
def test_ursula_startup_ip_checkup(click_runner, mocker, test_registry_source_manager):
|
||||
target = "nucypher.cli.actions.configure.determine_external_ip_address"
|
||||
|
||||
# Patch the get_external_ip call
|
||||
mocker.patch(target, return_value=MOCK_IP_ADDRESS)
|
||||
mocker.patch.object(UrsulaConfiguration, "to_configuration_file", return_value=None)
|
||||
mocker.patch.object(
|
||||
ursula, "get_nucypher_password", return_value=INSECURE_DEVELOPMENT_PASSWORD
|
||||
)
|
||||
mocker.patch.object(
|
||||
ursula, "get_client_password", return_value=INSECURE_DEVELOPMENT_PASSWORD
|
||||
)
|
||||
|
||||
args = (
|
||||
"ursula",
|
||||
"init",
|
||||
"--network",
|
||||
TEMPORARY_DOMAIN,
|
||||
"--eth-provider",
|
||||
TEST_ETH_PROVIDER_URI,
|
||||
"--payment-provider",
|
||||
TEST_POLYGON_PROVIDER_URI,
|
||||
"--force",
|
||||
)
|
||||
user_input = YES_ENTER + "0\n0\n"
|
||||
result = click_runner.invoke(
|
||||
nucypher_cli, args, catch_exceptions=False, input=user_input
|
||||
)
|
||||
assert result.exit_code == 0, result.output
|
||||
assert MOCK_IP_ADDRESS in result.output
|
||||
|
||||
args = (
|
||||
"ursula",
|
||||
"init",
|
||||
"--network",
|
||||
TEMPORARY_DOMAIN,
|
||||
"--force",
|
||||
"--eth-provider",
|
||||
TEST_ETH_PROVIDER_URI,
|
||||
"--payment-provider",
|
||||
TEST_POLYGON_PROVIDER_URI,
|
||||
)
|
||||
result = click_runner.invoke(
|
||||
nucypher_cli, args, catch_exceptions=False, input=FAKE_PASSWORD_CONFIRMED
|
||||
)
|
||||
assert result.exit_code == 0, result.output
|
||||
|
||||
# Patch get_external_ip call to error output
|
||||
mocker.patch(target, side_effect=UnknownIPAddress)
|
||||
args = ('ursula', 'init', '--network', TEMPORARY_DOMAIN, '--force', '--eth-provider', TEST_ETH_PROVIDER_URI, '--payment-provider', TEST_POLYGON_PROVIDER_URI)
|
||||
result = click_runner.invoke(nucypher_cli, args, catch_exceptions=True, input=FAKE_PASSWORD_CONFIRMED)
|
||||
assert result.exit_code == 1, result.output
|
||||
assert isinstance(result.exception, UnknownIPAddress)
|
||||
|
||||
|
||||
def test_ursula_run_ip_checkup(
|
||||
testerchain,
|
||||
custom_filepath,
|
||||
click_runner,
|
||||
mocker,
|
||||
ursulas,
|
||||
monkeypatch,
|
||||
ursula_test_config,
|
||||
tempfile_path,
|
||||
):
|
||||
|
||||
# Mock IP determination
|
||||
target = 'nucypher.cli.actions.configure.determine_external_ip_address'
|
||||
mocker.patch(target, return_value=MOCK_IP_ADDRESS)
|
||||
|
||||
# Mock Teacher Resolution
|
||||
from nucypher.characters.lawful import Ursula
|
||||
|
||||
teacher = ursulas[0]
|
||||
mocker.patch.object(Ursula, 'from_teacher_uri', return_value=teacher)
|
||||
|
||||
# Mock worker qualification
|
||||
staking_provider = ursulas[1]
|
||||
|
||||
def set_staking_provider_address(operator, *args, **kwargs):
|
||||
operator.checksum_address = staking_provider.checksum_address
|
||||
return True
|
||||
|
||||
monkeypatch.setattr(Operator, "block_until_ready", set_staking_provider_address)
|
||||
|
||||
ursula_test_config.rest_host = MOCK_IP_ADDRESS
|
||||
mocker.patch.object(
|
||||
UrsulaConfiguration, "from_configuration_file", return_value=ursula_test_config
|
||||
)
|
||||
|
||||
# Setup
|
||||
teacher = ursulas[2]
|
||||
|
||||
# manual teacher
|
||||
run_args = (
|
||||
"ursula",
|
||||
"run",
|
||||
"--dry-run",
|
||||
"--debug",
|
||||
"--config-file",
|
||||
str(tempfile_path.absolute()),
|
||||
"--teacher",
|
||||
teacher.rest_url(),
|
||||
)
|
||||
result = click_runner.invoke(
|
||||
nucypher_cli, run_args, catch_exceptions=False, input=FAKE_PASSWORD_CONFIRMED
|
||||
)
|
||||
assert result.exit_code == 0, result.output
|
||||
|
||||
# default teacher
|
||||
run_args = (
|
||||
"ursula",
|
||||
"run",
|
||||
"--dry-run",
|
||||
"--debug",
|
||||
"--config-file",
|
||||
str(tempfile_path.absolute()),
|
||||
)
|
||||
result = click_runner.invoke(
|
||||
nucypher_cli, run_args, catch_exceptions=False, input=FAKE_PASSWORD_CONFIRMED
|
||||
)
|
||||
assert result.exit_code == 0, result.output
|
||||
|
||||
ursulas.clear()
|
|
@ -5,7 +5,14 @@ from unittest.mock import PropertyMock
|
|||
|
||||
import pytest
|
||||
|
||||
from nucypher.cli.literature import COLLECT_NUCYPHER_PASSWORD, SUCCESSFUL_DESTRUCTION
|
||||
from nucypher.cli.literature import (
|
||||
COLLECT_NUCYPHER_PASSWORD,
|
||||
CONFIRM_IPV4_ADDRESS_QUESTION,
|
||||
REPEAT_FOR_CONFIRMATION,
|
||||
SELECT_OPERATOR_ACCOUNT,
|
||||
SELECT_PAYMENT_NETWORK,
|
||||
SUCCESSFUL_DESTRUCTION,
|
||||
)
|
||||
from nucypher.cli.main import nucypher_cli
|
||||
from nucypher.config.base import CharacterConfiguration
|
||||
from nucypher.config.characters import UrsulaConfiguration
|
||||
|
@ -21,12 +28,13 @@ from tests.constants import (
|
|||
INSECURE_DEVELOPMENT_PASSWORD,
|
||||
MOCK_CUSTOM_INSTALLATION_PATH,
|
||||
MOCK_IP_ADDRESS,
|
||||
TEST_ETH_PROVIDER_URI,
|
||||
YES_ENTER,
|
||||
)
|
||||
from tests.utils.ursula import select_test_port
|
||||
|
||||
|
||||
def test_initialize_ursula_defaults(click_runner, mocker, tmpdir):
|
||||
def test_interactive_initialize_ursula(click_runner, mocker, tmpdir, test_registry_source_manager):
|
||||
|
||||
# Mock out filesystem writes
|
||||
mocker.patch.object(UrsulaConfiguration, 'initialize', autospec=True)
|
||||
|
@ -37,32 +45,44 @@ def test_initialize_ursula_defaults(click_runner, mocker, tmpdir):
|
|||
mocker.patch.object(CharacterConfiguration, 'keystore', return_value=keystore, new_callable=PropertyMock)
|
||||
|
||||
# Use default ursula init args
|
||||
init_args = ('ursula', 'init', '--network', TEMPORARY_DOMAIN, '--federated-only')
|
||||
init_args = ('ursula', 'init',
|
||||
'--network', TEMPORARY_DOMAIN,
|
||||
'--eth-provider', TEST_ETH_PROVIDER_URI,
|
||||
'--payment-provider', TEST_ETH_PROVIDER_URI)
|
||||
|
||||
user_input = YES_ENTER + FAKE_PASSWORD_CONFIRMED
|
||||
user_input = '0\n' + '0\n' + YES_ENTER + FAKE_PASSWORD_CONFIRMED
|
||||
result = click_runner.invoke(nucypher_cli, init_args, input=user_input, catch_exceptions=False)
|
||||
assert result.exit_code == 0
|
||||
assert result.exit_code == 0, result.output
|
||||
|
||||
# Select network
|
||||
assert SELECT_PAYMENT_NETWORK in result.output
|
||||
|
||||
# Select account
|
||||
assert SELECT_OPERATOR_ACCOUNT in result.output
|
||||
|
||||
# REST Host
|
||||
assert "Is this the public-facing address of Ursula? " in result.output
|
||||
assert CONFIRM_IPV4_ADDRESS_QUESTION in result.output
|
||||
|
||||
# Auth
|
||||
assert COLLECT_NUCYPHER_PASSWORD in result.output, 'WARNING: User was not prompted for password'
|
||||
assert 'Repeat for confirmation:' in result.output, 'User was not prompted to confirm password'
|
||||
assert REPEAT_FOR_CONFIRMATION in result.output, 'User was not prompted to confirm password'
|
||||
|
||||
|
||||
def test_initialize_custom_configuration_root(click_runner, custom_filepath: Path):
|
||||
def test_initialize_custom_configuration_root(click_runner, custom_filepath: Path, test_registry_source_manager, testerchain):
|
||||
|
||||
deploy_port = select_test_port()
|
||||
# Use a custom local filepath for configuration
|
||||
init_args = ('ursula', 'init',
|
||||
'--network', TEMPORARY_DOMAIN,
|
||||
'--federated-only',
|
||||
'--config-root', str(custom_filepath.absolute()),
|
||||
'--rest-host', MOCK_IP_ADDRESS,
|
||||
'--rest-port', deploy_port)
|
||||
'--rest-port', deploy_port,
|
||||
'--eth-provider', TEST_ETH_PROVIDER_URI,
|
||||
'--payment-provider', TEST_ETH_PROVIDER_URI,
|
||||
'--payment-network', TEMPORARY_DOMAIN,
|
||||
'--operator-address', testerchain.ursulas_accounts[0])
|
||||
result = click_runner.invoke(nucypher_cli, init_args, input=FAKE_PASSWORD_CONFIRMED, catch_exceptions=False)
|
||||
assert result.exit_code == 0
|
||||
assert result.exit_code == 0, result.output
|
||||
|
||||
# CLI Output
|
||||
assert str(MOCK_CUSTOM_INSTALLATION_PATH) in result.output, "Configuration not in system temporary directory"
|
||||
|
@ -82,10 +102,11 @@ def test_initialize_custom_configuration_root(click_runner, custom_filepath: Pat
|
|||
|
||||
# Auth
|
||||
assert COLLECT_NUCYPHER_PASSWORD in result.output, 'WARNING: User was not prompted for password'
|
||||
assert 'Repeat for confirmation:' in result.output, 'User was not prompted to confirm password'
|
||||
assert REPEAT_FOR_CONFIRMATION in result.output, 'User was not prompted to confirm password'
|
||||
|
||||
|
||||
def test_configuration_file_contents(custom_filepath: Path, nominal_federated_configuration_fields):
|
||||
def test_configuration_file_contents(custom_filepath: Path, nominal_configuration_fields, test_registry_source_manager):
|
||||
|
||||
custom_config_filepath = custom_filepath / UrsulaConfiguration.generate_filename()
|
||||
assert custom_config_filepath.is_file(), 'Configuration file does not exist'
|
||||
|
||||
|
@ -98,7 +119,7 @@ def test_configuration_file_contents(custom_filepath: Path, nominal_federated_co
|
|||
except JSONDecodeError:
|
||||
raise pytest.fail(msg="Invalid JSON configuration file {}".format(custom_config_filepath))
|
||||
|
||||
for field in nominal_federated_configuration_fields:
|
||||
for field in nominal_configuration_fields:
|
||||
assert field in data, "Missing field '{}' from configuration file."
|
||||
if any(keyword in field for keyword in ('path', 'dir')):
|
||||
path = data[field]
|
||||
|
@ -109,7 +130,7 @@ def test_configuration_file_contents(custom_filepath: Path, nominal_federated_co
|
|||
assert custom_config_filepath.is_file(), 'Configuration file does not exist'
|
||||
|
||||
|
||||
def test_ursula_view_configuration(custom_filepath: Path, click_runner, nominal_federated_configuration_fields):
|
||||
def test_ursula_view_configuration(custom_filepath: Path, click_runner, nominal_configuration_fields):
|
||||
|
||||
# Ensure the configuration file still exists
|
||||
custom_config_filepath = custom_filepath / UrsulaConfiguration.generate_filename()
|
||||
|
@ -124,14 +145,14 @@ def test_ursula_view_configuration(custom_filepath: Path, click_runner, nominal_
|
|||
|
||||
# CLI Output
|
||||
assert str(MOCK_CUSTOM_INSTALLATION_PATH) in result.output
|
||||
for field in nominal_federated_configuration_fields:
|
||||
for field in nominal_configuration_fields:
|
||||
assert field in result.output, "Missing field '{}' from configuration file."
|
||||
|
||||
# Make sure nothing crazy is happening...
|
||||
assert custom_config_filepath.is_file(), 'Configuration file does not exist'
|
||||
|
||||
|
||||
def test_run_federated_ursula_from_config_file(custom_filepath: Path, click_runner):
|
||||
def test_run_ursula_from_config_file(custom_filepath: Path, click_runner, agency, mock_funding_and_bonding):
|
||||
|
||||
# Ensure the configuration file still exists
|
||||
custom_config_filepath = custom_filepath / UrsulaConfiguration.generate_filename()
|
||||
|
@ -144,23 +165,20 @@ def test_run_federated_ursula_from_config_file(custom_filepath: Path, click_runn
|
|||
'--config-file', str(custom_config_filepath.absolute()))
|
||||
|
||||
result = click_runner.invoke(nucypher_cli, run_args,
|
||||
input='{}\nY\n'.format(INSECURE_DEVELOPMENT_PASSWORD),
|
||||
input='{0}\n{0}\nY\n'.format(INSECURE_DEVELOPMENT_PASSWORD),
|
||||
catch_exceptions=False)
|
||||
|
||||
# CLI Output
|
||||
assert result.exit_code == 0, result.output
|
||||
assert 'Federated' in result.output, 'WARNING: Federated ursula is not running in federated mode'
|
||||
assert 'Running' in result.output
|
||||
assert f"Rest Server https://{MOCK_IP_ADDRESS}" in result.output
|
||||
|
||||
|
||||
def test_ursula_save_metadata(click_runner, custom_filepath):
|
||||
# Run Ursula
|
||||
save_metadata_args = ('ursula', 'save-metadata',
|
||||
'--dev',
|
||||
'--federated-only')
|
||||
|
||||
def test_ursula_save_metadata(click_runner, custom_filepath, mocker, testerchain):
|
||||
mocker.patch.object(CharacterConfiguration, 'DEFAULT_PAYMENT_NETWORK', TEMPORARY_DOMAIN)
|
||||
save_metadata_args = ('ursula', 'save-metadata', '--dev',
|
||||
'--operator-address', testerchain.ursulas_accounts[0],
|
||||
'--eth-provider', TEST_ETH_PROVIDER_URI)
|
||||
result = click_runner.invoke(nucypher_cli, save_metadata_args, catch_exceptions=False)
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "Successfully saved node metadata" in result.output, "Node metadata successfully saved"
|
||||
|
|
@ -10,13 +10,12 @@ from tests.utils.middleware import MockRestMiddleware
|
|||
from tests.utils.ursula import make_ursula_for_staking_provider
|
||||
|
||||
|
||||
# @pytest.mark.skip()
|
||||
def test_blockchain_ursula_stamp_verification_tolerance(blockchain_ursulas, mocker):
|
||||
def test_ursula_stamp_verification_tolerance(ursulas, mocker):
|
||||
#
|
||||
# Setup
|
||||
#
|
||||
|
||||
lonely_blockchain_learner, blockchain_teacher, unsigned, *the_others = list(blockchain_ursulas)
|
||||
lonely_learner, teacher, unsigned, *the_others = list(ursulas)
|
||||
|
||||
warnings = []
|
||||
|
||||
|
@ -30,12 +29,12 @@ def test_blockchain_ursula_stamp_verification_tolerance(blockchain_ursulas, mock
|
|||
unsigned._metadata = None
|
||||
|
||||
# Wipe known nodes!
|
||||
lonely_blockchain_learner._Learner__known_nodes = FleetSensor(domain=TEMPORARY_DOMAIN)
|
||||
lonely_blockchain_learner._current_teacher_node = blockchain_teacher
|
||||
lonely_blockchain_learner.remember_node(blockchain_teacher)
|
||||
lonely_learner._Learner__known_nodes = FleetSensor(domain=TEMPORARY_DOMAIN)
|
||||
lonely_learner._current_teacher_node = teacher
|
||||
lonely_learner.remember_node(teacher)
|
||||
|
||||
globalLogPublisher.addObserver(warning_trapper)
|
||||
lonely_blockchain_learner.learn_from_teacher_node(eager=True)
|
||||
lonely_learner.learn_from_teacher_node(eager=True)
|
||||
globalLogPublisher.removeObserver(warning_trapper)
|
||||
|
||||
# We received one warning during learning, and it was about this very matter.
|
||||
|
@ -45,47 +44,52 @@ def test_blockchain_ursula_stamp_verification_tolerance(blockchain_ursulas, mock
|
|||
assert "Verification Failed" in warning # TODO: Cleanup logging templates
|
||||
|
||||
# TODO: Buckets! #567
|
||||
# assert unsigned not in lonely_blockchain_learner.known_nodes
|
||||
# assert unsigned not in lonely_learner.known_nodes
|
||||
|
||||
# minus 2: self and the unsigned ursula.
|
||||
# assert len(lonely_blockchain_learner.known_nodes) == len(blockchain_ursulas) - 2
|
||||
assert blockchain_teacher in lonely_blockchain_learner.known_nodes
|
||||
# assert len(lonely_learner.known_nodes) == len(ursulas) - 2
|
||||
assert teacher in lonely_learner.known_nodes
|
||||
|
||||
# Learn about a node with a badly signed payload
|
||||
|
||||
def bad_bytestring_of_known_nodes():
|
||||
# Signing with the learner's signer instead of the teacher's signer
|
||||
response_payload = MetadataResponsePayload(timestamp_epoch=blockchain_teacher.known_nodes.timestamp.epoch,
|
||||
announce_nodes=[])
|
||||
response = MetadataResponse(signer=lonely_blockchain_learner.stamp.as_umbral_signer(),
|
||||
payload=response_payload)
|
||||
response_payload = MetadataResponsePayload(
|
||||
timestamp_epoch=teacher.known_nodes.timestamp.epoch, announce_nodes=[]
|
||||
)
|
||||
response = MetadataResponse(
|
||||
signer=lonely_learner.stamp.as_umbral_signer(), payload=response_payload
|
||||
)
|
||||
return bytes(response)
|
||||
|
||||
mocker.patch.object(blockchain_teacher, 'bytestring_of_known_nodes', bad_bytestring_of_known_nodes)
|
||||
mocker.patch.object(
|
||||
teacher, "bytestring_of_known_nodes", bad_bytestring_of_known_nodes
|
||||
)
|
||||
|
||||
globalLogPublisher.addObserver(warning_trapper)
|
||||
lonely_blockchain_learner.learn_from_teacher_node(eager=True)
|
||||
lonely_learner.learn_from_teacher_node(eager=True)
|
||||
globalLogPublisher.removeObserver(warning_trapper)
|
||||
|
||||
assert len(warnings) == 2
|
||||
warning = warnings[1]['log_format']
|
||||
assert str(blockchain_teacher) in warning
|
||||
assert str(teacher) in warning
|
||||
assert "Failed to verify MetadataResponse from Teacher" in warning # TODO: Cleanup logging templates
|
||||
|
||||
|
||||
@pytest.mark.skip("See Issue #1075") # TODO: Issue #1075
|
||||
def test_invalid_operators_tolerance(testerchain,
|
||||
test_registry,
|
||||
blockchain_ursulas,
|
||||
agency,
|
||||
idle_staker,
|
||||
application_economics,
|
||||
ursula_decentralized_test_config
|
||||
):
|
||||
def test_invalid_operators_tolerance(
|
||||
testerchain,
|
||||
test_registry,
|
||||
ursulas,
|
||||
agency,
|
||||
idle_staker,
|
||||
application_economics,
|
||||
ursula_test_config,
|
||||
):
|
||||
#
|
||||
# Setup
|
||||
#
|
||||
lonely_blockchain_learner, blockchain_teacher, unsigned, *the_others = list(blockchain_ursulas)
|
||||
lonely_blockchain_learner, blockchain_teacher, unsigned, *the_others = list(ursulas)
|
||||
_, staking_agent, _ = agency
|
||||
|
||||
warnings = []
|
||||
|
@ -109,11 +113,13 @@ def test_invalid_operators_tolerance(testerchain,
|
|||
idle_staker.stake_tracker.refresh()
|
||||
|
||||
# We create an active worker node for this staker
|
||||
worker = make_ursula_for_staking_provider(staking_provider=idle_staker,
|
||||
operator_address=testerchain.unassigned_accounts[-1],
|
||||
ursula_config=ursula_decentralized_test_config,
|
||||
blockchain=testerchain,
|
||||
ursulas_to_learn_about=None)
|
||||
worker = make_ursula_for_staking_provider(
|
||||
staking_provider=idle_staker,
|
||||
operator_address=testerchain.unassigned_accounts[-1],
|
||||
ursula_config=ursula_test_config,
|
||||
blockchain=testerchain,
|
||||
ursulas_to_learn_about=None,
|
||||
)
|
||||
|
||||
# Since we made a commitment, we need to advance one period
|
||||
testerchain.time_travel(periods=1)
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
|
||||
|
||||
import time
|
||||
|
||||
import maya
|
||||
import pytest
|
||||
import pytest_twisted as pt
|
||||
import time
|
||||
from flask import Response
|
||||
from twisted.internet import threads
|
||||
|
||||
|
@ -15,10 +13,10 @@ from tests.utils.ursula import start_pytest_ursula_services
|
|||
|
||||
@pytest.mark.skip('See #2024 - skipped tests')
|
||||
@pt.inlineCallbacks
|
||||
def test_availability_tracker_success(blockchain_ursulas):
|
||||
def test_availability_tracker_success(ursulas):
|
||||
|
||||
# Start up self-services
|
||||
ursula = blockchain_ursulas.pop()
|
||||
ursula = ursulas[6]
|
||||
start_pytest_ursula_services(ursula=ursula)
|
||||
|
||||
ursula._availability_tracker = AvailabilityTracker(ursula=ursula)
|
||||
|
@ -82,10 +80,10 @@ def test_availability_tracker_success(blockchain_ursulas):
|
|||
|
||||
@pytest.mark.skip('See #2024 - skipped tests')
|
||||
@pt.inlineCallbacks
|
||||
def test_availability_tracker_integration(blockchain_ursulas, monkeypatch):
|
||||
def test_availability_tracker_integration(ursulas, monkeypatch):
|
||||
|
||||
# Start up self-services
|
||||
ursula = blockchain_ursulas.pop()
|
||||
ursula = ursulas[8]
|
||||
start_pytest_ursula_services(ursula=ursula)
|
||||
|
||||
ursula._availability_tracker = AvailabilityTracker(ursula=ursula)
|
||||
|
|
|
@ -13,7 +13,7 @@ from nucypher.config.constants import TEMPORARY_DOMAIN
|
|||
from tests.utils.middleware import MockRestMiddleware
|
||||
|
||||
|
||||
def test_all_blockchain_ursulas_know_about_all_other_ursulas(blockchain_ursulas, agency, test_registry):
|
||||
def test_all_ursulas_know_about_all_other_ursulas(ursulas, agency, test_registry):
|
||||
"""
|
||||
Once launched, all Ursulas know about - and can help locate - all other Ursulas in the network.
|
||||
"""
|
||||
|
@ -21,7 +21,7 @@ def test_all_blockchain_ursulas_know_about_all_other_ursulas(blockchain_ursulas,
|
|||
|
||||
for record in application_agent.get_active_staking_providers(0, 10)[1]:
|
||||
address = to_checksum_address(record[0]) #TODO: something better
|
||||
for propagating_ursula in blockchain_ursulas[:1]: # Last Ursula is not staking
|
||||
for propagating_ursula in ursulas[:1]: # Last Ursula is not staking
|
||||
if address == propagating_ursula.checksum_address:
|
||||
continue
|
||||
else:
|
||||
|
@ -29,19 +29,19 @@ def test_all_blockchain_ursulas_know_about_all_other_ursulas(blockchain_ursulas,
|
|||
format(propagating_ursula, Nickname.from_seed(address))
|
||||
|
||||
|
||||
def test_blockchain_alice_finds_ursula_via_rest(blockchain_alice, blockchain_ursulas):
|
||||
def test_alice_finds_ursula_via_rest(alice, ursulas):
|
||||
# Imagine alice knows of nobody.
|
||||
blockchain_alice._Learner__known_nodes = FleetSensor(domain=TEMPORARY_DOMAIN)
|
||||
alice._Learner__known_nodes = FleetSensor(domain=TEMPORARY_DOMAIN)
|
||||
|
||||
blockchain_alice.remember_node(blockchain_ursulas[0])
|
||||
blockchain_alice.learn_from_teacher_node()
|
||||
assert len(blockchain_alice.known_nodes) == len(blockchain_ursulas)
|
||||
alice.remember_node(ursulas[0])
|
||||
alice.learn_from_teacher_node()
|
||||
assert len(alice.known_nodes) == len(ursulas)
|
||||
|
||||
for ursula in blockchain_ursulas:
|
||||
assert ursula in blockchain_alice.known_nodes
|
||||
for ursula in ursulas:
|
||||
assert ursula in alice.known_nodes
|
||||
|
||||
|
||||
def test_vladimir_illegal_interface_key_does_not_propagate(blockchain_ursulas):
|
||||
def test_vladimir_illegal_interface_key_does_not_propagate(ursulas):
|
||||
"""
|
||||
Although Ursulas propagate each other's interface information, as demonstrated above,
|
||||
they do not propagate interface information for Vladimir.
|
||||
|
@ -59,7 +59,7 @@ def test_vladimir_illegal_interface_key_does_not_propagate(blockchain_ursulas):
|
|||
warnings.append(event)
|
||||
|
||||
|
||||
ursulas = list(blockchain_ursulas)
|
||||
ursulas = list(ursulas)
|
||||
ursula_whom_vladimir_will_imitate, other_ursula = ursulas[0], ursulas[1]
|
||||
|
||||
# Vladimir sees Ursula on the network and tries to use her public information.
|
||||
|
@ -97,11 +97,11 @@ def test_vladimir_illegal_interface_key_does_not_propagate(blockchain_ursulas):
|
|||
# assert vladimir not in other_ursula.known_nodes
|
||||
|
||||
|
||||
def test_alice_refuses_to_select_node_unless_ursula_is_valid(blockchain_alice,
|
||||
idle_blockchain_policy,
|
||||
blockchain_ursulas):
|
||||
def test_alice_refuses_to_select_node_unless_ursula_is_valid(
|
||||
alice, idle_policy, ursulas
|
||||
):
|
||||
|
||||
target = list(blockchain_ursulas)[2]
|
||||
target = list(ursulas)[2]
|
||||
# First, let's imagine that Alice has sampled a Vladimir while making this policy.
|
||||
vladimir = Vladimir.from_target_ursula(target,
|
||||
substitute_verifying_key=True,
|
||||
|
@ -112,9 +112,11 @@ def test_alice_refuses_to_select_node_unless_ursula_is_valid(blockchain_alice,
|
|||
# Ideally, a fishy node will be present in `known_nodes`,
|
||||
# This tests the case when it became fishy after discovering it
|
||||
# but before being selected for a policy.
|
||||
blockchain_alice.known_nodes.record_node(vladimir)
|
||||
blockchain_alice.known_nodes.record_fleet_state()
|
||||
alice.known_nodes.record_node(vladimir)
|
||||
alice.known_nodes.record_fleet_state()
|
||||
|
||||
with pytest.raises(vladimir.InvalidNode):
|
||||
idle_blockchain_policy._ping_node(address=vladimir.checksum_address,
|
||||
network_middleware=blockchain_alice.network_middleware)
|
||||
idle_policy._ping_node(
|
||||
address=vladimir.checksum_address,
|
||||
network_middleware=alice.network_middleware,
|
||||
)
|
||||
|
|
|
@ -6,16 +6,22 @@ import pytest_twisted
|
|||
import requests
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from twisted.internet import threads
|
||||
from tests.utils.ursula import make_federated_ursulas
|
||||
|
||||
from tests.utils.ursula import make_ursulas
|
||||
|
||||
|
||||
@pytest_twisted.inlineCallbacks
|
||||
def test_ursula_serves_statics(ursula_federated_test_config):
|
||||
def test_ursula_serves_statics(ursula_test_config, testerchain, agency):
|
||||
|
||||
with tempfile.TemporaryDirectory() as STATICS_DIR:
|
||||
os.environ['NUCYPHER_STATIC_FILES_ROOT'] = str(STATICS_DIR)
|
||||
|
||||
node = make_federated_ursulas(ursula_config=ursula_federated_test_config, quantity=1).pop()
|
||||
node = make_ursulas(
|
||||
ursula_config=ursula_test_config,
|
||||
quantity=1,
|
||||
staking_provider_addresses=testerchain.stake_providers_accounts,
|
||||
operator_addresses=testerchain.ursulas_accounts,
|
||||
).pop()
|
||||
node_deployer = node.get_deployer()
|
||||
|
||||
node_deployer.addServices()
|
||||
|
|
|
@ -28,11 +28,12 @@ except ImportError:
|
|||
PROMETHEUS_INSTALLED = False
|
||||
|
||||
|
||||
@pytest.mark.skipif(condition=(not PROMETHEUS_INSTALLED), reason="prometheus_client is required for test")
|
||||
def test_ursula_info_metrics_collector(test_registry,
|
||||
blockchain_ursulas,
|
||||
agency):
|
||||
ursula = random.choice(blockchain_ursulas)
|
||||
@pytest.mark.skipif(
|
||||
condition=(not PROMETHEUS_INSTALLED),
|
||||
reason="prometheus_client is required for test",
|
||||
)
|
||||
def test_ursula_info_metrics_collector(test_registry, ursulas, agency):
|
||||
ursula = random.choice(ursulas)
|
||||
collector = UrsulaInfoMetricsCollector(ursula=ursula)
|
||||
|
||||
collector_registry = CollectorRegistry()
|
||||
|
@ -119,8 +120,8 @@ def test_staking_provider_metrics_collector(test_registry, staking_providers):
|
|||
|
||||
|
||||
@pytest.mark.skipif(condition=(not PROMETHEUS_INSTALLED), reason="prometheus_client is required for test")
|
||||
def test_operator_metrics_collector(test_registry, blockchain_ursulas):
|
||||
ursula = random.choice(blockchain_ursulas)
|
||||
def test_operator_metrics_collector(test_registry, ursulas):
|
||||
ursula = random.choice(ursulas)
|
||||
collector = OperatorMetricsCollector(
|
||||
domain=ursula.domain,
|
||||
operator_address=ursula.operator_address,
|
||||
|
@ -137,8 +138,8 @@ def test_operator_metrics_collector(test_registry, blockchain_ursulas):
|
|||
|
||||
|
||||
@pytest.mark.skipif(condition=(not PROMETHEUS_INSTALLED), reason="prometheus_client is required for test")
|
||||
def test_all_metrics_collectors_sanity_collect(blockchain_ursulas):
|
||||
ursula = random.choice(blockchain_ursulas)
|
||||
def test_all_metrics_collectors_sanity_collect(ursulas):
|
||||
ursula = random.choice(ursulas)
|
||||
|
||||
collector_registry = CollectorRegistry()
|
||||
prefix = 'test_all_metrics_collectors'
|
||||
|
|
|
@ -53,6 +53,16 @@ def __very_pretty_and_insecure_scrypt_do_not_use(request):
|
|||
@pytest.fixture(scope='session')
|
||||
def monkeysession():
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
mpatch = MonkeyPatch()
|
||||
yield mpatch
|
||||
mpatch.undo()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def monkeymodule():
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
mpatch = MonkeyPatch()
|
||||
yield mpatch
|
||||
mpatch.undo()
|
||||
|
|
|
@ -12,6 +12,7 @@ from typing import Callable, Tuple
|
|||
import maya
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
from eth_account import Account
|
||||
from eth_utils import to_checksum_address
|
||||
from web3 import Web3
|
||||
from web3.contract import Contract
|
||||
|
@ -36,8 +37,9 @@ from nucypher.blockchain.eth.registry import (
|
|||
InMemoryContractRegistry,
|
||||
LocalContractRegistry,
|
||||
)
|
||||
from nucypher.blockchain.eth.signers.software import Web3Signer
|
||||
from nucypher.characters.lawful import Enrico
|
||||
from nucypher.blockchain.eth.signers.software import KeystoreSigner, Web3Signer
|
||||
from nucypher.characters.lawful import Enrico, Ursula
|
||||
from nucypher.config.base import CharacterConfiguration
|
||||
from nucypher.config.characters import (
|
||||
AliceConfiguration,
|
||||
BobConfiguration,
|
||||
|
@ -45,7 +47,7 @@ from nucypher.config.characters import (
|
|||
)
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN
|
||||
from nucypher.crypto.keystore import Keystore
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
from nucypher.crypto.powers import CryptoPower, TransactingPower
|
||||
from nucypher.network.nodes import TEACHER_NODES
|
||||
from nucypher.policy.conditions.context import USER_ADDRESS_CONTEXT
|
||||
from nucypher.policy.conditions.evm import ContractCondition, RPCCondition
|
||||
|
@ -54,6 +56,7 @@ from nucypher.policy.conditions.time import TimeCondition
|
|||
from nucypher.policy.payment import SubscriptionManagerPayment
|
||||
from nucypher.utilities.emitters import StdoutEmitter
|
||||
from nucypher.utilities.logging import GlobalLoggerSettings, Logger
|
||||
from nucypher.utilities.networking import LOOPBACK_ADDRESS
|
||||
from tests.constants import (
|
||||
BASE_TEMP_DIR,
|
||||
BASE_TEMP_PREFIX,
|
||||
|
@ -64,9 +67,9 @@ from tests.constants import (
|
|||
MIN_STAKE_FOR_TESTS,
|
||||
MOCK_CUSTOM_INSTALLATION_PATH,
|
||||
MOCK_CUSTOM_INSTALLATION_PATH_2,
|
||||
MOCK_POLICY_DEFAULT_THRESHOLD,
|
||||
MOCK_ETH_PROVIDER_URI,
|
||||
MOCK_IP_ADDRESS,
|
||||
MOCK_REGISTRY_FILEPATH,
|
||||
NUMBER_OF_URSULAS_IN_DEVELOPMENT_NETWORK,
|
||||
TEST_ETH_PROVIDER_URI,
|
||||
TEST_GAS_LIMIT,
|
||||
TESTERCHAIN_CHAIN_ID,
|
||||
|
@ -94,12 +97,7 @@ from tests.utils.middleware import (
|
|||
MockRestMiddlewareForLargeFleetTests,
|
||||
)
|
||||
from tests.utils.policy import generate_random_label
|
||||
from tests.utils.ursula import (
|
||||
MOCK_KNOWN_URSULAS_CACHE,
|
||||
make_decentralized_ursulas,
|
||||
make_federated_ursulas,
|
||||
select_test_port,
|
||||
)
|
||||
from tests.utils.ursula import MOCK_KNOWN_URSULAS_CACHE, make_ursulas, select_test_port
|
||||
|
||||
test_logger = Logger("test-logger")
|
||||
|
||||
|
@ -126,7 +124,6 @@ def temp_dir_path():
|
|||
temp_dir.cleanup()
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def certificates_tempdir():
|
||||
custom_filepath = '/tmp/nucypher-test-certificates-'
|
||||
|
@ -134,48 +131,35 @@ def certificates_tempdir():
|
|||
yield Path(cert_tmpdir.name)
|
||||
cert_tmpdir.cleanup()
|
||||
|
||||
|
||||
#
|
||||
# Federated Configuration
|
||||
# Accounts
|
||||
#
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def ursula_federated_test_config(test_registry):
|
||||
def random_account():
|
||||
key = Account.create(extra_entropy="lamborghini mercy")
|
||||
account = Account.from_key(private_key=key.key)
|
||||
return account
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def random_address(random_account):
|
||||
return random_account.address
|
||||
|
||||
#
|
||||
# Character Configurations
|
||||
#
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def ursula_test_config(test_registry, temp_dir_path, testerchain):
|
||||
config = make_ursula_test_configuration(
|
||||
federated=True, rest_port=select_test_port()
|
||||
)
|
||||
yield config
|
||||
config.cleanup()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def alice_federated_test_config(federated_ursulas):
|
||||
config = make_alice_test_configuration(federated=True, known_nodes=federated_ursulas)
|
||||
yield config
|
||||
config.cleanup()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def bob_federated_test_config():
|
||||
config = make_bob_test_configuration(federated=True)
|
||||
yield config
|
||||
config.cleanup()
|
||||
|
||||
|
||||
#
|
||||
# Decentralized Configuration
|
||||
#
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def ursula_decentralized_test_config(test_registry, temp_dir_path):
|
||||
config = make_ursula_test_configuration(
|
||||
federated=False,
|
||||
eth_provider_uri=TEST_ETH_PROVIDER_URI,
|
||||
payment_provider=TEST_ETH_PROVIDER_URI,
|
||||
test_registry=test_registry,
|
||||
rest_port=select_test_port(),
|
||||
operator_address=testerchain.ursulas_accounts.pop(),
|
||||
)
|
||||
yield config
|
||||
config.cleanup()
|
||||
|
@ -184,21 +168,21 @@ def ursula_decentralized_test_config(test_registry, temp_dir_path):
|
|||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def alice_blockchain_test_config(blockchain_ursulas, testerchain, test_registry):
|
||||
config = make_alice_test_configuration(federated=False,
|
||||
eth_provider_uri=TEST_ETH_PROVIDER_URI,
|
||||
payment_provider=TEST_ETH_PROVIDER_URI,
|
||||
known_nodes=blockchain_ursulas,
|
||||
checksum_address=testerchain.alice_account,
|
||||
test_registry=test_registry)
|
||||
def alice_test_config(ursulas, testerchain, test_registry):
|
||||
config = make_alice_test_configuration(
|
||||
eth_provider_uri=TEST_ETH_PROVIDER_URI,
|
||||
payment_provider=TEST_ETH_PROVIDER_URI,
|
||||
known_nodes=ursulas,
|
||||
checksum_address=testerchain.alice_account,
|
||||
test_registry=test_registry,
|
||||
)
|
||||
yield config
|
||||
config.cleanup()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def bob_blockchain_test_config(testerchain, test_registry):
|
||||
config = make_bob_test_configuration(federated=False,
|
||||
eth_provider_uri=TEST_ETH_PROVIDER_URI,
|
||||
def bob_test_config(testerchain, test_registry):
|
||||
config = make_bob_test_configuration(eth_provider_uri=TEST_ETH_PROVIDER_URI,
|
||||
test_registry=test_registry,
|
||||
checksum_address=testerchain.bob_account)
|
||||
yield config
|
||||
|
@ -211,104 +195,81 @@ def bob_blockchain_test_config(testerchain, test_registry):
|
|||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def idle_federated_policy(federated_alice, federated_bob):
|
||||
"""
|
||||
Creates a Policy, in a manner typical of how Alice might do it, with a unique label
|
||||
"""
|
||||
threshold = MOCK_POLICY_DEFAULT_THRESHOLD
|
||||
shares = NUMBER_OF_URSULAS_IN_DEVELOPMENT_NETWORK
|
||||
random_label = generate_random_label()
|
||||
policy = federated_alice.create_policy(federated_bob,
|
||||
label=random_label,
|
||||
threshold=threshold,
|
||||
shares=shares,
|
||||
expiration=maya.now() + timedelta(days=5))
|
||||
return policy
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def enacted_federated_policy(idle_federated_policy, federated_ursulas):
|
||||
# Alice has a policy in mind and knows of enough qualifies Ursulas; she crafts an offer for them.
|
||||
network_middleware = MockRestMiddleware()
|
||||
|
||||
# REST call happens here, as does population of TreasureMap.
|
||||
enacted_policy = idle_federated_policy.enact(network_middleware=network_middleware,
|
||||
ursulas=federated_ursulas)
|
||||
return enacted_policy
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def federated_treasure_map(enacted_federated_policy, federated_bob):
|
||||
"""
|
||||
The unencrypted treasure map corresponding to the one in `enacted_federated_policy`
|
||||
"""
|
||||
yield federated_bob._decrypt_treasure_map(enacted_federated_policy.treasure_map,
|
||||
enacted_federated_policy.publisher_verifying_key)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def idle_blockchain_policy(testerchain, blockchain_alice, blockchain_bob, application_economics):
|
||||
def idle_policy(testerchain, alice, bob, application_economics):
|
||||
"""Creates a Policy, in a manner typical of how Alice might do it, with a unique label"""
|
||||
random_label = generate_random_label()
|
||||
expiration = maya.now() + timedelta(days=1)
|
||||
threshold, shares = 2, 3
|
||||
price = blockchain_alice.payment_method.quote(expiration=expiration.epoch, shares=shares).value # TODO: use default quote option
|
||||
policy = blockchain_alice.create_policy(blockchain_bob,
|
||||
label=random_label,
|
||||
value=price,
|
||||
threshold=threshold,
|
||||
shares=shares,
|
||||
expiration=expiration)
|
||||
threshold, shares = 3, 5
|
||||
price = alice.payment_method.quote(
|
||||
expiration=expiration.epoch, shares=shares
|
||||
).value # TODO: use default quote option
|
||||
policy = alice.create_policy(
|
||||
bob,
|
||||
label=random_label,
|
||||
value=price,
|
||||
threshold=threshold,
|
||||
shares=shares,
|
||||
expiration=expiration,
|
||||
)
|
||||
return policy
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def enacted_blockchain_policy(idle_blockchain_policy, blockchain_ursulas):
|
||||
def enacted_policy(idle_policy, ursulas):
|
||||
# Alice has a policy in mind and knows of enough qualified Ursulas; she crafts an offer for them.
|
||||
|
||||
# value and expiration were set when creating idle_blockchain_policy already
|
||||
# value and expiration were set when creating idle_policy already
|
||||
# cannot set them again
|
||||
# deposit = NON_PAYMENT(b"0000000")
|
||||
# contract_end_datetime = maya.now() + datetime.timedelta(days=5)
|
||||
network_middleware = MockRestMiddleware()
|
||||
|
||||
# REST call happens here, as does population of TreasureMap.
|
||||
enacted_policy = idle_blockchain_policy.enact(network_middleware=network_middleware,
|
||||
ursulas=list(blockchain_ursulas))
|
||||
enacted_policy = idle_policy.enact(
|
||||
network_middleware=network_middleware, ursulas=list(ursulas)
|
||||
)
|
||||
return enacted_policy
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def blockchain_treasure_map(enacted_blockchain_policy, blockchain_bob):
|
||||
def treasure_map(enacted_policy, bob):
|
||||
"""
|
||||
The unencrypted treasure map corresponding to the one in `enacted_blockchain_policy`
|
||||
The unencrypted treasure map corresponding to the one in `enacted_policy`
|
||||
"""
|
||||
yield blockchain_bob._decrypt_treasure_map(enacted_blockchain_policy.treasure_map,
|
||||
enacted_blockchain_policy.publisher_verifying_key)
|
||||
yield bob._decrypt_treasure_map(
|
||||
enacted_policy.treasure_map, enacted_policy.publisher_verifying_key
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def random_blockchain_policy(testerchain, blockchain_alice, blockchain_bob, application_economics):
|
||||
def random_policy(testerchain, alice, bob, application_economics):
|
||||
random_label = generate_random_label()
|
||||
seconds = 60 * 60 * 24 # TODO This needs to be better thought out...?
|
||||
now = testerchain.w3.eth.get_block('latest').timestamp
|
||||
expiration = maya.MayaDT(now).add(seconds=seconds)
|
||||
shares = 3
|
||||
threshold = 2
|
||||
policy = blockchain_alice.create_policy(blockchain_bob,
|
||||
label=random_label,
|
||||
threshold=threshold,
|
||||
shares=shares,
|
||||
value=shares * seconds * 100, # calculation probably needs to incorporate actual cost per second
|
||||
expiration=expiration)
|
||||
policy = alice.create_policy(
|
||||
bob,
|
||||
label=random_label,
|
||||
threshold=threshold,
|
||||
shares=shares,
|
||||
value=shares
|
||||
* seconds
|
||||
* 100, # calculation probably needs to incorporate actual cost per second
|
||||
expiration=expiration,
|
||||
)
|
||||
return policy
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def capsule_side_channel(enacted_federated_policy):
|
||||
def capsule_side_channel(enacted_policy):
|
||||
class _CapsuleSideChannel:
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
self.enrico = Enrico(policy_encrypting_key=enacted_policy.public_key)
|
||||
self.messages = []
|
||||
self.plaintexts = []
|
||||
self.plaintext_passthrough = False
|
||||
|
||||
def __call__(self):
|
||||
message = "Welcome to flippering number {}.".format(len(self.messages)).encode()
|
||||
|
@ -319,33 +280,9 @@ def capsule_side_channel(enacted_federated_policy):
|
|||
return message_kit
|
||||
|
||||
def reset(self, plaintext_passthrough=False):
|
||||
self.enrico = Enrico(policy_encrypting_key=enacted_federated_policy.public_key)
|
||||
self.messages = []
|
||||
self.plaintexts = []
|
||||
self.plaintext_passthrough = plaintext_passthrough
|
||||
return self(), self.enrico
|
||||
|
||||
return _CapsuleSideChannel()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def capsule_side_channel_blockchain(enacted_blockchain_policy):
|
||||
class _CapsuleSideChannel:
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def __call__(self):
|
||||
message = "Welcome to flippering number {}.".format(len(self.messages)).encode()
|
||||
message_kit = self.enrico.encrypt_message(message)
|
||||
self.messages.append((message_kit, self.enrico))
|
||||
if self.plaintext_passthrough:
|
||||
self.plaintexts.append(message)
|
||||
return message_kit
|
||||
|
||||
def reset(self, plaintext_passthrough=False):
|
||||
self.enrico = Enrico(policy_encrypting_key=enacted_blockchain_policy.public_key)
|
||||
self.messages = []
|
||||
self.plaintexts = []
|
||||
self.enrico = Enrico(policy_encrypting_key=enacted_policy.public_key)
|
||||
self.messages.clear()
|
||||
self.plaintexts.clear()
|
||||
self.plaintext_passthrough = plaintext_passthrough
|
||||
return self(), self.enrico
|
||||
|
||||
|
@ -362,69 +299,29 @@ def random_policy_label():
|
|||
#
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def federated_alice(alice_federated_test_config):
|
||||
alice = alice_federated_test_config.produce()
|
||||
def alice(alice_test_config, testerchain):
|
||||
alice = alice_test_config.produce()
|
||||
yield alice
|
||||
alice.disenchant()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def blockchain_alice(alice_blockchain_test_config, testerchain):
|
||||
alice = alice_blockchain_test_config.produce()
|
||||
yield alice
|
||||
alice.disenchant()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def federated_bob(bob_federated_test_config):
|
||||
bob = bob_federated_test_config.produce()
|
||||
def bob(bob_test_config, testerchain):
|
||||
bob = bob_test_config.produce()
|
||||
yield bob
|
||||
bob.disenchant()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def blockchain_bob(bob_blockchain_test_config, testerchain):
|
||||
bob = bob_blockchain_test_config.produce()
|
||||
yield bob
|
||||
bob.disenchant()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def federated_ursulas(ursula_federated_test_config):
|
||||
if MOCK_KNOWN_URSULAS_CACHE:
|
||||
raise RuntimeError("Ursulas cache was unclear at fixture loading time. "
|
||||
"Did you use one of the ursula maker functions without cleaning up?")
|
||||
# MOCK_KNOWN_URSULAS_CACHE.clear()
|
||||
|
||||
_ursulas = make_federated_ursulas(ursula_config=ursula_federated_test_config,
|
||||
quantity=NUMBER_OF_URSULAS_IN_DEVELOPMENT_NETWORK)
|
||||
|
||||
# Since we mutate this list in some tests, it's not enough to remember and remove the Ursulas; we have to remember them by port.
|
||||
# The same is true of blockchain_ursulas below.
|
||||
_ports_to_remove = [ursula.rest_interface.port for ursula in _ursulas]
|
||||
yield _ursulas
|
||||
|
||||
for port in _ports_to_remove:
|
||||
if port in MOCK_KNOWN_URSULAS_CACHE:
|
||||
test_logger.debug(f"Removing {port} ({MOCK_KNOWN_URSULAS_CACHE[port]}).")
|
||||
del MOCK_KNOWN_URSULAS_CACHE[port]
|
||||
|
||||
for u in _ursulas:
|
||||
u.stop()
|
||||
u._finalize()
|
||||
|
||||
# Pytest will hold on to this object, need to clear it manually.
|
||||
# See https://github.com/pytest-dev/pytest/issues/5642
|
||||
_ursulas.clear()
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def lonely_ursula_maker(ursula_federated_test_config):
|
||||
def lonely_ursula_maker(ursula_test_config, testerchain):
|
||||
class _PartialUrsulaMaker:
|
||||
_partial = partial(make_federated_ursulas,
|
||||
ursula_config=ursula_federated_test_config,
|
||||
know_each_other=False,
|
||||
)
|
||||
_partial = partial(
|
||||
make_ursulas,
|
||||
ursula_config=ursula_test_config,
|
||||
know_each_other=False,
|
||||
staking_provider_addresses=testerchain.stake_providers_accounts,
|
||||
operator_addresses=testerchain.ursulas_accounts,
|
||||
)
|
||||
_made = []
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
|
@ -609,17 +506,24 @@ def staking_providers(testerchain, agency, test_registry, threshold_staking):
|
|||
operator=operator_address,
|
||||
transacting_power=provider_power)
|
||||
|
||||
operator_power = TransactingPower(account=operator_address, signer=Web3Signer(testerchain.client))
|
||||
operator = Operator(is_me=True,
|
||||
operator_address=operator_address,
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
registry=test_registry,
|
||||
transacting_power=operator_power,
|
||||
payment_method=SubscriptionManagerPayment(
|
||||
eth_provider=testerchain.eth_provider_uri,
|
||||
network=TEMPORARY_DOMAIN,
|
||||
registry=test_registry)
|
||||
)
|
||||
operator_power = TransactingPower(
|
||||
account=operator_address, signer=Web3Signer(testerchain.client)
|
||||
)
|
||||
operator = Operator(
|
||||
is_me=True,
|
||||
operator_address=operator_address,
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
registry=test_registry,
|
||||
transacting_power=operator_power,
|
||||
eth_provider_uri=testerchain.eth_provider_uri,
|
||||
signer=Web3Signer(testerchain.client),
|
||||
crypto_power=CryptoPower(power_ups=[operator_power]),
|
||||
payment_method=SubscriptionManagerPayment(
|
||||
eth_provider=testerchain.eth_provider_uri,
|
||||
network=TEMPORARY_DOMAIN,
|
||||
registry=test_registry,
|
||||
),
|
||||
)
|
||||
operator.confirm_address() # assume we always need a "pre-confirmed" operator for now.
|
||||
|
||||
# track
|
||||
|
@ -628,27 +532,42 @@ def staking_providers(testerchain, agency, test_registry, threshold_staking):
|
|||
yield staking_providers
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def light_ursula(temp_dir_path, test_registry_source_manager, random_account, mocker):
|
||||
mocker.patch.object(
|
||||
KeystoreSigner, "_KeystoreSigner__get_signer", return_value=random_account
|
||||
)
|
||||
payment_method = SubscriptionManagerPayment(
|
||||
eth_provider=MOCK_ETH_PROVIDER_URI, network=TEMPORARY_DOMAIN
|
||||
)
|
||||
ursula = Ursula(
|
||||
rest_host=LOOPBACK_ADDRESS,
|
||||
rest_port=select_test_port(),
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
payment_method=payment_method,
|
||||
checksum_address=random_account.address,
|
||||
operator_address=random_account.address,
|
||||
eth_provider_uri=MOCK_ETH_PROVIDER_URI,
|
||||
signer=KeystoreSigner(path=temp_dir_path),
|
||||
)
|
||||
return ursula
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def blockchain_ursulas(testerchain, staking_providers, ursula_decentralized_test_config):
|
||||
def ursulas(testerchain, staking_providers, ursula_test_config):
|
||||
if MOCK_KNOWN_URSULAS_CACHE:
|
||||
# TODO: Is this a safe assumption / test behaviour?
|
||||
# raise RuntimeError("Ursulas cache was unclear at fixture loading time. Did you use one of the ursula maker functions without cleaning up?")
|
||||
MOCK_KNOWN_URSULAS_CACHE.clear()
|
||||
|
||||
_ursulas = make_decentralized_ursulas(ursula_config=ursula_decentralized_test_config,
|
||||
staking_provider_addresses=testerchain.stake_providers_accounts,
|
||||
operator_addresses=testerchain.ursulas_accounts)
|
||||
_ursulas = make_ursulas(
|
||||
ursula_config=ursula_test_config,
|
||||
staking_provider_addresses=testerchain.stake_providers_accounts,
|
||||
operator_addresses=testerchain.ursulas_accounts,
|
||||
know_each_other=True,
|
||||
)
|
||||
for u in _ursulas:
|
||||
u.synchronous_query_timeout = .01 # We expect to never have to wait for content that is actually on-chain during tests.
|
||||
#testerchain.time_travel(periods=1)
|
||||
|
||||
# Bootstrap the network
|
||||
for ursula_to_teach in _ursulas:
|
||||
for ursula_to_learn_about in _ursulas:
|
||||
# FIXME #2588: FleetSensor should not own fully-functional Ursulas.
|
||||
# It only needs to see whatever public info we can normally get via REST.
|
||||
# Also sharing mutable Ursulas like that can lead to unpredictable results.
|
||||
ursula_to_teach.remember_node(ursula_to_learn_about)
|
||||
|
||||
_ports_to_remove = [ursula.rest_interface.port for ursula in _ursulas]
|
||||
yield _ursulas
|
||||
|
@ -755,7 +674,7 @@ def get_random_checksum_address():
|
|||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def fleet_of_highperf_mocked_ursulas(ursula_federated_test_config, request):
|
||||
def fleet_of_highperf_mocked_ursulas(ursula_test_config, request, testerchain):
|
||||
|
||||
mocks = (
|
||||
mock_cert_storage,
|
||||
|
@ -770,14 +689,23 @@ def fleet_of_highperf_mocked_ursulas(ursula_federated_test_config, request):
|
|||
quantity = request.param
|
||||
except AttributeError:
|
||||
quantity = 5000 # Bigass fleet by default; that's kinda the point.
|
||||
|
||||
staking_addresses = (to_checksum_address('0x' + os.urandom(20).hex()) for _ in range(5000))
|
||||
operator_addresses = (to_checksum_address('0x' + os.urandom(20).hex()) for _ in range(5000))
|
||||
|
||||
with GlobalLoggerSettings.pause_all_logging_while():
|
||||
with contextlib.ExitStack() as stack:
|
||||
|
||||
for mock in mocks:
|
||||
stack.enter_context(mock)
|
||||
|
||||
_ursulas = make_federated_ursulas(ursula_config=ursula_federated_test_config,
|
||||
quantity=quantity, know_each_other=False)
|
||||
_ursulas = make_ursulas(
|
||||
ursula_config=ursula_test_config,
|
||||
quantity=quantity,
|
||||
know_each_other=False,
|
||||
staking_provider_addresses=staking_addresses,
|
||||
operator_addresses=operator_addresses,
|
||||
)
|
||||
all_ursulas = {u.checksum_address: u for u in _ursulas}
|
||||
|
||||
for ursula in _ursulas:
|
||||
|
@ -794,11 +722,13 @@ def fleet_of_highperf_mocked_ursulas(ursula_federated_test_config, request):
|
|||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def highperf_mocked_alice(fleet_of_highperf_mocked_ursulas):
|
||||
def highperf_mocked_alice(fleet_of_highperf_mocked_ursulas, test_registry_source_manager, monkeymodule, testerchain):
|
||||
monkeymodule.setattr(CharacterConfiguration, 'DEFAULT_PAYMENT_NETWORK', TEMPORARY_DOMAIN)
|
||||
|
||||
config = AliceConfiguration(dev_mode=True,
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
checksum_address=testerchain.alice_account,
|
||||
network_middleware=MockRestMiddlewareForLargeFleetTests(),
|
||||
federated_only=True,
|
||||
abort_on_learning_error=True,
|
||||
save_metadata=False,
|
||||
reload_metadata=False)
|
||||
|
@ -815,7 +745,6 @@ def highperf_mocked_bob(fleet_of_highperf_mocked_ursulas):
|
|||
config = BobConfiguration(dev_mode=True,
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
network_middleware=MockRestMiddlewareForLargeFleetTests(),
|
||||
federated_only=True,
|
||||
abort_on_learning_error=True,
|
||||
save_metadata=False,
|
||||
reload_metadata=False)
|
||||
|
@ -846,9 +775,9 @@ def click_runner():
|
|||
yield runner
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def nominal_federated_configuration_fields():
|
||||
config = UrsulaConfiguration(dev_mode=True, federated_only=True)
|
||||
@pytest.fixture(scope='module')
|
||||
def nominal_configuration_fields(test_registry_source_manager):
|
||||
config = UrsulaConfiguration(dev_mode=True, payment_network=TEMPORARY_DOMAIN, domain=TEMPORARY_DOMAIN)
|
||||
config_fields = config.static_payload()
|
||||
yield tuple(config_fields.keys())
|
||||
del config
|
||||
|
|
|
@ -12,21 +12,21 @@ real-world scenarios.
|
|||
"""
|
||||
|
||||
|
||||
def test_sign_cleartext_and_encrypt(federated_alice, federated_bob):
|
||||
def test_sign_cleartext_and_encrypt(alice, bob):
|
||||
"""
|
||||
Exhibit One: federated_alice signs the cleartext and encrypts her signature inside
|
||||
Exhibit One: alice signs the cleartext and encrypts her signature inside
|
||||
the ciphertext.
|
||||
"""
|
||||
message = b"Have you accepted my answer on StackOverflow yet?"
|
||||
message_kit = federated_alice.encrypt_for(federated_alice, message)
|
||||
cleartext = federated_alice.decrypt_message_kit(federated_alice, message_kit)
|
||||
message_kit = alice.encrypt_for(alice, message)
|
||||
cleartext = alice.decrypt_message_kit(alice, message_kit)
|
||||
assert cleartext == message
|
||||
|
||||
|
||||
def test_alice_can_decrypt(federated_alice):
|
||||
def test_alice_can_decrypt(alice):
|
||||
label = b"boring test label"
|
||||
|
||||
policy_pubkey = federated_alice.get_policy_encrypting_key_from_label(label)
|
||||
policy_pubkey = alice.get_policy_encrypting_key_from_label(label)
|
||||
|
||||
enrico = Enrico(policy_encrypting_key=policy_pubkey)
|
||||
|
||||
|
@ -34,6 +34,5 @@ def test_alice_can_decrypt(federated_alice):
|
|||
message_kit = enrico.encrypt_message(plaintext=message)
|
||||
|
||||
# Interesting thing: if Alice wants to decrypt, she needs to provide the label directly.
|
||||
cleartexts = federated_alice.decrypt_message_kit(label=label,
|
||||
message_kit=message_kit)
|
||||
cleartexts = alice.decrypt_message_kit(label=label, message_kit=message_kit)
|
||||
assert cleartexts == [message]
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import json
|
||||
|
||||
import pytest
|
||||
from nucypher_core import Address, Conditions, RetrievalKit
|
||||
from nucypher_core._nucypher_core import MessageKit
|
||||
|
||||
|
@ -14,13 +15,13 @@ def _policy_info_kwargs(enacted_policy):
|
|||
)
|
||||
|
||||
|
||||
def test_retrieval_kit(enacted_federated_policy, federated_ursulas):
|
||||
messages, message_kits = make_message_kits(enacted_federated_policy.public_key)
|
||||
def test_retrieval_kit(enacted_policy, ursulas):
|
||||
messages, message_kits = make_message_kits(enacted_policy.public_key)
|
||||
|
||||
capsule = message_kits[0].capsule
|
||||
addresses = {Address(ursula.canonical_address) for ursula in list(federated_ursulas)[:2]}
|
||||
addresses = {Address(ursula.canonical_address) for ursula in list(ursulas)[:2]}
|
||||
|
||||
retrieval_kit = RetrievalKit(capsule, addresses)
|
||||
retrieval_kit = RetrievalKit(capsule, addresses, conditions=None)
|
||||
serialized = bytes(retrieval_kit)
|
||||
retrieval_kit_back = RetrievalKit.from_bytes(serialized)
|
||||
|
||||
|
@ -28,72 +29,69 @@ def test_retrieval_kit(enacted_federated_policy, federated_ursulas):
|
|||
assert retrieval_kit.queried_addresses == retrieval_kit_back.queried_addresses
|
||||
|
||||
|
||||
def test_single_retrieve(enacted_federated_policy, federated_bob, federated_ursulas):
|
||||
federated_bob.start_learning_loop()
|
||||
messages, message_kits = make_message_kits(enacted_federated_policy.public_key)
|
||||
def test_single_retrieve(enacted_policy, bob, ursulas):
|
||||
bob.remember_node(ursulas[0])
|
||||
bob.start_learning_loop()
|
||||
messages, message_kits = make_message_kits(enacted_policy.public_key)
|
||||
|
||||
cleartexts = federated_bob.retrieve_and_decrypt(
|
||||
cleartexts = bob.retrieve_and_decrypt(
|
||||
message_kits=message_kits,
|
||||
**_policy_info_kwargs(enacted_federated_policy),
|
||||
**_policy_info_kwargs(enacted_policy),
|
||||
)
|
||||
|
||||
assert cleartexts == messages
|
||||
|
||||
|
||||
def test_single_retrieve_conditions_set_directly_to_none(
|
||||
enacted_federated_policy, federated_bob, federated_ursulas
|
||||
):
|
||||
federated_bob.start_learning_loop()
|
||||
def test_single_retrieve_conditions_set_directly_to_none(enacted_policy, bob, ursulas):
|
||||
bob.start_learning_loop()
|
||||
message = b"plaintext1"
|
||||
|
||||
# MessageKit is created directly in this test, to ensure consistency
|
||||
message_kit = MessageKit(
|
||||
policy_encrypting_key=enacted_federated_policy.public_key,
|
||||
policy_encrypting_key=enacted_policy.public_key,
|
||||
plaintext=message,
|
||||
conditions=None,
|
||||
)
|
||||
cleartexts = federated_bob.retrieve_and_decrypt(
|
||||
cleartexts = bob.retrieve_and_decrypt(
|
||||
message_kits=[message_kit],
|
||||
**_policy_info_kwargs(enacted_federated_policy),
|
||||
**_policy_info_kwargs(enacted_policy),
|
||||
)
|
||||
assert cleartexts == [message]
|
||||
|
||||
|
||||
def test_single_retrieve_conditions_empty_list(
|
||||
enacted_federated_policy, federated_bob, federated_ursulas
|
||||
):
|
||||
federated_bob.start_learning_loop()
|
||||
def test_single_retrieve_conditions_empty_list(enacted_policy, bob, ursulas):
|
||||
bob.start_learning_loop()
|
||||
message = b"plaintext1"
|
||||
|
||||
# MessageKit is created directly in this test, to ensure consistency
|
||||
message_kit = MessageKit(
|
||||
policy_encrypting_key=enacted_federated_policy.public_key,
|
||||
policy_encrypting_key=enacted_policy.public_key,
|
||||
plaintext=message,
|
||||
conditions=Conditions(json.dumps([])),
|
||||
)
|
||||
cleartexts = federated_bob.retrieve_and_decrypt(
|
||||
cleartexts = bob.retrieve_and_decrypt(
|
||||
message_kits=[message_kit],
|
||||
**_policy_info_kwargs(enacted_federated_policy),
|
||||
**_policy_info_kwargs(enacted_policy),
|
||||
)
|
||||
assert cleartexts == [message]
|
||||
|
||||
|
||||
def test_use_external_cache(enacted_federated_policy, federated_bob, federated_ursulas):
|
||||
def test_use_external_cache(enacted_policy, bob, ursulas):
|
||||
|
||||
federated_bob.start_learning_loop()
|
||||
messages, message_kits = make_message_kits(enacted_federated_policy.public_key)
|
||||
bob.start_learning_loop()
|
||||
messages, message_kits = make_message_kits(enacted_policy.public_key)
|
||||
|
||||
ursulas = list(federated_ursulas)
|
||||
ursulas = list(ursulas)
|
||||
|
||||
# All Ursulas are down except for two
|
||||
federated_bob.network_middleware = NodeIsDownMiddleware()
|
||||
bob.network_middleware = NodeIsDownMiddleware()
|
||||
for ursula in ursulas[2:]:
|
||||
federated_bob.network_middleware.node_is_down(ursula)
|
||||
bob.network_middleware.node_is_down(ursula)
|
||||
|
||||
# Fetch what we can without decrypting
|
||||
loaded_message_kits = federated_bob.retrieve(
|
||||
loaded_message_kits = bob.retrieve(
|
||||
message_kits=message_kits,
|
||||
**_policy_info_kwargs(enacted_federated_policy),
|
||||
**_policy_info_kwargs(enacted_policy),
|
||||
)
|
||||
|
||||
# Not enough cfrags yet
|
||||
|
@ -101,15 +99,15 @@ def test_use_external_cache(enacted_federated_policy, federated_bob, federated_u
|
|||
|
||||
# Now the remaining two Ursulas go down.
|
||||
for ursula in ursulas[:2]:
|
||||
federated_bob.network_middleware.node_is_down(ursula)
|
||||
bob.network_middleware.node_is_down(ursula)
|
||||
|
||||
# ...but one other comes up.
|
||||
federated_bob.network_middleware.node_is_up(ursulas[2])
|
||||
bob.network_middleware.node_is_up(ursulas[2])
|
||||
|
||||
# Try again, building on top of the existing cache
|
||||
loaded_message_kits = federated_bob.retrieve(
|
||||
loaded_message_kits = bob.retrieve(
|
||||
message_kits=loaded_message_kits,
|
||||
**_policy_info_kwargs(enacted_federated_policy),
|
||||
**_policy_info_kwargs(enacted_policy),
|
||||
)
|
||||
|
||||
assert all(mk.is_decryptable_by_receiver() for mk in loaded_message_kits)
|
||||
|
@ -117,11 +115,11 @@ def test_use_external_cache(enacted_federated_policy, federated_bob, federated_u
|
|||
# Should be enough cfrags now. Disconnect all Ursulas
|
||||
# to be sure Bob doesn't cheat and contact them again.
|
||||
for ursula in ursulas:
|
||||
federated_bob.network_middleware.node_is_down(ursula)
|
||||
bob.network_middleware.node_is_down(ursula)
|
||||
|
||||
cleartexts = federated_bob.retrieve_and_decrypt(
|
||||
cleartexts = bob.retrieve_and_decrypt(
|
||||
message_kits=loaded_message_kits,
|
||||
**_policy_info_kwargs(enacted_federated_policy),
|
||||
**_policy_info_kwargs(enacted_policy),
|
||||
)
|
||||
|
||||
assert cleartexts == messages
|
||||
|
|
|
@ -1,59 +1,55 @@
|
|||
|
||||
|
||||
|
||||
import datetime
|
||||
import maya
|
||||
import os
|
||||
import pytest
|
||||
import time
|
||||
from constant_sorrow.constants import NO_DECRYPTION_PERFORMED
|
||||
|
||||
import maya
|
||||
import pytest
|
||||
from twisted.internet.task import Clock
|
||||
|
||||
from nucypher.characters.lawful import Bob, Enrico, Ursula
|
||||
from nucypher.characters.lawful import Bob, Enrico
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN
|
||||
from tests.constants import NUMBER_OF_URSULAS_IN_DEVELOPMENT_NETWORK
|
||||
from tests.utils.middleware import MockRestMiddleware
|
||||
|
||||
|
||||
def test_federated_bob_full_retrieve_flow(federated_ursulas,
|
||||
federated_bob,
|
||||
federated_alice,
|
||||
capsule_side_channel,
|
||||
federated_treasure_map,
|
||||
enacted_federated_policy):
|
||||
def test_bob_full_retrieve_flow(
|
||||
ursulas, bob, alice, capsule_side_channel, treasure_map, enacted_policy
|
||||
):
|
||||
|
||||
for ursula in federated_ursulas:
|
||||
federated_bob.remember_node(ursula)
|
||||
for ursula in ursulas:
|
||||
bob.remember_node(ursula)
|
||||
|
||||
# The side channel delivers all that Bob needs at this point:
|
||||
# - A single MessageKit, containing a Capsule
|
||||
# - A representation of the data source
|
||||
the_message_kit = capsule_side_channel()
|
||||
alices_verifying_key = federated_alice.stamp.as_umbral_pubkey()
|
||||
alices_verifying_key = alice.stamp.as_umbral_pubkey()
|
||||
|
||||
delivered_cleartexts = federated_bob.retrieve_and_decrypt([the_message_kit],
|
||||
alice_verifying_key=alices_verifying_key,
|
||||
encrypted_treasure_map=enacted_federated_policy.treasure_map)
|
||||
delivered_cleartexts = bob.retrieve_and_decrypt(
|
||||
[the_message_kit],
|
||||
alice_verifying_key=alices_verifying_key,
|
||||
encrypted_treasure_map=enacted_policy.treasure_map,
|
||||
)
|
||||
|
||||
# We show that indeed this is the passage originally encrypted by the Enrico.
|
||||
assert b"Welcome to flippering number 1." == delivered_cleartexts[0]
|
||||
assert b"Welcome to flippering number 0." == delivered_cleartexts[0]
|
||||
|
||||
|
||||
def test_bob_retrieves(federated_alice,
|
||||
federated_ursulas,
|
||||
certificates_tempdir):
|
||||
def test_bob_retrieves(
|
||||
alice, ursulas, certificates_tempdir, test_registry_source_manager
|
||||
):
|
||||
"""A test to show that Bob can retrieve data from Ursula"""
|
||||
|
||||
# Let's partition Ursulas in two parts
|
||||
a_couple_of_ursulas = list(federated_ursulas)[:2]
|
||||
rest_of_ursulas = list(federated_ursulas)[2:]
|
||||
a_couple_of_ursulas = list(ursulas)[:2]
|
||||
rest_of_ursulas = list(ursulas)[2:]
|
||||
|
||||
# Bob becomes
|
||||
bob = Bob(federated_only=True,
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
bob = Bob(domain=TEMPORARY_DOMAIN,
|
||||
start_learning_now=True,
|
||||
network_middleware=MockRestMiddleware(),
|
||||
abort_on_learning_error=True,
|
||||
known_nodes=a_couple_of_ursulas,
|
||||
)
|
||||
known_nodes=a_couple_of_ursulas)
|
||||
|
||||
# Bob has only connected to - at most - 2 nodes.
|
||||
assert sum(node.verified_node for node in bob.known_nodes) <= 2
|
||||
|
@ -63,13 +59,14 @@ def test_bob_retrieves(federated_alice,
|
|||
shares = NUMBER_OF_URSULAS_IN_DEVELOPMENT_NETWORK - 2
|
||||
label = b'label://' + os.urandom(32)
|
||||
contract_end_datetime = maya.now() + datetime.timedelta(days=5)
|
||||
policy = federated_alice.grant(bob=bob,
|
||||
label=label,
|
||||
threshold=3,
|
||||
shares=shares,
|
||||
expiration=contract_end_datetime,
|
||||
ursulas=set(rest_of_ursulas),
|
||||
)
|
||||
policy = alice.grant(
|
||||
bob=bob,
|
||||
label=label,
|
||||
threshold=3,
|
||||
shares=shares,
|
||||
expiration=contract_end_datetime,
|
||||
ursulas=set(rest_of_ursulas),
|
||||
)
|
||||
|
||||
assert label == policy.label
|
||||
|
||||
|
@ -79,7 +76,7 @@ def test_bob_retrieves(federated_alice,
|
|||
plaintext = b"What's your approach? Mississippis or what?"
|
||||
message_kit = enrico.encrypt_message(plaintext)
|
||||
|
||||
alices_verifying_key = federated_alice.stamp.as_umbral_pubkey()
|
||||
alices_verifying_key = alice.stamp.as_umbral_pubkey()
|
||||
|
||||
# Bob takes the message_kit and retrieves the message within
|
||||
delivered_cleartexts = bob.retrieve_and_decrypt([message_kit],
|
||||
|
@ -96,7 +93,7 @@ def test_bob_retrieves(federated_alice,
|
|||
assert delivered_cleartexts == cleartexts_delivered_a_second_time
|
||||
|
||||
# Let's try retrieve again, but Alice revoked the policy.
|
||||
receipt, failed_revocations = federated_alice.revoke(policy)
|
||||
receipt, failed_revocations = alice.revoke(policy)
|
||||
assert len(failed_revocations) == 0
|
||||
|
||||
# One thing to note here is that Bob *can* still retrieve with the cached CFrags,
|
||||
|
@ -110,44 +107,39 @@ def test_bob_retrieves(federated_alice,
|
|||
|
||||
|
||||
def test_bob_retrieves_with_treasure_map(
|
||||
federated_bob, federated_ursulas,
|
||||
enacted_federated_policy, capsule_side_channel):
|
||||
bob, ursulas, enacted_policy, capsule_side_channel
|
||||
):
|
||||
enrico = capsule_side_channel.enrico
|
||||
message_kit = capsule_side_channel()
|
||||
treasure_map = enacted_federated_policy.treasure_map
|
||||
alice_verifying_key = enacted_federated_policy.publisher_verifying_key
|
||||
treasure_map = enacted_policy.treasure_map
|
||||
alice_verifying_key = enacted_policy.publisher_verifying_key
|
||||
|
||||
# Teach Bob about the network
|
||||
federated_bob.remember_node(list(federated_ursulas)[0])
|
||||
federated_bob.learn_from_teacher_node(eager=True)
|
||||
bob.remember_node(list(ursulas)[0])
|
||||
bob.learn_from_teacher_node(eager=True)
|
||||
|
||||
# Deserialized treasure map
|
||||
text1 = federated_bob.retrieve_and_decrypt(
|
||||
text1 = bob.retrieve_and_decrypt(
|
||||
[message_kit],
|
||||
alice_verifying_key=alice_verifying_key,
|
||||
encrypted_treasure_map=treasure_map)
|
||||
|
||||
assert text1 == [b'Welcome to flippering number 2.']
|
||||
assert text1 == [b'Welcome to flippering number 1.']
|
||||
|
||||
|
||||
# TODO: #2813 Without kfrag and arrangement storage by nodes,
|
||||
# Federated policies are no longer time-based, and expiration cannot be enforced on them
|
||||
@pytest.mark.skip()
|
||||
def test_bob_retrieves_too_late(federated_bob,
|
||||
federated_ursulas,
|
||||
enacted_federated_policy,
|
||||
capsule_side_channel):
|
||||
|
||||
def test_bob_retrieves_too_late(bob, ursulas, enacted_policy, capsule_side_channel):
|
||||
clock = Clock()
|
||||
clock.advance(time.time())
|
||||
clock.advance(86400 * 8) # 1 week # TODO: this is supposed to be seven days, not eight
|
||||
|
||||
message_kit = capsule_side_channel()
|
||||
treasure_map = enacted_federated_policy.treasure_map
|
||||
alice_verifying_key = enacted_federated_policy.publisher_verifying_key
|
||||
treasure_map = enacted_policy.treasure_map
|
||||
alice_verifying_key = enacted_policy.publisher_verifying_key
|
||||
|
||||
# with pytest.raises(Ursula.NotEnoughUrsulas):
|
||||
federated_bob.retrieve_and_decrypt(
|
||||
bob.retrieve_and_decrypt(
|
||||
[message_kit],
|
||||
alice_verifying_key=alice_verifying_key,
|
||||
encrypted_treasure_map=treasure_map
|
||||
|
|
|
@ -15,12 +15,14 @@ def _policy_info_kwargs(enacted_policy):
|
|||
)
|
||||
|
||||
|
||||
def test_single_retrieve_with_truthy_conditions(enacted_federated_policy, federated_bob, federated_ursulas, mocker):
|
||||
def test_single_retrieve_with_truthy_conditions(enacted_policy, bob, ursulas, mocker):
|
||||
from nucypher_core import MessageKit
|
||||
|
||||
reencrypt_spy = mocker.spy(Ursula, '_reencrypt')
|
||||
|
||||
federated_bob.start_learning_loop()
|
||||
bob.remember_node(ursulas[0])
|
||||
bob.start_learning_loop()
|
||||
|
||||
conditions = [
|
||||
{'returnValueTest': {'value': '0', 'comparator': '>'}, 'method': 'timelock'},
|
||||
{'operator': 'and'},
|
||||
|
@ -28,24 +30,18 @@ def test_single_retrieve_with_truthy_conditions(enacted_federated_policy, federa
|
|||
]
|
||||
json_conditions = json.dumps(conditions)
|
||||
rust_conditions = Conditions(json_conditions)
|
||||
message_kits = [
|
||||
MessageKit(
|
||||
enacted_federated_policy.public_key,
|
||||
b'lab',
|
||||
rust_conditions
|
||||
)
|
||||
]
|
||||
message_kits = [MessageKit(enacted_policy.public_key, b"lab", rust_conditions)]
|
||||
|
||||
cleartexts = federated_bob.retrieve_and_decrypt(
|
||||
cleartexts = bob.retrieve_and_decrypt(
|
||||
message_kits=message_kits,
|
||||
**_policy_info_kwargs(enacted_federated_policy),
|
||||
**_policy_info_kwargs(enacted_policy),
|
||||
)
|
||||
|
||||
assert b'lab' in cleartexts
|
||||
assert reencrypt_spy.call_count == 3
|
||||
assert reencrypt_spy.call_count == enacted_policy.threshold
|
||||
|
||||
|
||||
def test_single_retrieve_with_falsy_conditions(enacted_federated_policy, federated_bob, federated_ursulas, mocker):
|
||||
def test_single_retrieve_with_falsy_conditions(enacted_policy, bob, ursulas, mocker):
|
||||
from nucypher_core import MessageKit
|
||||
|
||||
reencrypt_spy = mocker.spy(Ursula, '_reencrypt')
|
||||
|
@ -57,22 +53,15 @@ def test_single_retrieve_with_falsy_conditions(enacted_federated_policy, federat
|
|||
[{'returnValueTest': {'value': '0', 'comparator': '>'}, 'method': 'timelock'}]
|
||||
))
|
||||
|
||||
federated_bob.start_learning_loop()
|
||||
bob.start_learning_loop()
|
||||
|
||||
message_kits = [
|
||||
MessageKit(
|
||||
enacted_federated_policy.public_key,
|
||||
b'radio',
|
||||
conditions
|
||||
)
|
||||
]
|
||||
message_kits = [MessageKit(enacted_policy.public_key, b"radio", conditions)]
|
||||
|
||||
with pytest.raises(Ursula.NotEnoughUrsulas):
|
||||
federated_bob.retrieve_and_decrypt(
|
||||
bob.retrieve_and_decrypt(
|
||||
message_kits=message_kits,
|
||||
**_policy_info_kwargs(enacted_federated_policy),
|
||||
**_policy_info_kwargs(enacted_policy),
|
||||
)
|
||||
|
||||
reencrypt_spy.assert_not_called()
|
||||
assert isinstance(reencrypt_http_spy.spy_exception, MockRestMiddleware.Unauthorized)
|
||||
|
||||
|
|
|
@ -5,34 +5,35 @@ import datetime
|
|||
|
||||
import maya
|
||||
import pytest
|
||||
|
||||
from nucypher_core import EncryptedKeyFrag, RevocationOrder
|
||||
|
||||
from nucypher.characters.lawful import Enrico
|
||||
from nucypher.crypto.utils import keccak_digest
|
||||
|
||||
|
||||
def test_federated_grant(federated_alice, federated_bob, federated_ursulas):
|
||||
def test_grant(alice, bob, ursulas):
|
||||
# Setup the policy details
|
||||
threshold, shares = 2, 3
|
||||
policy_end_datetime = maya.now() + datetime.timedelta(days=5)
|
||||
label = b"this_is_the_path_to_which_access_is_being_granted"
|
||||
|
||||
# Create the Policy, granting access to Bob
|
||||
policy = federated_alice.grant(federated_bob, label, threshold=threshold, shares=shares, expiration=policy_end_datetime)
|
||||
policy = alice.grant(
|
||||
bob, label, threshold=threshold, shares=shares, expiration=policy_end_datetime
|
||||
)
|
||||
|
||||
# Check Alice's active policies
|
||||
assert policy.hrac in federated_alice.active_policies
|
||||
assert federated_alice.active_policies[policy.hrac] == policy
|
||||
assert policy.hrac in alice.active_policies
|
||||
assert alice.active_policies[policy.hrac] == policy
|
||||
|
||||
treasure_map = federated_bob._decrypt_treasure_map(policy.treasure_map,
|
||||
policy.publisher_verifying_key)
|
||||
treasure_map = bob._decrypt_treasure_map(
|
||||
policy.treasure_map, policy.publisher_verifying_key
|
||||
)
|
||||
|
||||
# The number of map destinations is exactly equal to shares.
|
||||
assert len(treasure_map.destinations) == shares
|
||||
|
||||
# Let's look at the destinations.
|
||||
for ursula in federated_ursulas:
|
||||
for ursula in ursulas:
|
||||
if ursula.canonical_address in treasure_map.destinations:
|
||||
kfrag_kit = treasure_map.destinations[ursula.canonical_address]
|
||||
|
||||
|
@ -41,7 +42,7 @@ def test_federated_grant(federated_alice, federated_bob, federated_ursulas):
|
|||
assert isinstance(kfrag_kit, EncryptedKeyFrag)
|
||||
|
||||
|
||||
def test_federated_alice_can_decrypt(federated_alice, federated_bob):
|
||||
def test_alice_can_decrypt(alice, bob):
|
||||
"""
|
||||
Test that alice can decrypt data encrypted by an enrico
|
||||
for her own derived policy pubkey.
|
||||
|
@ -52,8 +53,8 @@ def test_federated_alice_can_decrypt(federated_alice, federated_bob):
|
|||
policy_end_datetime = maya.now() + datetime.timedelta(days=5)
|
||||
label = b"this_is_the_path_to_which_access_is_being_granted"
|
||||
|
||||
policy = federated_alice.create_policy(
|
||||
bob=federated_bob,
|
||||
policy = alice.create_policy(
|
||||
bob=bob,
|
||||
label=label,
|
||||
threshold=threshold,
|
||||
shares=shares,
|
||||
|
@ -61,7 +62,7 @@ def test_federated_alice_can_decrypt(federated_alice, federated_bob):
|
|||
)
|
||||
|
||||
enrico = Enrico.from_alice(
|
||||
federated_alice,
|
||||
alice,
|
||||
policy.label,
|
||||
)
|
||||
plaintext = b"this is the first thing i'm encrypting ever."
|
||||
|
@ -70,7 +71,7 @@ def test_federated_alice_can_decrypt(federated_alice, federated_bob):
|
|||
message_kit = enrico.encrypt_message(plaintext)
|
||||
|
||||
# decrypt the data
|
||||
decrypted_data = federated_alice.decrypt_message_kit(
|
||||
decrypted_data = alice.decrypt_message_kit(
|
||||
label=policy.label,
|
||||
message_kit=message_kit,
|
||||
)
|
||||
|
@ -79,20 +80,22 @@ def test_federated_alice_can_decrypt(federated_alice, federated_bob):
|
|||
|
||||
|
||||
@pytest.mark.skip("Needs rework post-TMcKF") # TODO: Implement offchain revocation.
|
||||
@pytest.mark.usefixtures('federated_ursulas')
|
||||
def test_revocation(federated_alice, federated_bob):
|
||||
@pytest.mark.usefixtures("bursulas")
|
||||
def test_revocation(alice, bob):
|
||||
threshold, shares = 2, 3
|
||||
policy_end_datetime = maya.now() + datetime.timedelta(days=5)
|
||||
label = b"revocation test"
|
||||
|
||||
policy = federated_alice.grant(federated_bob, label, threshold=threshold, shares=shares, expiration=policy_end_datetime)
|
||||
policy = alice.grant(
|
||||
bob, label, threshold=threshold, shares=shares, expiration=policy_end_datetime
|
||||
)
|
||||
|
||||
for node_id, encrypted_kfrag in policy.treasure_map:
|
||||
assert policy.revocation_kit[node_id]
|
||||
|
||||
# Test revocation kit's signatures
|
||||
for revocation in policy.revocation_kit:
|
||||
assert revocation.verify_signature(federated_alice.stamp.as_umbral_pubkey())
|
||||
assert revocation.verify_signature(alice.stamp.as_umbral_pubkey())
|
||||
|
||||
# Test Revocation deserialization
|
||||
revocation = policy.revocation_kit[node_id]
|
||||
|
@ -101,9 +104,9 @@ def test_revocation(federated_alice, federated_bob):
|
|||
assert deserialized_revocation == revocation
|
||||
|
||||
# Attempt to revoke the new policy
|
||||
receipt, failed_revocations = federated_alice.revoke(policy)
|
||||
receipt, failed_revocations = alice.revoke(policy)
|
||||
assert len(failed_revocations) == 0
|
||||
|
||||
# Try to revoke the already revoked policy
|
||||
receipt, already_revoked = federated_alice.revoke(policy)
|
||||
receipt, already_revoked = alice.revoke(policy)
|
||||
assert len(already_revoked) == 3
|
||||
|
|
|
@ -1,11 +1,4 @@
|
|||
|
||||
import pytest
|
||||
|
||||
from tests.utils.middleware import MockRestMiddleware
|
||||
from tests.utils.ursula import make_federated_ursulas
|
||||
|
||||
|
||||
def test_new_federated_ursula_announces_herself(lonely_ursula_maker):
|
||||
def test_new_ursula_announces_herself(lonely_ursula_maker):
|
||||
ursula_in_a_house, ursula_with_a_mouse = lonely_ursula_maker(quantity=2, domain="useless_domain")
|
||||
|
||||
# Neither Ursula knows about the other.
|
||||
|
@ -25,8 +18,8 @@ def test_new_federated_ursula_announces_herself(lonely_ursula_maker):
|
|||
assert ursula_in_a_house in ursula_with_a_mouse.known_nodes
|
||||
|
||||
|
||||
def test_node_deployer(federated_ursulas):
|
||||
for ursula in federated_ursulas:
|
||||
def test_node_deployer(ursulas):
|
||||
for ursula in ursulas:
|
||||
deployer = ursula.get_deployer()
|
||||
assert deployer.options['https_port'] == ursula.rest_information()[0].port
|
||||
assert deployer.application == ursula.rest_app
|
||||
|
|
|
@ -7,23 +7,24 @@ from constant_sorrow.constants import NO_PASSWORD
|
|||
from mnemonic.mnemonic import Mnemonic
|
||||
|
||||
from nucypher.blockchain.eth.decorators import InvalidChecksumAddress
|
||||
from nucypher.utilities.emitters import StdoutEmitter
|
||||
from nucypher.cli.actions.auth import (
|
||||
get_client_password,
|
||||
get_nucypher_password,
|
||||
get_password_from_prompt,
|
||||
unlock_nucypher_keystore
|
||||
unlock_nucypher_keystore,
|
||||
)
|
||||
from nucypher.cli.literature import (
|
||||
COLLECT_ETH_PASSWORD,
|
||||
COLLECT_NUCYPHER_PASSWORD,
|
||||
DECRYPTING_CHARACTER_KEYSTORE,
|
||||
GENERIC_PASSWORD_PROMPT
|
||||
GENERIC_PASSWORD_PROMPT,
|
||||
REPEAT_FOR_CONFIRMATION,
|
||||
)
|
||||
from nucypher.config.base import CharacterConfiguration
|
||||
from nucypher.crypto import passwords
|
||||
from nucypher.crypto.keystore import Keystore
|
||||
from nucypher.crypto.passwords import SecretBoxAuthenticationError
|
||||
from nucypher.utilities.emitters import StdoutEmitter
|
||||
from tests.constants import INSECURE_DEVELOPMENT_PASSWORD
|
||||
|
||||
|
||||
|
@ -42,7 +43,7 @@ def test_get_password_from_prompt_cli_action(mocker, mock_stdin, confirm, capsys
|
|||
captured = capsys.readouterr()
|
||||
assert GENERIC_PASSWORD_PROMPT in captured.out
|
||||
if confirm:
|
||||
assert "Repeat for confirmation:" in captured.out
|
||||
assert REPEAT_FOR_CONFIRMATION in captured.out
|
||||
|
||||
# From env var
|
||||
mocker.patch.dict(os.environ, {test_envvar: another_password})
|
||||
|
@ -73,7 +74,7 @@ def test_get_client_password(mock_stdin, mock_account, confirm, capsys):
|
|||
captured = capsys.readouterr()
|
||||
assert message in captured.out
|
||||
if confirm:
|
||||
assert "Repeat for confirmation:" in captured.out
|
||||
assert REPEAT_FOR_CONFIRMATION in captured.out
|
||||
|
||||
|
||||
@pytest.mark.parametrize('confirm', (True, False))
|
||||
|
@ -89,12 +90,14 @@ def test_get_nucypher_password(mock_stdin, mock_account, confirm, capsys):
|
|||
assert prompt in captured.out
|
||||
|
||||
|
||||
def test_unlock_nucypher_keystore_invalid_password(mocker,
|
||||
test_emitter,
|
||||
alice_blockchain_test_config,
|
||||
capsys,
|
||||
tmpdir,
|
||||
test_registry_source_manager):
|
||||
def test_unlock_nucypher_keystore_invalid_password(
|
||||
mocker,
|
||||
test_emitter,
|
||||
alice_test_config,
|
||||
capsys,
|
||||
tmpdir,
|
||||
test_registry_source_manager,
|
||||
):
|
||||
|
||||
# Setup
|
||||
mocker.patch.object(passwords, 'secret_box_decrypt', side_effect=SecretBoxAuthenticationError)
|
||||
|
@ -103,19 +106,26 @@ def test_unlock_nucypher_keystore_invalid_password(mocker,
|
|||
return_value=False,
|
||||
new_callable=mocker.PropertyMock)
|
||||
keystore = Keystore.generate(password=INSECURE_DEVELOPMENT_PASSWORD, keystore_dir=tmpdir)
|
||||
alice_blockchain_test_config.attach_keystore(keystore)
|
||||
alice_test_config.attach_keystore(keystore)
|
||||
|
||||
# Test
|
||||
with pytest.raises(Keystore.AuthenticationFailed):
|
||||
unlock_nucypher_keystore(emitter=test_emitter,
|
||||
password=INSECURE_DEVELOPMENT_PASSWORD+'typo',
|
||||
character_configuration=alice_blockchain_test_config)
|
||||
unlock_nucypher_keystore(
|
||||
emitter=test_emitter,
|
||||
password=INSECURE_DEVELOPMENT_PASSWORD + "typo",
|
||||
character_configuration=alice_test_config,
|
||||
)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert DECRYPTING_CHARACTER_KEYSTORE.format(name=alice_blockchain_test_config.NAME.capitalize()) in captured.out
|
||||
assert (
|
||||
DECRYPTING_CHARACTER_KEYSTORE.format(name=alice_test_config.NAME.capitalize())
|
||||
in captured.out
|
||||
)
|
||||
|
||||
|
||||
def test_unlock_nucypher_keystore_dev_mode(mocker, test_emitter, capsys, alice_blockchain_test_config, tmpdir):
|
||||
def test_unlock_nucypher_keystore_dev_mode(
|
||||
mocker, test_emitter, capsys, alice_test_config, tmpdir
|
||||
):
|
||||
|
||||
# Setup
|
||||
unlock_spy = mocker.spy(Keystore, 'unlock')
|
||||
|
@ -124,26 +134,27 @@ def test_unlock_nucypher_keystore_dev_mode(mocker, test_emitter, capsys, alice_b
|
|||
return_value=True,
|
||||
new_callable=mocker.PropertyMock)
|
||||
keystore = Keystore.generate(password=INSECURE_DEVELOPMENT_PASSWORD, keystore_dir=tmpdir)
|
||||
alice_blockchain_test_config.attach_keystore(keystore)
|
||||
alice_test_config.attach_keystore(keystore)
|
||||
|
||||
result = unlock_nucypher_keystore(emitter=test_emitter,
|
||||
password=INSECURE_DEVELOPMENT_PASSWORD,
|
||||
character_configuration=alice_blockchain_test_config)
|
||||
result = unlock_nucypher_keystore(
|
||||
emitter=test_emitter,
|
||||
password=INSECURE_DEVELOPMENT_PASSWORD,
|
||||
character_configuration=alice_test_config,
|
||||
)
|
||||
|
||||
assert result
|
||||
output = capsys.readouterr().out
|
||||
message = DECRYPTING_CHARACTER_KEYSTORE.format(name=alice_blockchain_test_config.NAME.capitalize())
|
||||
message = DECRYPTING_CHARACTER_KEYSTORE.format(
|
||||
name=alice_test_config.NAME.capitalize()
|
||||
)
|
||||
assert message in output
|
||||
|
||||
unlock_spy.assert_not_called()
|
||||
|
||||
|
||||
def test_unlock_nucypher_keystore(mocker,
|
||||
test_emitter,
|
||||
capsys,
|
||||
alice_blockchain_test_config,
|
||||
patch_keystore,
|
||||
tmpdir):
|
||||
def test_unlock_nucypher_keystore(
|
||||
mocker, test_emitter, capsys, alice_test_config, patch_keystore, tmpdir
|
||||
):
|
||||
|
||||
# Setup
|
||||
# Do not test "real" unlocking here, just the plumbing
|
||||
|
@ -154,15 +165,19 @@ def test_unlock_nucypher_keystore(mocker,
|
|||
new_callable=mocker.PropertyMock)
|
||||
mocker.patch.object(Mnemonic, 'detect_language', return_value='english')
|
||||
keystore = Keystore.generate(password=INSECURE_DEVELOPMENT_PASSWORD, keystore_dir=tmpdir)
|
||||
alice_blockchain_test_config.attach_keystore(keystore)
|
||||
alice_test_config.attach_keystore(keystore)
|
||||
|
||||
result = unlock_nucypher_keystore(emitter=test_emitter,
|
||||
password=INSECURE_DEVELOPMENT_PASSWORD,
|
||||
character_configuration=alice_blockchain_test_config)
|
||||
result = unlock_nucypher_keystore(
|
||||
emitter=test_emitter,
|
||||
password=INSECURE_DEVELOPMENT_PASSWORD,
|
||||
character_configuration=alice_test_config,
|
||||
)
|
||||
|
||||
assert result
|
||||
captured = capsys.readouterr()
|
||||
message = DECRYPTING_CHARACTER_KEYSTORE.format(name=alice_blockchain_test_config.NAME.capitalize())
|
||||
message = DECRYPTING_CHARACTER_KEYSTORE.format(
|
||||
name=alice_test_config.NAME.capitalize()
|
||||
)
|
||||
assert message in captured.out
|
||||
|
||||
unlock_spy.assert_called_once_with(password=INSECURE_DEVELOPMENT_PASSWORD)
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
from nucypher.cli.actions import configure
|
||||
from nucypher.cli.actions.configure import (
|
||||
|
@ -10,18 +9,19 @@ from nucypher.cli.actions.configure import (
|
|||
forget,
|
||||
get_or_update_configuration,
|
||||
handle_invalid_configuration_file,
|
||||
handle_missing_configuration_file
|
||||
handle_missing_configuration_file,
|
||||
)
|
||||
from nucypher.cli.literature import (
|
||||
CONFIRM_FORGET_NODES,
|
||||
INVALID_CONFIGURATION_FILE_WARNING,
|
||||
INVALID_JSON_IN_CONFIGURATION_WARNING,
|
||||
MISSING_CONFIGURATION_FILE,
|
||||
SUCCESSFUL_DESTRUCTION,
|
||||
SUCCESSFUL_UPDATE_CONFIGURATION_VALUES,
|
||||
CONFIRM_FORGET_NODES,
|
||||
SUCCESSFUL_FORGET_NODES,
|
||||
SUCCESSFUL_UPDATE_CONFIGURATION_VALUES,
|
||||
)
|
||||
from nucypher.config.base import CharacterConfiguration
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN
|
||||
from tests.constants import YES
|
||||
|
||||
BAD_CONFIG_FILE_CONTENTS = (
|
||||
|
@ -34,9 +34,9 @@ BAD_CONFIG_FILE_CONTENTS = (
|
|||
|
||||
# For parameterized fixture
|
||||
CONFIGS = [
|
||||
'alice_blockchain_test_config',
|
||||
'bob_blockchain_test_config',
|
||||
'ursula_decentralized_test_config',
|
||||
"alice_test_config",
|
||||
"bob_test_config",
|
||||
"ursula_test_config",
|
||||
]
|
||||
|
||||
|
||||
|
@ -67,10 +67,10 @@ def config(request, mocker):
|
|||
mocker.resetall() # dont carry over context between functions
|
||||
|
||||
|
||||
def test_forget_cli_action(alice_blockchain_test_config, test_emitter, mock_stdin, mocker, capsys):
|
||||
def test_forget_cli_action(alice_test_config, test_emitter, mock_stdin, mocker, capsys):
|
||||
mock_forget = mocker.patch.object(CharacterConfiguration, 'forget_nodes')
|
||||
mock_stdin.line(YES)
|
||||
forget(emitter=test_emitter, configuration=alice_blockchain_test_config)
|
||||
forget(emitter=test_emitter, configuration=alice_test_config)
|
||||
mock_forget.assert_called_once()
|
||||
assert mock_stdin.empty()
|
||||
captured = capsys.readouterr()
|
||||
|
@ -80,13 +80,13 @@ def test_forget_cli_action(alice_blockchain_test_config, test_emitter, mock_stdi
|
|||
|
||||
def test_update_configuration_cli_action(config, test_emitter, test_registry_source_manager, capsys):
|
||||
config_class, config_file = config.__class__, config.filepath
|
||||
updates = dict(federated_only=True)
|
||||
updates = dict(domain=TEMPORARY_DOMAIN)
|
||||
get_or_update_configuration(emitter=test_emitter, config_class=config_class, filepath=config_file, updates=updates)
|
||||
config.update.assert_called_once_with(**updates)
|
||||
configure.handle_invalid_configuration_file.assert_not_called()
|
||||
configure.handle_missing_configuration_file.assert_not_called()
|
||||
captured = capsys.readouterr()
|
||||
assert SUCCESSFUL_UPDATE_CONFIGURATION_VALUES.format(fields='federated_only') in captured.out
|
||||
assert SUCCESSFUL_UPDATE_CONFIGURATION_VALUES.format(fields='domain') in captured.out
|
||||
|
||||
|
||||
def test_handle_update_missing_configuration_file_cli_action(config,
|
||||
|
@ -95,7 +95,7 @@ def test_handle_update_missing_configuration_file_cli_action(config,
|
|||
mocker):
|
||||
config_class, config_file = config.__class__, config.filepath
|
||||
mocker.patch.object(config_class, '_read_configuration_file', side_effect=FileNotFoundError)
|
||||
updates = dict(federated_only=True)
|
||||
updates = dict(domain=TEMPORARY_DOMAIN)
|
||||
with pytest.raises(click.FileError):
|
||||
get_or_update_configuration(emitter=test_emitter,
|
||||
config_class=config_class,
|
||||
|
@ -114,7 +114,7 @@ def test_handle_update_invalid_configuration_file_cli_action(config,
|
|||
config_class = config.__class__
|
||||
config_file = config.filepath
|
||||
mocker.patch.object(config_class, '_read_configuration_file', side_effect=config_class.ConfigurationError)
|
||||
updates = dict(federated_only=True)
|
||||
updates = dict(domain=TEMPORARY_DOMAIN)
|
||||
with pytest.raises(config_class.ConfigurationError):
|
||||
get_or_update_configuration(emitter=test_emitter,
|
||||
config_class=config_class,
|
||||
|
|
|
@ -3,21 +3,25 @@
|
|||
import click
|
||||
import pytest
|
||||
|
||||
from nucypher.blockchain.eth.clients import EthereumTesterClient, PUBLIC_CHAINS
|
||||
from nucypher.blockchain.eth.clients import PUBLIC_CHAINS, EthereumTesterClient
|
||||
from nucypher.cli.actions.confirm import confirm_deployment
|
||||
from nucypher.cli.literature import ABORT_DEPLOYMENT
|
||||
|
||||
|
||||
def test_confirm_deployment_cli_action(mocker, mock_stdin, test_emitter, capsys, mock_testerchain):
|
||||
mock_stdin.line('foo') # anything different from `deployer_interface.client.chain_name.upper()`
|
||||
def test_confirm_deployment_cli_action(
|
||||
mocker, mock_stdin, test_emitter, capsys, testerchain
|
||||
):
|
||||
mock_stdin.line(
|
||||
"foo"
|
||||
) # anything different from `deployer_interface.client.chain_name.upper()`
|
||||
with pytest.raises(click.Abort):
|
||||
confirm_deployment(emitter=test_emitter, deployer_interface=mock_testerchain)
|
||||
confirm_deployment(emitter=test_emitter, deployer_interface=testerchain)
|
||||
captured = capsys.readouterr()
|
||||
assert ABORT_DEPLOYMENT in captured.out
|
||||
assert mock_stdin.empty()
|
||||
|
||||
mock_stdin.line('DEPLOY') # say the magic word
|
||||
result = confirm_deployment(emitter=test_emitter, deployer_interface=mock_testerchain)
|
||||
result = confirm_deployment(emitter=test_emitter, deployer_interface=testerchain)
|
||||
assert result
|
||||
captured = capsys.readouterr()
|
||||
assert "Type 'DEPLOY' to continue: " in captured.out
|
||||
|
@ -36,11 +40,11 @@ def test_confirm_deployment_cli_action(mocker, mock_stdin, test_emitter, capsys,
|
|||
'chain_name',
|
||||
return_value=llamanet,
|
||||
new_callable=mocker.PropertyMock)
|
||||
mock_testerchain.client.is_local = False
|
||||
testerchain.client.is_local = False
|
||||
|
||||
mock_stdin.line('DEPLOY') # say the (wrong) magic word
|
||||
with pytest.raises(click.Abort):
|
||||
confirm_deployment(emitter=test_emitter, deployer_interface=mock_testerchain)
|
||||
confirm_deployment(emitter=test_emitter, deployer_interface=testerchain)
|
||||
assert mock_stdin.empty()
|
||||
captured = capsys.readouterr()
|
||||
assert f"Type '{llamanet.upper()}' to continue: " in captured.out
|
||||
|
@ -48,14 +52,14 @@ def test_confirm_deployment_cli_action(mocker, mock_stdin, test_emitter, capsys,
|
|||
|
||||
mock_stdin.line(llamanet) # say the (almost correct) magic word
|
||||
with pytest.raises(click.Abort):
|
||||
confirm_deployment(emitter=test_emitter, deployer_interface=mock_testerchain)
|
||||
confirm_deployment(emitter=test_emitter, deployer_interface=testerchain)
|
||||
assert mock_stdin.empty()
|
||||
captured = capsys.readouterr()
|
||||
assert f"Type '{llamanet.upper()}' to continue: " in captured.out
|
||||
assert ABORT_DEPLOYMENT in captured.out
|
||||
|
||||
mock_stdin.line(llamanet.upper()) # say the (correct, uppercase) network name
|
||||
result = confirm_deployment(emitter=test_emitter, deployer_interface=mock_testerchain)
|
||||
result = confirm_deployment(emitter=test_emitter, deployer_interface=testerchain)
|
||||
assert result
|
||||
assert mock_stdin.empty()
|
||||
captured = capsys.readouterr()
|
||||
|
|
|
@ -14,22 +14,27 @@ from nucypher.blockchain.eth.signers import KeystoreSigner
|
|||
from nucypher.blockchain.eth.signers.software import Web3Signer
|
||||
from nucypher.blockchain.eth.token import NU
|
||||
from nucypher.cli.actions.select import select_client_account
|
||||
from nucypher.cli.literature import (
|
||||
NO_ETH_ACCOUNTS,
|
||||
GENERIC_SELECT_ACCOUNT,
|
||||
)
|
||||
from nucypher.cli.literature import GENERIC_SELECT_ACCOUNT, NO_ETH_ACCOUNTS
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN
|
||||
from tests.constants import MOCK_SIGNER_URI, NUMBER_OF_ETH_TEST_ACCOUNTS, MOCK_ETH_PROVIDER_URI
|
||||
from tests.constants import (
|
||||
MOCK_ETH_PROVIDER_URI,
|
||||
MOCK_SIGNER_URI,
|
||||
NUMBER_OF_ETH_TEST_ACCOUNTS,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('selection', range(NUMBER_OF_ETH_TEST_ACCOUNTS))
|
||||
def test_select_client_account(mock_stdin, test_emitter, mock_testerchain, selection, capsys):
|
||||
@pytest.mark.parametrize("selection", range(NUMBER_OF_ETH_TEST_ACCOUNTS))
|
||||
def test_select_client_account(
|
||||
mock_stdin, test_emitter, testerchain, selection, capsys
|
||||
):
|
||||
"""Fine-grained assertions about the return value of interactive client account selection"""
|
||||
mock_stdin.line(str(selection))
|
||||
expected_account = mock_testerchain.client.accounts[selection]
|
||||
selected_account = select_client_account(emitter=test_emitter,
|
||||
signer=Web3Signer(mock_testerchain.client),
|
||||
eth_provider_uri=MOCK_ETH_PROVIDER_URI)
|
||||
expected_account = testerchain.client.accounts[selection]
|
||||
selected_account = select_client_account(
|
||||
emitter=test_emitter,
|
||||
signer=Web3Signer(testerchain.client),
|
||||
eth_provider_uri=MOCK_ETH_PROVIDER_URI,
|
||||
)
|
||||
assert selected_account, "Account selection returned Falsy instead of an address"
|
||||
assert isinstance(selected_account, str), "Selection is not a str"
|
||||
assert is_checksum_address(selected_account), "Selection is not a valid checksum address"
|
||||
|
@ -39,23 +44,27 @@ def test_select_client_account(mock_stdin, test_emitter, mock_testerchain, selec
|
|||
assert GENERIC_SELECT_ACCOUNT in captured.out
|
||||
|
||||
|
||||
def test_select_client_account_with_no_accounts(mocker,
|
||||
mock_stdin, # used to assert the user was not prompted
|
||||
test_emitter,
|
||||
mock_testerchain,
|
||||
capsys):
|
||||
mocker.patch.object(EthereumClient, 'accounts', return_value=[])
|
||||
def test_select_client_account_with_no_accounts(
|
||||
mocker,
|
||||
mock_stdin, # used to assert the user was not prompted
|
||||
test_emitter,
|
||||
testerchain,
|
||||
capsys,
|
||||
):
|
||||
mocker.patch.object(EthereumClient, "accounts", return_value=[])
|
||||
with pytest.raises(click.Abort):
|
||||
select_client_account(emitter=test_emitter,
|
||||
signer=Web3Signer(mock_testerchain.client),
|
||||
eth_provider_uri=MOCK_ETH_PROVIDER_URI)
|
||||
select_client_account(
|
||||
emitter=test_emitter,
|
||||
signer=Web3Signer(testerchain.client),
|
||||
eth_provider_uri=MOCK_ETH_PROVIDER_URI,
|
||||
)
|
||||
captured = capsys.readouterr()
|
||||
assert NO_ETH_ACCOUNTS in captured.out
|
||||
|
||||
|
||||
def test_select_client_account_ambiguous_source(mock_stdin, # used to assert the user was not prompted
|
||||
test_emitter,
|
||||
mock_testerchain):
|
||||
def test_select_client_account_ambiguous_source(
|
||||
mock_stdin, test_emitter, testerchain # used to assert the user was not prompted
|
||||
):
|
||||
|
||||
#
|
||||
# Implicit wallet # TODO: Are all cases covered?
|
||||
|
@ -70,21 +79,27 @@ def test_select_client_account_ambiguous_source(mock_stdin, # used to assert th
|
|||
select_client_account(emitter=test_emitter, signer=Mock(), signer_uri=MOCK_SIGNER_URI)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('selection', range(NUMBER_OF_ETH_TEST_ACCOUNTS))
|
||||
def test_select_client_account_valid_sources(mocker,
|
||||
mock_stdin,
|
||||
test_emitter,
|
||||
mock_testerchain,
|
||||
patch_keystore,
|
||||
mock_accounts,
|
||||
selection,
|
||||
capsys):
|
||||
@pytest.mark.parametrize("selection", range(NUMBER_OF_ETH_TEST_ACCOUNTS))
|
||||
def test_select_client_account_valid_sources(
|
||||
mocker,
|
||||
mock_stdin,
|
||||
test_emitter,
|
||||
testerchain,
|
||||
patch_keystore,
|
||||
mock_accounts,
|
||||
selection,
|
||||
capsys,
|
||||
):
|
||||
|
||||
# From External Signer
|
||||
mock_stdin.line(str(selection))
|
||||
mock_signer = mocker.patch.object(KeystoreSigner, 'from_signer_uri', return_value=Web3Signer(mock_testerchain.client))
|
||||
selected_account = select_client_account(emitter=test_emitter, signer_uri=MOCK_SIGNER_URI)
|
||||
expected_account = mock_testerchain.client.accounts[selection]
|
||||
mock_signer = mocker.patch.object(
|
||||
KeystoreSigner, "from_signer_uri", return_value=Web3Signer(testerchain.client)
|
||||
)
|
||||
selected_account = select_client_account(
|
||||
emitter=test_emitter, signer_uri=MOCK_SIGNER_URI
|
||||
)
|
||||
expected_account = testerchain.client.accounts[selection]
|
||||
assert selected_account == expected_account
|
||||
mock_signer.assert_called_once_with(uri=MOCK_SIGNER_URI, testnet=True)
|
||||
assert mock_stdin.empty()
|
||||
|
@ -93,8 +108,10 @@ def test_select_client_account_valid_sources(mocker,
|
|||
|
||||
# From Wallet
|
||||
mock_stdin.line(str(selection))
|
||||
expected_account = mock_testerchain.client.accounts[selection]
|
||||
selected_account = select_client_account(emitter=test_emitter, signer=Web3Signer(mock_testerchain.client))
|
||||
expected_account = testerchain.client.accounts[selection]
|
||||
selected_account = select_client_account(
|
||||
emitter=test_emitter, signer=Web3Signer(testerchain.client)
|
||||
)
|
||||
assert selected_account == expected_account
|
||||
assert mock_stdin.empty()
|
||||
captured = capsys.readouterr()
|
||||
|
@ -102,7 +119,7 @@ def test_select_client_account_valid_sources(mocker,
|
|||
|
||||
# From pre-initialized Provider
|
||||
mock_stdin.line(str(selection))
|
||||
expected_account = mock_testerchain.client.accounts[selection]
|
||||
expected_account = testerchain.client.accounts[selection]
|
||||
selected_account = select_client_account(emitter=test_emitter, eth_provider_uri=MOCK_ETH_PROVIDER_URI)
|
||||
assert selected_account == expected_account
|
||||
assert mock_stdin.empty()
|
||||
|
@ -111,10 +128,16 @@ def test_select_client_account_valid_sources(mocker,
|
|||
|
||||
# From uninitialized Provider
|
||||
mock_stdin.line(str(selection))
|
||||
mocker.patch.object(BlockchainInterfaceFactory, 'is_interface_initialized', return_value=False)
|
||||
mocker.patch.object(BlockchainInterfaceFactory, '_interfaces', return_value={})
|
||||
mocker.patch.object(BlockchainInterfaceFactory, 'get_interface', return_value=mock_testerchain)
|
||||
selected_account = select_client_account(emitter=test_emitter, eth_provider_uri=MOCK_ETH_PROVIDER_URI)
|
||||
mocker.patch.object(
|
||||
BlockchainInterfaceFactory, "is_interface_initialized", return_value=False
|
||||
)
|
||||
mocker.patch.object(BlockchainInterfaceFactory, "_interfaces", return_value={})
|
||||
mocker.patch.object(
|
||||
BlockchainInterfaceFactory, "get_interface", return_value=testerchain
|
||||
)
|
||||
selected_account = select_client_account(
|
||||
emitter=test_emitter, eth_provider_uri=MOCK_ETH_PROVIDER_URI
|
||||
)
|
||||
assert selected_account == expected_account
|
||||
assert mock_stdin.empty()
|
||||
captured = capsys.readouterr()
|
||||
|
@ -130,19 +153,22 @@ def test_select_client_account_valid_sources(mocker,
|
|||
(0, False, True, True, []),
|
||||
(0, False, False, True, []),
|
||||
(0, False, False, False, []),
|
||||
))
|
||||
def test_select_client_account_with_balance_display(mock_stdin,
|
||||
test_emitter,
|
||||
mock_testerchain,
|
||||
capsys,
|
||||
test_registry_source_manager,
|
||||
mock_staking_agent,
|
||||
mock_token_agent,
|
||||
selection,
|
||||
show_staking,
|
||||
show_eth,
|
||||
show_tokens,
|
||||
stake_info):
|
||||
),
|
||||
)
|
||||
def test_select_client_account_with_balance_display(
|
||||
mock_stdin,
|
||||
test_emitter,
|
||||
testerchain,
|
||||
capsys,
|
||||
test_registry_source_manager,
|
||||
mock_staking_agent,
|
||||
mock_token_agent,
|
||||
selection,
|
||||
show_staking,
|
||||
show_eth,
|
||||
show_tokens,
|
||||
stake_info,
|
||||
):
|
||||
|
||||
# Setup
|
||||
mock_staking_agent.get_all_stakes.return_value = stake_info
|
||||
|
@ -167,7 +193,7 @@ def test_select_client_account_with_balance_display(mock_stdin,
|
|||
eth_provider_uri=MOCK_ETH_PROVIDER_URI)
|
||||
|
||||
# check for accurate selection consistency with client index
|
||||
assert selected_account == mock_testerchain.client.accounts[selection]
|
||||
assert selected_account == testerchain.client.accounts[selection]
|
||||
assert mock_stdin.empty()
|
||||
|
||||
# Display account info
|
||||
|
@ -183,7 +209,7 @@ def test_select_client_account_with_balance_display(mock_stdin,
|
|||
for column_name in headers:
|
||||
assert column_name in captured.out, f'"{column_name}" column was not displayed'
|
||||
|
||||
for account in mock_testerchain.client.accounts:
|
||||
for account in testerchain.client.accounts:
|
||||
assert account in captured.out
|
||||
|
||||
if show_tokens:
|
||||
|
@ -191,7 +217,7 @@ def test_select_client_account_with_balance_display(mock_stdin,
|
|||
assert str(NU.from_units(balance)) in captured.out
|
||||
|
||||
if show_eth:
|
||||
balance = mock_testerchain.client.get_balance(account=account)
|
||||
balance = testerchain.client.get_balance(account=account)
|
||||
assert str(Web3.from_wei(balance, 'ether')) in captured.out
|
||||
|
||||
if show_staking:
|
||||
|
|
|
@ -1,23 +1,24 @@
|
|||
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
import pytest
|
||||
|
||||
from nucypher.cli.actions.select import select_config_file
|
||||
from nucypher.cli.literature import NO_CONFIGURATIONS_ON_DISK, DEFAULT_TO_LONE_CONFIG_FILE
|
||||
from nucypher.cli.literature import (
|
||||
DEFAULT_TO_LONE_CONFIG_FILE,
|
||||
NO_CONFIGURATIONS_ON_DISK,
|
||||
)
|
||||
|
||||
|
||||
def test_select_config_file_with_no_config_files(test_emitter,
|
||||
capsys,
|
||||
alice_blockchain_test_config,
|
||||
temp_dir_path):
|
||||
def test_select_config_file_with_no_config_files(
|
||||
test_emitter, capsys, alice_test_config, temp_dir_path
|
||||
):
|
||||
|
||||
# Setup
|
||||
config_class = alice_blockchain_test_config
|
||||
config_class = alice_test_config
|
||||
|
||||
# Prove there are no config files on the disk.
|
||||
assert not list(temp_dir_path.iterdir())
|
||||
|
@ -33,14 +34,12 @@ def test_select_config_file_with_no_config_files(test_emitter,
|
|||
assert message in captured.out
|
||||
|
||||
|
||||
def test_auto_select_config_file(test_emitter,
|
||||
capsys,
|
||||
alice_blockchain_test_config,
|
||||
temp_dir_path,
|
||||
mock_stdin):
|
||||
def test_auto_select_config_file(
|
||||
test_emitter, capsys, alice_test_config, temp_dir_path, mock_stdin
|
||||
):
|
||||
"""Only one configuration was found, so it was chosen automatically"""
|
||||
|
||||
config_class = alice_blockchain_test_config
|
||||
config_class = alice_test_config
|
||||
config_path = temp_dir_path / config_class.generate_filename()
|
||||
|
||||
# Make one configuration
|
||||
|
@ -63,18 +62,20 @@ def test_auto_select_config_file(test_emitter,
|
|||
config_file=str(config_path)) in captured.out
|
||||
|
||||
|
||||
def test_interactive_select_config_file(test_emitter,
|
||||
capsys,
|
||||
alice_blockchain_test_config,
|
||||
temp_dir_path,
|
||||
mock_stdin,
|
||||
mock_accounts,
|
||||
patch_keystore):
|
||||
def test_interactive_select_config_file(
|
||||
test_emitter,
|
||||
capsys,
|
||||
alice_test_config,
|
||||
temp_dir_path,
|
||||
mock_stdin,
|
||||
mock_accounts,
|
||||
patch_keystore,
|
||||
):
|
||||
|
||||
"""Multiple configurations found - Prompt the user for a selection"""
|
||||
|
||||
user_input = 0
|
||||
config = alice_blockchain_test_config
|
||||
config = alice_test_config
|
||||
config_class = config.__class__
|
||||
|
||||
# Make one configuration...
|
||||
|
|
|
@ -5,15 +5,19 @@ import pytest
|
|||
from eth_typing import ChecksumAddress
|
||||
|
||||
from nucypher.blockchain.eth.constants import NULL_ADDRESS
|
||||
from nucypher.cli.commands.bond import unbond, bond
|
||||
from nucypher.cli.literature import UNEXPECTED_HUMAN_OPERATOR, BONDING_TIME, ALREADY_BONDED
|
||||
from nucypher.cli.commands.bond import bond, unbond
|
||||
from nucypher.cli.literature import (
|
||||
ALREADY_BONDED,
|
||||
BONDING_TIME,
|
||||
UNEXPECTED_HUMAN_OPERATOR,
|
||||
)
|
||||
from nucypher.config.constants import (
|
||||
NUCYPHER_ENVVAR_STAKING_PROVIDER_ETH_PASSWORD,
|
||||
TEMPORARY_DOMAIN,
|
||||
NUCYPHER_ENVVAR_STAKING_PROVIDER_ETH_PASSWORD
|
||||
)
|
||||
from nucypher.crypto.powers import TransactingPower
|
||||
from nucypher.types import StakingProviderInfo
|
||||
from tests.constants import TEST_ETH_PROVIDER_URI, INSECURE_DEVELOPMENT_PASSWORD
|
||||
from tests.constants import INSECURE_DEVELOPMENT_PASSWORD, TEST_ETH_PROVIDER_URI
|
||||
|
||||
cli_env = {NUCYPHER_ENVVAR_STAKING_PROVIDER_ETH_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD}
|
||||
|
||||
|
@ -24,17 +28,17 @@ def mock_transacting_power(module_mocker):
|
|||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def operator_address(mock_testerchain):
|
||||
return mock_testerchain.unassigned_accounts[1]
|
||||
def operator_address(testerchain):
|
||||
return testerchain.unassigned_accounts[1]
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
@pytest.mark.usefixtures('test_registry_source_manager', 'mock_contract_agency')
|
||||
def staking_provider_address(mock_testerchain):
|
||||
return mock_testerchain.unassigned_accounts[2]
|
||||
def staking_provider_address(testerchain):
|
||||
return testerchain.unassigned_accounts[2]
|
||||
|
||||
|
||||
def test_nucypher_bond_help(click_runner, mock_testerchain):
|
||||
def test_nucypher_bond_help(click_runner, testerchain):
|
||||
command = '--help'
|
||||
result = click_runner.invoke(bond, command, catch_exceptions=False)
|
||||
assert result.exit_code == 0
|
||||
|
@ -63,8 +67,16 @@ def exec_unbond(click_runner, staking_provider_address: ChecksumAddress):
|
|||
return result
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('test_registry_source_manager', 'mock_contract_agency', 'patch_keystore')
|
||||
def test_nucypher_bond_unauthorized(click_runner, mock_testerchain, operator_address, staking_provider_address, mock_application_agent):
|
||||
@pytest.mark.usefixtures(
|
||||
"test_registry_source_manager", "mock_contract_agency", "patch_keystore"
|
||||
)
|
||||
def test_nucypher_bond_unauthorized(
|
||||
click_runner,
|
||||
testerchain,
|
||||
operator_address,
|
||||
staking_provider_address,
|
||||
mock_application_agent,
|
||||
):
|
||||
|
||||
mock_application_agent.is_authorized.return_value = False
|
||||
mock_application_agent.get_staking_provider_info.return_value = StakingProviderInfo(
|
||||
|
@ -83,16 +95,28 @@ def test_nucypher_bond_unauthorized(click_runner, mock_testerchain, operator_add
|
|||
assert error_message in result.output
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('test_registry_source_manager', 'mock_contract_agency', 'test_registry')
|
||||
def test_nucypher_unexpected_beneficiary(click_runner, mock_testerchain, operator_address, staking_provider_address, mock_application_agent):
|
||||
@pytest.mark.usefixtures(
|
||||
"test_registry_source_manager", "mock_contract_agency", "test_registry"
|
||||
)
|
||||
def test_nucypher_unexpected_beneficiary(
|
||||
click_runner,
|
||||
testerchain,
|
||||
operator_address,
|
||||
staking_provider_address,
|
||||
mock_application_agent,
|
||||
):
|
||||
|
||||
mock_application_agent.get_staking_provider_info.return_value = StakingProviderInfo(
|
||||
operator=NULL_ADDRESS,
|
||||
operator_confirmed=False,
|
||||
operator_start_timestamp=1
|
||||
)
|
||||
mock_application_agent.get_beneficiary.return_value = mock_testerchain.unassigned_accounts[-1]
|
||||
mock_application_agent.get_staking_provider_from_operator.return_value = NULL_ADDRESS
|
||||
mock_application_agent.get_beneficiary.return_value = (
|
||||
testerchain.unassigned_accounts[-1]
|
||||
)
|
||||
mock_application_agent.get_staking_provider_from_operator.return_value = (
|
||||
NULL_ADDRESS
|
||||
)
|
||||
|
||||
result = exec_bond(
|
||||
click_runner=click_runner,
|
||||
|
@ -104,8 +128,16 @@ def test_nucypher_unexpected_beneficiary(click_runner, mock_testerchain, operato
|
|||
assert UNEXPECTED_HUMAN_OPERATOR in result.output
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('test_registry_source_manager', 'mock_contract_agency', 'test_registry')
|
||||
def test_nucypher_bond(click_runner, mock_testerchain, operator_address, staking_provider_address, mock_application_agent):
|
||||
@pytest.mark.usefixtures(
|
||||
"test_registry_source_manager", "mock_contract_agency", "test_registry"
|
||||
)
|
||||
def test_nucypher_bond(
|
||||
click_runner,
|
||||
testerchain,
|
||||
operator_address,
|
||||
staking_provider_address,
|
||||
mock_application_agent,
|
||||
):
|
||||
|
||||
mock_application_agent.get_staking_provider_info.return_value = StakingProviderInfo(
|
||||
operator=NULL_ADDRESS,
|
||||
|
@ -124,8 +156,14 @@ def test_nucypher_bond(click_runner, mock_testerchain, operator_address, staking
|
|||
assert result.exit_code == 0
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('test_registry_source_manager', 'mock_contract_agency')
|
||||
def test_nucypher_unbond_operator(click_runner, mock_testerchain, staking_provider_address, mock_application_agent, operator_address):
|
||||
@pytest.mark.usefixtures("test_registry_source_manager", "mock_contract_agency")
|
||||
def test_nucypher_unbond_operator(
|
||||
click_runner,
|
||||
testerchain,
|
||||
staking_provider_address,
|
||||
mock_application_agent,
|
||||
operator_address,
|
||||
):
|
||||
|
||||
mock_application_agent.get_staking_provider_info.return_value = StakingProviderInfo(
|
||||
operator=operator_address,
|
||||
|
@ -139,11 +177,17 @@ def test_nucypher_unbond_operator(click_runner, mock_testerchain, staking_provid
|
|||
assert result.exit_code == 0
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('test_registry_source_manager', 'mock_contract_agency')
|
||||
def test_nucypher_rebond_too_soon(click_runner, mock_testerchain, operator_address, staking_provider_address, mock_application_agent):
|
||||
@pytest.mark.usefixtures("test_registry_source_manager", "mock_contract_agency")
|
||||
def test_nucypher_rebond_too_soon(
|
||||
click_runner,
|
||||
testerchain,
|
||||
operator_address,
|
||||
staking_provider_address,
|
||||
mock_application_agent,
|
||||
):
|
||||
|
||||
min_authorized_seconds = 5
|
||||
now = mock_testerchain.get_blocktime()
|
||||
now = testerchain.get_blocktime()
|
||||
operator_start_timestamp = now
|
||||
termination = operator_start_timestamp + min_authorized_seconds
|
||||
|
||||
|
@ -164,16 +208,26 @@ def test_nucypher_rebond_too_soon(click_runner, mock_testerchain, operator_addre
|
|||
assert error_message in result.output
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('test_registry_source_manager', 'mock_contract_agency')
|
||||
def test_nucypher_bond_already_claimed_operator(click_runner, mock_testerchain, operator_address, staking_provider_address, mock_application_agent):
|
||||
@pytest.mark.usefixtures("test_registry_source_manager", "mock_contract_agency")
|
||||
def test_nucypher_bond_already_claimed_operator(
|
||||
click_runner,
|
||||
testerchain,
|
||||
operator_address,
|
||||
staking_provider_address,
|
||||
mock_application_agent,
|
||||
):
|
||||
mock_application_agent.get_staking_provider_info.return_value = StakingProviderInfo(
|
||||
operator=NULL_ADDRESS,
|
||||
operator_confirmed=False,
|
||||
operator_start_timestamp=1
|
||||
)
|
||||
mock_application_agent.get_beneficiary.return_value = NULL_ADDRESS
|
||||
mock_application_agent.get_operator_from_staking_provider.return_value = NULL_ADDRESS
|
||||
mock_application_agent.get_staking_provider_from_operator.return_value = mock_testerchain.unassigned_accounts[4]
|
||||
mock_application_agent.get_operator_from_staking_provider.return_value = (
|
||||
NULL_ADDRESS
|
||||
)
|
||||
mock_application_agent.get_staking_provider_from_operator.return_value = (
|
||||
testerchain.unassigned_accounts[4]
|
||||
)
|
||||
|
||||
result = exec_bond(
|
||||
click_runner=click_runner,
|
||||
|
@ -183,10 +237,16 @@ def test_nucypher_bond_already_claimed_operator(click_runner, mock_testerchain,
|
|||
assert result.exit_code == 1
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('test_registry_source_manager', 'mock_contract_agency')
|
||||
def test_nucypher_rebond_operator(click_runner, mock_testerchain, operator_address, staking_provider_address, mock_application_agent):
|
||||
@pytest.mark.usefixtures("test_registry_source_manager", "mock_contract_agency")
|
||||
def test_nucypher_rebond_operator(
|
||||
click_runner,
|
||||
testerchain,
|
||||
operator_address,
|
||||
staking_provider_address,
|
||||
mock_application_agent,
|
||||
):
|
||||
mock_application_agent.get_staking_provider_info.return_value = StakingProviderInfo(
|
||||
operator=mock_testerchain.unassigned_accounts[-1],
|
||||
operator=testerchain.unassigned_accounts[-1],
|
||||
operator_confirmed=False,
|
||||
operator_start_timestamp=1
|
||||
)
|
||||
|
|
|
@ -13,7 +13,7 @@ from nucypher.config.characters import UrsulaConfiguration
|
|||
from nucypher.config.constants import (
|
||||
NUCYPHER_ENVVAR_KEYSTORE_PASSWORD,
|
||||
NUCYPHER_ENVVAR_OPERATOR_ETH_PASSWORD,
|
||||
TEMPORARY_DOMAIN
|
||||
TEMPORARY_DOMAIN,
|
||||
)
|
||||
from tests.constants import MOCK_IP_ADDRESS
|
||||
from tests.utils.ursula import select_test_port
|
||||
|
@ -30,12 +30,10 @@ def mock_account_password_keystore(tmp_path_factory):
|
|||
return account, password, keystore
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('test_registry_source_manager')
|
||||
def test_ursula_init_with_local_keystore_signer(click_runner,
|
||||
temp_dir_path,
|
||||
mocker,
|
||||
mock_testerchain,
|
||||
mock_account_password_keystore):
|
||||
@pytest.mark.usefixtures("test_registry_source_manager")
|
||||
def test_ursula_init_with_local_keystore_signer(
|
||||
click_runner, temp_dir_path, mocker, testerchain, mock_account_password_keystore
|
||||
):
|
||||
custom_filepath = temp_dir_path
|
||||
custom_config_filepath = temp_dir_path / UrsulaConfiguration.generate_filename()
|
||||
worker_account, password, mock_keystore_path = mock_account_password_keystore
|
||||
|
@ -46,24 +44,31 @@ def test_ursula_init_with_local_keystore_signer(click_runner,
|
|||
|
||||
deploy_port = select_test_port()
|
||||
|
||||
init_args = ('ursula', 'init',
|
||||
|
||||
# Layer 1
|
||||
'--network', TEMPORARY_DOMAIN,
|
||||
'--eth-provider', mock_testerchain.eth_provider_uri,
|
||||
|
||||
# Layer 2
|
||||
'--payment-network', TEMPORARY_DOMAIN,
|
||||
'--payment-provider', mock_testerchain.eth_provider_uri,
|
||||
|
||||
'--rest-host', MOCK_IP_ADDRESS,
|
||||
'--rest-port', deploy_port,
|
||||
|
||||
'--operator-address', worker_account.address,
|
||||
'--config-root', str(custom_filepath.absolute()),
|
||||
|
||||
# The bit we are testing here
|
||||
'--signer', mock_signer_uri)
|
||||
init_args = (
|
||||
"ursula",
|
||||
"init",
|
||||
# Layer 1
|
||||
"--network",
|
||||
TEMPORARY_DOMAIN,
|
||||
"--eth-provider",
|
||||
testerchain.eth_provider_uri,
|
||||
# Layer 2
|
||||
"--payment-network",
|
||||
TEMPORARY_DOMAIN,
|
||||
"--payment-provider",
|
||||
testerchain.eth_provider_uri,
|
||||
"--rest-host",
|
||||
MOCK_IP_ADDRESS,
|
||||
"--rest-port",
|
||||
deploy_port,
|
||||
"--operator-address",
|
||||
worker_account.address,
|
||||
"--config-root",
|
||||
str(custom_filepath.absolute()),
|
||||
# The bit we are testing here
|
||||
"--signer",
|
||||
mock_signer_uri,
|
||||
)
|
||||
|
||||
cli_env = {
|
||||
NUCYPHER_ENVVAR_KEYSTORE_PASSWORD: password,
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
@ -13,13 +11,16 @@ from nucypher.config.base import CharacterConfiguration
|
|||
from nucypher.config.characters import (
|
||||
AliceConfiguration,
|
||||
BobConfiguration,
|
||||
UrsulaConfiguration
|
||||
UrsulaConfiguration,
|
||||
)
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN
|
||||
from nucypher.config.storages import ForgetfulNodeStorage
|
||||
from nucypher.crypto.keystore import Keystore
|
||||
from tests.constants import INSECURE_DEVELOPMENT_PASSWORD, MOCK_ETH_PROVIDER_URI
|
||||
from tests.constants import MOCK_IP_ADDRESS
|
||||
from tests.constants import (
|
||||
INSECURE_DEVELOPMENT_PASSWORD,
|
||||
MOCK_ETH_PROVIDER_URI,
|
||||
MOCK_IP_ADDRESS,
|
||||
)
|
||||
|
||||
# Main Cast
|
||||
configurations = (AliceConfiguration, BobConfiguration, UrsulaConfiguration)
|
||||
|
@ -32,17 +33,27 @@ all_configurations = tuple(configurations, )
|
|||
|
||||
|
||||
@pytest.mark.parametrize("character,configuration", characters_and_configurations)
|
||||
def test_federated_development_character_configurations(character, configuration):
|
||||
def test_development_character_configurations(
|
||||
character, configuration, test_registry_source_manager, mocker, testerchain
|
||||
):
|
||||
|
||||
config = configuration(dev_mode=True,
|
||||
federated_only=True,
|
||||
lonely=True,
|
||||
domain=TEMPORARY_DOMAIN)
|
||||
mocker.patch.object(
|
||||
CharacterConfiguration, "DEFAULT_PAYMENT_NETWORK", TEMPORARY_DOMAIN
|
||||
)
|
||||
params = dict(
|
||||
dev_mode=True,
|
||||
lonely=True,
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
checksum_address=testerchain.unassigned_accounts[0],
|
||||
eth_provider_uri=MOCK_ETH_PROVIDER_URI,
|
||||
)
|
||||
if character is Ursula:
|
||||
params.update(dict(operator_address=testerchain.unassigned_accounts[0]))
|
||||
config = configuration(**params)
|
||||
|
||||
assert config.is_me is True
|
||||
assert config.dev_mode is True
|
||||
assert config.keystore == NO_KEYSTORE_ATTACHED
|
||||
assert config.eth_provider_uri is None
|
||||
|
||||
# Production
|
||||
thing_one = config()
|
||||
|
@ -57,9 +68,6 @@ def test_federated_development_character_configurations(character, configuration
|
|||
# Ethereum Address
|
||||
assert len(thing_one.checksum_address) == 42
|
||||
|
||||
# Operating Mode
|
||||
assert thing_one.federated_only is True
|
||||
|
||||
# Domain
|
||||
assert TEMPORARY_DOMAIN == thing_one.domain
|
||||
|
||||
|
@ -79,12 +87,14 @@ def test_federated_development_character_configurations(character, configuration
|
|||
alice.disenchant()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('configuration_class', all_configurations)
|
||||
def test_default_character_configuration_preservation(configuration_class,
|
||||
mock_testerchain,
|
||||
test_registry_source_manager,
|
||||
tmpdir,
|
||||
test_registry):
|
||||
@pytest.mark.parametrize("configuration_class", all_configurations)
|
||||
def test_default_character_configuration_preservation(
|
||||
configuration_class,
|
||||
testerchain,
|
||||
test_registry_source_manager,
|
||||
tmpdir,
|
||||
test_registry,
|
||||
):
|
||||
|
||||
configuration_class.DEFAULT_CONFIG_ROOT = Path('/tmp')
|
||||
fake_address = '0xdeadbeef'
|
||||
|
@ -142,8 +152,15 @@ def test_default_character_configuration_preservation(configuration_class,
|
|||
expected_filepath.unlink()
|
||||
|
||||
|
||||
def test_ursula_development_configuration(federated_only=True):
|
||||
config = UrsulaConfiguration(dev_mode=True, federated_only=federated_only)
|
||||
def test_ursula_development_configuration(test_registry_source_manager, testerchain):
|
||||
config = UrsulaConfiguration(
|
||||
dev_mode=True,
|
||||
checksum_address=testerchain.unassigned_accounts[0],
|
||||
operator_address=testerchain.unassigned_accounts[1],
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
payment_network=TEMPORARY_DOMAIN,
|
||||
eth_provider_uri=MOCK_ETH_PROVIDER_URI,
|
||||
)
|
||||
assert config.is_me is True
|
||||
assert config.dev_mode is True
|
||||
assert config.keystore == NO_KEYSTORE_ATTACHED
|
||||
|
@ -154,7 +171,6 @@ def test_ursula_development_configuration(federated_only=True):
|
|||
# Ensure we do in fact have an Ursula here
|
||||
assert isinstance(ursula_one, Ursula)
|
||||
assert len(ursula_one.checksum_address) == 42
|
||||
assert ursula_one.federated_only is federated_only
|
||||
|
||||
# A Temporary Ursula
|
||||
port = ursula_one.rest_information()[0].port
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
|
||||
import datetime
|
||||
|
||||
import maya
|
||||
|
||||
from nucypher.characters.lawful import Bob
|
||||
|
@ -11,17 +12,21 @@ from tests.constants import INSECURE_DEVELOPMENT_PASSWORD
|
|||
from tests.utils.middleware import MockRestMiddleware
|
||||
|
||||
|
||||
def test_alices_powers_are_persistent(federated_ursulas, temp_dir_path):
|
||||
def test_alices_powers_are_persistent(
|
||||
ursulas, temp_dir_path, test_registry_source_manager, testerchain
|
||||
):
|
||||
# Create a non-learning AliceConfiguration
|
||||
config_root = temp_dir_path / 'nucypher-custom-alice-config'
|
||||
alice_config = AliceConfiguration(
|
||||
config_root=config_root,
|
||||
network_middleware=MockRestMiddleware(),
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
payment_network=TEMPORARY_DOMAIN,
|
||||
checksum_address=testerchain.alice_account,
|
||||
start_learning_now=False,
|
||||
federated_only=True,
|
||||
save_metadata=False,
|
||||
reload_metadata=False
|
||||
reload_metadata=False,
|
||||
known_nodes=ursulas,
|
||||
)
|
||||
|
||||
# Generate keys and write them the disk
|
||||
|
@ -52,9 +57,7 @@ def test_alices_powers_are_persistent(federated_ursulas, temp_dir_path):
|
|||
threshold, shares = 3, 4
|
||||
policy_end_datetime = maya.now() + datetime.timedelta(days=5)
|
||||
|
||||
bob = Bob(federated_only=True,
|
||||
start_learning_now=False,
|
||||
network_middleware=MockRestMiddleware())
|
||||
bob = Bob(start_learning_now=False, domain=TEMPORARY_DOMAIN, network_middleware=MockRestMiddleware())
|
||||
|
||||
bob_policy = alice.grant(bob, label, threshold=threshold, shares=shares, expiration=policy_end_datetime)
|
||||
|
||||
|
@ -77,9 +80,9 @@ def test_alices_powers_are_persistent(federated_ursulas, temp_dir_path):
|
|||
new_alice_config = AliceConfiguration.from_configuration_file(
|
||||
filepath=alice_config_file,
|
||||
network_middleware=MockRestMiddleware(),
|
||||
known_nodes=federated_ursulas,
|
||||
start_learning_now=False,
|
||||
config_root=config_root
|
||||
config_root=config_root,
|
||||
known_nodes=ursulas,
|
||||
)
|
||||
|
||||
# Alice unlocks her restored keystore from disk
|
||||
|
@ -91,9 +94,7 @@ def test_alices_powers_are_persistent(federated_ursulas, temp_dir_path):
|
|||
assert alices_receiving_key == new_alice.public_keys(DecryptingPower)
|
||||
|
||||
# Bob's eldest brother, Roberto, appears too
|
||||
roberto = Bob(federated_only=True,
|
||||
start_learning_now=False,
|
||||
network_middleware=MockRestMiddleware())
|
||||
roberto = Bob(domain=TEMPORARY_DOMAIN, start_learning_now=False, network_middleware=MockRestMiddleware())
|
||||
|
||||
# Alice creates a new policy for Roberto. Note how all the parameters
|
||||
# except for the label (i.e., recipient, m, n, policy_end) are different
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue