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()
|
@cli.command()
|
||||||
@click.option('--dev', is_flag=True, default=False)
|
@click.option('--dev', is_flag=True, default=False)
|
||||||
@click.option('--federated-only', is_flag=True)
|
@click.option('--federated-only', is_flag=True)
|
||||||
|
@click.option('--poa', is_flag=True)
|
||||||
@click.option('--rest-host', type=str)
|
@click.option('--rest-host', type=str)
|
||||||
@click.option('--rest-port', type=int)
|
@click.option('--rest-port', type=int)
|
||||||
@click.option('--db-name', type=str)
|
@click.option('--db-name', type=str)
|
||||||
@click.option('--provider-uri', type=str)
|
@click.option('--provider-uri', type=str)
|
||||||
@click.option('--registry-filepath', type=click.Path())
|
@click.option('--registry-filepath', type=click.Path())
|
||||||
@click.option('--checksum-address', type=str)
|
@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('--metadata-dir', type=click.Path())
|
||||||
@click.option('--config-file', type=click.Path())
|
@click.option('--config-file', type=click.Path())
|
||||||
def run_ursula(rest_port,
|
def run_ursula(rest_port,
|
||||||
|
@ -772,9 +775,12 @@ def run_ursula(rest_port,
|
||||||
provider_uri,
|
provider_uri,
|
||||||
registry_filepath,
|
registry_filepath,
|
||||||
checksum_address,
|
checksum_address,
|
||||||
|
stake_amount,
|
||||||
|
stake_periods,
|
||||||
federated_only,
|
federated_only,
|
||||||
metadata_dir,
|
metadata_dir,
|
||||||
config_file,
|
config_file,
|
||||||
|
poa,
|
||||||
dev
|
dev
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -802,6 +808,7 @@ def run_ursula(rest_port,
|
||||||
else:
|
else:
|
||||||
ursula_config = UrsulaConfiguration(temp=temp,
|
ursula_config = UrsulaConfiguration(temp=temp,
|
||||||
auto_initialize=temp,
|
auto_initialize=temp,
|
||||||
|
poa=poa,
|
||||||
rest_host=rest_host,
|
rest_host=rest_host,
|
||||||
rest_port=rest_port,
|
rest_port=rest_port,
|
||||||
db_name=db_name,
|
db_name=db_name,
|
||||||
|
@ -820,9 +827,12 @@ def run_ursula(rest_port,
|
||||||
try:
|
try:
|
||||||
|
|
||||||
URSULA = ursula_config.produce(passphrase=passphrase)
|
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)
|
URSULA.get_deployer().run() # Run TLS Deploy (Reactor)
|
||||||
if not URSULA.federated_only: # TODO: Resume / Init
|
|
||||||
URSULA.stake() # Start Staking Daemon
|
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from logging import getLogger
|
||||||
|
|
||||||
import maya
|
import maya
|
||||||
|
from constant_sorrow import constants
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from twisted.internet import task, reactor
|
||||||
from typing import Tuple, List
|
from typing import Tuple, List
|
||||||
|
|
||||||
from nucypher.blockchain.eth.agents import NucypherTokenAgent, MinerAgent, PolicyAgent
|
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,
|
from nucypher.blockchain.eth.utils import (datetime_to_period,
|
||||||
validate_stake_amount,
|
validate_stake_amount,
|
||||||
validate_locktime,
|
validate_locktime,
|
||||||
|
@ -79,26 +80,123 @@ class Miner(NucypherTokenActor):
|
||||||
Ursula baseclass for blockchain operations, practically carrying a pickaxe.
|
Ursula baseclass for blockchain operations, practically carrying a pickaxe.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
__current_period_sample_rate = 10
|
||||||
|
|
||||||
class MinerError(NucypherTokenActor.ActorError):
|
class MinerError(NucypherTokenActor.ActorError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __init__(self, miner_agent: MinerAgent = None, is_me=True, *args, **kwargs) -> None:
|
def __init__(self, is_me: bool, miner_agent: MinerAgent = None, *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)
|
|
||||||
|
|
||||||
# Extrapolate dependencies
|
self.log = getLogger("miner")
|
||||||
self.miner_agent = miner_agent
|
|
||||||
self.token_agent = miner_agent.token_agent
|
|
||||||
self.blockchain = self.token_agent.blockchain
|
|
||||||
|
|
||||||
# Establish initial state
|
|
||||||
self.is_me = is_me
|
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
|
# 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
|
@property
|
||||||
def is_staking(self):
|
def is_staking(self):
|
||||||
"""Checks if this Miner currently has locked tokens."""
|
"""Checks if this Miner currently has locked tokens."""
|
||||||
|
@ -118,8 +216,6 @@ class Miner(NucypherTokenActor):
|
||||||
@only_me
|
@only_me
|
||||||
def deposit(self, amount: int, lock_periods: int) -> Tuple[str, str]:
|
def deposit(self, amount: int, lock_periods: int) -> Tuple[str, str]:
|
||||||
"""Public facing method for token locking."""
|
"""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,
|
approve_txhash = self.token_agent.approve_transfer(amount=amount,
|
||||||
target_address=self.miner_agent.contract_address,
|
target_address=self.miner_agent.contract_address,
|
||||||
|
@ -178,7 +274,7 @@ class Miner(NucypherTokenActor):
|
||||||
@only_me
|
@only_me
|
||||||
def __validate_stake(self, amount: int, lock_periods: int) -> bool:
|
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)
|
assert validate_locktime(lock_periods=lock_periods)
|
||||||
|
|
||||||
if not self.token_balance >= amount:
|
if not self.token_balance >= amount:
|
||||||
|
|
|
@ -701,74 +701,3 @@ class Ursula(Character, VerifiableNode, Miner):
|
||||||
if work_order.bob == bob:
|
if work_order.bob == bob:
|
||||||
work_orders_from_bob.append(work_order)
|
work_orders_from_bob.append(work_order)
|
||||||
return work_orders_from_bob
|
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 import ec
|
||||||
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurve
|
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurve
|
||||||
from cryptography.x509 import Certificate
|
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.agents import EthereumContractAgent, NucypherTokenAgent, MinerAgent
|
||||||
from nucypher.blockchain.eth.chains import Blockchain
|
from nucypher.blockchain.eth.chains import Blockchain
|
||||||
|
@ -37,6 +38,7 @@ class UrsulaConfiguration(NodeConfiguration):
|
||||||
crypto_power: CryptoPower = None,
|
crypto_power: CryptoPower = None,
|
||||||
|
|
||||||
# Blockchain
|
# Blockchain
|
||||||
|
poa: bool = False,
|
||||||
provider_uri: str = None,
|
provider_uri: str = None,
|
||||||
miner_agent: EthereumContractAgent = None,
|
miner_agent: EthereumContractAgent = None,
|
||||||
|
|
||||||
|
@ -63,6 +65,7 @@ class UrsulaConfiguration(NodeConfiguration):
|
||||||
#
|
#
|
||||||
# Blockchain
|
# Blockchain
|
||||||
#
|
#
|
||||||
|
self.poa = poa
|
||||||
self.blockchain_uri = provider_uri
|
self.blockchain_uri = provider_uri
|
||||||
self.miner_agent = miner_agent
|
self.miner_agent = miner_agent
|
||||||
|
|
||||||
|
@ -116,6 +119,7 @@ class UrsulaConfiguration(NodeConfiguration):
|
||||||
from nucypher.characters.lawful import Ursula
|
from nucypher.characters.lawful import Ursula
|
||||||
|
|
||||||
if self.federated_only is False:
|
if self.federated_only is False:
|
||||||
|
|
||||||
blockchain = Blockchain.connect(provider_uri=self.blockchain_uri) # TODO: move this..?
|
blockchain = Blockchain.connect(provider_uri=self.blockchain_uri) # TODO: move this..?
|
||||||
token_agent = NucypherTokenAgent(blockchain=blockchain, registry_filepath=self.registry_filepath)
|
token_agent = NucypherTokenAgent(blockchain=blockchain, registry_filepath=self.registry_filepath)
|
||||||
miner_agent = MinerAgent(token_agent=token_agent)
|
miner_agent = MinerAgent(token_agent=token_agent)
|
||||||
|
@ -123,6 +127,10 @@ class UrsulaConfiguration(NodeConfiguration):
|
||||||
|
|
||||||
ursula = Ursula(**merged_parameters)
|
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..?
|
# if self.save_metadata: # TODO: Does this belong here..?
|
||||||
ursula.write_node_metadata(node=ursula)
|
ursula.write_node_metadata(node=ursula)
|
||||||
ursula.save_certificate_to_disk(directory=ursula.known_certificates_dir) # TODO: Move this..?
|
ursula.save_certificate_to_disk(directory=ursula.known_certificates_dir) # TODO: Move this..?
|
||||||
|
|
Loading…
Reference in New Issue