[KMS-ETH]- Payment model and client methods updates; Additional shared base class methods for contract agents and deployers

pull/195/head^2
Kieran Prasch 2018-03-07 19:55:12 -08:00
parent d12572546c
commit 9a4c4ad135
12 changed files with 76 additions and 217 deletions

View File

@ -53,13 +53,13 @@ class PolicyArrangement:
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
@ -148,7 +148,7 @@ class Miner(Actor):
return txhash
def collect_policy_reward(self, policy_manager):
def collect_policy_reward(self, policy_manager) -> str:
"""Collect policy reward in ETH"""
txhash = policy_manager.transact({'from': self.address}).withdraw()
@ -191,8 +191,8 @@ class Miner(Actor):
def token_balance(self) -> int:
"""Check miner's current token balance"""
self._token_agent._check_contract_deployment()
balance = self._token_agent().balanceOf(self.address)
# self._token_agent._check_contract_deployment()
balance = self._token_agent.call().balanceOf(self.address)
return balance
@ -261,12 +261,12 @@ class PolicyAuthor(Actor):
try:
arrangement = self._arrangements[arrangement_id]
except KeyError:
raise Exception('No such arrangement')
raise Exception('No such arrangement') #TODO
else:
txhash = arrangement.revoke()
return txhash
def select_miners(self, quantity: int) -> List[str]:
def recruit(self, quantity: int) -> List[str]:
miner_addresses = self.policy_agent.miner_agent.sample(quantity=quantity)
return miner_addresses

View File

@ -18,7 +18,7 @@ class NuCypherKMSTokenAgent(EthereumContractAgent, deployer=NuCypherKMSTokenDepl
all_known_address = self._blockchain._chain.registrar.get_contract_address(self._principal_contract_name)
return all_known_address
def check_balance(self, address: str) -> int:
def balance(self, address: str) -> int:
"""Get the balance of a token address"""
return self.call().balanceOf(address)
@ -83,8 +83,8 @@ class MinerAgent(EthereumContractAgent, deployer=MinerEscrowDeployer):
system_random = random.SystemRandom()
n_select = round(quantity*additional_ursulas) # Select more Ursulas
n_tokens = self.call().getAllLockedTokens()
n_tokens = self.call().getAllLockedTokens() # Check for locked tokens
if not n_tokens > 0:
raise self.NotEnoughUrsulas('There are no locked tokens.')
@ -92,9 +92,9 @@ class MinerAgent(EthereumContractAgent, deployer=MinerEscrowDeployer):
points = [0] + sorted(system_random.randrange(n_tokens) for _ in range(n_select))
deltas = [i-j for i, j in zip(points[1:], points[:-1])]
addrs, addr, shift = set(), MinerEscrowDeployer._null_addr, 0
addrs, addr, index, shift = set(), self._deployer._null_addr, 0, 0
for delta in deltas:
addr, shift = self.call().findCumSum(addr, delta+shift, duration)
addr, index, shift = self.call().findCumSum(index, delta+shift, duration)
addrs.add(addr)
if len(addrs) >= quantity:

View File

@ -1,5 +1,7 @@
from abc import ABC, abstractmethod
from nkms_eth.blockchain import TheBlockchain
class Actor(ABC):
def __init__(self, address):
@ -15,6 +17,7 @@ class Actor(ABC):
class ContractDeployer(ABC):
_contract_name = NotImplemented
class ContractDeploymentError(Exception):
@ -22,31 +25,38 @@ class ContractDeployer(ABC):
def __init__(self, blockchain):
self.__armed = False
self.__contract = None
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
return self._contract.address == other.address
@property
def address(self) -> str:
return self.__contract.address
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)
return bool(self._contract is not None)
@property
def is_armed(self) -> bool:
return bool(self.__armed is True)
@classmethod
def contract_name(cls) -> str:
return cls._contract_name
def _verify_contract_deployment(self) -> None:
"""Raises ContractDeploymentError if the contract has not been armed and deployed."""
if not self.__contract:
if not self._contract:
class_name = self.__class__.__name__
message = '{} contract is not deployed. Arm, then deploy.'.format(class_name)
raise self.ContractDeploymentError(message)
@ -65,50 +75,62 @@ class ContractDeployer(ABC):
# 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
@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(Exception):
class ContractNotDeployed(ContractDeployer.ContractDeploymentError):
pass
def __init__(self, agent, *args, **kwargs):
if not self._blockchain:
self._blockchain = agent._blockchain
self._blockchain = agent._blockchain
contract = self._blockchain._chain.provider.get_contract(self._principal_contract_name)
self.__contract = contract
self._contract = contract
def __init_subclass__(cls, deployer):
@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()
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)
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()
return self._contract.call()
def transact(self, *args, **kwargs):
return self.__contract.transact(*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
return self._contract.address
@property
def contract_name(self):

View File

@ -36,14 +36,14 @@ class NuCypherKMSTokenDeployer(ContractDeployer):
message = '{} contract already deployed, use .get() to retrieve it.'.format(class_name)
raise self.ContractDeploymentError(message)
the_nucypherKMS_token_contract, deployment_txhash = self._blockchain._chain.provider.deploy_contract(
the_nucypher_token_contract, deployment_txhash = self._blockchain._chain.provider.deploy_contract(
self._contract_name,
deploy_args=[self._token_config.saturation],
deploy_args=[self.saturation],
deploy_transaction={'from': self.origin})
self._blockchain._chain.wait.for_receipt(deployment_txhash, timeout=self._blockchain._timeout)
self._contract = the_nucypher_token_contract
self._contract = the_nucypherKMS_token_contract
return deployment_txhash
@ -104,21 +104,21 @@ class MinerEscrowDeployer(ContractDeployer):
message = '{} contract already deployed, use .get() to retrieve it.'.format(class_name)
raise self.ContractDeploymentError(message)
deploy_args = [self._token_agent._contract.address] + self._config.mining_coefficient
deploy_tx = {'from': self._token_agent._creator}
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)
self.__contract = the_escrow_contract
self._contract = the_escrow_contract
reward_txhash = self._token_agent.transact({'from': self._token_agent.origin}).transfer(self.__contract.address,
self._config.reward)
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)
init_txhash = self.__contract.transact({'from': self._token_agent.origin}).initialize()
init_txhash = self._contract.transact({'from': self._token_agent.origin}).initialize()
self._blockchain._chain.wait.for_receipt(init_txhash, timeout=self._blockchain._timeout)
return deploy_txhash, reward_txhash, init_txhash

