Powerups can be 'activated' - Returning to a single-account-based TransactingPower.

pull/1092/head
Kieran Prasch 2019-06-20 10:13:13 -07:00
parent b9fa6310be
commit e9a3fe7878
No known key found for this signature in database
GPG Key ID: 199AB839D4125A62
8 changed files with 97 additions and 67 deletions

View File

@ -170,12 +170,6 @@ class Deployer(NucypherTokenActor):
deployer_address=self.deployer_address)
return r
@classmethod
def from_blockchain(cls, provider_uri: str, registry=None, *args, **kwargs):
blockchain = BlockchainInterface.connect(provider_uri=provider_uri, registry=registry)
instance = cls(blockchain=blockchain, *args, **kwargs)
return instance
@property
def deployer_address(self):
return self.blockchain.deployer_address

View File

@ -49,7 +49,7 @@ from nucypher.blockchain.eth.providers import (
)
from nucypher.blockchain.eth.registry import EthereumContractRegistry
from nucypher.blockchain.eth.sol.compile import SolidityCompiler
from nucypher.crypto.powers import BlockchainPower
from nucypher.crypto.powers import TransactingPower
Web3Providers = Union[IPCProvider, WebsocketProvider, HTTPProvider, EthereumTester]
@ -86,7 +86,7 @@ class BlockchainInterface:
sync_now: bool = True,
provider_process: NuCypherGethProcess = NO_PROVIDER_PROCESS,
provider_uri: str = NO_BLOCKCHAIN_CONNECTION,
transacting_power: BlockchainPower = READ_ONLY_INTERFACE,
transacting_power: TransactingPower = READ_ONLY_INTERFACE,
provider: Web3Providers = NO_BLOCKCHAIN_CONNECTION,
registry: EthereumContractRegistry = None,
fetch_registry: bool = True):
@ -164,10 +164,10 @@ class BlockchainInterface:
self.transacting_power = transacting_power
self.registry = registry
self.connect(provider=provider,
provider_uri=provider_uri,
fetch_registry=fetch_registry,
sync_now=sync_now)
# self.connect(provider=provider,
# provider_uri=provider_uri,
# fetch_registry=fetch_registry,
# sync_now=sync_now)
BlockchainInterface._instance = self
@ -208,21 +208,18 @@ class BlockchainInterface:
self.log.debug('Injecting POA middleware at layer 0')
self.client.inject_middleware(geth_poa_middleware, layer=0)
def connect(self,
provider: Web3Providers = None,
provider_uri: str = None,
fetch_registry: bool = True,
sync_now: bool = True):
def connect(self, fetch_registry: bool = True, sync_now: bool = True):
# Spawn child process
if self._provider_process:
self._provider_process.start()
provider_uri = self._provider_process.provider_uri(scheme='file')
else:
self.log.info(f"Using external Web3 Provider '{provider_uri}'")
provider_uri = self.provider_uri
self.log.info(f"Using external Web3 Provider '{self.provider_uri}'")
# Attach Provider
self._attach_provider(provider=provider, provider_uri=provider_uri)
self._attach_provider(provider=self._provider, provider_uri=provider_uri)
self.log.info("Connecting to {}".format(self.provider_uri))
if self._provider is NO_BLOCKCHAIN_CONNECTION:
raise self.NoProvider("There are no configured blockchain providers")

View File

