[KMS-ETH]- Extracts policies module, API updates, further collapse contract wrapper logic up inheritance tree. Enhances docstrings.

pull/195/head^2
Kieran Prasch 2018-03-22 17:02:09 -07:00
parent 84f9454436
commit e8f44e611e
9 changed files with 314 additions and 337 deletions

View File

@ -1,73 +1,43 @@
from abc import ABC
from collections import OrderedDict
from typing import Tuple, List
from datetime import datetime
from typing import Tuple, List, Union
# from nkms_eth.agents import MinerAgent, PolicyAgent
from nkms_eth.base import Actor
from nkms_eth.agents import NuCypherKMSTokenAgent
from nkms_eth.policies import BlockchainArrangement
class PolicyArrangement:
def __init__(self, author: 'PolicyAuthor', miner: 'Miner', value: int,
periods: int, arrangement_id: bytes=None):
class TokenActor(ABC):
if arrangement_id is None:
self.id = self.__class__._generate_arrangement_id() # TODO: Generate policy ID
def __init__(self, token_agent: NuCypherKMSTokenAgent, address: Union[bytes, str]):
self.token_agent = token_agent
# The relationship exists between two addresses
self.author = author
self.policy_agent = author.policy_agent
self.miner = miner
# Arrangement value, rate, and duration
rate = value // periods
self._rate = rate
self.value = value
self.periods = periods # TODO: datetime -> duration in blocks
self.is_published = False
@staticmethod
def _generate_arrangement_id(policy_hrac: bytes) -> bytes:
pass # TODO
if isinstance(address, bytes):
address = address.hex()
self.address = address
def __repr__(self):
class_name = self.__class__.__name__
r = "{}(client={}, node={})"
r = r.format(class_name, self.author, self.miner)
r = "{}(address='{}')"
r.format(class_name, self.address)
return r
def publish(self, gas_price: int) -> str:
def eth_balance(self):
"""Return this actors's current ETH balance"""
payload = {'from': self.author.address,
'value': self.value,
'gas_price': gas_price}
balance = self.token_agent._blockchain._chain.web3.eth.getBalance(self.address)
return balance
txhash = self.policy_agent.transact(payload).createPolicy(self.id,
self.miner.address,
self.periods)
def token_balance(self):
"""Return this actors's current token balance"""
self.policy_agent._blockchain._chain.wait.for_receipt(txhash)
self.publish_transaction = txhash
self.is_published = True
return txhash
def __update_periods(self) -> None:
blockchain_record = self.policy_agent.fetch_arrangement_data(self.id)
client, delegate, rate, *periods = blockchain_record
self._elapsed_periods = periods
def revoke(self, gas_price: int) -> str:
"""Revoke this arrangement and return the transaction hash as hex."""
txhash = self.policy_agent.revoke_arrangement(self.id, author=self.author, gas_price=gas_price)
self.revoke_transaction = txhash
return txhash
balance = self.token_agent.get_balance(address=self.address)
return balance
class Miner(Actor):
class Miner(TokenActor):
"""
Practically carrying a pickaxe.
Intended for use as an Ursula mixin.
Ursula - practically carrying a pickaxe.
Accepts a running blockchain, deployed token contract, and deployed escrow contract.
If the provided token and escrow contracts are not deployed,
@ -76,28 +46,30 @@ class Miner(Actor):
"""
def __init__(self, miner_agent, address):
super().__init__(address)
super().__init__(token_agent=miner_agent.token_agent, address=address)
self.miner_agent = miner_agent
miner_agent.miners.append(self) # Track Miners
self._token_agent = miner_agent._token
self._blockchain = self._token_agent._blockchain
self.token_agent = miner_agent.token_agent
self._blockchain = self.token_agent._blockchain
self._transactions = list()
self._transactions = OrderedDict()
self._locked_tokens = self._update_locked_tokens()
def _update_locked_tokens(self) -> None:
"""Query the contract for the amount of locked tokens on this miner's eth address and cache it"""
self._locked_tokens = self.miner_agent.call().getLockedTokens(self.address)
return None
def _approve_escrow(self, amount: int) -> str:
"""Approve the transfer of token from the miner's address to the escrow contract."""
txhash = self._token_agent.transact({'from': self.address}).approve(self.miner_agent._contract.address, amount)
txhash = self.token_agent.transact({'from': self.address}).approve(self.miner_agent._contract.address, amount)
self._blockchain._chain.wait.for_receipt(txhash, timeout=self._blockchain._timeout)
self._transactions.append(txhash)
self._transactions[datetime.now()] = txhash
return txhash
@ -107,12 +79,16 @@ class Miner(Actor):
deposit_txhash = self.miner_agent.transact({'from': self.address}).deposit(amount, locktime)
self._blockchain._chain.wait.for_receipt(deposit_txhash, timeout=self._blockchain._timeout)
self._transactions.append(deposit_txhash)
self._transactions[datetime.now()] = deposit_txhash
return deposit_txhash
@property
def is_staking(self):
def is_staking(self, query=True):
"""Checks if this Miner currently has locked tokens."""
if query:
self._update_locked_tokens()
return bool(self._locked_tokens > 0)
def lock(self, amount: int, locktime: int) -> Tuple[str, str, str]:
@ -124,7 +100,7 @@ class Miner(Actor):
lock_txhash = self.miner_agent.transact({'from': self.address}).switchLock()
self._blockchain._chain.wait.for_receipt(lock_txhash, timeout=self._blockchain._timeout)
self._transactions.extend([approve_txhash, deposit_txhash, lock_txhash])
self._transactions[datetime.now()] = (approve_txhash, deposit_txhash, lock_txhash)
return approve_txhash, deposit_txhash, lock_txhash
@ -134,7 +110,7 @@ class Miner(Actor):
txhash = self.miner_agent.transact({'from': self.address}).confirmActivity()
self._blockchain._chain.wait.for_receipt(txhash)
self._transactions.append(txhash)
self._transactions[datetime.now()] = txhash
return txhash
@ -144,7 +120,7 @@ class Miner(Actor):
txhash = self.miner_agent.transact({'from': self.address}).mint()
self._blockchain._chain.wait.for_receipt(txhash, timeout=self._blockchain._timeout)
self._transactions.append(txhash)
self._transactions[datetime.now()] = txhash
return txhash
@ -154,7 +130,7 @@ class Miner(Actor):
txhash = policy_manager.transact({'from': self.address}).withdraw()
self._blockchain._chain.wait.for_receipt(txhash)
self._transactions.append(txhash)
self._transactions[datetime.now()] = txhash
return txhash
@ -164,45 +140,34 @@ class Miner(Actor):
txhash = self.miner_agent.transact({'from': self.address}).setMinerId(miner_id)
self._blockchain._chain.wait.for_receipt(txhash)
self._transactions.append(txhash)
self._transactions[datetime.now()] = txhash
return txhash
def fetch_miner_ids(self) -> tuple:
"""Retrieve all stored Miner IDs on this miner"""
count = self.escrow().getMinerInfo(self.escrow.MinerInfoField.MINER_IDS_LENGTH.value,
self.address,
0).encode('latin-1')
count = self.miner_agent.call().getMinerInfo(self.miner_agent.MinerInfoField.MINER_IDS_LENGTH.value,
self.address,
0).encode('latin-1')
count = self._blockchain._chain.web3.toInt(count)
miner_ids = list()
for index in range(count):
miner_id = self.miner_agent.call().getMinerInfo(self.escrow.MinerInfoField.MINER_ID.value, self.address, index)
miner_id = self.miner_agent.call().getMinerInfo(self.miner_agent.MinerInfoField.MINER_ID.value, self.address, index)
encoded_miner_id = miner_id.encode('latin-1') # TODO change when v4 of web3.py is released
miner_ids.append(encoded_miner_id)
return tuple(miner_ids)
def eth_balance(self):
return self._blockchain._chain.web3.eth.getBalance(self.address)
def token_balance(self) -> int:
"""Check miner's current token balance"""
# self._token_agent._check_contract_deployment()
balance = self._token_agent.call().balanceOf(self.address)
return balance
def withdraw(self, amount: int=0, entire_balance=False) -> str:
"""Withdraw tokens"""
tokens_amount = self._blockchain._chain.web3.toInt(
self.escrow().getMinerInfo(self.escrow.MinerInfoField.VALUE.value, self.address, 0).encode('latin-1'))
self.miner_agent.call().getMinerInfo(self.miner_agent.MinerInfoField.VALUE.value, self.address, 0).encode('latin-1'))
txhash = self.escrow.transact({'from': self.address}).withdraw(tokens_amount)
txhash = self.miner_agent.call().transact({'from': self.address}).withdraw(tokens_amount)
self._blockchain._chain.wait.for_receipt(txhash, timeout=self._blockchain._timeout)
@ -210,39 +175,39 @@ class Miner(Actor):
raise Exception("Specify an amount or entire balance, not both")
if entire_balance:
txhash = self.escrow.transact({'from': self.address}).withdraw(tokens_amount)
txhash = self.miner_agent.call().transact({'from': self.address}).withdraw(tokens_amount)
else:
txhash = self.escrow.transact({'from': self.address}).withdraw(amount)
txhash = self.miner_agent.call().transact({'from': self.address}).withdraw(amount)
self._transactions.append(txhash)
self._blockchain._chain.wait.for_receipt(txhash, timeout=self._blockchain._timeout)
return txhash
class PolicyAuthor(Actor):
class PolicyAuthor(TokenActor):
"""Alice"""
def __init__(self, address: bytes, policy_agent):
super().__init__(token_agent=policy_agent._token, address=address)
self.policy_agent = policy_agent
super().__init__(address)
self._arrangements = OrderedDict() # Track authored policies by id
def make_arrangement(self, miner: Miner, periods: int, rate: int, arrangement_id: bytes=None) -> PolicyArrangement:
def make_arrangement(self, miner: Miner, periods: int, rate: int, arrangement_id: bytes=None) -> 'BlockchainArrangement':
"""
Create a new arrangement to carry out a blockchain policy for the specified rate and time.
"""
value = rate * periods
arrangement = PolicyArrangement(author=self,
miner=miner,
value=value,
periods=periods)
arrangement = BlockchainArrangement(author=self,
miner=miner,
value=value,
periods=periods)
self._arrangements[arrangement.id] = {arrangement_id: arrangement}
return arrangement
def get_arrangement(self, arrangement_id: bytes) -> PolicyArrangement:
def get_arrangement(self, arrangement_id: bytes) -> BlockchainArrangement:
"""Fetch a published arrangement from the blockchain"""
blockchain_record = self.policy_agent.call().policies(arrangement_id)
@ -251,13 +216,13 @@ class PolicyAuthor(Actor):
duration = end_block - start_block
miner = Miner(address=miner_address, miner_agent=self.policy_agent.miner_agent)
arrangement = PolicyArrangement(author=self, miner=miner, periods=duration)
arrangement = BlockchainArrangement(author=self, miner=miner, periods=duration)
arrangement.is_published = True
return arrangement
def revoke_arrangement(self, arrangement_id):
"""Lookup the arrangement in the cache and revoke it on the blockchain"""
"""Get the arrangement from the cache and revoke it on the blockchain"""
try:
arrangement = self._arrangements[arrangement_id]
except KeyError:
@ -267,9 +232,12 @@ class PolicyAuthor(Actor):
return txhash
def recruit(self, quantity: int) -> List[str]:
"""Uses sampling logic to gather"""
miner_addresses = self.policy_agent.miner_agent.sample(quantity=quantity)
return miner_addresses
def balance(self):
"""Get the balance of this actor's address"""
return self.policy_agent.miner_agent.call().balanceOf(self.address)

