Merge pull request #3491 from derekpierre/eth-client-agnostic

RPC Client Agnosticism
v7.3.x
KPrasch 2024-04-30 18:02:29 +02:00 committed by GitHub
commit 498b9887a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
45 changed files with 550 additions and 1158 deletions

View File

@ -0,0 +1 @@
RPC connectivity is now agnostic to the EVM client fingerprint and implementation technology.

View File

@ -0,0 +1 @@
Wallet management and transaction signing via blockchain clients is deprecated for production use.

View File

@ -1,14 +1,9 @@
import os
import time
from functools import cached_property
from typing import Union
from constant_sorrow.constants import UNKNOWN_DEVELOPMENT_CHAIN_ID
from cytoolz.dicttoolz import dissoc
from eth_account import Account
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 eth_typing.evm import BlockNumber
from web3 import Web3
from web3._utils.threads import Timeout
from web3.contract.contract import Contract
@ -38,30 +33,13 @@ class Web3ClientUnexpectedVersionString(Web3ClientError):
pass
# TODO: Consider creating a ChainInventory class and/or moving this to a separate module
PUBLIC_CHAINS = {
0: "Olympic",
1: "Mainnet",
2: "Morden",
3: "Ropsten",
4: "Rinkeby",
5: "Goerli",
6: "Kotti",
42: "Kovan",
77: "Sokol",
100: "xDai",
137: "Polygon/Mainnet",
11155111: "Sepolia",
80001: "Polygon/Mumbai",
80002: "Polygon/Amoy",
}
LOCAL_CHAINS = {
1337: "GethDev",
5777: "Ganache/TestRPC"
}
# This list is not exhaustive,
# but is sufficient for the current needs of the project.
POA_CHAINS = {
@ -78,18 +56,6 @@ POA_CHAINS = {
class EthereumClient:
is_local = False
# These two are used by Infura
GETH = 'Geth'
BOR = 'bor'
PARITY = 'Parity'
ALT_PARITY = 'Parity-Ethereum'
GANACHE = 'EthereumJS TestRPC'
ETHEREUM_TESTER = 'EthereumTester' # (PyEVM)
BLOCK_CONFIRMATIONS_POLLING_TIME = 3 # seconds
TRANSACTION_POLLING_TIME = 0.5 # seconds
COOLING_TIME = 5 # seconds
@ -125,107 +91,33 @@ class EthereumClient:
block_hash=Web3.to_hex(receipt['blockHash']))
super().__init__(self.message)
def __init__(self,
w3,
node_technology: str,
version: str,
platform: str,
backend: str):
def __init__(self, w3):
self.w3 = w3
self.node_technology = node_technology
self.node_version = version
self.platform = platform
self.backend = backend
self.log = Logger(self.__class__.__name__)
self._add_default_middleware()
def _add_default_middleware(self):
# default retry functionality
self.log.debug('Adding RPC retry middleware to client')
self.add_middleware(RetryRequestMiddleware)
@classmethod
def _get_variant(cls, w3):
return cls
@classmethod
def from_w3(cls, w3: Web3) -> 'EthereumClient':
"""
Client version strings:
Geth -> 'Geth/v1.4.11-stable-fed692f6/darwin/go1.7'
Parity -> 'Parity-Ethereum/v2.5.1-beta-e0141f8-20190510/x86_64-linux-gnu/rustc1.34.1'
Ganache -> 'EthereumJS TestRPC/v2.1.5/ethereum-js'
PyEVM -> 'EthereumTester/0.1.0b39/linux/python3.6.7'
Bor -> 'bor/v0.2.13-beta2-c227a072/linux-amd64/go1.17.5'
"""
clients = {
# Geth
cls.GETH: GethClient,
cls.BOR: BorClient,
# Parity
cls.PARITY: ParityClient,
cls.ALT_PARITY: ParityClient,
# Test Clients
cls.GANACHE: GanacheClient,
cls.ETHEREUM_TESTER: EthereumTesterClient,
}
try:
client_data = w3.client_version.split('/')
node_technology = client_data[0]
ClientSubclass = clients[node_technology]
except (ValueError, IndexError):
raise ValueError(f"Invalid client version string. Got '{w3.client_version}'")
except KeyError:
raise NotImplementedError(f'{w3.client_version} is not a supported ethereum client')
client_kwargs = {
'node_technology': node_technology,
'version': client_data[1],
'backend': client_data[-1],
'platform': client_data[2] if len(client_data) == 4 else None # Platform is optional
}
instance = ClientSubclass._get_variant(w3)(w3, **client_kwargs)
return instance
@property
def peers(self):
raise NotImplementedError
endpoint_uri = getattr(self.w3.provider, "endpoint_uri", "")
if "infura" in endpoint_uri:
self.log.debug("Adding Infura RPC retry middleware to client")
self.add_middleware(InfuraRetryRequestMiddleware)
elif "alchemyapi.io" in endpoint_uri:
self.log.debug("Adding Alchemy RPC retry middleware to client")
self.add_middleware(AlchemyRetryRequestMiddleware)
else:
self.log.debug("Adding RPC retry middleware to client")
self.add_middleware(RetryRequestMiddleware)
@property
def chain_name(self) -> str:
chain_inventory = LOCAL_CHAINS if self.is_local else PUBLIC_CHAINS
name = chain_inventory.get(self.chain_id, UNKNOWN_DEVELOPMENT_CHAIN_ID)
name = PUBLIC_CHAINS.get(self.chain_id, UNKNOWN_DEVELOPMENT_CHAIN_ID)
return name
def lock_account(self, account) -> bool:
if self.is_local:
return True
return NotImplemented
def unlock_account(self, account, password, duration=None) -> bool:
if self.is_local:
return True
return NotImplemented
@property
def is_connected(self):
return self.w3.is_connected()
@property
def etherbase(self) -> str:
return self.w3.eth.accounts[0]
@property
def accounts(self):
return self.w3.eth.accounts
@ -244,15 +136,8 @@ class EthereumClient:
@cached_property
def chain_id(self) -> int:
result = self.w3.eth.chain_id
try:
# from hex-str
chain_id = int(result, 16)
except TypeError:
# from str
chain_id = int(result)
return chain_id
_chain_id = self._get_chain_id(self.w3)
return _chain_id
@property
def net_version(self) -> int:
@ -279,10 +164,6 @@ class EthereumClient:
def block_number(self) -> BlockNumber:
return self.w3.eth.block_number
@property
def coinbase(self) -> ChecksumAddress:
return self.w3.eth.coinbase
def wait_for_receipt(self,
transaction_hash: str,
timeout: float,
@ -361,9 +242,6 @@ class EthereumClient:
# TODO: Consider adding an optional param in this exception to include extra info (e.g. new block)
return True
def sign_transaction(self, transaction_dict: dict) -> bytes:
raise NotImplementedError
def get_transaction(self, transaction_hash) -> dict:
return self.w3.eth.get_transaction(transaction_hash)
@ -380,14 +258,6 @@ class EthereumClient:
def send_raw_transaction(self, transaction_bytes: bytes) -> str:
return self.w3.eth.send_raw_transaction(transaction_bytes)
def sign_message(self, account: str, message: bytes) -> str:
"""
Calls the appropriate signing function for the specified account on the
backend. If the backend is based on eth-tester, then it uses the
eth-tester signing interface to do so.
"""
return self.w3.eth.sign(account, data=message)
def get_blocktime(self):
highest_block = self.w3.eth.get_block('latest')
now = highest_block['timestamp']
@ -401,177 +271,14 @@ class EthereumClient:
# check that our local chain data is up to date
return (time.time() - self.get_blocktime()) < self.STALECHECK_ALLOWABLE_DELAY
def parse_transaction_data(self, transaction):
return transaction.input
class GethClient(EthereumClient):
@classmethod
def _get_variant(cls, w3):
endpoint_uri = getattr(w3.provider, 'endpoint_uri', '')
if 'infura' in endpoint_uri:
return InfuraClient
elif 'alchemyapi.io' in endpoint_uri:
return AlchemyClient
return cls
@property
def is_local(self):
return self.chain_id not in PUBLIC_CHAINS
@property
def peers(self):
return self.w3.geth.admin.peers()
def new_account(self, password: str) -> str:
new_account = self.w3.geth.personal.new_account(password)
return to_checksum_address(new_account) # cast and validate
def unlock_account(self, account: str, password: str, duration: int = None):
if self.is_local:
return True
debug_message = f"Unlocking account {account}"
if duration is None:
debug_message += " for 5 minutes"
elif duration == 0:
debug_message += " indefinitely"
elif duration > 0:
debug_message += f" for {duration} seconds"
if password is None:
debug_message += " with no password."
self.log.debug(debug_message)
return self.w3.geth.personal.unlock_account(account, password, duration)
def lock_account(self, account):
return self.w3.geth.personal.lock_account(account)
def sign_transaction(self, transaction_dict: dict) -> bytes:
# Do not include a 'to' field for contract creation.
if transaction_dict['to'] == b'':
transaction_dict = dissoc(transaction_dict, 'to')
# Sign
result = self.w3.eth.sign_transaction(transaction_dict)
# Return RLP bytes
rlp_encoded_transaction = result.raw
return rlp_encoded_transaction
@property
def wallets(self):
return self.w3.geth.personal.list_wallets()
class BorClient(GethClient):
"""Geth to Bor adapter"""
class ParityClient(EthereumClient):
@property
def peers(self) -> list:
"""
TODO: Look for web3.py support for Parity Peers endpoint
"""
return self.w3.manager.request_blocking("parity_netPeers", [])
def new_account(self, password: str) -> str:
new_account = self.w3.parity.personal.new_account(password)
return to_checksum_address(new_account) # cast and validate
def unlock_account(self, account, password, duration: int = None) -> bool:
return self.w3.parity.personal.unlock_account(account, password, duration)
def lock_account(self, account):
return self.w3.parity.personal.lock_account(account)
class GanacheClient(EthereumClient):
is_local = True
def unlock_account(self, *args, **kwargs) -> bool:
return True
class InfuraClient(EthereumClient):
is_local = False
TRANSACTION_POLLING_TIME = 2 # seconds
def _add_default_middleware(self):
# default retry functionality
self.log.debug('Adding Infura RPC retry middleware to client')
self.add_middleware(InfuraRetryRequestMiddleware)
def unlock_account(self, *args, **kwargs) -> bool:
return True
class AlchemyClient(EthereumClient):
def _add_default_middleware(self):
# default retry functionality
self.log.debug('Adding Alchemy RPC retry middleware to client')
self.add_middleware(AlchemyRetryRequestMiddleware)
class EthereumTesterClient(EthereumClient):
is_local = True
def unlock_account(self, account, password, duration: int = None) -> bool:
"""Returns True if the testing backend keystore has control of the given address."""
account = to_checksum_address(account)
keystore_accounts = self.w3.provider.ethereum_tester.get_accounts()
if account in keystore_accounts:
return True
else:
return self.w3.provider.ethereum_tester.unlock_account(account=account,
password=password,
unlock_seconds=duration)
def lock_account(self, account) -> bool:
"""Returns True if the testing backend keystore has control of the given address."""
account = to_canonical_address(account)
keystore_accounts = self.w3.provider.ethereum_tester.backend.get_accounts()
if account in keystore_accounts:
return True
else:
return self.w3.provider.ethereum_tester.lock_account(account=account)
def new_account(self, password: str) -> str:
insecure_account = self.w3.provider.ethereum_tester.add_account(private_key=os.urandom(32).hex(),
password=password)
return insecure_account
def __get_signing_key(self, account: bytes):
"""Get signing key of test account"""
account = to_canonical_address(account)
def _get_chain_id(cls, w3: Web3):
result = w3.eth.chain_id
try:
signing_key = self.w3.provider.ethereum_tester.backend._key_lookup[account]._raw_key
except KeyError:
raise self.UnknownAccount(account)
return signing_key
# from hex-str
chain_id = int(result, 16)
except TypeError:
# from str
chain_id = int(result)
def sign_transaction(self, transaction_dict: dict) -> bytes:
# Sign using a local private key
address = to_canonical_address(transaction_dict['from'])
signing_key = self.__get_signing_key(account=address)
raw_transaction = self.w3.eth.account.sign_transaction(
transaction_dict, private_key=signing_key
).rawTransaction
return raw_transaction
def sign_message(self, account: str, message: bytes) -> str:
"""Sign, EIP-191 (Geth) Style"""
signing_key = self.__get_signing_key(account=account)
signable_message = encode_defunct(primitive=message)
signature_and_stuff = Account.sign_message(signable_message=signable_message, private_key=signing_key)
return signature_and_stuff['signature']
def parse_transaction_data(self, transaction):
return transaction.data # See https://github.com/ethereum/eth-tester/issues/173
return chain_id

View File

@ -1,6 +1,5 @@
import math
import pprint
from pathlib import Path
from typing import Callable, Dict, NamedTuple, Optional, Union
from urllib.parse import urlparse
@ -22,15 +21,12 @@ from web3.middleware import geth_poa_middleware, simple_cache_middleware
from web3.providers import BaseProvider
from web3.types import TxParams, TxReceipt
from nucypher.blockchain.eth.clients import POA_CHAINS, EthereumClient, InfuraClient
from nucypher.blockchain.eth.clients import POA_CHAINS, EthereumClient
from nucypher.blockchain.eth.decorators import validate_checksum_address
from nucypher.blockchain.eth.providers import (
_get_auto_provider,
_get_HTTP_provider,
_get_IPC_provider,
_get_http_provider,
_get_mock_test_provider,
_get_pyevm_test_provider,
_get_websocket_provider,
)
from nucypher.blockchain.eth.registry import ContractRegistry
from nucypher.blockchain.eth.utils import (
@ -42,7 +38,6 @@ from nucypher.crypto.powers import TransactingPower
from nucypher.utilities.emitters import StdoutEmitter
from nucypher.utilities.gas_strategies import (
WEB3_GAS_STRATEGIES,
construct_datafeed_median_strategy,
max_price_gas_strategy_wrapper,
)
from nucypher.utilities.logging import Logger
@ -318,11 +313,6 @@ class BlockchainInterface:
def configure_gas_strategy(self, gas_strategy: Optional[Callable] = None) -> None:
if gas_strategy:
reported_gas_strategy = f"fixed/{gas_strategy.name}"
elif isinstance(self.client, InfuraClient):
gas_strategy = construct_datafeed_median_strategy(speed=self.gas_strategy)
reported_gas_strategy = f"datafeed/{self.gas_strategy}"
else:
reported_gas_strategy = f"web3/{self.gas_strategy}"
gas_strategy = self.get_gas_strategy(self.gas_strategy)
@ -362,7 +352,7 @@ class BlockchainInterface:
try:
self.w3 = self.Web3(provider=self._provider)
self.tx_machine.w3 = self.w3 # share this web3 instance with the tracker
self.client = EthereumClient.from_w3(w3=self.w3)
self.client = EthereumClient(w3=self.w3)
except requests.ConnectionError: # RPC
raise self.ConnectionFailed(
f"Connection Failed - {str(self.endpoint)} - is RPC enabled?"
@ -394,43 +384,22 @@ class BlockchainInterface:
if endpoint and not provider:
uri_breakdown = urlparse(endpoint)
if uri_breakdown.scheme == "tester":
providers = {
"pyevm": _get_pyevm_test_provider,
"mock": _get_mock_test_provider,
}
provider_scheme = uri_breakdown.netloc
provider_scheme = (
uri_breakdown.netloc
if uri_breakdown.scheme == "tester"
else uri_breakdown.scheme
)
if provider_scheme == "pyevm":
self._provider = _get_pyevm_test_provider(endpoint)
elif provider_scheme == "mock":
self._provider = _get_mock_test_provider(endpoint)
elif provider_scheme == "http" or provider_scheme == "https":
self._provider = _get_http_provider(endpoint)
else:
providers = {
"auto": _get_auto_provider,
"ipc": _get_IPC_provider,
"file": _get_IPC_provider,
"ws": _get_websocket_provider,
"wss": _get_websocket_provider,
"http": _get_HTTP_provider,
"https": _get_HTTP_provider,
}
provider_scheme = uri_breakdown.scheme
# auto-detect for file based ipc
if not provider_scheme:
if Path(endpoint).is_file():
# file is available - assume ipc/file scheme
provider_scheme = "file"
self.log.info(
f"Auto-detected provider scheme as 'file://' for provider {endpoint}"
)
try:
self._provider = providers[provider_scheme](endpoint)
except KeyError:
raise self.UnsupportedProvider(
f"{endpoint} is an invalid or unsupported blockchain provider URI"
)
else:
self.endpoint = endpoint or NO_BLOCKCHAIN_CONNECTION
self.endpoint = endpoint or NO_BLOCKCHAIN_CONNECTION
else:
self._provider = provider

View File

@ -1,6 +1,5 @@
from urllib.parse import urlparse
from web3 import HTTPProvider, IPCProvider, WebsocketProvider
from web3 import HTTPProvider
from web3.providers import BaseProvider
from nucypher.exceptions import DevelopmentInstallationRequired
@ -10,15 +9,7 @@ class ProviderError(Exception):
pass
def _get_IPC_provider(endpoint) -> BaseProvider:
uri_breakdown = urlparse(endpoint)
from nucypher.blockchain.eth.interfaces import BlockchainInterface
return IPCProvider(ipc_path=uri_breakdown.path,
timeout=BlockchainInterface.TIMEOUT,
request_kwargs={'timeout': BlockchainInterface.TIMEOUT})
def _get_HTTP_provider(endpoint) -> BaseProvider:
def _get_http_provider(endpoint) -> BaseProvider:
from nucypher.blockchain.eth.interfaces import BlockchainInterface
return HTTPProvider(
@ -27,24 +18,6 @@ def _get_HTTP_provider(endpoint) -> BaseProvider:
)
def _get_websocket_provider(endpoint) -> BaseProvider:
from nucypher.blockchain.eth.interfaces import BlockchainInterface
return WebsocketProvider(
endpoint_uri=endpoint,
websocket_kwargs={"timeout": BlockchainInterface.TIMEOUT},
)
def _get_auto_provider(endpoint) -> BaseProvider:
from web3.auto import w3
# how-automated-detection-works: https://web3py.readthedocs.io/en/latest/providers.html
connected = w3.isConnected()
if not connected:
raise ProviderError('Cannot auto-detect node. Provide a full URI instead.')
return w3.provider
def _get_pyevm_test_backend():
try:
@ -91,8 +64,3 @@ def _get_mock_test_provider(endpoint) -> BaseProvider:
mock_backend = MockBackend()
provider = _get_ethereum_tester(test_backend=mock_backend)
return provider
def _get_tester_ganache(endpoint=None) -> BaseProvider:
endpoint_uri = endpoint or "http://localhost:7545"
return HTTPProvider(endpoint_uri=endpoint_uri)

View File

@ -8,7 +8,7 @@ from cytoolz.dicttoolz import dissoc
from eth_account.account import Account
from eth_account.messages import encode_defunct
from eth_account.signers.local import LocalAccount
from eth_utils.address import is_address, to_canonical_address, to_checksum_address
from eth_utils.address import is_address, to_checksum_address
from hexbytes.main import BytesLike, HexBytes
from nucypher.blockchain.eth.decorators import validate_checksum_address
@ -20,12 +20,11 @@ class Web3Signer(Signer):
super().__init__()
self.__client = client
def _get_signer(self, account: str) -> LocalAccount:
"""Test helper to get a signer from the client's backend"""
account = to_canonical_address(account)
_eth_tester = self.__client.w3.provider.ethereum_tester
signer = Account.from_key(_eth_tester.backend._key_lookup[account]._raw_key)
return signer
@validate_checksum_address
def _get_signer(self, account: str):
raise NotImplementedError(
"Can't sign via external blockchain clients; provide an explicit Signer"
)
@classmethod
def uri_scheme(cls) -> str:
@ -56,53 +55,30 @@ class Web3Signer(Signer):
@validate_checksum_address
def is_device(self, account: str):
try:
# TODO: Temporary fix for #1128 and #1385. It's ugly af, but it works. Move somewhere else?
wallets = self.__client.wallets
except AttributeError:
return False
else:
HW_WALLET_URL_PREFIXES = ("trezor", "ledger")
hw_accounts = [
w["accounts"]
for w in wallets
if w["url"].startswith(HW_WALLET_URL_PREFIXES)
]
hw_addresses = [
to_checksum_address(account["address"])
for sublist in hw_accounts
for account in sublist
]
return account in hw_addresses
return False
@validate_checksum_address
def unlock_account(self, account: str, password: str, duration: int = None):
if self.is_device(account=account):
unlocked = True
else:
unlocked = self.__client.unlock_account(
account=account, password=password, duration=duration
)
return unlocked
raise NotImplementedError(
"Can't manage accounts via external blockchain clients; provide an explicit Signer"
)
@validate_checksum_address
def lock_account(self, account: str):
if self.is_device(account=account):
result = None # TODO: Force Disconnect Devices?
else:
result = self.__client.lock_account(account=account)
return result
raise NotImplementedError(
"Can't manage accounts via external blockchain clients; provide an explicit Signer"
)
@validate_checksum_address
def sign_message(self, account: str, message: bytes, **kwargs) -> HexBytes:
signature = self.__client.sign_message(account=account, message=message)
return HexBytes(signature)
raise NotImplementedError(
"Can't sign via external blockchain clients; provide an explicit Signer"
)
def sign_transaction(self, transaction_dict: dict) -> bytes:
raw_transaction = self.__client.sign_transaction(
transaction_dict=transaction_dict
raise NotImplementedError(
"Can't sign via external blockchain clients; provide an explicit Signer"
)
return raw_transaction
class KeystoreSigner(Signer):
@ -343,7 +319,7 @@ class InMemorySigner(Signer):
return True
@validate_checksum_address
def __get_signer(self, account: str) -> LocalAccount:
def _get_signer(self, account: str) -> LocalAccount:
"""Lookup a known keystore account by its checksum address or raise an error"""
try:
return self.__signers[account]
@ -356,7 +332,7 @@ class InMemorySigner(Signer):
@validate_checksum_address
def sign_transaction(self, transaction_dict: dict) -> bytes:
sender = transaction_dict["from"]
signer = self.__get_signer(account=sender)
signer = self._get_signer(account=sender)
if not transaction_dict["to"]:
transaction_dict = dissoc(transaction_dict, "to")
raw_transaction = signer.sign_transaction(
@ -366,7 +342,7 @@ class InMemorySigner(Signer):
@validate_checksum_address
def sign_message(self, account: str, message: bytes, **kwargs) -> HexBytes:
signer = self.__get_signer(account=account)
signer = self._get_signer(account=account)
signature = signer.sign_message(
signable_message=encode_defunct(primitive=message)
).signature

View File

@ -4,7 +4,7 @@ from unittest.mock import Mock, patch
from nucypher_core import NodeMetadata
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.blockchain.eth.signers.software import InMemorySigner
from nucypher.characters.lawful import Alice, Ursula
from nucypher.config.constants import TEMPORARY_DOMAIN_NAME
from nucypher.crypto.powers import CryptoPower
@ -49,8 +49,6 @@ class Vladimir(Ursula):
crypto_power = CryptoPower(power_ups=target_ursula._default_crypto_powerups)
cls.attach_transacting_key(blockchain=eth_blockchain)
# Vladimir does not care about payment.
bogus_pre_payment_method = FreeReencryptions()
bogus_pre_payment_method.provider = Mock()
@ -73,7 +71,7 @@ class Vladimir(Ursula):
network_middleware=cls.network_middleware,
checksum_address=cls.fraud_address,
operator_address=cls.fraud_address,
signer=Web3Signer(eth_blockchain.client),
signer=InMemorySigner(private_key=cls.fraud_key),
eth_endpoint=eth_blockchain.endpoint,
polygon_endpoint=polygon_blockchain.endpoint,
pre_payment_method=bogus_pre_payment_method,
@ -114,28 +112,6 @@ class Vladimir(Ursula):
return vladimir
@classmethod
def attach_transacting_key(cls, blockchain):
"""
Upload Vladimir's ETH keys to the keychain via web3.
"""
try:
from eth_tester.exceptions import ValidationError
except ImportError:
raise DevelopmentInstallationRequired(
importable_name="eth_tester.exceptions.ValidationError"
)
try:
password = 'iamverybadass'
blockchain.w3.provider.ethereum_tester.add_account(cls.fraud_key, password=password)
except (ValidationError,):
# check if Vlad's key is already on the keystore...
if cls.fraud_address in blockchain.client.accounts:
return True
else:
raise
return True
class Amonia(Alice):
"""

View File

@ -10,7 +10,7 @@ from prometheus_client import REGISTRY
from nucypher.blockchain.eth.agents import ContractAgency, SubscriptionManagerAgent
from nucypher.blockchain.eth.constants import NULL_ADDRESS
from nucypher.blockchain.eth.models import Coordinator
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.blockchain.eth.signers.software import InMemorySigner
from nucypher.characters.lawful import Enrico, Ursula
from nucypher.policy.conditions.evm import ContractCondition, RPCCondition
from nucypher.policy.conditions.lingo import (
@ -49,8 +49,8 @@ def interval(testerchain):
@pytest.fixture(scope="module")
def signer(testerchain):
return Web3Signer(testerchain.client)
def signer():
return InMemorySigner()
@pytest.fixture(scope="module")

View File

@ -1,5 +1,4 @@
from nucypher.blockchain.eth.constants import NULL_ADDRESS
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.crypto.powers import TransactingPower
from nucypher.utilities.logging import Logger
from tests.utils.ursula import select_test_port
@ -19,9 +18,10 @@ def test_ursula_operator_confirmation(
taco_application_agent,
taco_child_application_agent,
deployer_account,
accounts,
):
staking_provider = testerchain.stake_provider_account(0)
operator_address = testerchain.ursula_account(0)
staking_provider = accounts.staking_provider_account(0)
operator_address = accounts.ursula_account(0)
min_authorization = taco_application_agent.get_min_authorization()
# make an staking_providers and some stakes
@ -47,7 +47,7 @@ def test_ursula_operator_confirmation(
# bond this operator
tpower = TransactingPower(
account=staking_provider, signer=Web3Signer(testerchain.client)
account=staking_provider, signer=accounts.get_account_signer(staking_provider)
)
taco_application_agent.bond_operator(
staking_provider=staking_provider,
@ -57,7 +57,9 @@ def test_ursula_operator_confirmation(
# make an ursula.
ursula = ursula_test_config.produce(
operator_address=operator_address, rest_port=select_test_port()
operator_address=operator_address,
rest_port=select_test_port(),
signer=accounts.get_account_signer(operator_address),
)
# now the worker has a staking provider

View File

@ -9,7 +9,6 @@ from twisted.internet.task import deferLater
from nucypher.blockchain.eth.agents import CoordinatorAgent
from nucypher.blockchain.eth.models import Coordinator
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.crypto.powers import TransactingPower
@ -44,9 +43,9 @@ def cohort_ursulas(cohort, taco_application_agent):
@pytest.fixture(scope='module')
def transacting_powers(testerchain, cohort_ursulas):
def transacting_powers(accounts, cohort_ursulas):
return [
TransactingPower(account=ursula, signer=Web3Signer(testerchain.client))
TransactingPower(account=ursula, signer=accounts.get_account_signer(ursula))
for ursula in cohort_ursulas
]

View File

@ -9,7 +9,6 @@ from nucypher.blockchain.eth.agents import (
WeightedSampler,
)
from nucypher.blockchain.eth.constants import NULL_ADDRESS
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.crypto.powers import TransactingPower
@ -19,9 +18,10 @@ def test_sampling_distribution(
threshold_staking,
coordinator_agent,
deployer_account,
accounts,
):
# setup
stake_provider_accounts = testerchain.stake_providers_accounts
stake_provider_accounts = accounts.staking_providers_accounts
amount = taco_application_agent.get_min_authorization()
all_locked_tokens = len(stake_provider_accounts) * amount
@ -35,7 +35,10 @@ def test_sampling_distribution(
provider_address, 0, amount, sender=deployer_account
)
power = TransactingPower(account=provider_address, signer=Web3Signer(testerchain.client))
power = TransactingPower(
account=provider_address,
signer=accounts.get_account_signer(provider_address),
)
# We assume that the staking provider knows in advance the account of her operator
taco_application_agent.bond_operator(

View File

@ -3,7 +3,6 @@ import random
import pytest
from nucypher.blockchain.eth.constants import NULL_ADDRESS
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.crypto.powers import TransactingPower
@ -29,13 +28,14 @@ def test_authorized_tokens(
def test_staking_providers_and_operators_relationships(
testerchain,
accounts,
taco_application_agent,
threshold_staking,
taco_application,
deployer_account,
get_random_checksum_address,
):
staking_provider_account, operator_account, *other = testerchain.unassigned_accounts
staking_provider_account, operator_account, *other = accounts.unassigned_accounts
threshold_staking.setRoles(staking_provider_account, sender=deployer_account)
threshold_staking.authorizationIncreased(
staking_provider_account,
@ -50,7 +50,8 @@ def test_staking_providers_and_operators_relationships(
)
tpower = TransactingPower(
account=staking_provider_account, signer=Web3Signer(testerchain.client)
account=staking_provider_account,
signer=accounts.get_account_signer(staking_provider_account),
)
_txhash = taco_application_agent.bond_operator(
transacting_power=tpower,

View File

@ -5,11 +5,13 @@ from twisted.logger import LogLevel, globalLogPublisher
from nucypher.acumen.perception import FleetSensor
from nucypher.blockchain.eth.constants import NULL_ADDRESS
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.config.constants import TEMPORARY_DOMAIN_NAME
from nucypher.crypto.powers import TransactingPower
from tests.constants import TEST_ETH_PROVIDER_URI
from tests.utils.ursula import make_ursulas, start_pytest_ursula_services
from tests.utils.ursula import (
make_ursulas,
start_pytest_ursula_services,
)
def test_ursula_stamp_verification_tolerance(ursulas, mocker):
@ -79,6 +81,7 @@ def test_ursula_stamp_verification_tolerance(ursulas, mocker):
def test_invalid_operators_tolerance(
testerchain,
accounts,
test_registry,
ursulas,
threshold_staking,
@ -90,15 +93,12 @@ def test_invalid_operators_tolerance(
#
# Setup
#
(
creator,
_staking_provider,
operator_address,
*everyone_else,
) = testerchain.client.accounts
existing_ursula, other_ursula, *the_others = list(ursulas)
_staking_provider, operator_address = (
accounts.unassigned_accounts[0],
accounts.unassigned_accounts[1],
)
# We start with an ursula with no tokens staked
owner, _, _ = threshold_staking.rolesOf(_staking_provider, sender=deployer_account)
assert owner == NULL_ADDRESS
@ -112,7 +112,7 @@ def test_invalid_operators_tolerance(
# now lets bond this worker
tpower = TransactingPower(
account=_staking_provider, signer=Web3Signer(testerchain.client)
account=_staking_provider, signer=accounts.get_account_signer(_staking_provider)
)
taco_application_agent.bond_operator(
staking_provider=_staking_provider,
@ -121,7 +121,11 @@ def test_invalid_operators_tolerance(
)
# Make the Operator
ursulas = make_ursulas(ursula_test_config, [_staking_provider], [operator_address])
ursulas = make_ursulas(
ursula_test_config,
[_staking_provider],
[accounts.get_account_signer(operator_address)],
)
ursula = ursulas[0]
ursula.run(
preflight=False,

View File

@ -4,7 +4,6 @@ from eth_utils import to_checksum_address
from nucypher_core.ferveo import Keypair
from nucypher.blockchain.eth.models import Ferveo
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.characters.lawful import Character
from nucypher.config.constants import TEMPORARY_DOMAIN_NAME
from nucypher.crypto.powers import TransactingPower
@ -15,9 +14,9 @@ from tests.constants import INSECURE_DEVELOPMENT_PASSWORD, TEST_ETH_PROVIDER_URI
TransactingPower.lock_account = LOCK_FUNCTION
def test_character_transacting_power_signing(testerchain, test_registry):
def test_character_transacting_power_signing(testerchain, test_registry, accounts):
# Pretend to be a character.
eth_address = testerchain.etherbase_account
eth_address = accounts.etherbase_account
signer = Character(
is_me=True,
domain=TEMPORARY_DOMAIN_NAME,
@ -29,7 +28,7 @@ def test_character_transacting_power_signing(testerchain, test_registry):
# Manually consume the power up
transacting_power = TransactingPower(
password=INSECURE_DEVELOPMENT_PASSWORD,
signer=Web3Signer(testerchain.client),
signer=accounts.get_account_signer(eth_address),
account=eth_address,
)
@ -54,7 +53,7 @@ def test_character_transacting_power_signing(testerchain, test_registry):
"gasPrice": testerchain.client.w3.eth.gas_price,
"gas": 100000,
"from": eth_address,
"to": testerchain.unassigned_accounts[1],
"to": accounts.unassigned_accounts[1],
"value": 1,
"data": b"",
}
@ -67,12 +66,12 @@ def test_character_transacting_power_signing(testerchain, test_registry):
assert to_checksum_address(restored_dict["to"]) == transaction_dict["to"]
def test_transacting_power_sign_message(testerchain):
def test_transacting_power_sign_message(testerchain, accounts):
# Manually create a TransactingPower
eth_address = testerchain.etherbase_account
eth_address = accounts.etherbase_account
power = TransactingPower(
password=INSECURE_DEVELOPMENT_PASSWORD,
signer=Web3Signer(testerchain.client),
signer=accounts.get_account_signer(eth_address),
account=eth_address,
)
@ -91,18 +90,18 @@ def test_transacting_power_sign_message(testerchain):
# Test invalid address/pubkey pair
is_verified = verify_eip_191(
address=testerchain.client.accounts[1],
address=accounts.accounts_addresses[1],
message=data_to_sign,
signature=signature,
)
assert is_verified is False
def test_transacting_power_sign_transaction(testerchain):
eth_address = testerchain.unassigned_accounts[2]
def test_transacting_power_sign_transaction(testerchain, accounts):
eth_address = accounts.unassigned_accounts[2]
power = TransactingPower(
password=INSECURE_DEVELOPMENT_PASSWORD,
signer=Web3Signer(testerchain.client),
signer=accounts.get_account_signer(eth_address),
account=eth_address,
)
@ -111,7 +110,7 @@ def test_transacting_power_sign_transaction(testerchain):
"gasPrice": testerchain.client.w3.eth.gas_price,
"gas": 100000,
"from": eth_address,
"to": testerchain.unassigned_accounts[1],
"to": accounts.unassigned_accounts[1],
"value": 1,
"data": b"",
}
@ -134,7 +133,9 @@ def test_transacting_power_sign_transaction(testerchain):
power.sign_transaction(transaction_dict=transaction_dict)
def test_transacting_power_sign_agent_transaction(testerchain, coordinator_agent):
def test_transacting_power_sign_agent_transaction(
testerchain, coordinator_agent, accounts
):
public_key = Keypair.random().public_key()
g2_point = Ferveo.G2Point.from_public_key(public_key)
contract_function = coordinator_agent.contract.functions.setProviderPublicKey(
@ -144,9 +145,9 @@ def test_transacting_power_sign_agent_transaction(testerchain, coordinator_agent
payload = {
"chainId": int(testerchain.client.chain_id),
"nonce": testerchain.client.w3.eth.get_transaction_count(
testerchain.etherbase_account
accounts.etherbase_account
),
"from": testerchain.etherbase_account,
"from": accounts.etherbase_account,
"gasPrice": testerchain.client.gas_price,
"gas": 500_000,
}
@ -156,8 +157,8 @@ def test_transacting_power_sign_agent_transaction(testerchain, coordinator_agent
# Sign with Transacting Power
transacting_power = TransactingPower(
password=INSECURE_DEVELOPMENT_PASSWORD,
signer=Web3Signer(testerchain.client),
account=testerchain.etherbase_account,
signer=accounts.get_account_signer(accounts.etherbase_account),
account=accounts.etherbase_account,
)
signed_raw_transaction = transacting_power.sign_transaction(unsigned_transaction)

View File

@ -8,7 +8,6 @@ from web3 import Web3
from nucypher.blockchain.eth.agents import ContractAgency, TACoApplicationAgent
from nucypher.blockchain.eth.signers import KeystoreSigner
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.cli.main import nucypher_cli
from nucypher.config.characters import UrsulaConfiguration
from nucypher.config.constants import (
@ -34,6 +33,7 @@ def mock_funded_account_password_keystore(
taco_application_agent,
test_registry,
deployer_account,
accounts,
):
"""
Generate a random keypair & password and create a local keystore. Then prepare a staking provider
@ -49,14 +49,14 @@ def mock_funded_account_password_keystore(
testerchain.client.w3.eth.send_transaction(
{
"to": account.address,
"from": testerchain.etherbase_account,
"from": accounts.etherbase_account,
"value": Web3.to_wei("100", "ether"),
}
)
)
# initialize threshold stake
provider_address = testerchain.unassigned_accounts[0]
provider_address = accounts.unassigned_accounts[0]
threshold_staking.setRoles(provider_address, sender=deployer_account)
threshold_staking.setStakes(
provider_address,
@ -66,7 +66,7 @@ def mock_funded_account_password_keystore(
)
provider_power = TransactingPower(
account=provider_address, signer=Web3Signer(testerchain.client)
account=provider_address, signer=accounts.get_account_signer(provider_address)
)
provider_power.unlock(password=INSECURE_DEVELOPMENT_PASSWORD)
@ -92,6 +92,7 @@ def test_ursula_and_local_keystore_signer_integration(
mocker,
mock_funded_account_password_keystore,
testerchain,
accounts,
):
config_root_path = tmp_path
ursula_config_path = config_root_path / UrsulaConfiguration.generate_filename()
@ -101,7 +102,7 @@ def test_ursula_and_local_keystore_signer_integration(
testerchain.client.w3.eth.send_transaction(
{
"to": worker_account,
"from": testerchain.etherbase_account,
"from": accounts.etherbase_account,
"value": Web3.to_wei("100", "ether"),
}
)

View File

@ -3,6 +3,7 @@ from unittest import mock
import pytest
import pytest_twisted as pt
from eth_account import Account
from twisted.internet import threads
from nucypher.characters.base import Learner
@ -16,7 +17,6 @@ from nucypher.utilities.networking import LOOPBACK_ADDRESS
from tests.constants import (
INSECURE_DEVELOPMENT_PASSWORD,
TEST_ETH_PROVIDER_URI,
TEST_POLYGON_PROVIDER_URI,
)
from tests.utils.ursula import select_test_port, start_pytest_ursula_services
@ -32,8 +32,9 @@ def test_missing_configuration_file(_default_filepath_mock, click_runner):
@pt.inlineCallbacks
def test_run_lone_default_development_ursula(click_runner, ursulas, testerchain):
def test_run_lone_default_development_ursula(click_runner, mocker, ursulas, accounts):
deploy_port = select_test_port()
operator_address = ursulas[0].operator_address
args = (
"ursula",
"run", # Stat Ursula Command
@ -44,13 +45,18 @@ def test_run_lone_default_development_ursula(click_runner, ursulas, testerchain)
"--dry-run", # Disable twisted reactor in subprocess
"--lonely", # Do not load seednodes,
"--operator-address",
ursulas[0].operator_address,
operator_address,
"--eth-endpoint",
TEST_ETH_PROVIDER_URI,
"--polygon-endpoint",
TEST_ETH_PROVIDER_URI,
"--signer",
"memory://",
)
account = Account.from_key(private_key=accounts[operator_address].private_key)
mocker.patch.object(Account, "create", return_value=account)
result = yield threads.deferToThread(
click_runner.invoke,
nucypher_cli,

View File

@ -79,7 +79,9 @@ def test_user_address_context_invalid_eip712_typed_data(valid_user_address_conte
get_context_value(USER_ADDRESS_CONTEXT, **context)
def test_user_address_context_variable_verification(testerchain, valid_user_address_context):
def test_user_address_context_variable_verification(
valid_user_address_context, accounts
):
# valid user address context - signature matches address
address = get_context_value(USER_ADDRESS_CONTEXT, **valid_user_address_context)
assert address == valid_user_address_context[USER_ADDRESS_CONTEXT]["address"]
@ -89,7 +91,7 @@ def test_user_address_context_variable_verification(testerchain, valid_user_addr
mismatch_with_address_context = copy.deepcopy(valid_user_address_context)
mismatch_with_address_context[USER_ADDRESS_CONTEXT][
"address"
] = testerchain.etherbase_account
] = accounts.etherbase_account
with pytest.raises(ContextVariableVerificationFailed):
get_context_value(USER_ADDRESS_CONTEXT, **mismatch_with_address_context)
@ -119,9 +121,9 @@ def test_user_address_context_variable_verification(testerchain, valid_user_addr
side_effect=_dont_validate_user_address,
)
def test_rpc_condition_evaluation_no_providers(
get_context_value_mock, testerchain, rpc_condition
get_context_value_mock, testerchain, accounts, rpc_condition
):
context = {USER_ADDRESS_CONTEXT: {"address": testerchain.unassigned_accounts[0]}}
context = {USER_ADDRESS_CONTEXT: {"address": accounts.unassigned_accounts[0]}}
with pytest.raises(NoConnectionToChain):
_ = rpc_condition.verify(providers={}, **context)
@ -136,9 +138,9 @@ def test_rpc_condition_evaluation_no_providers(
side_effect=_dont_validate_user_address,
)
def test_rpc_condition_evaluation_invalid_provider_for_chain(
get_context_value_mock, testerchain, rpc_condition
get_context_value_mock, testerchain, accounts, rpc_condition
):
context = {USER_ADDRESS_CONTEXT: {"address": testerchain.unassigned_accounts[0]}}
context = {USER_ADDRESS_CONTEXT: {"address": accounts.unassigned_accounts[0]}}
new_chain = 23
rpc_condition.chain = new_chain
condition_providers = {new_chain: {testerchain.provider}}
@ -152,8 +154,10 @@ def test_rpc_condition_evaluation_invalid_provider_for_chain(
GET_CONTEXT_VALUE_IMPORT_PATH,
side_effect=_dont_validate_user_address,
)
def test_rpc_condition_evaluation(get_context_value_mock, testerchain, rpc_condition, condition_providers):
context = {USER_ADDRESS_CONTEXT: {"address": testerchain.unassigned_accounts[0]}}
def test_rpc_condition_evaluation(
get_context_value_mock, accounts, rpc_condition, condition_providers
):
context = {USER_ADDRESS_CONTEXT: {"address": accounts.unassigned_accounts[0]}}
condition_result, call_result = rpc_condition.verify(
providers=condition_providers, **context
)
@ -168,9 +172,9 @@ def test_rpc_condition_evaluation(get_context_value_mock, testerchain, rpc_condi
side_effect=_dont_validate_user_address,
)
def test_rpc_condition_evaluation_multiple_chain_providers(
get_context_value_mock, testerchain, rpc_condition
get_context_value_mock, testerchain, accounts, rpc_condition
):
context = {USER_ADDRESS_CONTEXT: {"address": testerchain.unassigned_accounts[0]}}
context = {USER_ADDRESS_CONTEXT: {"address": accounts.unassigned_accounts[0]}}
condition_providers = {
"1": {"fake1a", "fake1b"},
@ -194,9 +198,9 @@ def test_rpc_condition_evaluation_multiple_chain_providers(
side_effect=_dont_validate_user_address,
)
def test_rpc_condition_evaluation_multiple_providers_no_valid_fallback(
get_context_value_mock, mocker, testerchain, rpc_condition
get_context_value_mock, mocker, accounts, rpc_condition
):
context = {USER_ADDRESS_CONTEXT: {"address": testerchain.unassigned_accounts[0]}}
context = {USER_ADDRESS_CONTEXT: {"address": accounts.unassigned_accounts[0]}}
def my_configure_w3(provider: BaseProvider):
return Web3(provider)
@ -223,9 +227,9 @@ def test_rpc_condition_evaluation_multiple_providers_no_valid_fallback(
side_effect=_dont_validate_user_address,
)
def test_rpc_condition_evaluation_multiple_providers_valid_fallback(
get_context_value_mock, mocker, testerchain, rpc_condition
get_context_value_mock, mocker, testerchain, accounts, rpc_condition
):
context = {USER_ADDRESS_CONTEXT: {"address": testerchain.unassigned_accounts[0]}}
context = {USER_ADDRESS_CONTEXT: {"address": accounts.unassigned_accounts[0]}}
def my_configure_w3(provider: BaseProvider):
return Web3(provider)
@ -260,9 +264,9 @@ def test_rpc_condition_evaluation_multiple_providers_valid_fallback(
side_effect=_dont_validate_user_address,
)
def test_rpc_condition_evaluation_no_connection_to_chain(
get_context_value_mock, testerchain, rpc_condition
get_context_value_mock, testerchain, accounts, rpc_condition
):
context = {USER_ADDRESS_CONTEXT: {"address": testerchain.unassigned_accounts[0]}}
context = {USER_ADDRESS_CONTEXT: {"address": accounts.unassigned_accounts[0]}}
# condition providers for other unrelated chains
providers = {
@ -279,9 +283,9 @@ def test_rpc_condition_evaluation_no_connection_to_chain(
side_effect=_dont_validate_user_address,
)
def test_rpc_condition_evaluation_with_context_var_in_return_value_test(
get_context_value_mock, testerchain, condition_providers
get_context_value_mock, testerchain, accounts, condition_providers
):
account, *other_accounts = testerchain.client.accounts
account, *other_accounts = accounts.accounts_addresses
balance = testerchain.client.get_balance(account)
# we have balance stored, use for rpc condition with context variable
@ -318,15 +322,15 @@ def test_rpc_condition_evaluation_with_context_var_in_return_value_test(
side_effect=_dont_validate_user_address,
)
def test_erc20_evm_condition_evaluation(
get_context_value_mock, testerchain, erc20_evm_condition_balanceof, condition_providers
get_context_value_mock, erc20_evm_condition_balanceof, condition_providers, accounts
):
context = {USER_ADDRESS_CONTEXT: {"address": testerchain.unassigned_accounts[0]}}
context = {USER_ADDRESS_CONTEXT: {"address": accounts.unassigned_accounts[0]}}
condition_result, call_result = erc20_evm_condition_balanceof.verify(
providers=condition_providers, **context
)
assert condition_result is True
context[USER_ADDRESS_CONTEXT]["address"] = testerchain.etherbase_account
context[USER_ADDRESS_CONTEXT]["address"] = accounts.etherbase_account
condition_result, call_result = erc20_evm_condition_balanceof.verify(
providers=condition_providers, **context
)
@ -334,15 +338,15 @@ def test_erc20_evm_condition_evaluation(
def test_erc20_evm_condition_evaluation_with_custom_context_variable(
testerchain, custom_context_variable_erc20_condition, condition_providers
custom_context_variable_erc20_condition, condition_providers, accounts
):
context = {":addressToUse": testerchain.unassigned_accounts[0]}
context = {":addressToUse": accounts.unassigned_accounts[0]}
condition_result, call_result = custom_context_variable_erc20_condition.verify(
providers=condition_providers, **context
)
assert condition_result is True
context[":addressToUse"] = testerchain.etherbase_account
context[":addressToUse"] = accounts.etherbase_account
condition_result, call_result = custom_context_variable_erc20_condition.verify(
providers=condition_providers, **context
)
@ -354,9 +358,13 @@ def test_erc20_evm_condition_evaluation_with_custom_context_variable(
side_effect=_dont_validate_user_address,
)
def test_erc721_evm_condition_owner_evaluation(
get_context_value_mock, testerchain, test_registry, erc721_evm_condition_owner, condition_providers
get_context_value_mock,
accounts,
test_registry,
erc721_evm_condition_owner,
condition_providers,
):
account, *other_accounts = testerchain.client.accounts
account, *other_accounts = accounts.accounts_addresses
# valid owner of nft
context = {
USER_ADDRESS_CONTEXT: {"address": account},
@ -393,9 +401,13 @@ def test_erc721_evm_condition_owner_evaluation(
side_effect=_dont_validate_user_address,
)
def test_erc721_evm_condition_balanceof_evaluation(
get_context_value_mock, testerchain, test_registry, erc721_evm_condition_balanceof, condition_providers
get_context_value_mock,
accounts,
test_registry,
erc721_evm_condition_balanceof,
condition_providers,
):
account, *other_accounts = testerchain.client.accounts
account, *other_accounts = accounts.accounts_addresses
context = {USER_ADDRESS_CONTEXT: {"address": account}} # owner of NFT
condition_result, call_result = erc721_evm_condition_balanceof.verify(
providers=condition_providers, **context
@ -723,11 +735,11 @@ def test_not_of_simple_compound_conditions_lingo_evaluation(
)
def test_onchain_conditions_lingo_evaluation(
get_context_value_mock,
testerchain,
compound_lingo,
condition_providers,
accounts,
):
context = {USER_ADDRESS_CONTEXT: {"address": testerchain.etherbase_account}}
context = {USER_ADDRESS_CONTEXT: {"address": accounts.etherbase_account}}
result = compound_lingo.eval(providers=condition_providers, **context)
assert result is True
@ -738,11 +750,11 @@ def test_onchain_conditions_lingo_evaluation(
)
def test_not_of_onchain_conditions_lingo_evaluation(
get_context_value_mock,
testerchain,
compound_lingo,
condition_providers,
accounts,
):
context = {USER_ADDRESS_CONTEXT: {"address": testerchain.etherbase_account}}
context = {USER_ADDRESS_CONTEXT: {"address": accounts.etherbase_account}}
result = compound_lingo.eval(providers=condition_providers, **context)
assert result is True

View File

@ -14,19 +14,16 @@ from nucypher.blockchain.eth.agents import (
)
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
from nucypher.blockchain.eth.registry import ContractRegistry, RegistrySourceManager
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.crypto.powers import TransactingPower
from nucypher.policy.conditions.evm import RPCCondition
from nucypher.utilities.logging import Logger
from tests.constants import (
BONUS_TOKENS_FOR_TESTS,
INSECURE_DEVELOPMENT_PASSWORD,
MIN_OPERATOR_SECONDS,
TEMPORARY_DOMAIN,
TEST_ETH_PROVIDER_URI,
TESTERCHAIN_CHAIN_ID,
)
from tests.utils.blockchain import TesterBlockchain
from tests.utils.blockchain import ReservedTestAccountManager, TesterBlockchain
from tests.utils.registry import ApeRegistrySource
from tests.utils.ursula import (
mock_permitted_multichain_connections,
@ -78,6 +75,11 @@ def monkeymodule():
#
@pytest.fixture(scope="session")
def accounts():
return ReservedTestAccountManager()
@pytest.fixture(scope="module")
def deployer_account(accounts):
return accounts[0]
@ -301,7 +303,7 @@ def test_registry(deployed_contracts, module_mocker):
@pytest.mark.usefixtures("test_registry")
@pytest.fixture(scope="module")
def testerchain(project, clock) -> TesterBlockchain:
def testerchain(project, clock, accounts) -> TesterBlockchain:
# Extract the web3 provider containing EthereumTester from the ape project's chain manager
provider = project.chain_manager.provider.web3.provider
testerchain = TesterBlockchain(provider=provider)
@ -327,13 +329,8 @@ def staking_providers(
staking_providers = list()
for provider_address, operator_address in zip(
testerchain.stake_providers_accounts, testerchain.ursulas_accounts
accounts.staking_providers_accounts, accounts.ursulas_accounts
):
provider_power = TransactingPower(
account=provider_address, signer=Web3Signer(testerchain.client)
)
provider_power.unlock(password=INSECURE_DEVELOPMENT_PASSWORD)
# for a random amount
amount = minimum_stake + random.randrange(BONUS_TOKENS_FOR_TESTS)
@ -345,7 +342,9 @@ def staking_providers(
)
taco_application.bondOperator(
provider_address, operator_address, sender=accounts[provider_address]
provider_address,
operator_address,
sender=accounts[provider_address],
)
# track

View File

@ -1,68 +0,0 @@
import pytest
from tests.constants import (
DEVELOPMENT_ETH_AIRDROP_AMOUNT,
NUMBER_OF_ETH_TEST_ACCOUNTS,
NUMBER_OF_STAKING_PROVIDERS_IN_BLOCKCHAIN_TESTS,
NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS,
)
# Prevents TesterBlockchain to be picked up by py.test as a test class
from tests.utils.blockchain import TesterBlockchain as _TesterBlockchain
@pytest.fixture()
def another_testerchain():
testerchain = _TesterBlockchain(eth_airdrop=True, light=True)
testerchain.deployer_address = testerchain.etherbase_account
assert testerchain.is_light
yield testerchain
def test_testerchain_creation(testerchain, another_testerchain):
chains = (testerchain, another_testerchain)
for chain in chains:
# Ensure we are testing on the correct network...
assert "tester" in chain.endpoint
# ... and that there are already some blocks mined
chain.w3.eth.w3.testing.mine(1)
assert chain.w3.eth.block_number > 0
# Check that we have enough test accounts
assert len(chain.client.accounts) >= NUMBER_OF_ETH_TEST_ACCOUNTS
# Check that distinguished accounts are assigned
etherbase = chain.etherbase_account
assert etherbase == chain.client.accounts[0]
alice = chain.alice_account
assert alice == chain.client.accounts[1]
bob = chain.bob_account
assert bob == chain.client.accounts[2]
stakers = [chain.stake_provider_account(i) for i in range(NUMBER_OF_STAKING_PROVIDERS_IN_BLOCKCHAIN_TESTS)]
assert stakers == chain.stake_providers_accounts
ursulas = [chain.ursula_account(i) for i in range(NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS)]
assert ursulas == chain.ursulas_accounts
# Check that the remaining accounts are different from the previous ones:
assert set([etherbase, alice, bob] + ursulas + stakers).isdisjoint(set(chain.unassigned_accounts))
# Check that accounts are funded
for account in chain.client.accounts:
assert chain.client.get_balance(account) >= DEVELOPMENT_ETH_AIRDROP_AMOUNT
# Check that accounts can send transactions
for account in chain.client.accounts:
balance = chain.client.get_balance(account)
assert balance
tx = {'to': etherbase, 'from': account, 'value': 100}
txhash = chain.client.send_transaction(tx)
_receipt = chain.wait_for_receipt(txhash)

View File

@ -32,14 +32,8 @@ GLOBAL_ALLOW_LIST = "GlobalAllowList"
# Ursula
#
NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS = 10
NUMBER_OF_STAKING_PROVIDERS_IN_BLOCKCHAIN_TESTS = NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS
# Ursulas (Operators) and Staking Providers have their own account
NUMBER_OF_ETH_TEST_ACCOUNTS = NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS + NUMBER_OF_STAKING_PROVIDERS_IN_BLOCKCHAIN_TESTS + 10
NUMBER_OF_URSULAS_IN_DEVELOPMENT_DOMAIN = NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS
NUMBER_OF_ETH_TEST_ACCOUNTS = 30
#
# Local Signer Keystore

View File

@ -68,6 +68,7 @@ from tests.mock.performance_mocks import (
mock_rest_app_creation,
mock_verify_node,
)
from tests.utils.blockchain import ReservedTestAccountManager
from tests.utils.config import (
make_alice_test_configuration,
make_bob_test_configuration,
@ -78,7 +79,12 @@ from tests.utils.middleware import (
MockRestMiddlewareForLargeFleetTests,
)
from tests.utils.policy import generate_random_label
from tests.utils.ursula import MOCK_KNOWN_URSULAS_CACHE, make_ursulas, select_test_port
from tests.utils.ursula import (
MOCK_KNOWN_URSULAS_CACHE,
make_random_ursulas,
make_reserved_ursulas,
select_test_port,
)
test_logger = Logger("test-logger")
@ -111,6 +117,12 @@ def temp_dir_path():
#
@pytest.fixture(scope="session", autouse=True)
def accounts():
"""a la ape"""
return ReservedTestAccountManager()
@pytest.fixture(scope="module")
def random_account():
key = Account.create(extra_entropy="lamborghini mercy")
@ -129,13 +141,12 @@ def random_address(random_account):
@pytest.fixture(scope="module")
def ursula_test_config(test_registry, temp_dir_path, testerchain):
def ursula_test_config(test_registry, temp_dir_path):
config = make_ursula_test_configuration(
eth_endpoint=TEST_ETH_PROVIDER_URI,
polygon_endpoint=TEST_ETH_PROVIDER_URI,
test_registry=test_registry,
rest_port=select_test_port(),
operator_address=testerchain.ursulas_accounts.pop(),
)
yield config
config.cleanup()
@ -144,12 +155,12 @@ def ursula_test_config(test_registry, temp_dir_path, testerchain):
@pytest.fixture(scope="module")
def alice_test_config(ursulas, testerchain, test_registry):
def alice_test_config(ursulas, accounts, test_registry):
config = make_alice_test_configuration(
eth_endpoint=TEST_ETH_PROVIDER_URI,
polygon_endpoint=TEST_ETH_PROVIDER_URI,
known_nodes=ursulas,
checksum_address=testerchain.alice_account,
checksum_address=accounts.alice_account,
test_registry=test_registry,
)
yield config
@ -157,11 +168,11 @@ def alice_test_config(ursulas, testerchain, test_registry):
@pytest.fixture(scope="module")
def bob_test_config(testerchain, test_registry):
def bob_test_config(accounts, test_registry):
config = make_bob_test_configuration(
eth_endpoint=TEST_ETH_PROVIDER_URI,
test_registry=test_registry,
checksum_address=testerchain.bob_account,
checksum_address=accounts.bob_account,
)
yield config
config.cleanup()
@ -259,30 +270,32 @@ def random_policy_label():
@pytest.fixture(scope="module")
def alice(alice_test_config, ursulas, testerchain):
alice = alice_test_config.produce()
def alice(alice_test_config, accounts):
alice = alice_test_config.produce(
signer=accounts.get_account_signer(accounts.alice_account)
)
yield alice
alice.disenchant()
@pytest.fixture(scope="module")
def bob(bob_test_config, testerchain):
def bob(bob_test_config, accounts):
bob = bob_test_config.produce(
polygon_endpoint=TEST_ETH_PROVIDER_URI,
signer=accounts.get_account_signer(accounts.bob_account),
)
yield bob
bob.disenchant()
@pytest.fixture(scope="function")
def lonely_ursula_maker(ursula_test_config, testerchain):
def lonely_ursula_maker(ursula_test_config, accounts):
class _PartialUrsulaMaker:
_partial = partial(
make_ursulas,
make_reserved_ursulas,
accounts=accounts,
ursula_config=ursula_test_config,
know_each_other=False,
staking_provider_addresses=testerchain.stake_providers_accounts,
operator_addresses=testerchain.ursulas_accounts,
)
_made = []
@ -399,24 +412,15 @@ def fleet_of_highperf_mocked_ursulas(ursula_test_config, request, testerchain):
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_ursulas(
_ursulas = make_random_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}
@ -439,13 +443,13 @@ def fleet_of_highperf_mocked_ursulas(ursula_test_config, request, testerchain):
def highperf_mocked_alice(
fleet_of_highperf_mocked_ursulas,
monkeymodule,
testerchain,
accounts,
):
config = AliceConfiguration(
dev_mode=True,
domain=TEMPORARY_DOMAIN_NAME,
eth_endpoint=TEST_ETH_PROVIDER_URI,
checksum_address=testerchain.alice_account,
checksum_address=accounts.alice_account,
network_middleware=MockRestMiddlewareForLargeFleetTests(
eth_endpoint=TEST_ETH_PROVIDER_URI
),
@ -691,16 +695,15 @@ def clock():
@pytest.fixture(scope="module")
def ursulas(testerchain, ursula_test_config, staking_providers):
def ursulas(accounts, ursula_test_config, staking_providers):
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_ursulas(
_ursulas = make_reserved_ursulas(
accounts=accounts,
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:

View File

@ -1,17 +1,16 @@
import pytest
from nucypher.blockchain.eth.clients import EthereumClient
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.crypto.powers import TransactingPower
@pytest.mark.skip(
"This test need to be refactored to use some other transaction than deployment"
)
def test_block_confirmations(testerchain, test_registry, mocker):
origin = testerchain.etherbase_account
def test_block_confirmations(testerchain, test_registry, mocker, accounts):
origin = accounts.etherbase_account
transacting_power = TransactingPower(
account=origin, signer=Web3Signer(testerchain.client)
account=origin, signer=accounts.get_account_signer(origin)
)
# Mocks and test adjustments

View File

@ -11,7 +11,7 @@ from web3.datastructures import AttributeDict
from nucypher.blockchain.eth.agents import CoordinatorAgent
from nucypher.blockchain.eth.models import Coordinator
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.blockchain.eth.signers.software import InMemorySigner
from nucypher.characters.lawful import Enrico, Ursula
from nucypher.crypto.powers import RitualisticPower
from nucypher.policy.conditions.lingo import ConditionLingo, ConditionType
@ -123,7 +123,6 @@ def execute_round_2(ritual_id: int, cohort: List[Ursula]):
)
@pytest.mark.usefixtures("mock_sign_message")
@pytest.mark.parametrize("dkg_size, ritual_id, variant", PARAMS)
@pytest_twisted.inlineCallbacks()
def test_ursula_ritualist(
@ -212,8 +211,7 @@ def test_ursula_ritualist(
plaintext = PLAINTEXT.encode()
# create Enrico
signer = Web3Signer(client=testerchain.client)
enrico = Enrico(encrypting_key=encrypting_key, signer=signer)
enrico = Enrico(encrypting_key=encrypting_key, signer=InMemorySigner())
# encrypt
print(f"encrypting for DKG with key {bytes(encrypting_key).hex()}")

View File

@ -8,10 +8,7 @@ from twisted.internet.task import Clock
from nucypher.characters.lawful import Bob, Enrico
from nucypher.config.constants import TEMPORARY_DOMAIN_NAME
from tests.constants import (
MOCK_ETH_PROVIDER_URI,
NUMBER_OF_URSULAS_IN_DEVELOPMENT_DOMAIN,
)
from tests.constants import MOCK_ETH_PROVIDER_URI
from tests.utils.middleware import MockRestMiddleware
@ -38,7 +35,7 @@ def test_bob_full_retrieve_flow(
assert b"Welcome to flippering number 0." == delivered_cleartexts[0]
def test_bob_retrieves(alice, ursulas):
def test_bob_retrieves(accounts, alice, ursulas):
"""A test to show that Bob can retrieve data from Ursula"""
# Let's partition Ursulas in two parts
@ -60,7 +57,7 @@ def test_bob_retrieves(alice, ursulas):
# Alice creates a policy granting access to Bob
# Just for fun, let's assume she distributes KFrags among Ursulas unknown to Bob
shares = NUMBER_OF_URSULAS_IN_DEVELOPMENT_DOMAIN - 2
shares = accounts.NUMBER_OF_URSULAS_IN_TESTS - 2
label = b'label://' + os.urandom(32)
contract_end_datetime = maya.now() + datetime.timedelta(days=5)
policy = alice.grant(

View File

@ -1,7 +1,7 @@
import pytest
from nucypher.blockchain.eth import domains
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.blockchain.eth.signers.software import InMemorySigner
from nucypher.characters.chaotic import (
NiceGuyEddie,
ThisBobAlwaysDecrypts,
@ -19,8 +19,7 @@ from tests.constants import (
def _attempt_decryption(BobClass, plaintext, testerchain):
trinket = 80 # Doens't matter.
signer = Web3Signer(client=testerchain.client)
enrico = NiceGuyEddie(encrypting_key=trinket, signer=signer)
enrico = NiceGuyEddie(encrypting_key=trinket, signer=InMemorySigner())
bob = BobClass(
registry=MOCK_REGISTRY_FILEPATH,
domain=domains.LYNX,
@ -50,14 +49,12 @@ def _attempt_decryption(BobClass, plaintext, testerchain):
return decrypted_cleartext
@pytest.mark.usefixtures("mock_sign_message")
def test_user_controls_success(testerchain):
plaintext = b"ever thus to deadbeats"
result = _attempt_decryption(ThisBobAlwaysDecrypts, plaintext, testerchain)
assert bytes(result) == bytes(plaintext)
@pytest.mark.usefixtures("mock_sign_message")
def test_user_controls_failure(testerchain):
plaintext = b"ever thus to deadbeats"
with pytest.raises(Ursula.NotEnoughUrsulas):

View File

@ -5,10 +5,7 @@ import pytest
from eth_utils import is_checksum_address
from web3 import Web3
from nucypher.blockchain.eth.clients import EthereumClient
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
from nucypher.blockchain.eth.signers import KeystoreSigner
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.cli.actions.select import select_client_account
from nucypher.cli.literature import GENERIC_SELECT_ACCOUNT, NO_ACCOUNTS
from nucypher.config.constants import TEMPORARY_DOMAIN_NAME
@ -21,14 +18,17 @@ from tests.constants import (
@pytest.mark.parametrize("selection", range(NUMBER_OF_ETH_TEST_ACCOUNTS))
def test_select_client_account(
mock_stdin, test_emitter, testerchain, selection, capsys
mock_stdin, test_emitter, testerchain, accounts, selection, capsys, mocker
):
"""Fine-grained assertions about the return value of interactive client account selection"""
signer = mocker.Mock()
signer.accounts = accounts.accounts_addresses
mock_stdin.line(str(selection))
expected_account = testerchain.client.accounts[selection]
expected_account = accounts.accounts_addresses[selection]
selected_account = select_client_account(
emitter=test_emitter,
signer=Web3Signer(testerchain.client),
signer=signer,
polygon_endpoint=MOCK_ETH_PROVIDER_URI,
domain=TEMPORARY_DOMAIN_NAME,
)
@ -50,11 +50,13 @@ def test_select_client_account_with_no_accounts(
testerchain,
capsys,
):
mocker.patch.object(EthereumClient, "accounts", return_value=[])
signer = mocker.Mock()
signer.accounts = []
with pytest.raises(click.Abort):
select_client_account(
emitter=test_emitter,
signer=Web3Signer(testerchain.client),
signer=signer,
polygon_endpoint=MOCK_ETH_PROVIDER_URI,
domain=TEMPORARY_DOMAIN_NAME,
)
@ -81,11 +83,13 @@ def test_select_client_account_ambiguous_source(
@pytest.mark.parametrize("selection", range(NUMBER_OF_ETH_TEST_ACCOUNTS))
@pytest.mark.usefixtures("mock_registry_sources")
def test_select_client_account_valid_sources(
mocker,
mock_stdin,
test_emitter,
testerchain,
accounts,
patch_keystore,
mock_accounts,
selection,
@ -93,15 +97,19 @@ def test_select_client_account_valid_sources(
):
# From External Signer
mock_stdin.line(str(selection))
signer = mocker.Mock()
signer.accounts = accounts.accounts_addresses
mock_signer = mocker.patch.object(
KeystoreSigner, "from_signer_uri", return_value=Web3Signer(testerchain.client)
KeystoreSigner, "from_signer_uri", return_value=signer
)
selected_account = select_client_account(
domain=TEMPORARY_DOMAIN_NAME,
emitter=test_emitter,
signer_uri=MOCK_SIGNER_URI,
)
expected_account = testerchain.client.accounts[selection]
expected_account = accounts.accounts_addresses[selection]
assert selected_account == expected_account
mock_signer.assert_called_once_with(uri=MOCK_SIGNER_URI, testnet=True)
assert mock_stdin.empty()
@ -113,49 +121,11 @@ def test_select_client_account_valid_sources(
# From Wallet
mock_stdin.line(str(selection))
expected_account = testerchain.client.accounts[selection]
expected_account = accounts.accounts_addresses[selection]
selected_account = select_client_account(
domain=TEMPORARY_DOMAIN_NAME,
emitter=test_emitter,
signer=Web3Signer(testerchain.client),
)
assert selected_account == expected_account
assert mock_stdin.empty()
captured = capsys.readouterr()
assert (
GENERIC_SELECT_ACCOUNT in captured.out
and f"Selected {selection}" in captured.out
)
# From pre-initialized Provider
mock_stdin.line(str(selection))
expected_account = testerchain.client.accounts[selection]
selected_account = select_client_account(
domain=TEMPORARY_DOMAIN_NAME,
emitter=test_emitter,
polygon_endpoint=MOCK_ETH_PROVIDER_URI,
)
assert selected_account == expected_account
assert mock_stdin.empty()
captured = capsys.readouterr()
assert (
GENERIC_SELECT_ACCOUNT in captured.out
and f"Selected {selection}" in captured.out
)
# 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=testerchain
)
selected_account = select_client_account(
domain=TEMPORARY_DOMAIN_NAME,
emitter=test_emitter,
polygon_endpoint=MOCK_ETH_PROVIDER_URI,
signer=signer,
)
assert selected_account == expected_account
assert mock_stdin.empty()
@ -183,6 +153,7 @@ def test_select_client_account_with_balance_display(
mock_stdin,
test_emitter,
testerchain,
accounts,
capsys,
selection,
show_matic,
@ -199,7 +170,7 @@ def test_select_client_account_with_balance_display(
)
# check for accurate selection consistency with client index
assert selected_account == testerchain.client.accounts[selection]
assert selected_account == accounts[selection]
assert mock_stdin.empty()
# Display account info
@ -212,7 +183,7 @@ def test_select_client_account_with_balance_display(
for column_name in headers:
assert column_name in captured.out, f'"{column_name}" column was not displayed'
for account in testerchain.client.accounts:
for account in accounts:
assert account in captured.out
if show_matic:

View File

@ -42,7 +42,7 @@ def test_destroy_with_no_configurations(click_runner, custom_filepath):
def test_corrupted_configuration(
click_runner, custom_filepath, testerchain, test_registry, mocker
click_runner, custom_filepath, accounts, test_registry, mocker
):
#
# Setup
@ -53,7 +53,13 @@ def test_corrupted_configuration(
shutil.rmtree(custom_filepath, ignore_errors=True)
assert not custom_filepath.exists()
alice, ursula, another_ursula, staking_provider, *all_yall = testerchain.unassigned_accounts
(
alice,
ursula,
another_ursula,
staking_provider,
*all_yall,
) = accounts.unassigned_accounts
#
# Chaos

View File

@ -87,7 +87,6 @@ def test_ursula_startup_ip_checkup(click_runner, mocker):
def test_ursula_run_ip_checkup(
testerchain,
custom_filepath,
click_runner,
mocker,
ursulas,
@ -110,10 +109,10 @@ def test_ursula_run_ip_checkup(
mocker.patch.object(Ursula, 'from_teacher_uri', return_value=teacher)
# Mock worker qualification
staking_provider = ursulas[1]
worker = ursulas[1]
def set_staking_provider_address(operator, *args, **kwargs):
operator.checksum_address = staking_provider.checksum_address
operator.checksum_address = worker.checksum_address
return True
monkeypatch.setattr(Operator, "block_until_ready", set_staking_provider_address)
@ -121,6 +120,7 @@ def test_ursula_run_ip_checkup(
mocker.patch("nucypher.cli.commands.ursula.migrate", return_value=None)
ursula_test_config.rest_host = MOCK_IP_ADDRESS
ursula_test_config.operator_address = worker.operator_address
mocker.patch.object(
UrsulaConfiguration, "from_configuration_file", return_value=ursula_test_config
)

View File

@ -12,10 +12,10 @@ def mock_ursula_run(mocker, ursulas, monkeypatch, ursula_test_config, mock_prome
ursula_test_config.rest_host = MOCK_IP_ADDRESS
# Mock worker qualification
staking_provider = ursulas[1]
worker = ursulas[1]
def set_staking_provider_address(operator):
operator.checksum_address = staking_provider.checksum_address
operator.checksum_address = worker.checksum_address
return True
monkeypatch.setattr(Operator, "block_until_ready", set_staking_provider_address)
@ -23,6 +23,8 @@ def mock_ursula_run(mocker, ursulas, monkeypatch, ursula_test_config, mock_prome
# Mock migration
mocker.patch("nucypher.cli.commands.ursula.migrate", return_value=None)
ursula_test_config.operator_address = worker.operator_address
# Mock Ursula configuration
mocker.patch.object(
UrsulaConfiguration, "from_configuration_file", return_value=ursula_test_config

View File

@ -11,7 +11,7 @@ from tests.constants import FAKE_PASSWORD_CONFIRMED, MOCK_IP_ADDRESS
@pytest.fixture
def v4_config_file(tempfile_path, ursula_test_config):
def v4_config_file(tempfile_path, ursula_test_config, accounts):
config_dictionary = {
"federated_only": None,
"checksum_address": None,
@ -29,7 +29,7 @@ def v4_config_file(tempfile_path, ursula_test_config):
"signer_uri": ursula_test_config.signer_uri,
"gas_strategy": "fast",
"max_gas_price": None,
"operator_address": ursula_test_config.operator_address,
"operator_address": accounts.ursulas_accounts[0],
"rest_host": MOCK_IP_ADDRESS,
"rest_port": ursula_test_config.rest_port,
"db_filepath": "/root/.local/share/nucypher/ursula.db",
@ -69,10 +69,10 @@ def test_ursula_run_specified_config_file(
mocker.patch("nucypher.cli.utils.unlock_nucypher_keystore", return_value=True)
# Mock worker qualification
staking_provider = ursulas[1]
worker = ursulas[1]
def set_staking_provider_address(operator, *args, **kwargs):
operator.checksum_address = staking_provider.checksum_address
operator.checksum_address = worker.checksum_address
return True
monkeypatch.setattr(Operator, "block_until_ready", set_staking_provider_address)

View File

@ -84,7 +84,7 @@ def test_interactive_initialize_ursula(click_runner, mocker, tmpdir):
def test_initialize_custom_configuration_root(
click_runner, custom_filepath: Path, testerchain
click_runner, custom_filepath: Path, accounts
):
deploy_port = select_test_port()
# Use a custom local filepath for configuration
@ -104,7 +104,7 @@ def test_initialize_custom_configuration_root(
"--polygon-endpoint",
MOCK_ETH_PROVIDER_URI,
"--operator-address",
testerchain.ursulas_accounts[0],
accounts.ursulas_accounts[0],
)
result = click_runner.invoke(
nucypher_cli, init_args, input=FAKE_PASSWORD_CONFIRMED, catch_exceptions=False

View File

@ -41,18 +41,18 @@ all_configurations = tuple(
)
@pytest.mark.parametrize("character,configuration", characters_and_configurations)
def test_development_character_configurations(
character, configuration, mocker, testerchain
character, configuration, mocker, accounts
):
params = dict(
dev_mode=True,
lonely=True,
domain=TEMPORARY_DOMAIN_NAME,
checksum_address=testerchain.unassigned_accounts[0],
checksum_address=accounts.unassigned_accounts[0],
eth_endpoint=MOCK_ETH_PROVIDER_URI,
polygon_endpoint=MOCK_ETH_PROVIDER_URI,
)
if character is Ursula:
params.update(dict(operator_address=testerchain.unassigned_accounts[0]))
params.update(dict(operator_address=accounts.unassigned_accounts[0]))
config = configuration(**params)
assert config.is_me is True
@ -160,11 +160,11 @@ def test_default_character_configuration_preservation(
expected_filepath.unlink()
def test_ursula_development_configuration(testerchain):
def test_ursula_development_configuration(accounts):
config = UrsulaConfiguration(
dev_mode=True,
checksum_address=testerchain.unassigned_accounts[0],
operator_address=testerchain.unassigned_accounts[1],
checksum_address=accounts.unassigned_accounts[0],
operator_address=accounts.unassigned_accounts[1],
domain=TEMPORARY_DOMAIN_NAME,
eth_endpoint=MOCK_ETH_PROVIDER_URI,
polygon_endpoint=MOCK_ETH_PROVIDER_URI,

View File

@ -10,7 +10,7 @@ from tests.constants import INSECURE_DEVELOPMENT_PASSWORD, MOCK_ETH_PROVIDER_URI
from tests.utils.middleware import MockRestMiddleware
def test_alices_powers_are_persistent(ursulas, temp_dir_path, testerchain):
def test_alices_powers_are_persistent(ursulas, temp_dir_path, accounts):
# Create a non-learning AliceConfiguration
config_root = temp_dir_path / 'nucypher-custom-alice-config'
alice_config = AliceConfiguration(
@ -18,7 +18,7 @@ def test_alices_powers_are_persistent(ursulas, temp_dir_path, testerchain):
config_root=config_root,
network_middleware=MockRestMiddleware(eth_endpoint=MOCK_ETH_PROVIDER_URI),
domain=TEMPORARY_DOMAIN_NAME,
checksum_address=testerchain.alice_account,
checksum_address=accounts.alice_account,
start_learning_now=False,
save_metadata=False,
reload_metadata=False,

View File

@ -10,7 +10,7 @@ from nucypher_core import (
)
from nucypher_core.umbral import SecretKey, Signer
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.blockchain.eth.signers.software import InMemorySigner
from nucypher.characters.lawful import Alice, Bob, Enrico, Ursula
from nucypher.config.constants import TEMPORARY_DOMAIN_NAME
from nucypher.crypto.ferveo.dkg import FerveoVariant
@ -66,7 +66,7 @@ def test_generate_alice_keystore(temp_dir_path):
@pytest.mark.usefixtures("mock_registry_sources")
def test_characters_use_keystore(temp_dir_path, testerchain):
def test_characters_use_keystore(temp_dir_path, testerchain, accounts):
keystore = Keystore.generate(
password=INSECURE_DEVELOPMENT_PASSWORD,
keystore_dir=temp_dir_path
@ -83,7 +83,7 @@ def test_characters_use_keystore(temp_dir_path, testerchain):
domain=TEMPORARY_DOMAIN_NAME,
eth_endpoint=MOCK_ETH_PROVIDER_URI,
polygon_endpoint=MOCK_ETH_PROVIDER_URI,
checksum_address=testerchain.alice_account,
checksum_address=accounts.alice_account,
pre_payment_method=pre_payment_method,
)
Bob(
@ -92,6 +92,7 @@ def test_characters_use_keystore(temp_dir_path, testerchain):
keystore=keystore,
domain=TEMPORARY_DOMAIN_NAME,
)
ursula_account = accounts.ursulas_accounts[0]
Ursula(
eth_endpoint=MOCK_ETH_PROVIDER_URI,
polygon_endpoint=MOCK_ETH_PROVIDER_URI,
@ -101,8 +102,8 @@ def test_characters_use_keystore(temp_dir_path, testerchain):
rest_port=12345,
domain=TEMPORARY_DOMAIN_NAME,
pre_payment_method=pre_payment_method,
operator_address=testerchain.ursulas_accounts[0],
signer=Web3Signer(testerchain.client),
operator_address=ursula_account,
signer=accounts.get_account_signer(ursula_account),
condition_blockchain_endpoints={TESTERCHAIN_CHAIN_ID: MOCK_ETH_PROVIDER_URI},
)
alice.disenchant() # To stop Alice's publication threadpool. TODO: Maybe only start it at first enactment?
@ -153,8 +154,7 @@ def test_tls_hosting_certificate_remains_the_same(temp_dir_path, mocker):
recreated_ursula.disenchant()
@pytest.mark.usefixtures("mock_sign_message")
def test_ritualist(temp_dir_path, testerchain, dkg_public_key):
def test_ritualist(temp_dir_path, testerchain, accounts, dkg_public_key):
keystore = Keystore.generate(
password=INSECURE_DEVELOPMENT_PASSWORD, keystore_dir=temp_dir_path
)
@ -164,6 +164,7 @@ def test_ritualist(temp_dir_path, testerchain, dkg_public_key):
blockchain_endpoint=MOCK_ETH_PROVIDER_URI, domain=TEMPORARY_DOMAIN_NAME
)
ursula_account = accounts.ursulas_accounts[0]
ursula = Ursula(
start_learning_now=False,
keystore=keystore,
@ -171,8 +172,8 @@ def test_ritualist(temp_dir_path, testerchain, dkg_public_key):
rest_port=12345,
domain=TEMPORARY_DOMAIN_NAME,
pre_payment_method=pre_payment_method,
operator_address=testerchain.ursulas_accounts[0],
signer=Web3Signer(testerchain.client),
operator_address=ursula_account,
signer=accounts.get_account_signer(ursula_account),
eth_endpoint=MOCK_ETH_PROVIDER_URI,
polygon_endpoint=MOCK_ETH_PROVIDER_URI,
condition_blockchain_endpoints={TESTERCHAIN_CHAIN_ID: MOCK_ETH_PROVIDER_URI},
@ -192,8 +193,7 @@ def test_ritualist(temp_dir_path, testerchain, dkg_public_key):
}
# create enrico
signer = Web3Signer(client=testerchain.client)
enrico = Enrico(encrypting_key=dkg_public_key, signer=signer)
enrico = Enrico(encrypting_key=dkg_public_key, signer=InMemorySigner())
# encrypt
threshold_message_kit = enrico.encrypt_for_dkg(

View File

@ -1,4 +1,3 @@
import os
from pathlib import Path
from typing import Iterable, Optional
@ -22,7 +21,6 @@ from nucypher.blockchain.eth.registry import (
ContractRegistry,
)
from nucypher.blockchain.eth.signers import KeystoreSigner
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.characters.lawful import Ursula
from nucypher.cli.types import ChecksumAddress
from nucypher.config.characters import UrsulaConfiguration
@ -49,13 +47,13 @@ def pytest_addhooks(pluginmanager):
@pytest.fixture(scope="module", autouse=True)
def mock_sample_reservoir(testerchain, mock_contract_agency):
def mock_sample_reservoir(accounts, mock_contract_agency):
def mock_reservoir(
without: Optional[Iterable[ChecksumAddress]] = None, *args, **kwargs
):
addresses = {
address: 1
for address in testerchain.stake_providers_accounts
for address in accounts.staking_providers_accounts
if address not in without
}
return StakingProvidersReservoir(addresses)
@ -64,14 +62,6 @@ def mock_sample_reservoir(testerchain, mock_contract_agency):
mock_agent.get_staking_provider_reservoir = mock_reservoir
@pytest.fixture(scope="function")
def mock_sign_message(mocker):
mocked_sign_message = mocker.patch.object(
Web3Signer, "sign_message", return_value=os.urandom(32)
)
return mocked_sign_message
@pytest.fixture(scope="function", autouse=True)
def mock_taco_application_agent(testerchain, mock_contract_agency):
mock_agent = mock_contract_agency.get_agent(TACoApplicationAgent)
@ -166,13 +156,13 @@ def agency(mock_contract_agency):
@pytest.fixture(scope="function")
def mock_funding_and_bonding(
testerchain, mocker, mock_taco_application_agent, mock_taco_child_application_agent
accounts, mocker, mock_taco_application_agent, mock_taco_child_application_agent
):
# funding
mocker.patch.object(EthereumClient, "get_balance", return_value=1)
# bonding
staking_provider = testerchain.stake_providers_accounts[0]
staking_provider = accounts.staking_providers_accounts[0]
mock_taco_application_agent.get_staking_provider_from_operator.return_value = (
staking_provider
)
@ -262,12 +252,14 @@ def real_operator_get_staking_provider_address():
@pytest.mark.usefixtures("monkeymodule")
@pytest.fixture(scope="module", autouse=True)
def staking_providers(real_operator_get_staking_provider_address, testerchain):
def staking_providers(real_operator_get_staking_provider_address, accounts):
def faked(self, *args, **kwargs):
return testerchain.stake_providers_accounts[testerchain.ursulas_accounts.index(self.transacting_power.account)]
return accounts.staking_providers_accounts[
accounts.ursulas_accounts.index(self.transacting_power.account)
]
Operator.get_staking_provider_address = faked
return testerchain.stake_providers_accounts
return accounts.staking_providers_accounts
@pytest.fixture(scope="module")

View File

@ -7,19 +7,18 @@ import requests
from cryptography.hazmat.primitives import serialization
from twisted.internet import threads
from tests.utils.ursula import make_ursulas
from tests.utils.ursula import make_reserved_ursulas
@pytest_twisted.inlineCallbacks
def test_ursula_serves_statics(ursula_test_config, testerchain):
def test_ursula_serves_statics(ursula_test_config, accounts):
with tempfile.TemporaryDirectory() as STATICS_DIR:
os.environ['NUCYPHER_STATIC_FILES_ROOT'] = str(STATICS_DIR)
node = make_ursulas(
node = make_reserved_ursulas(
accounts=accounts,
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()

View File

@ -3,7 +3,7 @@ from typing import Optional, Union
from atxm.tx import FutureTx
from hexbytes import HexBytes
from nucypher.blockchain.eth.clients import EthereumTesterClient
from nucypher.blockchain.eth.clients import EthereumClient
from nucypher.blockchain.eth.interfaces import BlockchainInterface
from tests.constants import MOCK_ETH_PROVIDER_URI, TESTERCHAIN_CHAIN_ID
from tests.utils.blockchain import TesterBlockchain
@ -62,10 +62,11 @@ class MockBlockchain(TesterBlockchain):
return future_tx
class MockEthereumClient(EthereumTesterClient):
class MockEthereumClient(EthereumClient):
def __init__(self, w3):
super().__init__(w3=w3, node_technology=None, version=None, platform=None, backend=None)
super().__init__(w3=w3)
def add_middleware(self, middleware):
pass

View File

@ -8,7 +8,7 @@ from nucypher.blockchain.eth.registry import ContractRegistry
from nucypher.crypto.ferveo import dkg
from nucypher.crypto.powers import TransactingPower
from nucypher.network.nodes import Teacher
from tests.constants import TEMPORARY_DOMAIN
from tests.constants import MOCK_ETH_PROVIDER_URI, TEMPORARY_DOMAIN
from tests.mock.interfaces import MockBlockchain, MockEthereumClient
from tests.utils.registry import MockRegistrySource, mock_registry_sources
@ -28,6 +28,8 @@ def test_registry(module_mocker):
@pytest.fixture(scope='function')
def mock_ethereum_client(mocker):
web3_mock = mocker.Mock()
web3_mock.provider = mocker.Mock()
web3_mock.provider.endpoint_uri = MOCK_ETH_PROVIDER_URI
mock_client = MockEthereumClient(w3=web3_mock)
return mock_client
@ -74,14 +76,14 @@ def testerchain(mock_testerchain, module_mocker, clock) -> MockBlockchain:
@pytest.fixture(scope="module", autouse=True)
def staking_providers(testerchain, test_registry, monkeymodule):
def staking_providers(accounts, test_registry, monkeymodule):
def faked(self, *args, **kwargs):
return testerchain.stake_providers_accounts[
testerchain.ursulas_accounts.index(self.transacting_power.account)
return accounts.staking_providers_accounts[
accounts.ursulas_accounts.index(self.transacting_power.account)
]
Operator.get_staking_provider_address = faked
return testerchain.stake_providers_accounts
return accounts.staking_providers_accounts
@pytest.fixture(scope="module", autouse=True)

View File

@ -10,9 +10,7 @@ CHAIN_ID = 23
@pytest.mark.parametrize("chain_id_return_value", [hex(CHAIN_ID), CHAIN_ID])
def test_cached_chain_id(mocker, chain_id_return_value):
web3_mock = mocker.MagicMock()
mock_client = EthereumClient(
w3=web3_mock, node_technology=None, version=None, platform=None, backend=None
)
mock_client = EthereumClient(w3=web3_mock)
chain_id_property_mock = PropertyMock(return_value=chain_id_return_value)
type(web3_mock.eth).chain_id = chain_id_property_mock
@ -24,9 +22,7 @@ def test_cached_chain_id(mocker, chain_id_return_value):
chain_id_property_mock.assert_called_once(), "not called again since cached"
# second instance of client, but uses the same w3 mock
mock_client_2 = EthereumClient(
w3=web3_mock, node_technology=None, version=None, platform=None, backend=None
)
mock_client_2 = EthereumClient(w3=web3_mock)
assert mock_client_2.chain_id == CHAIN_ID
assert (
chain_id_property_mock.call_count == 2

View File

@ -0,0 +1,31 @@
from tests.constants import NUMBER_OF_ETH_TEST_ACCOUNTS
from tests.utils.blockchain import ReservedTestAccountManager
def test_account_organization():
account_manager = ReservedTestAccountManager()
account_addresses = account_manager.accounts_addresses
assert (
len(set(account_addresses)) == NUMBER_OF_ETH_TEST_ACCOUNTS
), "all unique addresses"
# special accounts
assert account_manager.etherbase_account == account_addresses[0]
assert account_manager.alice_account == account_addresses[1]
assert account_manager.bob_account == account_addresses[2]
# staking provider addresses
staking_providers = account_manager.staking_providers_accounts
assert staking_providers == account_addresses[3:13]
for i in range(len(staking_providers)):
assert account_manager.staking_provider_account(i) == staking_providers[i]
# ursula addresses
ursulas = account_manager.ursulas_accounts
assert ursulas == account_addresses[13:23]
for i in range(len(ursulas)):
assert account_manager.ursula_account(i) == ursulas[i]
# unassigned addresses
assert account_manager.unassigned_accounts == account_addresses[23:30]

View File

@ -3,8 +3,7 @@ from atxm.exceptions import Fault, InsufficientFunds
from nucypher.blockchain.eth.agents import CoordinatorAgent
from nucypher.blockchain.eth.models import PHASE1, PHASE2, Coordinator
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.crypto.powers import RitualisticPower, TransactingPower
from nucypher.crypto.powers import RitualisticPower
from nucypher.types import PhaseId
from tests.constants import MOCK_ETH_PROVIDER_URI
from tests.mock.coordinator import MockCoordinatorAgent
@ -39,10 +38,8 @@ def cohort(ursulas):
@pytest.fixture(scope="module")
def transacting_power(testerchain, alice):
return TransactingPower(
account=alice.transacting_power.account, signer=Web3Signer(testerchain.client)
)
def transacting_power(alice):
return alice.transacting_power
def test_initiate_ritual(

View File

@ -1,64 +1,54 @@
import datetime
from unittest.mock import Mock, PropertyMock
import pytest
from web3 import HTTPProvider, IPCProvider, WebsocketProvider
from web3 import HTTPProvider
from nucypher.blockchain.eth.clients import (
AlchemyClient,
GanacheClient,
GethClient,
InfuraClient,
ParityClient,
)
from nucypher.blockchain.eth.clients import EthereumClient
from nucypher.blockchain.eth.interfaces import BlockchainInterface
from nucypher.utilities.networking import LOOPBACK_ADDRESS
DEFAULT_GAS_PRICE = 42
GAS_PRICE_FROM_STRATEGY = 1234
CHAIN_ID = 11155111 # pretend to be sepolia
#
# Mock Providers
#
@pytest.mark.parametrize("chain_id_return_value", [hex(CHAIN_ID), CHAIN_ID])
def test_cached_chain_id(mocker, chain_id_return_value):
web3_mock = mocker.MagicMock()
mock_client = EthereumClient(w3=web3_mock)
chain_id_property_mock = PropertyMock(return_value=chain_id_return_value)
type(web3_mock.eth).chain_id = chain_id_property_mock
assert mock_client.chain_id == CHAIN_ID
chain_id_property_mock.assert_called_once()
assert mock_client.chain_id == CHAIN_ID
chain_id_property_mock.assert_called_once(), "not called again since cached"
# second instance of client, but uses the same w3 mock
mock_client_2 = EthereumClient(
w3=web3_mock,
)
assert mock_client_2.chain_id == CHAIN_ID
assert (
chain_id_property_mock.call_count == 2
), "additional call since different client instance"
assert mock_client_2.chain_id == CHAIN_ID
assert chain_id_property_mock.call_count == 2, "not called again since cached"
class MockGethProvider:
endpoint_uri = 'file:///ipc.geth'
client_version = 'Geth/v1.4.11-stable-fed692f6/darwin/go1.7'
class MockParityProvider:
endpoint_uri = 'file:///ipc.parity'
client_version = 'Parity-Ethereum/v2.5.1-beta-e0141f8-20190510/x86_64-linux-gnu/rustc1.34.1'
class MockGanacheProvider:
endpoint_uri = 'http://ganache:8445'
client_version = 'EthereumJS TestRPC/v2.1.5/ethereum-js'
class MockInfuraProvider:
endpoint_uri = "wss://:@sepolia.infura.io/ws/v3/1234567890987654321abcdef"
client_version = "Geth/v1.8.23-omnibus-2ad89aaa/linux-amd64/go1.11.1"
class MockAlchemyProvider:
endpoint_uri = 'https://eth-rinkeby.alchemyapi.io/v2/1234567890987654321abcdef'
client_version = 'Geth/v1.9.20-stable-979fc968/linux-amd64/go1.15'
class MockWebSocketProvider:
endpoint_uri = f'ws://{LOOPBACK_ADDRESS}:8546'
client_version = 'Geth/v1.8.23-omnibus-2ad89aaa/linux-amd64/go1.11.1'
endpoint_uri = "http://192.168.9.0:8545"
client_version = "Geth/v1.4.11-stable-fed692f6/darwin/go1.7"
class SyncedMockW3Eth:
# Support older and newer versions of web3 py in-test
version = 5
chain_id = hex(5)
chain_id = hex(CHAIN_ID)
block_number = 5
def getBlock(self, blockNumber):
@ -67,32 +57,6 @@ class SyncedMockW3Eth:
}
class MockedW3GethWithPeers:
@property
def admin(self):
class GethAdmin:
def peers(self):
return [1, 2, 3]
return GethAdmin()
class MockedW3GethWithNoPeers:
@property
def admin(self):
class GethAdmin:
def peers(self):
return []
return GethAdmin()
#
# Mock Web3
#
@ -101,7 +65,6 @@ class SyncedMockWeb3:
net = SyncedMockW3Eth()
eth = SyncedMockW3Eth()
geth = MockedW3GethWithPeers()
def __init__(self, provider):
self.provider = provider
@ -153,36 +116,12 @@ class ProviderTypeTestClient(BlockchainInterfaceTestBase):
super()._attach_blockchain_provider(provider=self.test_provider_to_attach)
class InfuraTestClient(BlockchainInterfaceTestBase):
def _attach_blockchain_provider(self, *args, **kwargs) -> None:
super()._attach_blockchain_provider(provider=MockInfuraProvider())
class AlchemyTestClient(BlockchainInterfaceTestBase):
def _attach_blockchain_provider(self, *args, **kwargs) -> None:
super()._attach_blockchain_provider(provider=MockAlchemyProvider())
class GethClientTestBlockchain(BlockchainInterfaceTestBase):
def _attach_blockchain_provider(self, *args, **kwargs) -> None:
super()._attach_blockchain_provider(provider=MockGethProvider())
class ParityClientTestInterface(BlockchainInterfaceTestBase):
def _attach_blockchain_provider(self, *args, **kwargs) -> None:
super()._attach_blockchain_provider(provider=MockParityProvider())
class GanacheClientTestInterface(BlockchainInterfaceTestBase):
def _attach_blockchain_provider(self, *args, **kwargs) -> None:
super()._attach_blockchain_provider(provider=MockGanacheProvider())
def test_client_no_provider():
with pytest.raises(BlockchainInterface.NoProvider):
interface = BlockchainInterfaceTestBase()
@ -190,138 +129,30 @@ def test_client_no_provider():
def test_geth_web3_client():
interface = GethClientTestBlockchain(endpoint="file:///ipc.geth")
interface = GethClientTestBlockchain(endpoint="https://my.geth:8545")
interface.connect()
assert isinstance(interface.client, GethClient)
assert interface.client.node_technology == 'Geth'
assert interface.client.node_version == 'v1.4.11-stable-fed692f6'
assert interface.client.platform == 'darwin'
assert interface.client.backend == 'go1.7'
assert interface.client.is_local is False
assert interface.client.chain_id == 5 # Hardcoded above
def test_autodetect_provider_type_file(tempfile_path):
interface = ProviderTypeTestClient(
endpoint=str(tempfile_path), # existing file for test
expected_provider_class=IPCProvider,
actual_provider_to_attach=MockGethProvider(),
)
interface.connect()
assert isinstance(interface.client, GethClient)
def test_autodetect_provider_type_file_none_existent():
with pytest.raises(BlockchainInterface.UnsupportedProvider):
interface = BlockchainInterfaceTestBase(endpoint="/none_existent.ipc.geth")
interface.connect()
def test_detect_provider_type_file():
interface = ProviderTypeTestClient(
endpoint="file:///ipc.geth",
expected_provider_class=IPCProvider,
actual_provider_to_attach=MockGethProvider(),
)
interface.connect()
assert isinstance(interface.client, GethClient)
def test_detect_provider_type_ipc():
interface = ProviderTypeTestClient(
endpoint="ipc:///ipc.geth",
expected_provider_class=IPCProvider,
actual_provider_to_attach=MockGethProvider(),
)
interface.connect()
assert isinstance(interface.client, GethClient)
assert isinstance(interface.client, EthereumClient)
assert interface.client.chain_id == CHAIN_ID # Hardcoded above
def test_detect_provider_type_http():
interface = ProviderTypeTestClient(
endpoint="http://ganache:8445",
endpoint="http://mynode.com:8445",
expected_provider_class=HTTPProvider,
actual_provider_to_attach=MockGanacheProvider(),
actual_provider_to_attach=MockGethProvider(),
)
interface.connect()
assert isinstance(interface.client, GanacheClient)
assert isinstance(interface.client, EthereumClient)
def test_detect_provider_type_https():
interface = ProviderTypeTestClient(
endpoint="https://ganache:8445",
endpoint="https://public-node.io:8445",
expected_provider_class=HTTPProvider,
actual_provider_to_attach=MockGanacheProvider(),
actual_provider_to_attach=MockGethProvider,
)
interface.connect()
assert isinstance(interface.client, GanacheClient)
def test_detect_provider_type_ws():
interface = ProviderTypeTestClient(
endpoint=f"ws://{LOOPBACK_ADDRESS}:8546",
expected_provider_class=WebsocketProvider,
actual_provider_to_attach=MockWebSocketProvider(),
)
interface.connect()
assert isinstance(interface.client, GethClient)
def test_infura_web3_client():
interface = InfuraTestClient(
endpoint="wss://:@goerli.infura.io/ws/v3/1234567890987654321abcdef"
)
interface.connect()
assert isinstance(interface.client, InfuraClient)
assert interface.client.node_technology == 'Geth'
assert interface.client.node_version == 'v1.8.23-omnibus-2ad89aaa'
assert interface.client.platform == 'linux-amd64'
assert interface.client.backend == 'go1.11.1'
assert interface.client.is_local is False
assert interface.client.chain_id == 5
assert interface.client.unlock_account('address', 'password') # Returns True on success
def test_alchemy_web3_client():
interface = AlchemyTestClient(
endpoint="https://eth-rinkeby.alchemyapi.io/v2/1234567890987654321abcdef"
)
interface.connect()
assert isinstance(interface.client, AlchemyClient)
assert interface.client.node_technology == 'Geth'
assert interface.client.node_version == 'v1.9.20-stable-979fc968'
assert interface.client.platform == 'linux-amd64'
assert interface.client.backend == 'go1.15'
def test_parity_web3_client():
interface = ParityClientTestInterface(endpoint="file:///ipc.parity")
interface.connect()
assert isinstance(interface.client, ParityClient)
assert interface.client.node_technology == 'Parity-Ethereum'
assert interface.client.node_version == 'v2.5.1-beta-e0141f8-20190510'
assert interface.client.platform == 'x86_64-linux-gnu'
assert interface.client.backend == 'rustc1.34.1'
def test_ganache_web3_client():
interface = GanacheClientTestInterface(endpoint="http://ganache:8445")
interface.connect()
assert isinstance(interface.client, GanacheClient)
assert interface.client.node_technology == 'EthereumJS TestRPC'
assert interface.client.node_version == 'v2.1.5'
assert interface.client.platform is None
assert interface.client.backend == 'ethereum-js'
assert interface.client.is_local
assert isinstance(interface.client, EthereumClient)
def test_gas_prices(mocker, mock_ethereum_client):

View File

@ -1,48 +1,116 @@
import os
from functools import cached_property
from typing import List, Union
import maya
from ape.api import AccountAPI
from ape.managers.accounts import TestAccountManager
from eth_tester.exceptions import TransactionFailed
from eth_typing import ChecksumAddress
from hexbytes import HexBytes
from web3 import Web3
from nucypher.blockchain.eth.interfaces import (
BlockchainInterface,
)
from nucypher.blockchain.eth.signers.software import Web3Signer
from nucypher.blockchain.eth.token import NU
from nucypher.crypto.powers import TransactingPower
from nucypher.blockchain.eth.signers import Signer
from nucypher.blockchain.eth.signers.software import InMemorySigner
from nucypher.utilities.gas_strategies import EXPECTED_CONFIRMATION_TIME_IN_SECONDS
from nucypher.utilities.logging import Logger
from tests.constants import (
DEVELOPMENT_ETH_AIRDROP_AMOUNT,
INSECURE_DEVELOPMENT_PASSWORD,
NUMBER_OF_ETH_TEST_ACCOUNTS,
NUMBER_OF_STAKING_PROVIDERS_IN_BLOCKCHAIN_TESTS,
NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS,
TEST_ETH_PROVIDER_URI,
)
from tests.constants import NUMBER_OF_ETH_TEST_ACCOUNTS, TEST_ETH_PROVIDER_URI
def token_airdrop(token_agent, amount: NU, transacting_power: TransactingPower, addresses: List[str]):
"""Airdrops tokens from creator address to all other addresses!"""
class ReservedTestAccountManager(TestAccountManager):
signer = Web3Signer(token_agent.blockchain.client)
signer.unlock_account(account=transacting_power.account, password=INSECURE_DEVELOPMENT_PASSWORD)
NUMBER_OF_URSULAS_IN_TESTS = 10
NUMBER_OF_STAKING_PROVIDERS_IN_TESTS = NUMBER_OF_URSULAS_IN_TESTS
def txs():
args = {'from': transacting_power.account, 'gasPrice': token_agent.blockchain.client.gas_price}
for address in addresses:
contract_function = token_agent.contract.functions.transfer(address, int(amount))
_receipt = token_agent.blockchain.send_transaction(contract_function=contract_function,
transacting_power=transacting_power,
payload=args)
yield _receipt
# Internal
__STAKING_PROVIDERS_RANGE = range(NUMBER_OF_STAKING_PROVIDERS_IN_TESTS)
__OPERATORS_RANGE = range(NUMBER_OF_URSULAS_IN_TESTS)
receipts = list()
for receipt in txs(): # One at a time
receipts.append(receipt)
return receipts
# Reserved addresses
_ETHERBASE = 0
_ALICE = 1
_BOB = 2
_FIRST_STAKING_PROVIDER = 3
_FIRST_URSULA = _FIRST_STAKING_PROVIDER + NUMBER_OF_STAKING_PROVIDERS_IN_TESTS
# Unassigned
_FIRST_UNASSIGNED = _FIRST_URSULA + NUMBER_OF_URSULAS_IN_TESTS
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__ape_accounts = None
@cached_property
def accounts_addresses(self) -> List[str]:
test_accounts = [account.address for account in self.accounts]
return test_accounts
@property
def accounts(self) -> List[AccountAPI]:
if self.__ape_accounts:
return self.__ape_accounts
test_accounts = [test_account for test_account in list(super().accounts)]
# additional accounts only needed/applicable for unit/integration tests since acceptance
# tests use a ape-config.yml to specify number of accounts.
additional_required_accounts = NUMBER_OF_ETH_TEST_ACCOUNTS - len(test_accounts)
if additional_required_accounts > 0:
for i in range(additional_required_accounts):
new_test_account = super(
ReservedTestAccountManager, self
).generate_test_account()
test_accounts.append(new_test_account)
self.__ape_accounts = test_accounts
return test_accounts
@property
def etherbase_account(self) -> ChecksumAddress:
return self[self._ETHERBASE].address
@property
def alice_account(self) -> ChecksumAddress:
return self[self._ALICE].address
@property
def bob_account(self) -> ChecksumAddress:
return self[self._BOB].address
def ursula_account(self, index):
if index not in self.__OPERATORS_RANGE:
raise ValueError(
f"Ursula index must be lower than {self.NUMBER_OF_URSULAS_IN_TESTS}"
)
return self[index + self._FIRST_URSULA].address
@property
def ursulas_accounts(self) -> List[ChecksumAddress]:
return list(self.ursula_account(i) for i in self.__OPERATORS_RANGE)
def staking_provider_account(self, index) -> ChecksumAddress:
if index not in self.__STAKING_PROVIDERS_RANGE:
raise ValueError(
f"Stake provider index must be lower than {self.NUMBER_OF_URSULAS_IN_TESTS}"
)
return self[index + self._FIRST_STAKING_PROVIDER].address
@property
def staking_providers_accounts(self) -> List[ChecksumAddress]:
return list(
self.staking_provider_account(i) for i in self.__STAKING_PROVIDERS_RANGE
)
@property
def unassigned_accounts(self) -> List[ChecksumAddress]:
unassigned = [
account.address for account in self.accounts[self._FIRST_UNASSIGNED :]
]
return unassigned
def get_account_signer(self, account_address: str) -> Signer:
return InMemorySigner(private_key=self[account_address].private_key)
def free_gas_price_strategy(w3, transaction_params=None):
@ -61,24 +129,10 @@ class TesterBlockchain(BlockchainInterface):
ETH_PROVIDER_URI = TEST_ETH_PROVIDER_URI
DEFAULT_GAS_STRATEGY = 'free'
# Reserved addresses
_ETHERBASE = 0
_ALICE = 1
_BOB = 2
_FIRST_STAKING_PROVIDER = 5
_FIRST_URSULA = _FIRST_STAKING_PROVIDER + NUMBER_OF_STAKING_PROVIDERS_IN_BLOCKCHAIN_TESTS
# Internal
__STAKING_PROVIDERS_RANGE = range(NUMBER_OF_STAKING_PROVIDERS_IN_BLOCKCHAIN_TESTS)
__OPERATORS_RANGE = range(NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS)
__ACCOUNT_CACHE = list()
def __init__(
self,
test_accounts: int = NUMBER_OF_ETH_TEST_ACCOUNTS,
poa: bool = True,
light: bool = False,
eth_airdrop: bool = False,
*args,
**kwargs,
):
@ -94,45 +148,9 @@ class TesterBlockchain(BlockchainInterface):
self.log = Logger("test-blockchain")
self.connect()
# Generate additional ethereum accounts for testing
population = test_accounts
enough_accounts = len(self.client.accounts) >= population
if not enough_accounts:
accounts_to_make = population - len(self.client.accounts)
self.__generate_insecure_unlocked_accounts(quantity=accounts_to_make)
assert test_accounts == len(self.w3.eth.accounts)
if eth_airdrop is True: # ETH for everyone!
self.ether_airdrop(amount=DEVELOPMENT_ETH_AIRDROP_AMOUNT)
def attach_middleware(self):
pass
def __generate_insecure_unlocked_accounts(self, quantity: int) -> List[str]:
addresses = list()
for _ in range(quantity):
address = self.provider.ethereum_tester.add_account('0x' + os.urandom(32).hex())
addresses.append(address)
self.__ACCOUNT_CACHE.append(address)
self.log.info('Generated new insecure account {}'.format(address))
return addresses
def ether_airdrop(self, amount: int) -> List[str]:
"""Airdrops ether from creator address to all other addresses!"""
coinbase, *addresses = self.client.accounts
tx_hashes = list()
for address in addresses:
tx = {'to': address, 'from': coinbase, 'value': amount, 'gasPrice': self.w3.eth.generate_gas_price()}
txhash = self.w3.eth.send_transaction(tx)
_receipt = self.wait_for_receipt(txhash)
tx_hashes.append(txhash)
eth_amount = Web3().from_wei(amount, 'ether')
self.log.info("Airdropped {} ETH {} -> {}".format(eth_amount, tx['from'], tx['to']))
return tx_hashes
def time_travel(self,
hours: int = None,
seconds: int = None):
@ -164,43 +182,6 @@ class TesterBlockchain(BlockchainInterface):
self.log.info(f"Time traveled {delta} "
f"| epoch {end_timestamp}")
@property
def etherbase_account(self):
return self.client.accounts[self._ETHERBASE]
@property
def alice_account(self):
return self.client.accounts[self._ALICE]
@property
def bob_account(self):
return self.client.accounts[self._BOB]
def ursula_account(self, index):
if index not in self.__OPERATORS_RANGE:
raise ValueError(f"Ursula index must be lower than {NUMBER_OF_URSULAS_IN_BLOCKCHAIN_TESTS}")
return self.client.accounts[index + self._FIRST_URSULA]
def stake_provider_account(self, index):
if index not in self.__STAKING_PROVIDERS_RANGE:
raise ValueError(f"Stake provider index must be lower than {NUMBER_OF_STAKING_PROVIDERS_IN_BLOCKCHAIN_TESTS}")
return self.client.accounts[index + self._FIRST_STAKING_PROVIDER]
@property
def ursulas_accounts(self):
return list(self.ursula_account(i) for i in self.__OPERATORS_RANGE)
@property
def stake_providers_accounts(self):
return list(self.stake_provider_account(i) for i in self.__STAKING_PROVIDERS_RANGE)
@property
def unassigned_accounts(self):
special_accounts = [self.etherbase_account, self.alice_account, self.bob_account]
assigned_accounts = set(self.stake_providers_accounts + self.ursulas_accounts + special_accounts)
accounts = set(self.client.accounts)
return list(accounts.difference(assigned_accounts))
def wait_for_receipt(self, txhash: Union[bytes, str, HexBytes], timeout: int = None) -> dict:
"""Wait for a transaction receipt and return it"""
timeout = timeout or self.TIMEOUT

View File

@ -1,7 +1,5 @@
from typing import List
from eth_typing import ChecksumAddress
from nucypher.blockchain.eth.registry import ContractRegistry
from nucypher.characters.lawful import Ursula
from nucypher.config.characters import (
@ -45,7 +43,6 @@ def assemble(
def make_ursula_test_configuration(
operator_address: ChecksumAddress,
rest_port: int = select_test_port(),
polygon_endpoint: str = None,
**assemble_kwargs
@ -55,7 +52,6 @@ def make_ursula_test_configuration(
**test_params,
rest_port=rest_port,
polygon_endpoint=polygon_endpoint,
operator_address=operator_address,
)
return ursula_config

View File

@ -1,18 +1,19 @@
import contextlib
import os
import socket
from threading import Lock
from typing import Iterable, List
from typing import Iterable, List, Optional
from cryptography.x509 import Certificate
from eth_utils import to_checksum_address
from web3 import HTTPProvider
from nucypher.blockchain.eth.signers import InMemorySigner, Signer
from nucypher.characters.lawful import Ursula
from nucypher.config.characters import UrsulaConfiguration
from nucypher.policy.conditions.evm import _CONDITION_CHAINS
from tests.constants import (
NUMBER_OF_URSULAS_IN_DEVELOPMENT_DOMAIN,
TESTERCHAIN_CHAIN_ID,
)
from tests.constants import TESTERCHAIN_CHAIN_ID
from tests.utils.blockchain import ReservedTestAccountManager
class __ActivePortCache:
@ -64,23 +65,64 @@ def select_test_port() -> int:
return port
def make_reserved_ursulas(
accounts: ReservedTestAccountManager,
ursula_config: UrsulaConfiguration,
know_each_other: bool = True,
quantity: Optional[int] = None,
**ursula_overrides
):
num_values = quantity or accounts.NUMBER_OF_URSULAS_IN_TESTS
staking_providers = accounts.staking_providers_accounts[:num_values]
operator_signers = [
accounts.get_account_signer(operator_address)
for operator_address in accounts.ursulas_accounts[:num_values]
]
return make_ursulas(
ursula_config,
staking_providers,
operator_signers,
know_each_other,
**ursula_overrides
)
def make_random_ursulas(
ursula_config: UrsulaConfiguration,
quantity: int,
know_each_other: bool = True,
**ursula_overrides
):
staking_providers = [
to_checksum_address("0x" + os.urandom(20).hex()) for _ in range(quantity)
]
operator_signers = [InMemorySigner() for _ in range(quantity)]
return make_ursulas(
ursula_config,
staking_providers,
operator_signers,
know_each_other,
**ursula_overrides
)
def make_ursulas(
ursula_config: UrsulaConfiguration,
staking_provider_addresses: Iterable[str],
operator_addresses: Iterable[str],
quantity: int = NUMBER_OF_URSULAS_IN_DEVELOPMENT_DOMAIN,
operator_signers: Iterable[Signer],
know_each_other: bool = True,
**ursula_overrides
) -> List[Ursula]:
providers_and_operators = list(zip(staking_provider_addresses, operator_addresses))[:quantity]
):
providers_and_operators = list(zip(staking_provider_addresses, operator_signers))
ursulas = list()
for staking_provider_address, operator_address in providers_and_operators:
for staking_provider_address, operator_signer in providers_and_operators:
ursula = ursula_config.produce(
checksum_address=staking_provider_address,
operator_address=operator_address,
operator_address=operator_signer.accounts[0],
rest_port=select_test_port(),
signer=operator_signer,
**ursula_overrides
)
@ -102,7 +144,6 @@ def make_ursulas(
return ursulas
def start_pytest_ursula_services(ursula: Ursula) -> Certificate:
"""
Takes an ursula and starts its learning