View File

@ -1,6 +1,7 @@
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

View File

View File

View File

View File

@ -1,70 +0,0 @@
import random
import pytest
from populus.contracts.exceptions import NoKnownAddress
from pytest import raises
from nkms_eth.agents import MinerAgent, NuCypherKMSTokenAgent
from nkms_eth.actors import Miner
M = 10 ** 6
def test_create_escrow(testerchain):
with raises(NoKnownAddress):
NuCypherKMSTokenAgent.get(blockchain=testerchain)
token = NuCypherKMSTokenAgent(blockchain=testerchain)
token.arm()
token.deploy()
same_token = NuCypherKMSTokenAgent.get(blockchain=testerchain)
with raises(NuCypherKMSTokenAgent.ContractDeploymentError):
same_token.arm()
same_token.deploy()
assert len(token.__contract.address) == 42
assert token.__contract.address == same_token._contract.address
with raises(NoKnownAddress):
MinerAgent.get(token=token)
escrow = MinerAgent(token=token)
escrow.arm()
escrow.deploy()
same_escrow = MinerAgent.get(token=token)
with raises(MinerAgent.ContractDeploymentError):
same_escrow.arm()
same_escrow.deploy()
assert len(escrow.__contract.address) == 42
assert escrow.__contract.address == same_escrow._contract.address
def test_get_swarm(testerchain, token, escrow):
token._airdrop(amount=10000)
creator, *addresses = testerchain._chain.web3.eth.accounts
# Create 9 Miners
for address in addresses:
miner = Miner(miner_agent=escrow, address=address)
amount = (10+random.randrange(9000)) * M
miner.lock(amount=amount, locktime=1)
testerchain.wait_time(escrow.hours_per_period)
swarm = escrow.swarm()
swarm_addresses = list(swarm)
assert len(swarm_addresses) == 9
# Grab a miner address from the swarm
miner_addr = swarm_addresses[0]
assert isinstance(miner_addr, str)
# Verify the address is hex
try:
int(miner_addr, 16)
except ValueError:
pytest.fail()

View File

