Merge pull request #3030 from KPrasch/unfederated

Retire Federated Runtime
pull/3043/head
KPrasch 2022-12-22 11:53:31 -08:00 committed by GitHub
commit f2f8b27f88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
120 changed files with 2400 additions and 3699 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"]

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -1 +0,0 @@
docker-compose run nucypher-dev bash

View File

@ -1,2 +0,0 @@
#!/bin/bash
docker-compose run nucypher-dev bash

View File

@ -1 +0,0 @@
docker-compose run nucypher-dev pytest $args

View File

@ -1,3 +0,0 @@
#!/bin/bash
args="$@"
docker-compose run nucypher-dev pytest $args

View File

@ -1 +0,0 @@
docker-compose run nucypher-dev nucypher ursula run --dev --federated-only

View File

@ -1,2 +0,0 @@
#!/bin/bash
docker-compose run nucypher-dev nucypher ursula run --dev --federated-only

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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'

View File

@ -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()

View File

@ -0,0 +1 @@
Deprecated "federated mode" ursulas and the --federated-only launch flag.

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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."""

View File

@ -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:

View File

@ -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

View File

@ -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.

View File

@ -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}.'

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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,)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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'

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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/>.
"""

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 &

View File

@ -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()

View File

@ -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")

View File

@ -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")

View File

@ -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)

View File

@ -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,

View File

@ -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,
)

View File

@ -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)

View File

@ -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,
)

View File

@ -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,

View File

@ -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,
)

View File

@ -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

View File

@ -1,5 +1,3 @@
import pytest
from nucypher.blockchain.eth.clients import EthereumClient

View File

@ -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

View File

@ -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'

View 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)

View File

@ -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()

View File

@ -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()

View File

@ -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"

View File

@ -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)

View File

@ -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)

View File

@ -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,
)

View File

@ -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()

View File

@ -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'

View File

@ -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()

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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()

View File

@ -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:

View File

@ -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...

View File

@ -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
)

View File

@ -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,

View File

@ -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

View File

@ -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