@ -91,6 +91,7 @@ class Alice(Character, PolicyAuthor):
controller=True,
policy_agent=None,
device = NO_STAKING_DEVICE,
client_password: str = None,
*args, **kwargs) -> None:
#
@ -120,14 +121,16 @@ class Alice(Character, PolicyAuthor):
*args, **kwargs)
if is_me and not federated_only: # TODO: #289
transacting_power = TransactingPower(account=self.checksum_address,
device=device,
blockchain=self.blockchain)
self._crypto_power.consume_power_up(transacting_power, password=client_password)
PolicyAuthor.__init__(self,
blockchain=self.blockchain,
policy_agent=policy_agent,
checksum_address=checksum_address)
transacting_power = TransactingPower(blockchain=self.blockchain, account=self.checksum_address)
self._crypto_power.consume_power_up(transacting_power)
if is_me and controller:
self.controller = self._controller_class(alice=self)
@ -843,6 +846,7 @@ class Ursula(Teacher, Character, Worker):
stake_tracker: StakeTracker = None,
staking_agent: StakingEscrowAgent = None,
device = NO_STAKING_DEVICE,
client_password: str = None,
# Character
password: str = None,
@ -889,6 +893,17 @@ class Ursula(Teacher, Character, Worker):
# Ursula is a Decentralized Worker
#
if not federated_only:
# Access staking node via node's transacting keys
transacting_power = TransactingPower(account=self.checksum_address,
device=device,
password=client_password, # FIXME: password from somewhere
blockchain=self.blockchain)
self._crypto_power.consume_power_up(transacting_power)
# Use blockchain power to substantiate stamp
self.substantiate_stamp(client_password=password)
Worker.__init__(self,
is_me=is_me,
blockchain=self.blockchain,
@ -896,11 +911,6 @@ class Ursula(Teacher, Character, Worker):
worker_address=worker_address,
stake_tracker=stake_tracker)
# Access to worker's ETH client via node's transacting keys
transacting_power = TransactingPower(blockchain=self.blockchain, account=worker_address)
self._crypto_power.consume_power_up(transacting_power)
self.substantiate_stamp(client_password=password) # TODO: Use PowerUp / Derive from keyring
#
# ProxyRESTServer and TLSHostingPower #
#

View File

@ -193,6 +193,7 @@ class CharacterConfiguration(BaseConfiguration):
sync_now=sync_now)
# Read Ethereum Node Keyring
self.blockchain.connect(sync_now=sync_now)
self.accounts = self.blockchain.client.accounts
def connect_to_contracts(self) -> None:

View File