@ -1,92 +0,0 @@
import random
import os
import pytest
from nkms_eth.agents import MinerAgent, NuCypherKMSTokenAgent
from nkms_eth.actors import Miner
M = 10 ** 6
def test_deposit(testerchain, token, escrow):
token._airdrop(amount=10000) # weeee
ursula_address = testerchain._chain.web3.eth.accounts[1]
miner = Miner(miner_agent=escrow, address=ursula_address)
miner.lock(amount=1000*M, locktime=100)
def test_mine_withdraw(testerchain, token, escrow):
token._airdrop(amount=10000)
ursula_address = testerchain._chain.web3.eth.accounts[1]
miner = Miner(miner_agent=escrow, address=ursula_address)
ursula = miner
initial_balance = token.balance(address=ursula.address)
# Create a random set of miners (we have 9 in total)
for address in testerchain._chain.web3.eth.accounts[1:]:
miner = Miner(miner_agent=escrow, address=address)
amount = (10+random.randrange(9000)) * M
miner.lock(amount=amount, locktime=1)
testerchain.wait_time(escrow.hours_per_period*2)
ursula.mint()
ursula.withdraw(entire_balance=True)
final_balance = token.balance(ursula.address)
assert final_balance > initial_balance
def test_publish_miner_id(testerchain, token, escrow):
token._airdrop(amount=10000) # weeee
miner_addr = testerchain._chain.web3.eth.accounts[1]
miner = Miner(miner_agent=escrow, address=miner_addr)
balance = miner.token_balance()
miner.lock(amount=balance, locktime=1)
# Publish Miner IDs to the DHT
mock_miner_id = os.urandom(32)
txhash = miner.publish_miner_id(mock_miner_id)
stored_miner_ids = miner.fetch_miner_ids()
assert len(stored_miner_ids) == 1
assert mock_miner_id == stored_miner_ids[0]
another_mock_miner_id = os.urandom(32)
txhash = miner.publish_miner_id(another_mock_miner_id)
stored_miner_ids = miner.fetch_miner_ids()
assert len(stored_miner_ids) == 2
assert another_mock_miner_id == stored_miner_ids[1]
# TODO change when v4 of web3.py is released
assert another_mock_miner_id == escrow().getMinerId(miner_addr, 1).encode('latin-1')
def test_select_ursulas(testerchain, token, escrow):
token._airdrop(amount=10000)
# Create a random set of miners (we have 9 in total)
for u in testerchain._chain.web3.eth.accounts[1:]:
miner = Miner(miner_agent=escrow, address=u)
amount = (10 + random.randrange(9000))*M
miner.lock(amount=amount, locktime=100)
testerchain.wait_time(escrow.hours_per_period)
miners = escrow.sample(quantity=3)
assert len(miners) == 3
assert len(set(miners)) == 3
with pytest.raises(MinerAgent.NotEnoughUrsulas):
escrow.sample(quantity=100) # Waay more than we have deployed

View File

@ -25,9 +25,8 @@ def test_deploy_and_fetch_nucypherkms_token(testerchain):
token_agent = NuCypherKMSTokenAgent(blockchain=testerchain)
# Make sure we got the name right
deployer_contract_identifier = NuCypherKMSTokenDeployer.contract_name
contract_identifier = NuCypherKMSTokenAgent._contract_name
assert'NuCypherKMSToken' == contract_identifier == deployer_contract_identifier
deployer_contract_identifier = NuCypherKMSTokenDeployer._contract_name
assert'NuCypherKMSToken' == deployer_contract_identifier
# Ensure the contract is deployed and has a valid blockchain address
assert len(token_agent.contract_address) == 42

View File

@ -1,7 +1,6 @@
from os.path import join, dirname, abspath
import nkms_eth
from nkms_eth.agents import NuCypherKMSTokenAgent
from nkms_eth.deployers import NuCypherKMSTokenDeployer
@ -26,5 +25,5 @@ def test_nucypher_populus_project(testerchain):
assert testerchain._project.project_dir == populus_project_dir
# Ensure that solidity smart contacts are available, post-compile.
token_contract_identifier = NuCypherKMSTokenDeployer(blockchain=testerchain).contract_name()
token_contract_identifier = NuCypherKMSTokenDeployer(blockchain=testerchain)._contract_name
assert token_contract_identifier in testerchain._project.compiled_contract_data