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)
Beispiel #2
0
    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)
Beispiel #3
0
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)))
Beispiel #4
0
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}")
Beispiel #5
0
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)