mirror of https://github.com/nucypher/nucypher.git
Agent validation and self-awareness, and documentation.
parent
7a35c4b24b
commit
8bc795e630
|
@ -7,22 +7,27 @@ from nucypher.blockchain.eth.agents import NucypherTokenAgent, MinerAgent, Polic
|
|||
from constant_sorrow import constants
|
||||
|
||||
|
||||
|
||||
class NucypherTokenActor:
|
||||
"""
|
||||
Concrete base class for any actor that will interface with NuCypher's ethereum smart contracts
|
||||
Concrete base class for any actor that will interface with NuCypher's ethereum smart contracts.
|
||||
"""
|
||||
|
||||
class ActorError(Exception):
|
||||
pass
|
||||
|
||||
def __init__(self, ether_address: Union[str, bytes, None]=None,
|
||||
token_agent: NucypherTokenAgent=None, *args, **kwargs):
|
||||
def __init__(self, ether_address: str=None, token_agent: NucypherTokenAgent=None, *args, **kwargs):
|
||||
"""
|
||||
:param ether_address: If not passed, we assume this is an unknown actor
|
||||
|
||||
:param token_agent: The token agent with the blockchain attached; If not passed, A default
|
||||
token agent and blockchain connection will be created from default values.
|
||||
|
||||
"""
|
||||
|
||||
# Auto-connect, if needed
|
||||
self.token_agent = token_agent if token_agent is not None else NucypherTokenAgent()
|
||||
|
||||
self.__ether_address = ether_address if ether_address is not None else constants.UNKNOWN_ACTOR
|
||||
self.ether_address = ether_address if ether_address is not None else constants.UNKNOWN_ACTOR
|
||||
self._transaction_cache = list() # track transactions transmitted
|
||||
|
||||
def __repr__(self):
|
||||
|
@ -33,15 +38,9 @@ class NucypherTokenActor:
|
|||
|
||||
@classmethod
|
||||
def from_config(cls, config):
|
||||
"""Read actor data from a configuration file, and create an actor instance."""
|
||||
raise NotImplementedError
|
||||
|
||||
#
|
||||
# Crypto-asset balances
|
||||
#
|
||||
@property
|
||||
def ether_address(self):
|
||||
return self.__ether_address
|
||||
|
||||
def eth_balance(self):
|
||||
"""Return this actors's current ETH balance"""
|
||||
balance = self.token_agent.blockchain.interface.w3.eth.getBalance(self.ether_address)
|
||||
|
@ -55,13 +54,13 @@ class NucypherTokenActor:
|
|||
|
||||
class Miner(NucypherTokenActor):
|
||||
"""
|
||||
Ursula, practically carrying a pickaxe.
|
||||
Ursula baseclass for blockchain operations, practically carrying a pickaxe.
|
||||
"""
|
||||
|
||||
class MinerError(NucypherTokenActor.ActorError):
|
||||
pass
|
||||
|
||||
def __init__(self, miner_agent: MinerAgent=None, *args, **kwargs):
|
||||
def __init__(self, is_me=True, miner_agent: MinerAgent=None, *args, **kwargs):
|
||||
miner_agent = miner_agent if miner_agent is not None else MinerAgent()
|
||||
super().__init__(token_agent=miner_agent.token_agent, *args, **kwargs)
|
||||
|
||||
|
@ -71,6 +70,7 @@ class Miner(NucypherTokenActor):
|
|||
self.blockchain = self.token_agent.blockchain
|
||||
|
||||
# Establish initial state
|
||||
self.is_me = is_me
|
||||
self.__locked_tokens = constants.LOCKED_TOKENS_UNAVAILIBLE
|
||||
self.__datastore_entries = constants.CONTRACT_DATASTORE_UNAVAILIBLE
|
||||
self.__node_datastore = constants.CONTRACT_DATASTORE_UNAVAILIBLE
|
||||
|
@ -81,6 +81,7 @@ class Miner(NucypherTokenActor):
|
|||
|
||||
@classmethod
|
||||
def from_config(cls, blockchain_config) -> 'Miner':
|
||||
"""Read miner data from a configuration file, and create an miner instance."""
|
||||
|
||||
# Use BlockchainConfig to default to the first wallet address
|
||||
wallet_address = blockchain_config.wallet_addresses[0]
|
||||
|
@ -93,6 +94,8 @@ class Miner(NucypherTokenActor):
|
|||
#
|
||||
def _approve_escrow(self, amount: int) -> str:
|
||||
"""Approve the transfer of token from the miner's address to the escrow contract."""
|
||||
if not self.is_me:
|
||||
raise self.MinerError("Cannot execute contract staking functions with a non-self Miner instance.")
|
||||
|
||||
txhash = self.token_agent.contract.functions.approve(self.miner_agent.contract_address, amount).transact({'from': self.ether_address})
|
||||
self.blockchain.wait_for_receipt(txhash)
|
||||
|
@ -103,6 +106,8 @@ class Miner(NucypherTokenActor):
|
|||
|
||||
def _send_tokens_to_escrow(self, amount, lock_periods) -> str:
|
||||
"""Send tokes to the escrow from the miner's address"""
|
||||
if not self.is_me:
|
||||
raise self.MinerError("Cannot execute contract staking functions with a non-self Miner instance.")
|
||||
|
||||
deposit_txhash = self.miner_agent.contract.functions.deposit(amount, lock_periods).transact({'from': self.ether_address})
|
||||
|
||||
|
@ -113,6 +118,9 @@ class Miner(NucypherTokenActor):
|
|||
|
||||
def deposit(self, amount: int, lock_periods: int) -> Tuple[str, str]:
|
||||
"""Public facing method for token locking."""
|
||||
if not self.is_me:
|
||||
raise self.MinerError("Cannot execute contract staking functions with a non-self Miner instance.")
|
||||
|
||||
approve_txhash = self._approve_escrow(amount=amount)
|
||||
deposit_txhash = self._send_tokens_to_escrow(amount=amount, lock_periods=lock_periods)
|
||||
|
||||
|
@ -145,6 +153,9 @@ class Miner(NucypherTokenActor):
|
|||
#
|
||||
# TODO add divide_stake method
|
||||
def switch_lock(self):
|
||||
if not self.is_me:
|
||||
raise self.MinerError("Cannot execute contract staking functions with a non-self Miner instance.")
|
||||
|
||||
lock_txhash = self.miner_agent.contract.functions.switchLock().transact({'from': self.ether_address})
|
||||
self.blockchain.wait_for_receipt(lock_txhash)
|
||||
|
||||
|
@ -152,6 +163,8 @@ class Miner(NucypherTokenActor):
|
|||
return lock_txhash
|
||||
|
||||
def __validate_stake(self, amount: int, lock_periods: int) -> bool:
|
||||
if not self.is_me:
|
||||
raise self.MinerError("Cannot execute contract staking functions with a non-self Miner instance.")
|
||||
|
||||
from .constants import validate_locktime, validate_stake_amount
|
||||
assert validate_stake_amount(amount=amount)
|
||||
|
@ -165,9 +178,16 @@ class Miner(NucypherTokenActor):
|
|||
def stake(self, amount, lock_periods, entire_balance=False):
|
||||
"""
|
||||
High level staking method for Miners.
|
||||
"""
|
||||
|
||||
# manual type checking below this point; force an int to allow use of constants
|
||||
:param amount: Amount of tokens to stake denominated in the smallest unit.
|
||||
:param lock_periods: Duration of stake in periods.
|
||||
:param entire_balance: If True, stake the entire balance of this node, or the maximum possible.
|
||||
|
||||
"""
|
||||
if not self.is_me:
|
||||
raise self.MinerError("Cannot execute contract staking functions with a non-self Miner instance.")
|
||||
|
||||
# manual type checking below this point; force an int to allow use of constants
|
||||
amount, lock_periods = int(amount), int(lock_periods)
|
||||
|
||||
staking_transactions = OrderedDict() # Time series of txhases
|
||||
|
@ -193,6 +213,9 @@ class Miner(NucypherTokenActor):
|
|||
def confirm_activity(self) -> str:
|
||||
"""Miner rewarded for every confirmed period"""
|
||||
|
||||
if not self.is_me:
|
||||
raise self.MinerError("Cannot execute contract staking functions with a non-self Miner instance.")
|
||||
|
||||
txhash = self.miner_agent.contract.functions.confirmActivity().transact({'from': self.ether_address})
|
||||
self.blockchain.wait_for_receipt(txhash)
|
||||
|
||||
|
@ -203,6 +226,9 @@ class Miner(NucypherTokenActor):
|
|||
def mint(self) -> Tuple[str, str]:
|
||||
"""Computes and transfers tokens to the miner's account"""
|
||||
|
||||
if not self.is_me:
|
||||
raise self.MinerError("Cannot execute contract staking functions with a non-self Miner instance.")
|
||||
|
||||
mint_txhash = self.miner_agent.contract.functions.mint().transact({'from': self.ether_address})
|
||||
|
||||
self.blockchain.wait_for_receipt(mint_txhash)
|
||||
|
@ -213,6 +239,9 @@ class Miner(NucypherTokenActor):
|
|||
def collect_policy_reward(self, policy_manager):
|
||||
"""Collect rewarded ETH"""
|
||||
|
||||
if not self.is_me:
|
||||
raise self.MinerError("Cannot execute contract staking functions with a non-self Miner instance.")
|
||||
|
||||
policy_reward_txhash = policy_manager.contract.functions.withdraw().transact({'from': self.ether_address})
|
||||
self.blockchain.wait_for_receipt(policy_reward_txhash)
|
||||
|
||||
|
@ -223,6 +252,9 @@ class Miner(NucypherTokenActor):
|
|||
def collect_staking_reward(self) -> str:
|
||||
"""Withdraw tokens rewarded for staking."""
|
||||
|
||||
if not self.is_me:
|
||||
raise self.MinerError("Cannot execute contract staking functions with a non-self Miner instance.")
|
||||
|
||||
token_amount = self.miner_agent.contract.functions.minerInfo(self.ether_address).call()[0]
|
||||
staked_amount = max(self.miner_agent.contract.functions.getLockedTokens(self.ether_address).call(),
|
||||
self.miner_agent.contract.functions.getLockedTokens(self.ether_address, 1).call())
|
||||
|
@ -238,9 +270,12 @@ class Miner(NucypherTokenActor):
|
|||
# Miner Datastore
|
||||
#
|
||||
|
||||
def publish_datastore(self, data) -> str:
|
||||
def _publish_datastore(self, data) -> str:
|
||||
"""Publish new data to the MinerEscrow contract as a public record associated with this miner."""
|
||||
|
||||
if not self.is_me:
|
||||
raise self.MinerError("Cannot write to contract datastore with a non-self Miner instance.")
|
||||
|
||||
txhash = self.miner_agent.contract.functions.setMinerId(data).transact({'from': self.ether_address})
|
||||
self.blockchain.wait_for_receipt(txhash)
|
||||
|
||||
|
@ -260,13 +295,14 @@ class Miner(NucypherTokenActor):
|
|||
yield value
|
||||
self.__node_datastore = node_datastore_reader()
|
||||
|
||||
def read_datastore(self, index: int=None, refresh=False):
|
||||
def _read_datastore(self, index: int=None, refresh=False):
|
||||
"""
|
||||
Read a value from the nodes datastore, within the MinersEscrow ethereum contract.
|
||||
since there may be multiple values, select one, and return it. The most recently
|
||||
pushed entry is returned by default, and can be specified with the index parameter.
|
||||
|
||||
If refresh it True, read the node's data from the blockchain before returning.
|
||||
|
||||
"""
|
||||
if refresh is True:
|
||||
self.__fetch_node_datastore()
|
||||
|
@ -285,13 +321,18 @@ class Miner(NucypherTokenActor):
|
|||
|
||||
|
||||
class PolicyAuthor(NucypherTokenActor):
|
||||
"""Alice, mocking up new policies!"""
|
||||
"""Alice base class for blockchain operations, mocking up new policies!"""
|
||||
|
||||
def __init__(self, policy_agent: PolicyAgent=None, *args, **kwargs):
|
||||
"""
|
||||
|
||||
:param policy_agent: A policy agent with the blockchain attached; If not passed, A default policy
|
||||
agent and blockchain connection will be created from default values.
|
||||
|
||||
"""
|
||||
|
||||
# From defaults
|
||||
if policy_agent is None:
|
||||
# all defaults
|
||||
# From defaults
|
||||
self.token_agent = NucypherTokenAgent()
|
||||
self.miner_agent = MinerAgent(token_agent=self.token_agent)
|
||||
self.policy_agent = PolicyAgent(miner_agent=self.miner_agent)
|
||||
|
|
Loading…
Reference in New Issue