mirror of https://github.com/nucypher/nucypher.git
The Staking Loop; Add POA ans staking node configuration options
parent
74d52bba80
commit
bd64521992
14
cli/main.py
14
cli/main.py
|
@ -758,12 +758,15 @@ def status(config, provider, contracts, network):
|
|||
@cli.command()
|
||||
@click.option('--dev', is_flag=True, default=False)
|
||||
@click.option('--federated-only', is_flag=True)
|
||||
@click.option('--poa', is_flag=True)
|
||||
@click.option('--rest-host', type=str)
|
||||
@click.option('--rest-port', type=int)
|
||||
@click.option('--db-name', type=str)
|
||||
@click.option('--provider-uri', type=str)
|
||||
@click.option('--registry-filepath', type=click.Path())
|
||||
@click.option('--checksum-address', type=str)
|
||||
@click.option('--stake-amount', type=int)
|
||||
@click.option('--stake-periods', type=int)
|
||||
@click.option('--metadata-dir', type=click.Path())
|
||||
@click.option('--config-file', type=click.Path())
|
||||
def run_ursula(rest_port,
|
||||
|
@ -772,9 +775,12 @@ def run_ursula(rest_port,
|
|||
provider_uri,
|
||||
registry_filepath,
|
||||
checksum_address,
|
||||
stake_amount,
|
||||
stake_periods,
|
||||
federated_only,
|
||||
metadata_dir,
|
||||
config_file,
|
||||
poa,
|
||||
dev
|
||||
) -> None:
|
||||
"""
|
||||
|
@ -802,6 +808,7 @@ def run_ursula(rest_port,
|
|||
else:
|
||||
ursula_config = UrsulaConfiguration(temp=temp,
|
||||
auto_initialize=temp,
|
||||
poa=poa,
|
||||
rest_host=rest_host,
|
||||
rest_port=rest_port,
|
||||
db_name=db_name,
|
||||
|
@ -820,9 +827,12 @@ def run_ursula(rest_port,
|
|||
try:
|
||||
|
||||
URSULA = ursula_config.produce(passphrase=passphrase)
|
||||
|
||||
if not federated_only:
|
||||
|
||||
URSULA.stake(amount=stake_amount, lock_periods=stake_periods)
|
||||
|
||||
URSULA.get_deployer().run() # Run TLS Deploy (Reactor)
|
||||
if not URSULA.federated_only: # TODO: Resume / Init
|
||||
URSULA.stake() # Start Staking Daemon
|
||||
|
||||
finally:
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
from collections import OrderedDict
|
||||
from logging import getLogger
|
||||
|
||||
import maya
|
||||
from constant_sorrow import constants
|
||||
from datetime import datetime
|
||||
from twisted.internet import task, reactor
|
||||
from typing import Tuple, List
|
||||
|
||||
from nucypher.blockchain.eth.agents import NucypherTokenAgent, MinerAgent, PolicyAgent
|
||||
from nucypher.blockchain.eth.chains import Blockchain
|
||||
from nucypher.blockchain.eth.interfaces import EthereumContractRegistry
|
||||
from nucypher.blockchain.eth.utils import (datetime_to_period,
|
||||
validate_stake_amount,
|
||||
validate_locktime,
|
||||
|
@ -79,26 +80,123 @@ class Miner(NucypherTokenActor):
|
|||
Ursula baseclass for blockchain operations, practically carrying a pickaxe.
|
||||
"""
|
||||
|
||||
__current_period_sample_rate = 10
|
||||
|
||||
class MinerError(NucypherTokenActor.ActorError):
|
||||
pass
|
||||
|
||||
def __init__(self, miner_agent: MinerAgent = None, is_me=True, *args, **kwargs) -> None:
|
||||
if miner_agent is None:
|
||||
token_agent = NucypherTokenAgent()
|
||||
miner_agent = MinerAgent(token_agent=token_agent)
|
||||
super().__init__(token_agent=miner_agent.token_agent, *args, **kwargs)
|
||||
def __init__(self, is_me: bool, miner_agent: MinerAgent = None, *args, **kwargs) -> None:
|
||||
|
||||
# Extrapolate dependencies
|
||||
self.miner_agent = miner_agent
|
||||
self.token_agent = miner_agent.token_agent
|
||||
self.blockchain = self.token_agent.blockchain
|
||||
|
||||
# Establish initial state
|
||||
self.log = getLogger("miner")
|
||||
self.is_me = is_me
|
||||
if is_me:
|
||||
if miner_agent is None:
|
||||
token_agent = NucypherTokenAgent()
|
||||
miner_agent = MinerAgent(token_agent=token_agent)
|
||||
else:
|
||||
token_agent = miner_agent.token_agent
|
||||
blockchain = miner_agent.token_agent.blockchain
|
||||
else:
|
||||
token_agent = constants.STRANGER_MINER
|
||||
blockchain = constants.STRANGER_MINER
|
||||
|
||||
self.miner_agent = miner_agent
|
||||
self.token_agent = token_agent
|
||||
self.blockchain = blockchain
|
||||
|
||||
super().__init__(token_agent=self.token_agent, *args, **kwargs)
|
||||
|
||||
if is_me is True:
|
||||
self.__current_period = None # TODO: use constant
|
||||
self._abort_on_staking_error = True
|
||||
self._staking_task = task.LoopingCall(self._confirm_period)
|
||||
|
||||
#
|
||||
# Staking
|
||||
#
|
||||
|
||||
@only_me
|
||||
def stake(self,
|
||||
confirm_now=False,
|
||||
resume: bool = False,
|
||||
expiration: maya.MayaDT = None,
|
||||
lock_periods: int = None,
|
||||
*args, **kwargs) -> None:
|
||||
|
||||
"""High-level staking daemon loop"""
|
||||
|
||||
if lock_periods and expiration:
|
||||
raise ValueError("Pass the number of lock periods or an expiration MayaDT; not both.")
|
||||
if expiration:
|
||||
lock_periods = datetime_to_period(expiration)
|
||||
|
||||
if resume is False:
|
||||
_staking_receipts = self.initialize_stake(expiration=expiration,
|
||||
lock_periods=lock_periods,
|
||||
*args, **kwargs)
|
||||
|
||||
# TODO: Check if this period has already been confirmed
|
||||
# TODO: Check if there is an active stake in the current period: Resume staking daemon
|
||||
# TODO: Validation and Sanity checks
|
||||
|
||||
if confirm_now:
|
||||
self.confirm_activity()
|
||||
|
||||
# record start time and periods
|
||||
self.__start_time = maya.now()
|
||||
self.__uptime_period = self.miner_agent.get_current_period()
|
||||
self.__terminal_period = self.__uptime_period + lock_periods
|
||||
self.__current_period = self.__uptime_period
|
||||
self.start_staking_loop()
|
||||
|
||||
#
|
||||
# Daemon
|
||||
#
|
||||
|
||||
def _confirm_period(self):
|
||||
period = self.miner_agent.get_current_period()
|
||||
# check for stale sample data
|
||||
self.log.info("Checking for new period. Current period is {}".format(self.__current_period)) # TODO: set to debug?
|
||||
if self.__current_period != period:
|
||||
|
||||
# check for stake expiration
|
||||
stake_expired = self.__current_period >= self.__terminal_period
|
||||
if stake_expired:
|
||||
self.log.info('Stake duration expired')
|
||||
return True
|
||||
|
||||
self.confirm_activity()
|
||||
self.__current_period = period
|
||||
self.log.info("Confirmed activity for period {}".format(self.__current_period))
|
||||
|
||||
@only_me
|
||||
def _crash_gracefully(self, failure=None):
|
||||
"""
|
||||
A facility for crashing more gracefully in the event that an exception
|
||||
is unhandled in a different thread, especially inside a loop like the learning loop.
|
||||
"""
|
||||
self._crashed = failure
|
||||
failure.raiseException()
|
||||
|
||||
@only_me
|
||||
def handle_staking_errors(self, *args, **kwargs):
|
||||
failure = args[0]
|
||||
if self._abort_on_staking_error:
|
||||
self.log.critical("Unhandled error during node staking. Attempting graceful crash.")
|
||||
reactor.callFromThread(self._crash_gracefully, failure=failure)
|
||||
else:
|
||||
self.log.warning("Unhandled error during node learning: {}".format(failure.getTraceback()))
|
||||
|
||||
@only_me
|
||||
def start_staking_loop(self, now=True):
|
||||
if self._staking_task.running:
|
||||
return False
|
||||
else:
|
||||
d = self._staking_task.start(interval=self.__current_period_sample_rate, now=now)
|
||||
d.addErrback(self.handle_staking_errors)
|
||||
self.log.info("Started staking loop")
|
||||
return d
|
||||
|
||||
@property
|
||||
def is_staking(self):
|
||||
"""Checks if this Miner currently has locked tokens."""
|
||||
|
@ -118,8 +216,6 @@ class Miner(NucypherTokenActor):
|
|||
@only_me
|
||||
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 miner staking functions with a non-self Miner instance.")
|
||||
|
||||
approve_txhash = self.token_agent.approve_transfer(amount=amount,
|
||||
target_address=self.miner_agent.contract_address,
|
||||
|
@ -178,7 +274,7 @@ class Miner(NucypherTokenActor):
|
|||
@only_me
|
||||
def __validate_stake(self, amount: int, lock_periods: int) -> bool:
|
||||
|
||||
assert validate_stake_amount(amount=amount)
|
||||
assert validate_stake_amount(amount=amount) # TODO: remove assertions..?
|
||||
assert validate_locktime(lock_periods=lock_periods)
|
||||
|
||||
if not self.token_balance >= amount:
|
||||
|
|
|
@ -701,74 +701,3 @@ class Ursula(Character, VerifiableNode, Miner):
|
|||
if work_order.bob == bob:
|
||||
work_orders_from_bob.append(work_order)
|
||||
return work_orders_from_bob
|
||||
|
||||
@only_me
|
||||
def stake(self,
|
||||
sample_rate: int = 10,
|
||||
refresh_rate: int = 60,
|
||||
confirm_now=True,
|
||||
resume: bool = False,
|
||||
expiration: maya.MayaDT = None,
|
||||
lock_periods: int = None,
|
||||
*args, **kwargs) -> None:
|
||||
|
||||
"""High-level staking daemon loop"""
|
||||
|
||||
if lock_periods and expiration:
|
||||
raise ValueError("Pass the number of lock periods or an expiration MayaDT; not both.")
|
||||
if expiration:
|
||||
lock_periods = datetime_to_period(expiration)
|
||||
|
||||
if resume is False:
|
||||
_staking_receipts = super().initialize_stake(expiration=expiration,
|
||||
lock_periods=lock_periods,
|
||||
*args, **kwargs)
|
||||
|
||||
# TODO: Check if this period has already been confirmed
|
||||
# TODO: Check if there is an active stake in the current period: Resume staking daemon
|
||||
# TODO: Validation and Sanity checks
|
||||
|
||||
if confirm_now:
|
||||
self.confirm_activity()
|
||||
|
||||
# record start time and periods
|
||||
start_time = maya.now()
|
||||
uptime_period = self.miner_agent.get_current_period()
|
||||
terminal_period = uptime_period + lock_periods
|
||||
current_period = uptime_period
|
||||
|
||||
#
|
||||
# Daemon
|
||||
#
|
||||
|
||||
try:
|
||||
while True:
|
||||
|
||||
# calculate timedeltas
|
||||
now = maya.now()
|
||||
initialization_delta = now - start_time
|
||||
|
||||
# check if iteration re-samples
|
||||
sample_stale = initialization_delta.seconds > (refresh_rate - 1)
|
||||
if sample_stale:
|
||||
|
||||
period = self.miner_agent.get_current_period()
|
||||
# check for stale sample data
|
||||
if current_period != period:
|
||||
|
||||
# check for stake expiration
|
||||
stake_expired = current_period >= terminal_period
|
||||
if stake_expired:
|
||||
break
|
||||
|
||||
self.confirm_activity()
|
||||
current_period = period
|
||||
# wait before resampling
|
||||
time.sleep(sample_rate)
|
||||
continue
|
||||
|
||||
finally:
|
||||
|
||||
# TODO: Cleanup #
|
||||
|
||||
pass
|
||||
|
|
|
@ -4,6 +4,7 @@ from constant_sorrow import constants
|
|||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurve
|
||||
from cryptography.x509 import Certificate
|
||||
from web3.middleware import geth_poa_middleware
|
||||
|
||||
from nucypher.blockchain.eth.agents import EthereumContractAgent, NucypherTokenAgent, MinerAgent
|
||||
from nucypher.blockchain.eth.chains import Blockchain
|
||||
|
@ -37,6 +38,7 @@ class UrsulaConfiguration(NodeConfiguration):
|
|||
crypto_power: CryptoPower = None,
|
||||
|
||||
# Blockchain
|
||||
poa: bool = False,
|
||||
provider_uri: str = None,
|
||||
miner_agent: EthereumContractAgent = None,
|
||||
|
||||
|
@ -63,6 +65,7 @@ class UrsulaConfiguration(NodeConfiguration):
|
|||
#
|
||||
# Blockchain
|
||||
#
|
||||
self.poa = poa
|
||||
self.blockchain_uri = provider_uri
|
||||
self.miner_agent = miner_agent
|
||||
|
||||
|
@ -116,6 +119,7 @@ class UrsulaConfiguration(NodeConfiguration):
|
|||
from nucypher.characters.lawful import Ursula
|
||||
|
||||
if self.federated_only is False:
|
||||
|
||||
blockchain = Blockchain.connect(provider_uri=self.blockchain_uri) # TODO: move this..?
|
||||
token_agent = NucypherTokenAgent(blockchain=blockchain, registry_filepath=self.registry_filepath)
|
||||
miner_agent = MinerAgent(token_agent=token_agent)
|
||||
|
@ -123,6 +127,10 @@ class UrsulaConfiguration(NodeConfiguration):
|
|||
|
||||
ursula = Ursula(**merged_parameters)
|
||||
|
||||
if self.poa:
|
||||
w3 = ursula.blockchain.interface.w3
|
||||
w3.middleware_stack.inject(geth_poa_middleware, layer=0)
|
||||
|
||||
# if self.save_metadata: # TODO: Does this belong here..?
|
||||
ursula.write_node_metadata(node=ursula)
|
||||
ursula.save_certificate_to_disk(directory=ursula.known_certificates_dir) # TODO: Move this..?
|
||||
|
|
Loading…
Reference in New Issue