@ -62,9 +62,10 @@ class CryptoPower(object):
else:
return True
def consume_power_up(self, power_up):
def consume_power_up(self, power_up, *args, **kwargs):
if isinstance(power_up, CryptoPowerUp):
power_up_class = power_up.__class__
power_up.activate(*args, **kwargs)
power_up_instance = power_up
elif CryptoPowerUp in inspect.getmro(power_up):
power_up_class = power_up
@ -85,38 +86,66 @@ class CryptoPower(object):
raise power_up_class.not_found_error
class CryptoPowerUp(object):
class CryptoPowerUp:
"""
Gives you MORE CryptoPower!
"""
confers_public_key = False
def activate(self, *args, **kwargs):
return
class TransactingPower(CryptoPowerUp):
"""
Allows for transacting on a Blockchain via web3 backend.
"""
not_found_error = NoTransactingPower
__accounts = {}
def __init__(self, blockchain: 'Blockchain', account: str, device = NO_STAKING_DEVICE):
def __init__(self,
blockchain,
account: str,
password: str = None,
device=NO_STAKING_DEVICE):
"""
# TODO: TrustedDevice Integration
Instantiates a TransactingPower for the given checksum_address.
"""
self.blockchain = blockchain
self.client = self.blockchain.client
self.account = account
self.device = device
self.is_unlocked = False
if password and (device is not NO_STAKING_DEVICE):
raise ValueError(f"Cannot create a {self.__class__.__name__} with both a client and an device signer.")
def unlock_account(self, password: str):
self.blockchain = blockchain
self.account = account
self.client = self.blockchain.client
self.device = device
self.__password = password
self.__unlocked = False
self.unlock_account()
@property
def is_unlocked(self):
return self.__unlocked
def activate(self, password: str):
self.blockchain.connect()
self.unlock_account(password=password)
self.blockchain.transacting_power = self
self.__password = None
def lock_account(self):
if self.client:
self.client.lock_account(address=self.account)
elif self.device:
# TODO: Implement TrustedDevice
raise NotImplementedError
self.__unlocked = False
def unlock_account(self, password: str = None):
"""
Unlocks the account for the specified duration. If no duration is
provided, it will remain unlocked indefinitely.
"""
if not self.is_unlocked:
raise PowerUpError("Failed to unlock account {}".format(self.account))
if self.device is not NO_STAKING_DEVICE:
_hd_path = self.device.get_address_path(checksum_address=self.account)
@ -125,17 +154,15 @@ class TransactingPower(CryptoPowerUp):
if not ping == pong:
raise self.device.NoDeviceDetected
unlocked = True
else:
unlocked = self.client.unlock_account(address=self.account, password=password)
self.is_unlocked = unlocked
self.__unlocked = unlocked
def sign_message(self, message: bytes) -> bytes:
"""
Signs the message with the private key of the TransactingPower.
"""
if not self.is_unlocked:
raise PowerUpError("Failed to unlock account {}".format(self.account))
@ -150,17 +177,17 @@ class TransactingPower(CryptoPowerUp):
return signature
def sign_transaction(self, checksum_address: str, unsigned_transaction: dict) -> HexBytes:
if not self.__accounts.get(checksum_address, False):
raise PowerUpError("Account is locked.")
def sign_transaction(self, unsigned_transaction: dict) -> HexBytes:
if not self.is_unlocked:
raise PowerUpError("Failed to unlock account {}".format(self.account))
# HW Signer
if self.device is not NO_STAKING_DEVICE:
signed_raw_transaction = self.device.sign_eth_transaction(unsigned_transaction=unsigned_transaction,
checksum_address=checksum_address)
checksum_address=self.account)
# Web3 Signer
else:
# This check is also performed client-side.
# Note: This check is also performed client-side.
sender_address = unsigned_transaction['from']
if sender_address != self.account:
raise PowerUpError(f"'from' field must match key's {self.account}, but it was {sender_address}")

View File

@ -32,7 +32,7 @@ from nucypher.blockchain.eth.sol.compile import SolidityCompiler
from nucypher.blockchain.eth.token import NU
from nucypher.blockchain.eth.utils import epoch_to_period
from nucypher.config.constants import BASE_DIR
from nucypher.crypto.powers import BlockchainPower
from nucypher.crypto.powers import TransactingPower
from nucypher.utilities.sandbox.constants import (
NUMBER_OF_ETH_TEST_ACCOUNTS,
NUMBER_OF_STAKERS_IN_BLOCKCHAIN_TESTS,
@ -110,12 +110,13 @@ class TesterBlockchain(BlockchainDeployerInterface):
*args, **kwargs)
self.log = Logger("test-blockchain")
self.connect()
# Generate additional ethereum accounts for testing
population = test_accounts
enough_accounts = len(self.w3.eth.accounts) >= population
enough_accounts = len(self.client.accounts) >= population
if not enough_accounts:
accounts_to_make = population - len(self.w3.eth.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)
@ -212,7 +213,7 @@ class TesterBlockchain(BlockchainDeployerInterface):
"""For use with metric testing scripts"""
testerchain = cls(compiler=SolidityCompiler())
power = BlockchainPower(blockchain=testerchain, account=testerchain.client.etherbase)
power = TransactingPower(blockchain=testerchain, account=testerchain.etherbase_account)
power.unlock_account(password=INSECURE_DEVELOPMENT_PASSWORD)
testerchain.transacting_power = power

View File

