def confirm_staged_grant(emitter, grant_request: Dict, federated: bool, seconds_per_period=None) -> None: pretty_request = grant_request.copy() # WARNING: Do not mutate if federated: # Boring table = [[field.capitalize(), value] for field, value in pretty_request.items()] emitter.echo(tabulate(table, tablefmt="simple")) return period_rate = Web3.fromWei(pretty_request['n'] * pretty_request['rate'], 'gwei') pretty_request['rate'] = f"{pretty_request['rate']} wei/period * {pretty_request['n']} nodes" expiration = pretty_request['expiration'] periods = calculate_period_duration(future_time=MayaDT.from_datetime(expiration), seconds_per_period=seconds_per_period) periods += 1 # current period is always included pretty_request['expiration'] = f"{pretty_request['expiration']} ({periods} periods)" # M of N pretty_request['Threshold Shares'] = f"{pretty_request['m']} of {pretty_request['n']}" del pretty_request['m'] del pretty_request['n'] def prettify_field(field): field_words = [word.capitalize() for word in field.split('_')] field = ' '.join(field_words) return field table = [[prettify_field(field), value] for field, value in pretty_request.items()] table.append(['Period Rate', f'{period_rate} gwei']) table.append(['Policy Value', f'{period_rate * periods} gwei']) emitter.echo("\nSuccessfully staged grant, Please review the details:\n", color='green') emitter.echo(tabulate(table, tablefmt="simple")) click.confirm('\nGrant access and sign transaction?', abort=True)
def __init__(self, alice: PolicyAuthor, ursula: Ursula, value: int, expiration: maya.MayaDT, *args, **kwargs) -> None: super().__init__(alice=alice, ursula=ursula, expiration=expiration, value=value, *args, **kwargs) if not value > 0: raise self.InvalidArrangement("Value must be greater than 0.") # The relationship exists between two addresses self.author = alice # type: PolicyAuthor self.policy_agent = alice.policy_agent # type: PolicyManagerAgent self.staker = ursula # type: Staker # Arrangement value, rate, and duration lock_periods = calculate_period_duration(future_time=expiration) rate = value // lock_periods # type: int self._rate = rate # type: int self.value = value # type: int self.lock_periods = lock_periods # type: int # TODO: <datetime> -> lock_periods self.is_published = False # type: bool self.publish_transaction = None self.is_revoked = False # type: bool self.revoke_transaction = None
def make_arrangements(self, network_middleware: RestMiddleware, deposit: int, expiration: maya.MayaDT, handpicked_ursulas: Set[Ursula] = None ) -> None: """ Create and consider n Arrangements from sampled miners, a list of Ursulas, or a combination of both. """ ADDITIONAL_URSULAS = 1.5 # TODO: Make constant handpicked_ursulas = handpicked_ursulas or set() # type: set target_sample_quantity = self.n - len(handpicked_ursulas) selected_addresses = set() # type: set actual_sample_quantity = math.ceil(target_sample_quantity * ADDITIONAL_URSULAS) duration = int(calculate_period_duration(expiration)) try: # Sample by reading from the Blockchain sampled_addresses = self.alice.recruit(quantity=actual_sample_quantity, duration=duration) except MinerAgent.NotEnoughMiners as e: error = "Cannot create policy with {} arrangements: {}".format(target_sample_quantity, e) raise self.NotEnoughBlockchainUrsulas(error) selected_addresses.update(sampled_addresses) found_ursulas = self.__find_ursulas(sampled_addresses, target_sample_quantity) candidates = handpicked_ursulas candidates.update(found_ursulas) # # Consider Arrangements # # Attempt 1 accepted, rejected = self._consider_arrangements(network_middleware=network_middleware, candidate_ursulas=candidates, deposit=deposit, expiration=expiration) # After all is said and done... if len(accepted) < self.n: # Attempt 2: Find more ursulas from the spare pile remaining_quantity = self.n - len(accepted) # TODO: Handle spare Ursulas and try to claw back up to n. found_spare_ursulas, remaining_spare_addresses = self.__find_ursulas(spare_addresses, remaining_quantity) accepted_spares, rejected_spares = self._consider_arrangements(network_middleware, candidate_ursulas=found_spare_ursulas, deposit=deposit, expiration=expiration) accepted.update(accepted_spares) rejected.update(rejected_spares) if len(accepted) < self.n: raise Exception("Selected Ursulas rejected too many arrangements") # TODO: Better exception
def initialize_stake(self, amount: NU, lock_periods: int = None, expiration: maya.MayaDT = None, entire_balance: bool = False) -> Stake: """Create a new stake.""" # # Duration # if lock_periods and expiration: raise ValueError( "Pass the number of lock periods or an expiration MayaDT; not both." ) if expiration: lock_periods = calculate_period_duration(future_time=expiration) # # Value # if entire_balance and amount: raise ValueError("Specify an amount or entire balance, not both") if entire_balance: amount = self.token_balance if not self.token_balance >= amount: raise self.MinerError( f"Insufficient token balance ({self.token_agent}) for new stake initialization of {amount}" ) # Ensure the new stake will not exceed the staking limit if (self.current_stake + amount) > self.economics.maximum_allowed_locked: raise Stake.StakingError( f"Cannot divide stake - Maximum stake value exceeded with a target value of {amount}." ) # # Stake # # Write to blockchain new_stake = Stake.initialize_stake(miner=self, amount=amount, lock_periods=lock_periods) self.__read_stakes() # Update local staking cache return new_stake
def generate_policy_parameters( self, number_of_ursulas: int = None, duration_periods: int = None, expiration: maya.MayaDT = None, value: int = None, rate: int = None, first_period_reward: int = None, ) -> dict: """ Construct policy creation from parameters or overrides. """ if not duration_periods and not expiration: raise ValueError( "Policy end time must be specified as 'expiration' or 'duration_periods', got neither." ) # Merge injected and default params. first_period_reward = first_period_reward or self.first_period_reward rate = rate or self.rate duration_periods = duration_periods or self.duration_periods # Calculate duration in periods and expiration datetime if expiration: duration_periods = calculate_period_duration( future_time=expiration, seconds_per_period=self.economics.seconds_per_period) else: duration_periods = duration_periods or self.duration_periods expiration = datetime_at_period( self.staking_agent.get_current_period() + duration_periods, seconds_per_period=self.economics.seconds_per_period) from nucypher.policy.policies import BlockchainPolicy blockchain_payload = BlockchainPolicy.generate_policy_parameters( n=number_of_ursulas, duration_periods=duration_periods, first_period_reward=first_period_reward, value=value, rate=rate) # These values may have been recalculated in this block. policy_end_time = dict(duration_periods=duration_periods, expiration=expiration) payload = {**blockchain_payload, **policy_end_time} return payload
def initialize_stake(self, amount: int, lock_periods: int = None, expiration: maya.MayaDT = None, entire_balance: bool = False) -> dict: """ High level staking method for Miners. :param amount: Amount of tokens to stake denominated in the smallest unit. :param lock_periods: Duration of stake in periods. :param expiration: A MayaDT object representing the time the stake expires; used to calculate lock_periods. :param entire_balance: If True, stake the entire balance of this node, or the maximum possible. """ if lock_periods and expiration: raise ValueError( "Pass the number of lock periods or an expiration MayaDT; not both." ) if entire_balance and amount: raise self.MinerError( "Specify an amount or entire balance, not both") if expiration: lock_periods = calculate_period_duration(future_time=expiration) if entire_balance is True: amount = self.token_balance staking_transactions = OrderedDict( ) # type: OrderedDict # Time series of txhases # Validate assert self.__validate_stake(amount=amount, lock_periods=lock_periods) # Transact approve_txhash, initial_deposit_txhash = self.deposit( amount=amount, lock_periods=lock_periods) self._transaction_cache.append( (datetime.utcnow(), initial_deposit_txhash)) self.log.info( "{} Initialized new stake: {} tokens for {} periods".format( self.checksum_public_address, amount, lock_periods)) return staking_transactions
def initialize_stake(self, amount: NU, lock_periods: int = None, expiration: maya.MayaDT = None, entire_balance: bool = False) -> Stake: """Create a new stake.""" # Duration if lock_periods and expiration: raise ValueError( "Pass the number of lock periods or an expiration MayaDT; not both." ) if expiration: lock_periods = calculate_period_duration( future_time=expiration, seconds_per_period=self.economics.seconds_per_period) # Value if entire_balance and amount: raise ValueError("Specify an amount or entire balance, not both") if entire_balance: amount = self.token_balance if not self.token_balance >= amount: raise self.InsufficientTokens( f"Insufficient token balance ({self.token_agent}) " f"for new stake initialization of {amount}") # Ensure the new stake will not exceed the staking limit if (self.current_stake + amount) > self.economics.maximum_allowed_locked: raise Stake.StakingError( f"Cannot initialize stake - " f"Maximum stake value exceeded for {self.checksum_address} " f"with a target value of {amount}.") # Write to blockchain new_stake = Stake.initialize_stake(staker=self, amount=amount, lock_periods=lock_periods) # Update staking cache element self.stakes.refresh() return new_stake
def __init__(self, alice: PolicyAuthor, value: int, expiration: maya.MayaDT, handpicked_ursulas: set = None, initial_reward: int = 0, # TODO: move somewhere else? *args, **kwargs): self.initial_reward = initial_reward self.lock_periods = int(calculate_period_duration(expiration)) self.handpicked_ursulas = handpicked_ursulas or UNKNOWN_ARRANGEMENTS self.expiration = expiration self.value = value self.author = alice # Initial State self.publish_transaction = None self.is_published = False super().__init__(alice=alice, *args, **kwargs)