View File

@ -1,27 +1,80 @@
import random
from abc import ABC
from typing import Set, Generator, List
from nkms_eth.actors import PolicyAuthor
from nkms_eth.base import EthereumContractAgent
from nkms_eth.blockchain import TheBlockchain
from nkms_eth.deployers import MinerEscrowDeployer, NuCypherKMSTokenDeployer, PolicyManagerDeployer
from nkms_eth.deployers import MinerEscrowDeployer, NuCypherKMSTokenDeployer, PolicyManagerDeployer, ContractDeployer
class EthereumContractAgent(ABC):
_principal_contract_name = NotImplemented
_contract_subclasses = list()
class ContractNotDeployed(ContractDeployer.ContractDeploymentError):
pass
def __init__(self, blockchain, *args, **kwargs):
self._blockchain = blockchain
self._contract = self.__fetch_contract()
@classmethod
def __init_subclass__(cls, deployer, **kwargs):
"""
https://www.python.org/dev/peps/pep-0487/#proposal
"""
super().__init_subclass__(**kwargs)
cls._deployer = deployer
cls._principal_contract_name = deployer._contract_name
cls._contract_subclasses.append(cls)
def __repr__(self):
class_name = self.__class__.__name__
r = "{}(blockchain={}, contract={})"
return r.format(class_name, self._blockchain, self._contract)
def __eq__(self, other):
return bool(self.contract_address == other.contract_address)
@property
def contract_address(self):
return self._contract.address
@property
def contract_name(self) -> str:
return self._principal_contract_name
@property
def origin(self) -> str:
return self._blockchain._chain.web3.eth.accounts[0] # TODO: make swappable
def __fetch_contract(self):
contract = self._blockchain._chain.provider.get_contract(self._principal_contract_name)
return contract
def call(self):
return self._contract.call()
def transact(self, payload: dict):
"""Packs kwargs into payload dictionary and transmits an eth contract transaction"""
return self._contract.transact(payload)
def get_balance(self, address: str=None) -> int:
"""Get the balance of a token address, or of this contract address"""
if address is None:
address = self.contract_address
return self.call().balanceOf(address)
class NuCypherKMSTokenAgent(EthereumContractAgent, deployer=NuCypherKMSTokenDeployer):
def __init__(self, blockchain: TheBlockchain):
self._blockchain = blockchain
super().__init__(self)
_principal_contract_name = NotImplemented
def registrar(self):
"""Retrieve all known addresses for this contract"""
all_known_address = self._blockchain._chain.registrar.get_contract_address(self._principal_contract_name)
return all_known_address
def balance(self, address: str) -> int:
"""Get the balance of a token address"""
return self.call().balanceOf(address)
class MinerAgent(EthereumContractAgent, deployer=MinerEscrowDeployer):
"""
@ -36,20 +89,21 @@ class MinerAgent(EthereumContractAgent, deployer=MinerEscrowDeployer):
class NotEnoughUrsulas(Exception):
pass
def __init__(self, token: NuCypherKMSTokenAgent):
super().__init__(agent=token)
self._token = token
def __init__(self, token_agent: NuCypherKMSTokenAgent):
super().__init__(blockchain=token_agent._blockchain)
self.token_agent = token_agent
self.miners = list()
def get_miner_ids(self) -> Set[str]:
"""
Fetch all miner IDs from the local cache and return them in a set
"""
return {miner.get_id() for miner in self.miners}
def swarm(self) -> Generator[str, None, None]:
"""
Generates all miner addresses via cumulative sum on-network.
Returns an iterator of all miner addresses via cumulative sum, on-network.
"""
count = self.call().getMinerInfo(self._deployer.MinerInfoField.MINERS_LENGTH.value, self._deployer.null_address, 0).encode('latin-1')
count = self._blockchain._chain.web3.toInt(count)
@ -106,17 +160,19 @@ class MinerAgent(EthereumContractAgent, deployer=MinerEscrowDeployer):
class PolicyAgent(EthereumContractAgent, deployer=PolicyManagerDeployer):
def __init__(self, miner_agent):
super().__init__(miner_agent)
super().__init__(blockchain=miner_agent._blockchain)
self.miner_agent = miner_agent
def fetch_arrangement_data(self, arrangement_id: bytes) -> list:
blockchain_record = self.call().policies(arrangement_id)
return blockchain_record
def revoke_arrangement(self, arrangement_id: bytes, author: 'PolicyAuthor', gas_price: int):
def revoke_arrangement(self, arrangement_id: bytes, author, gas_price: int):
"""
Revoke by arrangement ID; Only the policy author can revoke the policy
"""
txhash = self.transact({'from': author.address, 'gas_price': gas_price}).revokePolicy(arrangement_id)
self._blockchain._chain.wait.for_receipt(txhash)
return txhash

View File

@ -1,137 +0,0 @@
from abc import ABC, abstractmethod
from nkms_eth.blockchain import TheBlockchain
class Actor(ABC):
def __init__(self, address):
if isinstance(address, bytes):
address = address.hex()
self.address = address
def __repr__(self):
class_name = self.__class__.__name__
r = "{}(address='{}')"
r.format(class_name, self.address)
return r
class ContractDeployer(ABC):
_contract_name = NotImplemented
class ContractDeploymentError(Exception):
pass
def __init__(self, blockchain):
self.__armed = False
self._contract = None
# Sanity check
if not isinstance(blockchain, TheBlockchain):
error = 'Only TheBlockchain can be used to create a deployer, got {}.'
raise ValueError(error.format(type(blockchain)))
self._blockchain = blockchain
def __eq__(self, other):
return self._contract.address == other.address
@property
def contract_address(self) -> str:
try:
address = self._contract.address
except AttributeError:
cls = self.__class__
raise cls.ContractDeploymentError('Contract not deployed')
else:
return address
@property
def is_deployed(self) -> bool:
return bool(self._contract is not None)
@property
def is_armed(self) -> bool:
return bool(self.__armed is True)
def _verify_contract_deployment(self) -> None:
"""Raises ContractDeploymentError if the contract has not been armed and deployed."""
if not self._contract:
class_name = self.__class__.__name__
message = '{} contract is not deployed. Arm, then deploy.'.format(class_name)
raise self.ContractDeploymentError(message)
return None
def arm(self) -> None:
self.__armed = True
return None
@abstractmethod
def deploy(self) -> str:
raise NotImplementedError
# TODO
# @abstractmethod
# def make_agent(self) -> 'EthereumContractAgent':
# raise NotImplementedError
@classmethod
def from_blockchain(cls, blockchain: TheBlockchain) -> 'ContractDeployer':
"""
Returns the NuCypherKMSToken object,
or raises UnknownContract if the contract has not been deployed.
"""
contract = blockchain._chain.provider.get_contract(cls._contract_name)
instance = cls(blockchain=blockchain)
instance._contract = contract
return instance
class EthereumContractAgent(ABC):
_deployer = NotImplemented
_principal_contract_name = NotImplemented
class ContractNotDeployed(ContractDeployer.ContractDeploymentError):
pass
def __init__(self, agent, *args, **kwargs):
self._blockchain = agent._blockchain
contract = self._blockchain._chain.provider.get_contract(self._principal_contract_name)
self._contract = contract
@classmethod
def __init_subclass__(cls, deployer, **kwargs):
"""
https://www.python.org/dev/peps/pep-0487/#proposal
"""
cls._deployer = deployer
cls._principal_contract_name = deployer._contract_name
super().__init_subclass__(**kwargs)
def __repr__(self):
class_name = self.__class__.__name__
r = "{}(blockchain={}, contract={})"
return r.format(class_name, self._blockchain, self._contract)
def __eq__(self, other):
return bool(self.contract_address == other.contract_address)
def call(self):
return self._contract.call()
def transact(self, *args, **kwargs):
return self._contract.transact(*args, **kwargs)
@property
def origin(self):
return self._blockchain._chain.web3.eth.accounts[0] # TODO
@property
def contract_address(self):
return self._contract.address
@property
def contract_name(self):
return self._principal_contract_name

View File

@ -83,4 +83,4 @@ class TheBlockchain:
while not_time_yet:
self._chain.wait.for_block(self._chain.web3.eth.blockNumber+step)
current_block = self._chain.web3.eth.getBlock(self._chain.web3.eth.blockNumber)
not_time_yet = current_block.timestamp < end_timestamp
not_time_yet = current_block.timestamp < end_timestamp

View File

@ -1,23 +1,86 @@
from abc import ABC, abstractmethod
from typing import Tuple
from nkms_eth.base import ContractDeployer
from nkms_eth.config import NuCypherMinerConfig, NuCypherTokenConfig
from .blockchain import TheBlockchain
addr = str
class ContractDeployer(ABC):
_contract_name = NotImplemented
_agency = NotImplemented
class ContractDeploymentError(Exception):
pass
def __init__(self, blockchain: TheBlockchain):
self.__armed = False
self._contract = None
# Sanity check
if not isinstance(blockchain, TheBlockchain):
error = 'Only TheBlockchain can be used to create a deployer, got {}.'
raise ValueError(error.format(type(blockchain)))
self._blockchain = blockchain
def __eq__(self, other):
return self._contract.address == other.address
@property
def contract_address(self) -> str:
try:
address = self._contract.address
except AttributeError:
cls = self.__class__
raise cls.ContractDeploymentError('Contract not deployed')
else:
return address
@property
def is_deployed(self) -> bool:
return bool(self._contract is not None)
@property
def is_armed(self) -> bool:
return bool(self.__armed is True)
def _ensure_contract_deployment(self) -> None:
"""Raises ContractDeploymentError if the contract has not been armed and deployed."""
if self._contract is None:
class_name = self.__class__.__name__
message = '{} contract is not deployed. Arm, then deploy.'.format(class_name)
raise self.ContractDeploymentError(message)
return None
def arm(self) -> None:
if self.__armed is True:
raise self.ContractDeploymentError("Deployer already armed, use .deploy() to deploy.")
self.__armed = True
return None
@abstractmethod
def deploy(self) -> str:
raise NotImplementedError
@classmethod
def from_blockchain(cls, blockchain: TheBlockchain) -> 'ContractDeployer':
"""Returns the NuCypherKMSToken object, or raises UnknownContract if the contract has not been deployed."""
contract = blockchain._chain.provider.get_contract(cls._contract_name)
instance = cls(blockchain=blockchain)
instance._contract = contract
return instance
class NuCypherKMSTokenDeployer(ContractDeployer, NuCypherTokenConfig):
_contract_name = 'NuCypherKMSToken'
def __init__(self, blockchain: TheBlockchain):
def __init__(self, blockchain):
super().__init__(blockchain=blockchain)
self.__creator = self._blockchain._chain.web3.eth.accounts[0]
@property
def origin(self):
return self.__creator
self._creator = self._blockchain._chain.web3.eth.accounts[0]
def deploy(self) -> str:
"""
@ -39,7 +102,7 @@ class NuCypherKMSTokenDeployer(ContractDeployer, NuCypherTokenConfig):
the_nucypher_token_contract, deployment_txhash = self._blockchain._chain.provider.deploy_contract(
self._contract_name,
deploy_args=[self.saturation],
deploy_transaction={'from': self.origin})
deploy_transaction={'from': self._creator})
self._blockchain._chain.wait.for_receipt(deployment_txhash, timeout=self._blockchain._timeout)
self._contract = the_nucypher_token_contract
@ -47,42 +110,16 @@ class NuCypherKMSTokenDeployer(ContractDeployer, NuCypherTokenConfig):
return deployment_txhash
class PolicyManagerDeployer(ContractDeployer):
_contract_name = 'PolicyManager'
def __init__(self, miner_agent):
super().__init__(miner_agent)
self.miner_agent = miner_agent
self.token_agent = miner_agent._token_agent
def deploy(self) -> Tuple[str, str]:
if self.is_armed is False:
raise self.ContractDeploymentError('PolicyManager contract not armed')
if self.is_deployed is True:
raise self.ContractDeploymentError('PolicyManager contract already deployed')
# Creator deploys the policy manager
the_policy_manager_contract, deploy_txhash = self._blockchain._chain.provider.deploy_contract(
self._contract_name,
deploy_args=[self.miner_agent._contract.address],
deploy_transaction={'from': self.token_agent.creator})
self._contract = the_policy_manager_contract
set_txhash = self.miner_agent.transact({'from': self.token_agent.creator}).setPolicyManager(the_policy_manager_contract.address)
self._blockchain._chain.wait.for_receipt(set_txhash)
return deploy_txhash, set_txhash
class MinerEscrowDeployer(ContractDeployer, NuCypherMinerConfig):
"""
Depends on NuCypherTokenAgent
"""
_contract_name = 'MinersEscrow'
def __init__(self, token_agent):
self._token_agent = token_agent
super().__init__(blockchain=token_agent._blockchain)
self.token_agent = token_agent
def deploy(self) -> Tuple[str, str, str]:
"""
@ -103,21 +140,54 @@ class MinerEscrowDeployer(ContractDeployer, NuCypherMinerConfig):
message = '{} contract already deployed, use .get() to retrieve it.'.format(class_name)
raise self.ContractDeploymentError(message)
deploy_args = [self._token_agent._contract.address] + self.mining_coefficient
deploy_tx = {'from': self._token_agent.origin}
deploy_args = [self.token_agent._contract.address] + self.mining_coefficient
deploy_tx = {'from': self.token_agent.origin}
the_escrow_contract, deploy_txhash = self._blockchain._chain.provider.deploy_contract(self._contract_name,
deploy_args=deploy_args,
deploy_transaction=deploy_tx)
self._blockchain._chain.wait.for_receipt(deploy_txhash, timeout=self._blockchain._timeout)
timeout = self._blockchain._timeout
self._blockchain._chain.wait.for_receipt(deploy_txhash, timeout=timeout)
self._contract = the_escrow_contract
reward_txhash = self._token_agent.transact({'from': self._token_agent.origin}).transfer(self.contract_address,
self.reward)
self._blockchain._chain.wait.for_receipt(reward_txhash, timeout=self._blockchain._timeout)
reward_txhash = self.token_agent.transact({'from': self.token_agent.origin}).transfer(self.contract_address,
self.reward)
self._blockchain._chain.wait.for_receipt(reward_txhash, timeout=timeout)
init_txhash = self._contract.transact({'from': self._token_agent.origin}).initialize()
self._blockchain._chain.wait.for_receipt(init_txhash, timeout=self._blockchain._timeout)
init_txhash = self._contract.transact({'from': self.token_agent.origin}).initialize()
self._blockchain._chain.wait.for_receipt(init_txhash, timeout=timeout)
return deploy_txhash, reward_txhash, init_txhash
class PolicyManagerDeployer(ContractDeployer):
"""
Depends on MinerAgent and NuCypherTokenAgent
"""
_contract_name = 'PolicyManager'
def __init__(self, miner_agent):
super().__init__(blockchain=token_agent._blockchain)
self.token_agent = miner_agent._token_agent
self.miner_agent = miner_agent
def deploy(self) -> Tuple[str, str]:
if self.is_armed is False:
raise self.ContractDeploymentError('PolicyManager contract not armed')
if self.is_deployed is True:
raise self.ContractDeploymentError('PolicyManager contract already deployed')
# Creator deploys the policy manager
the_policy_manager_contract, deploy_txhash = self._blockchain._chain.provider.deploy_contract(
self._contract_name,
deploy_args=[self.miner_agent._contract.address],
deploy_transaction={'from': self.token_agent.creator})
self._contract = the_policy_manager_contract
set_txhash = self.miner_agent.transact({'from': self.token_agent.creator}).setPolicyManager(the_policy_manager_contract.address)
self._blockchain._chain.wait.for_receipt(set_txhash)
return deploy_txhash, set_txhash

View File

@ -7,15 +7,19 @@ from nkms_eth.token import NuCypherKMSToken
class PolicyArrangement:
def __init__(self, author: 'PolicyAuthor', miner: 'Miner', value: int,
periods: int, arrangement_id: bytes=None):
"""
A relationship between Alice and a single Ursula as part of BlockchainPolicy
"""
def __init__(self, author, miner, value: int, periods: int, arrangement_id: bytes=None):
if arrangement_id is None:
self.id = self.__class__._generate_arrangement_id() # TODO: Generate policy ID
# The relationship between two addresses
# The relationship exists between two addresses
self.author = author
self.policy_manager = author.policy_manager
self.policy_agent = author.policy_agent
self.miner = miner
@ -28,7 +32,6 @@ class PolicyArrangement:
self.is_published = False
@staticmethod
def _generate_arrangement_id(policy_hrac: bytes) -> bytes:
pass # TODO
@ -45,23 +48,25 @@ class PolicyArrangement:
'value': self.value,
'gas_price': gas_price}
txhash = self.policy_manager.transact(payload).createPolicy(self.id,
self.miner.address,
self.periods)
self.policy_manager.blockchain._chain.wait.for_receipt(txhash)
txhash = self.policy_agent.transact(payload).createPolicy(self.id,
self.miner.address,
self.periods)
self.policy_agent._blockchain._chain.wait.for_receipt(txhash)
self.publish_transaction = txhash
self.is_published = True
return txhash
def __update_periods(self) -> None:
blockchain_record = self.policy_manager.fetch_arrangement_data(self.id)
blockchain_record = self.policy_agent.fetch_arrangement_data(self.id)
client, delegate, rate, *periods = blockchain_record
self._elapsed_periods = periods
def revoke(self, gas_price: int) -> str:
"""Revoke this arrangement and return the transaction hash as hex."""
txhash = self.policy_manager.revoke_arrangement(self.id, author=self.author, gas_price=gas_price)
txhash = self.policy_agent.revoke_arrangement(self.id, author=self.author, gas_price=gas_price)
self.revoke_transaction = txhash
return txhash
@ -195,3 +200,18 @@ class PolicyAuthor:
def balance(self):
return self.policy_manager.token().balanceOf(self.address)
def revoke(self, gas_price: int) -> str:
"""Revoke this arrangement and return the transaction hash as hex."""
txhash = self.policy_agent.revoke_arrangement(self.id, author=self.author, gas_price=gas_price)
self.revoke_transaction = txhash
return txhash
class BlockchainPolicy:
"""A collection of n BlockchainArrangements representing a single Policy"""
def __init__(self):
self._arrangements = list()

View File

@ -2,11 +2,10 @@ import pytest
from nkms_eth.agents import NuCypherKMSTokenAgent, MinerAgent
from nkms_eth.blockchain import TheBlockchain
from nkms_eth.deployers import NuCypherKMSTokenDeployer
from tests.utilities import TesterBlockchain, MockMinerEscrowDeployer
from tests.utilities import TesterBlockchain, MockNuCypherKMSTokenDeployer, MockMinerEscrowDeployer
@pytest.fixture(scope='function')
@pytest.fixture()
def testerchain():
chain = TesterBlockchain()
yield chain
@ -14,7 +13,7 @@ def testerchain():
TheBlockchain._TheBlockchain__instance = None
@pytest.fixture(scope='function')
@pytest.fixture()
def mock_token_deployer(testerchain):
token_deployer = MockNuCypherKMSTokenDeployer(blockchain=testerchain)
token_deployer.arm()
@ -22,12 +21,6 @@ def mock_token_deployer(testerchain):
yield token_deployer
@pytest.fixture(scope='function')
def token_agent(testerchain):
token = NuCypherKMSTokenAgent(blockchain=testerchain)
yield token
@pytest.fixture(scope='function')
def mock_miner_escrow_deployer(token_agent):
escrow = MockMinerEscrowDeployer(token_agent)
@ -38,8 +31,13 @@ def mock_miner_escrow_deployer(token_agent):
# Unused args preserve fixture dependency order #
@pytest.fixture()
def token_agent(testerchain, mock_token_deployer):
token = NuCypherKMSTokenAgent(blockchain=testerchain)
yield token
@pytest.fixture(scope='function')
@pytest.fixture()
def miner_agent(token_agent, mock_token_deployer, mock_miner_escrow_deployer):
miner_agent = MinerAgent(token_agent)
yield miner_agent
yield miner_agent

View File

@ -18,32 +18,34 @@ def test_deposit(testerchain, mock_token_deployer, token_agent, miner_agent):
miner.lock(amount=1000*M, locktime=100)
class MockNucypherMinerConfig(object):
pass
def test_mine_withdraw(testerchain, mock_token_deployer, token_agent, miner_agent, mock_miner_escrow_deployer):
"""
- Airdrop tokens to everyone
- Create an Ursula (Miner)
- Ursula locks tokens
- Spawn additional miners
- Wait
- Ursula mints new tokens
"""
def test_mine_withdraw(testerchain, mock_token_deployer, token_agent, miner_agent):
mock_token_deployer._global_airdrop(amount=10000)
ursula_address = testerchain._chain.web3.eth.accounts[1]
miner = Miner(miner_agent=miner_agent, address=ursula_address)
_origin, ursula_address, *everyone_else = testerchain._chain.web3.eth.accounts
ursula = miner
initial_balance = token_agent.balance(address=ursula.address)
ursula = Miner(miner_agent=miner_agent, address=ursula_address)
initial_balance = ursula.token_balance()
# Create a random set of miners (we have 9 in total)
for address in testerchain._chain.web3.eth.accounts[1:]:
miner = Miner(miner_agent=miner_agent, address=address)
amount = (10+random.randrange(9000)) * M
miner.lock(amount=amount, locktime=1)
amount = (10 + random.randrange(9000)) * M
ursula.lock(amount=amount, locktime=1)
testerchain.wait_time(MockNuCypherMinerConfig._hours_per_period*2)
spawn_miners(miner_agent=miner_agent, addresses=everyone_else, locktime=1, m=M)
testerchain.wait_time(wait_hours=miner_agent._deployer._hours_per_period*2)
ursula.mint()
ursula.withdraw(entire_balance=True)
final_balance = token_agent.balance(ursula.address)
final_balance = token_agent.balance(ursula.address)
assert final_balance > initial_balance

View File

@ -21,7 +21,7 @@ class MockNuCypherKMSTokenDeployer(NuCypherKMSTokenDeployer):
def txs():
for address in addresses:
yield self._contract.transact({'from': self.origin}).transfer(address, amount * (10 ** 6))
yield self._contract.transact({'from': self._creator}).transfer(address, amount * (10 ** 6))
for tx in txs():
self._blockchain._chain.wait.for_receipt(tx, timeout=10)