@ -118,8 +118,8 @@ def test_anybody_can_verify():
def test_character_client_transacting_power(testerchain, agency):
# TODO: Handle multiple providers
eth_address = testerchain.interface.w3.eth.accounts[0]
sig_privkey = testerchain.interface.provider.ethereum_tester.backend._key_lookup[eth_utils.to_canonical_address(eth_address)]
eth_address = testerchain.etherbase_account
sig_privkey = testerchain.provider.ethereum_tester.backend._key_lookup[eth_utils.to_canonical_address(eth_address)]
sig_pubkey = sig_privkey.public_key
signer = Character(is_me=True, blockchain=testerchain, checksum_address=eth_address)

View File

@ -39,15 +39,12 @@ from nucypher.blockchain.eth.deployers import (NucypherTokenDeployer,
PolicyManagerDeployer,
DispatcherDeployer,
AdjudicatorDeployer)
from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface
from nucypher.blockchain.eth.registry import InMemoryEthereumContractRegistry
from nucypher.blockchain.eth.sol.compile import SolidityCompiler
from nucypher.blockchain.eth.token import NU
from nucypher.characters.lawful import Enrico, Bob
from nucypher.config.characters import UrsulaConfiguration, AliceConfiguration, BobConfiguration
from nucypher.config.constants import BASE_DIR
from nucypher.config.node import CharacterConfiguration
from nucypher.crypto.powers import BlockchainPower
from nucypher.crypto.powers import TransactingPower
from nucypher.crypto.utils import canonical_address_from_umbral_key
from nucypher.keystore import keystore
from nucypher.keystore.db import Base
@ -59,8 +56,8 @@ from nucypher.utilities.sandbox.constants import (DEVELOPMENT_ETH_AIRDROP_AMOUNT
MOCK_URSULA_STARTING_PORT,
NUMBER_OF_URSULAS_IN_DEVELOPMENT_NETWORK,
TEMPORARY_DOMAIN,
TEST_PROVIDER_URI
)
TEST_PROVIDER_URI,
INSECURE_DEVELOPMENT_PASSWORD)
from nucypher.utilities.sandbox.middleware import MockRestMiddleware
from nucypher.utilities.sandbox.policy import generate_random_label
from nucypher.utilities.sandbox.ursula import (make_decentralized_ursulas,
@ -371,10 +368,12 @@ def testerchain():
# Create the blockchain
testerchain = TesterBlockchain(eth_airdrop=True, free_transactions=True)
# TODO: TransactingPower
# Mock TransactingPower Consumption
testerchain.transacting_power = BlockchainPower(blockchain=testerchain, account=testerchain.etherbase_account)
testerchain.transacting_power = TransactingPower(blockchain=testerchain,
password=INSECURE_DEVELOPMENT_PASSWORD,
account=testerchain.etherbase_account)
testerchain.deployer_address = testerchain.etherbase_account
testerchain.transacting_power.unlock_account()
yield testerchain
testerchain.disconnect()
@ -430,8 +429,8 @@ def stakers(agency, token_economics):
blockchain = token_agent.blockchain
# Mock Powerup consumption (Deployer)
blockchain.transacting_power = BlockchainPower(blockchain=blockchain,
account=blockchain.etherbase_account)
blockchain.transacting_power = TransactingPower(blockchain=blockchain,
account=blockchain.etherbase_account)
token_airdrop(origin=blockchain.etherbase_account,
addresses=blockchain.stakers_accounts,
@ -442,9 +441,10 @@ def stakers(agency, token_economics):
for index, account in enumerate(blockchain.stakers_accounts):
staker = Staker(is_me=True, checksum_address=account, blockchain=blockchain)
# Mock TransactingPower consumption (Ursula-Staker)
staker.blockchain.transacting_power = BlockchainPower(blockchain=staker.blockchain,
account=staker.checksum_address)
# Mock TransactingPower consumption
staker.blockchain.transacting_power = TransactingPower(blockchain=blockchain,
account=account,
password=INSECURE_DEVELOPMENT_PASSWORD)
min_stake, balance = token_economics.minimum_allowed_locked, staker.token_balance
amount = random.randint(min_stake, balance)