def test_estimate_block_number_for_period(): timestamp = maya.now().epoch period = timestamp // SECONDS_PER_PERIOD three_periods_back = period - 3 ten_periods_back = period - 10 latest_block_number = BlockNumber(12345678) now = maya.now() now_epoch = now.epoch # ensure the same time is used in method and in test with patch.object(maya, 'now', return_value=maya.MayaDT(epoch=now_epoch)): block_number_for_three_periods_back = estimate_block_number_for_period(period=three_periods_back, seconds_per_period=SECONDS_PER_PERIOD, latest_block=latest_block_number) block_number_for_ten_periods_back = estimate_block_number_for_period(period=ten_periods_back, seconds_per_period=SECONDS_PER_PERIOD, latest_block=latest_block_number) for past_period, block_number_for_past_period in ((three_periods_back, block_number_for_three_periods_back), (ten_periods_back, block_number_for_ten_periods_back)): start_of_past_period = maya.MayaDT(epoch=(past_period * SECONDS_PER_PERIOD)) diff_in_seconds = int((now - start_of_past_period).total_seconds()) diff_in_blocks = diff_in_seconds // AVERAGE_BLOCK_TIME_IN_SECONDS assert block_number_for_past_period < latest_block_number assert block_number_for_past_period == (latest_block_number - diff_in_blocks)
def initialize(self, metrics_prefix: str, registry: CollectorRegistry) -> None: super().initialize(metrics_prefix=metrics_prefix, registry=registry) missing_commitments = self.contract_agent.get_missing_commitments( checksum_address=self.staker_address) if missing_commitments == 0: # has either already committed to this period or the next period # use local event filter for initial data last_committed_period = self.contract_agent.get_last_committed_period( staker_address=self.staker_address) arg_filters = { 'staker': self.staker_address, 'period': last_committed_period } latest_block = self.contract_agent.blockchain.client.block_number previous_period = self.contract_agent.get_current_period( ) - 1 # just in case # we estimate the block number for the previous period to start search from since either # - commitment made during previous period for current period, OR # - commitment made during current period for next period block_number_for_previous_period = estimate_block_number_for_period( period=previous_period, seconds_per_period=self.contract_agent.staking_parameters()[0], latest_block=latest_block) events_throttler = ContractEventsThrottler( agent=self.contract_agent, event_name=self.event_name, from_block=block_number_for_previous_period, to_block=latest_block, **arg_filters) for event_record in events_throttler: self._event_occurred(event_record.raw_event)
def paint_staking_rewards(stakeholder, blockchain, emitter, past_periods, staking_address, staking_agent): if not past_periods: reward_amount = stakeholder.staker.calculate_staking_reward() emitter.echo(message=TOKEN_REWARD_CURRENT.format( reward_amount=round(reward_amount, TOKEN_DECIMAL_PLACE))) return economics = stakeholder.staker.economics seconds_per_period = economics.seconds_per_period current_period = staking_agent.get_current_period() from_period = current_period - past_periods latest_block = blockchain.client.block_number from_block = estimate_block_number_for_period( period=from_period, seconds_per_period=seconds_per_period, latest_block=latest_block) argument_filters = {'staker': staking_address} event_type = staking_agent.contract.events['Minted'] entries = event_type.getLogs(fromBlock=from_block, toBlock='latest', argument_filters=argument_filters) rows = [] rewards_total = NU(0, 'NU') for event_record in entries: event_block_number = int(event_record['blockNumber']) event_period = event_record['args']['period'] event_reward = NU(event_record['args']['value'], 'NuNit') timestamp = blockchain.client.get_block(event_block_number).timestamp event_date = maya.MayaDT( epoch=timestamp).local_datetime().strftime("%b %d %Y") rows.append([ event_date, event_block_number, int(event_period), round(event_reward, TOKEN_DECIMAL_PLACE), ]) rewards_total += event_reward if not rows: emitter.echo(TOKEN_REWARD_NOT_FOUND) return periods_as_days = economics.days_per_period * past_periods emitter.echo(message=TOKEN_REWARD_PAST_HEADER.format(periods=past_periods, days=periods_as_days)) emitter.echo( tabulate.tabulate(rows, headers=REWARDS_TABLE_COLUMNS, tablefmt="fancy_grid")) emitter.echo(message=TOKEN_REWARD_PAST.format( reward_amount=round(rewards_total, TOKEN_DECIMAL_PLACE)))
def events(general_config, registry_options, contract_name, from_block, to_block, event_name): """Show events associated to NuCypher contracts.""" emitter, registry, blockchain = registry_options.setup( general_config=general_config) if not contract_name: if event_name: raise click.BadOptionUsage( option_name='--event-name', message='--event-name requires --contract-name') contract_names = [ STAKING_ESCROW_CONTRACT_NAME, POLICY_MANAGER_CONTRACT_NAME ] else: contract_names = [contract_name] if from_block is None: # by default, this command only shows events of the current period last_block = blockchain.client.block_number staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=registry) current_period = staking_agent.get_current_period() from_block = estimate_block_number_for_period( period=current_period, seconds_per_period=staking_agent.staking_parameters()[0], latest_block=last_block) if to_block is None: to_block = 'latest' # TODO: additional input validation for block numbers emitter.echo(f"Showing events from block {from_block} to {to_block}") for contract_name in contract_names: title = f" {contract_name} Events ".center(40, "-") emitter.echo(f"\n{title}\n", bold=True, color='green') agent = ContractAgency.get_agent_by_contract_name( contract_name, registry) names = agent.events.names if not event_name else [event_name] for name in names: emitter.echo(f"{name}:", bold=True, color='yellow') event_method = agent.events[name] for event_record in event_method(from_block=from_block, to_block=to_block): emitter.echo(f" - {event_record}")
def events(general_config, registry_options, contract_name, from_block, to_block, event_name, csv, csv_file, event_filters): """Show events associated with NuCypher contracts.""" if csv or csv_file: if csv and csv_file: raise click.BadOptionUsage( option_name='--event-filter', message=f'Pass either --csv or --csv-file, not both.') # ensure that event name is specified - different events would have different columns in the csv file if csv_file and not all((event_name, contract_name)): # TODO consider a single csv that just gets appended to for each event # - each appended event adds their column names first # - single report-type functionality, see #2561 raise click.BadOptionUsage( option_name='--csv-file, --event-name, --contract_name', message= '--event-name and --contract-name must be specified when outputting to ' 'specific file using --csv-file; alternatively use --csv') if not contract_name: if event_name: raise click.BadOptionUsage( option_name='--event-name', message='--event-name requires --contract-name') # FIXME should we force a contract name to be specified? contract_names = [ STAKING_ESCROW_CONTRACT_NAME, POLICY_MANAGER_CONTRACT_NAME ] else: contract_names = [contract_name] emitter, registry, blockchain = registry_options.setup( general_config=general_config) if from_block is None: # by default, this command only shows events of the current period last_block = blockchain.client.block_number staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=registry) current_period = staking_agent.get_current_period() from_block = estimate_block_number_for_period( period=current_period, seconds_per_period=staking_agent.staking_parameters()[1], latest_block=last_block) if to_block is None: to_block = 'latest' else: # validate block range if from_block > to_block: raise click.BadOptionUsage( option_name='--to-block, --from-block', message=f'Invalid block range provided, ' f'from-block ({from_block}) > to-block ({to_block})') # event argument filters argument_filters = None if event_filters: try: argument_filters = parse_event_filters_into_argument_filters( event_filters) except ValueError as e: raise click.BadOptionUsage( option_name='--event-filter', message=f'Event filter must be specified as name-value pairs of ' f'the form `<name>=<value>` - {str(e)}') emitter.echo(f"Retrieving events from block {from_block} to {to_block}") for contract_name in contract_names: agent = ContractAgency.get_agent_by_contract_name( contract_name, registry) if event_name and event_name not in agent.events.names: raise click.BadOptionUsage( option_name='--event-name, --contract_name', message= f'{contract_name} contract does not have an event named {event_name}' ) title = f" {agent.contract_name} Events ".center(40, "-") emitter.echo(f"\n{title}\n", bold=True, color='green') names = agent.events.names if not event_name else [event_name] for name in names: # csv output file - one per (contract_name, event_name) pair csv_output_file = csv_file if csv or csv_output_file: if not csv_output_file: csv_output_file = generate_events_csv_filepath( contract_name=agent.contract_name, event_name=name) retrieve_events( emitter=emitter, agent=agent, event_name=name, # None is fine - just means all events from_block=from_block, to_block=to_block, argument_filters=argument_filters, csv_output_file=csv_output_file)