Beispiel #1
0
 def _query_and_save_deposits(
         self,
         dbeth2: DBEth2,
         indices_or_pubkeys: Union[List[int], List[Eth2PubKey]],
 ) -> List[Eth2Deposit]:
     new_deposits = self.beaconchain.get_validator_deposits(indices_or_pubkeys)
     dbeth2.add_eth2_deposits(new_deposits)
     return new_deposits
Beispiel #2
0
    def _query_services_for_validator_daily_stats(
        self,
        to_ts: Timestamp,
        msg_aggregator: MessagesAggregator,
    ) -> None:
        """Goes through all saved validators and sees which need to have their stats requeried"""
        now = ts_now()
        dbeth2 = DBEth2(self.database)
        result = dbeth2.get_validators_to_query_for_stats(up_to_ts=to_ts)

        for validator_index, last_ts in result:
            should_backoff = (now - self.last_stats_query_ts <
                              VALIDATOR_STATS_QUERY_BACKOFF_TIME_RANGE
                              and self.validator_stats_queried >=
                              VALIDATOR_STATS_QUERY_BACKOFF_EVERY_N_VALIDATORS)
            if should_backoff:
                log.debug(
                    f'Queried {self.validator_stats_queried} validators in the last '
                    f'{VALIDATOR_STATS_QUERY_BACKOFF_TIME_RANGE} seconds. Backing off for '
                    f'{VALIDATOR_STATS_QUERY_BACKOFF_TIME} seconds.', )
                self.validator_stats_queried = 0
                gevent.sleep(VALIDATOR_STATS_QUERY_BACKOFF_TIME)

            new_stats = scrape_validator_daily_stats(
                validator_index=validator_index,
                last_known_timestamp=last_ts,
                msg_aggregator=msg_aggregator,
            )
            self.validator_stats_queried += 1
            self.last_stats_query_ts = now

            if len(new_stats) != 0:
                dbeth2.add_validator_daily_stats(stats=new_stats)
Beispiel #3
0
    def get_validator_daily_stats(
        self,
        validator_index: int,
        msg_aggregator: MessagesAggregator,
        from_timestamp: Optional[Timestamp] = None,
        to_timestamp: Optional[Timestamp] = None,
    ) -> List[ValidatorDailyStats]:
        """Gets the daily stats of an ETH2 validator by index

        First queries the DB for the already known stats and then if needed also scrapes
        the beacocha.in website for more. Saves all new entries to the DB.
        """
        dbeth2 = DBEth2(self.database)
        known_stats = dbeth2.get_validator_daily_stats(
            validator_index=validator_index,
            from_ts=from_timestamp,
            to_ts=to_timestamp,
        )
        last_ts = Timestamp(0) if len(
            known_stats) == 0 else known_stats[-1].timestamp
        limit_ts = to_timestamp if to_timestamp else ts_now()
        if limit_ts - last_ts <= DAY_IN_SECONDS:
            return known_stats  # no need to requery if less than a day passed

        now = ts_now()
        should_backoff = (now - self.last_stats_query_ts <
                          VALIDATOR_STATS_QUERY_BACKOFF_TIME_RANGE
                          and self.validator_stats_queried >=
                          VALIDATOR_STATS_QUERY_BACKOFF_EVERY_N_VALIDATORS)
        if should_backoff:
            log.debug(
                f'Queried {self.validator_stats_queried} validators in the last '
                f'{VALIDATOR_STATS_QUERY_BACKOFF_TIME_RANGE} seconds. Backing off for '
                f'{VALIDATOR_STATS_QUERY_BACKOFF_TIME} seconds.', )
            self.validator_stats_queried = 0
            gevent.sleep(VALIDATOR_STATS_QUERY_BACKOFF_TIME)

        new_stats = scrape_validator_daily_stats(
            validator_index=validator_index,
            last_known_timestamp=last_ts,
            msg_aggregator=msg_aggregator,
        )
        self.validator_stats_queried += 1
        self.last_stats_query_ts = now

        if len(new_stats) != 0:
            dbeth2.add_validator_daily_stats(stats=new_stats)

        return dbeth2.get_validator_daily_stats(
            validator_index=validator_index,
            from_ts=from_timestamp,
            to_ts=to_timestamp,
        )
Beispiel #4
0
    def get_balances(
        self,
        addresses: List[ChecksumEthAddress],
        fetch_validators_for_eth1: bool,
    ) -> Dict[Eth2PubKey, Balance]:
        """
        Returns a mapping of validator public key to eth balance.
        If fetch_validators_for_eth1 is true then each eth1 address is also checked
        for the validators it has deposited and the deposits are fetched.

        May Raise:
        - RemoteError from beaconcha.in api
        """
        usd_price = Inquirer().find_usd_price(A_ETH)
        dbeth2 = DBEth2(self.database)
        balance_mapping: Dict[Eth2PubKey, Balance] = defaultdict(Balance)
        validators: Union[List[ValidatorID], List[Eth2Validator]]
        if fetch_validators_for_eth1:
            validators = self.fetch_eth1_validator_data(addresses)
        else:
            validators = dbeth2.get_validators()

        if validators == []:
            return {}  # nothing detected

        pubkeys = []
        index_to_pubkey = {}
        index_to_ownership = {}
        for validator in validators:
            # create a mapping of indices to pubkeys since the performance call returns indices
            if validator.index is not None:
                index_to_pubkey[validator.index] = validator.public_key
                pubkeys.append(validator.public_key)
            index_to_ownership[
                validator.index] = validator.ownership_proportion

        # Get current balance of all validators. This may miss some balance if it's
        # in the deposit queue but it's too much work to get it right and should be
        # visible as soon as deposit clears the queue
        performance = self.beaconchain.get_performance(pubkeys)
        for validator_index, entry in performance.items():
            pubkey = index_to_pubkey.get(validator_index)
            if pubkey is None:
                log.error(
                    f'At eth2 get_balances could not find matching pubkey for validator index {validator_index}'
                )  # noqa: E501
                continue  # should not happen
            ownership_proportion = index_to_ownership.get(validator_index, ONE)
            amount = from_gwei(entry.balance) * ownership_proportion
            balance_mapping[pubkey] += Balance(amount, amount *
                                               usd_price)  # noqa: E501

        return balance_mapping
Beispiel #5
0
    def get_staking_deposits(
        self,
        addresses: List[ChecksumEthAddress],
    ) -> List[Eth2Deposit]:
        """Get the eth2 deposits for all tracked validators and all validators associated
        with any given eth1 address.

        Also write them all in the DB.
        """
        relevant_pubkeys = set()
        relevant_validators = set()
        now = ts_now()
        for address in addresses:
            range_key = f'{ETH2_DEPOSITS_PREFIX}_{address}'
            query_range = self.database.get_used_query_range(range_key)
            if query_range is not None and now - query_range[
                    1] <= REQUEST_DELTA_TS:
                continue  # recently queried, skip

            result = self.beaconchain.get_eth1_address_validators(address)
            relevant_validators.update(result)
            relevant_pubkeys.update([x.public_key for x in result])
            self.database.update_used_query_range(range_key, Timestamp(0), now)

        dbeth2 = DBEth2(self.database)
        saved_deposits = dbeth2.get_eth2_deposits()
        saved_deposits_pubkeys = {x.pubkey for x in saved_deposits}

        new_validators = []
        pubkeys_query_deposits = set()
        for validator in relevant_validators:
            if validator.public_key not in saved_deposits_pubkeys and validator.index is not None:
                new_validators.append(
                    Eth2Validator(
                        index=validator.index,
                        public_key=validator.public_key,
                        ownership_proportion=ONE,
                    ))
                pubkeys_query_deposits.add(validator.public_key)

        dbeth2.add_validators(new_validators)
        saved_validators = dbeth2.get_validators()
        for saved_validator in saved_validators:
            if saved_validator.public_key not in saved_deposits_pubkeys:
                pubkeys_query_deposits.add(saved_validator.public_key)

        new_deposits = self._query_and_save_deposits(
            dbeth2, list(pubkeys_query_deposits))
        result_deposits = saved_deposits + new_deposits
        result_deposits.sort(
            key=lambda deposit: (deposit.timestamp, deposit.tx_index))
        return result_deposits
Beispiel #6
0
    def fetch_eth1_validator_data(
        self,
        addresses: List[ChecksumEthAddress],
    ) -> List[ValidatorID]:
        """Query all eth1 addresses for their validators and get all corresponding deposits.

        Returns the list of all tracked validators. It's ValidatorID  since
        for validators that are in the deposit queue we don't get a finalized validator index yet.
        So index may be missing for some validators.
        This is the only function that will also return validators in the deposit queue.

        May raise:
        - RemoteError
        """
        dbeth2 = DBEth2(self.database)
        all_validators = []
        pubkeys = set()
        for address in addresses:
            validators = self.beaconchain.get_eth1_address_validators(address)
            if len(validators) == 0:
                continue

            pubkeys.update([x.public_key for x in validators])
            all_validators.extend(validators)
            # if we already have any of those validators in the DB, no need to query deposits
            tracked_validators = dbeth2.get_validators()
            tracked_pubkeys = [x.public_key for x in tracked_validators]
            new_validators = [
                Eth2Validator(index=x.index,
                              public_key=x.public_key,
                              ownership_proportion=ONE) for x in validators
                if x.public_key not in tracked_pubkeys and x.index is not None
            ]
            dbeth2.add_validators(new_validators)
            self.beaconchain.get_validator_deposits(
                [x.public_key for x in new_validators])

        for x in dbeth2.get_validators():
            if x.public_key not in pubkeys:
                all_validators.append(
                    ValidatorID(
                        index=x.index,
                        public_key=x.public_key,
                        ownership_proportion=x.ownership_proportion,
                    ), )

        return all_validators
Beispiel #7
0
def get_validator_daily_stats(
    db: 'DBHandler',
    validator_index: int,
    msg_aggregator: MessagesAggregator,
    from_timestamp: Optional[Timestamp] = None,
    to_timestamp: Optional[Timestamp] = None,
) -> List[ValidatorDailyStats]:
    """Gets the daily stats of an ETH2 validator by index

    First queries the DB for the already known stats and then if needed also scrapes
    the beacocha.in website for more. Saves all new entries to the DB.
    """
    dbeth2 = DBEth2(db)
    known_stats = dbeth2.get_validator_daily_stats(
        validator_index=validator_index,
        from_ts=from_timestamp,
        to_ts=to_timestamp,
    )
    last_ts = Timestamp(0) if len(
        known_stats) == 0 else known_stats[-1].timestamp
    limit_ts = to_timestamp if to_timestamp else ts_now()
    if limit_ts - last_ts <= DAY_IN_SECONDS:
        return known_stats  # no need to requery if less than a day passed

    new_stats = _scrape_validator_daily_stats(
        validator_index=validator_index,
        last_known_timestamp=last_ts,
        msg_aggregator=msg_aggregator,
    )

    if len(new_stats) != 0:
        dbeth2.add_validator_daily_stats(validator_index=validator_index,
                                         stats=new_stats)

    return dbeth2.get_validator_daily_stats(
        validator_index=validator_index,
        from_ts=from_timestamp,
        to_ts=to_timestamp,
    )
Beispiel #8
0
    def get_validator_daily_stats(
            self,
            filter_query: Eth2DailyStatsFilterQuery,
            only_cache: bool,
            msg_aggregator: MessagesAggregator,
    ) -> Tuple[List[ValidatorDailyStats], int, FVal, FVal]:
        """Gets the daily stats eth2 validators depending on the given filter.

        This won't detect new validators

        Will query for new validator daily stats if only_cache is False.

        May raise:
        - RemoteError due to problems with beaconcha.in
        """
        if only_cache is False:
            self._query_services_for_validator_daily_stats(
                to_ts=filter_query.to_ts,
                msg_aggregator=msg_aggregator,
            )

        dbeth2 = DBEth2(self.database)
        return dbeth2.get_validator_daily_stats_and_limit_info(filter_query=filter_query)
Beispiel #9
0
def get_eth2_staking_deposits(
        ethereum: 'EthereumManager',
        addresses: List[ChecksumEthAddress],
        msg_aggregator: MessagesAggregator,
        database: 'DBHandler',
) -> List[Eth2Deposit]:
    """Get the addresses' ETH2 staking deposits

    For any given new address query on-chain from the ETH2 deposit contract
    deployment timestamp until now.

    For any existing address query on-chain from the minimum last used query
    range "end_ts" (among all the existing addresses) until now, as long as the
    difference between both is gte than REQUEST_DELTA_TS.

    Then write in DB all the new deposits and finally return them all.
    """
    new_deposits: List[Eth2Deposit] = []
    new_addresses: List[ChecksumEthAddress] = []
    existing_addresses: List[ChecksumEthAddress] = []
    to_ts = ts_now()
    min_from_ts = to_ts

    # Get addresses' last used query range for ETH2 deposits
    for address in addresses:
        entry_name = f'{ETH2_DEPOSITS_PREFIX}_{address}'
        deposits_range = database.get_used_query_range(name=entry_name)

        if not deposits_range:
            new_addresses.append(address)
        else:
            existing_addresses.append(address)
            min_from_ts = min(min_from_ts, deposits_range[1])

    # Get deposits for new addresses
    if new_addresses:
        deposits_ = _get_eth2_staking_deposits_onchain(
            ethereum=ethereum,
            addresses=new_addresses,
            msg_aggregator=msg_aggregator,
            from_ts=ETH2_DEPLOYED_TS,
            to_ts=to_ts,
        )
        new_deposits.extend(deposits_)

        for address in new_addresses:
            entry_name = f'{ETH2_DEPOSITS_PREFIX}_{address}'
            database.update_used_query_range(
                name=entry_name,
                start_ts=ETH2_DEPLOYED_TS,
                end_ts=to_ts,
            )

    # Get new deposits for existing addresses
    if existing_addresses and min_from_ts + REQUEST_DELTA_TS <= to_ts:
        deposits_ = _get_eth2_staking_deposits_onchain(
            ethereum=ethereum,
            addresses=existing_addresses,
            msg_aggregator=msg_aggregator,
            from_ts=Timestamp(min_from_ts),
            to_ts=to_ts,
        )
        new_deposits.extend(deposits_)

        for address in existing_addresses:
            entry_name = f'{ETH2_DEPOSITS_PREFIX}_{address}'
            database.update_used_query_range(
                name=entry_name,
                start_ts=Timestamp(min_from_ts),
                end_ts=to_ts,
            )

    dbeth2 = DBEth2(database)
    # Insert new deposits in DB
    if new_deposits:
        dbeth2.add_eth2_deposits(new_deposits)

    # Fetch all DB deposits for the given addresses
    deposits: List[Eth2Deposit] = []
    for address in addresses:
        db_deposits = dbeth2.get_eth2_deposits(address=address)
        deposits.extend(db_deposits)

    deposits.sort(key=lambda deposit: (deposit.timestamp, deposit.log_index))
    return deposits
Beispiel #10
0
def test_validator_daily_stats_with_db_interaction(  # pylint: disable=unused-argument  # noqa: E501
    price_historian,
    database,
    function_scope_messages_aggregator,
    eth2,
):
    stats_call_patch = patch(
        'requests.get',
        wraps=requests.get,
    )

    validator_index = 33710
    public_key = '0x9882b4c33c0d5394205b12d62952c50fe03c6c9fe08faa36425f70afb7caac0689dcd981af35d0d03defb8286d50911d'  # noqa: E501
    dbeth2 = DBEth2(database)
    dbeth2.add_validators([
        Eth2Validator(
            index=validator_index,
            public_key=public_key,
            ownership_proportion=ONE,
        ),
    ])
    with stats_call_patch as stats_call:
        filter_query = Eth2DailyStatsFilterQuery.make(
            validators=[validator_index],
            from_ts=1613606300,
            to_ts=1614038500,
        )
        stats, filter_total_found, sum_pnl, sum_usd_value = eth2.get_validator_daily_stats(
            filter_query=filter_query,
            only_cache=False,
            msg_aggregator=function_scope_messages_aggregator,
        )
        assert stats_call.call_count == 1
        assert len(stats) >= 6
        assert filter_total_found >= 6
        expected_stats = [
            ValidatorDailyStats(
                validator_index=validator_index,
                timestamp=1613606400,  # 2021/02/18
                start_usd_price=FVal(1.55),
                end_usd_price=FVal(1.55),
                pnl=FVal('0.00784'),
                start_amount=FVal('32.66'),
                end_amount=FVal('32.67'),
                missed_attestations=1,
            ),
            ValidatorDailyStats(
                validator_index=validator_index,
                timestamp=1613692800,  # 2021/02/19
                start_usd_price=FVal(1.55),
                end_usd_price=FVal(1.55),
                pnl=FVal('0.00683'),
                start_amount=FVal('32.67'),
                end_amount=FVal('32.68'),
                missed_attestations=19,
            ),
            ValidatorDailyStats(
                validator_index=validator_index,
                timestamp=1613779200,  # 2021/02/20
                start_usd_price=FVal(1.55),
                end_usd_price=FVal(1.55),
                pnl=FVal('0.00798'),
                start_amount=FVal('32.68'),
                end_amount=FVal('32.68'),
            ),
            ValidatorDailyStats(
                validator_index=validator_index,
                timestamp=1613865600,  # 2021/02/21
                start_usd_price=FVal(1.55),
                end_usd_price=FVal(1.55),
                pnl=FVal('0.01114'),
                start_amount=FVal('32.68'),
                end_amount=FVal('32.69'),
                missed_attestations=3,
                proposed_blocks=1,
            ),
            ValidatorDailyStats(
                validator_index=validator_index,
                timestamp=1613952000,  # 2021/02/22
                start_usd_price=FVal(1.55),
                end_usd_price=FVal(1.55),
                pnl=FVal('0.00782'),
                start_amount=FVal('32.69'),
                end_amount=FVal('32.7'),
                missed_attestations=1,
            ),
            ValidatorDailyStats(
                validator_index=validator_index,
                timestamp=1614038400,  # 2021/02/23
                start_usd_price=FVal(1.55),
                end_usd_price=FVal(1.55),
                pnl=FVal('0.00772'),
                start_amount=FVal('32.7'),
                end_amount=FVal('32.71'),
                missed_attestations=1,
            )
        ]
        assert stats[:len(expected_stats)] == expected_stats
        assert sum_pnl >= sum(x.pnl for x in expected_stats)
        assert sum_usd_value >= sum(x.pnl *
                                    ((x.start_usd_price + x.end_usd_price) / 2)
                                    for x in expected_stats)  # noqa: E501

        # Make sure that calling it again does not make an external call
        stats, filter_total_found, _, _ = eth2.get_validator_daily_stats(
            filter_query=filter_query,
            only_cache=False,
            msg_aggregator=function_scope_messages_aggregator,
        )
        assert stats_call.call_count == 1
        assert stats[:len(expected_stats)] == expected_stats

        # Check that changing ownership proportion works
        dbeth2.edit_validator(
            validator_index=validator_index,
            ownership_proportion=FVal(0.45),
        )
        stats, filter_total_found, _, _ = eth2.get_validator_daily_stats(
            filter_query=filter_query,
            only_cache=False,
            msg_aggregator=function_scope_messages_aggregator,
        )
        last_stat = stats[:len(expected_stats)][-1]
        assert last_stat.pnl_balance.amount == expected_stats[
            -1].pnl_balance.amount * FVal(0.45)
Beispiel #11
0
def test_get_validators_to_query_for_stats(database):
    db = DBEth2(database)
    now = ts_now()
    assert db.get_validators_to_query_for_stats(now) == []
    db.add_validators([
        Eth2Validator(index=1, public_key='0xfoo1', ownership_proportion=ONE)
    ])
    assert db.get_validators_to_query_for_stats(now) == [(1, 0)]

    db.add_validator_daily_stats([
        ValidatorDailyStats(
            validator_index=1,
            timestamp=1607126400,
            start_usd_price=FVal(1.55),
            end_usd_price=FVal(1.55),
            pnl=ZERO,
            start_amount=ZERO,
            end_amount=FVal(32),
            deposits_number=1,
            amount_deposited=FVal(32),
        ),
        ValidatorDailyStats(
            validator_index=1,
            timestamp=1607212800,
            start_usd_price=FVal(1.55),
            end_usd_price=FVal(1.55),
            pnl=ZERO,
            start_amount=FVal(32),
            end_amount=FVal(32),
        )
    ])
    assert db.get_validators_to_query_for_stats(now) == [(1, 1607212800)]

    # now add a daily stats entry closer than a day in the past and see we don't query anything
    db.add_validator_daily_stats([
        ValidatorDailyStats(
            validator_index=1,
            timestamp=now - 3600,
            start_usd_price=FVal(1.55),
            end_usd_price=FVal(1.55),
            pnl=ZERO,
            start_amount=ZERO,
            end_amount=FVal(32),
            deposits_number=1,
            amount_deposited=FVal(32),
        )
    ])
    assert db.get_validators_to_query_for_stats(now) == []

    # Now add multiple validators and daily stats and assert on result
    db.add_validators([
        Eth2Validator(index=2, public_key='0xfoo2', ownership_proportion=ONE),
        Eth2Validator(index=3, public_key='0xfoo3', ownership_proportion=ONE),
        Eth2Validator(index=4, public_key='0xfoo4', ownership_proportion=ONE),
    ])
    db.add_validator_daily_stats([
        ValidatorDailyStats(
            validator_index=3,
            timestamp=1607126400,
            start_usd_price=FVal(1.55),
            end_usd_price=FVal(1.55),
            pnl=ZERO,
            start_amount=ZERO,
            end_amount=FVal(32),
            deposits_number=1,
            amount_deposited=FVal(32),
        ),
        ValidatorDailyStats(
            validator_index=3,
            timestamp=1617512800,
            start_usd_price=FVal(1.55),
            end_usd_price=FVal(1.55),
            pnl=ZERO,
            start_amount=FVal(32),
            end_amount=FVal(32),
        ),
        ValidatorDailyStats(
            validator_index=4,
            timestamp=1617512800,
            start_usd_price=FVal(1.55),
            end_usd_price=FVal(1.55),
            pnl=ZERO,
            start_amount=FVal(32),
            end_amount=FVal(32),
        ),
        ValidatorDailyStats(
            validator_index=4,
            timestamp=now - 7200,
            start_usd_price=FVal(1.55),
            end_usd_price=FVal(1.55),
            pnl=ZERO,
            start_amount=FVal(32),
            end_amount=FVal(32),
        )
    ])
    assert db.get_validators_to_query_for_stats(now) == [(2, 0),
                                                         (3, 1617512800)]

    assert db.get_validators_to_query_for_stats(1617512800 + 100000) == [
        (2, 0), (3, 1617512800)
    ]
Beispiel #12
0
    def add_validator(
        self,
        validator_index: Optional[int],
        public_key: Optional[Eth2PubKey],
        ownership_proportion: FVal,
    ) -> None:
        """Adds the given validator to the DB. Due to marshmallow here at least
        either validator_index or public key is not None.

        May raise:
        - RemoteError if there is a problem with querying beaconcha.in for more info
        - InputError if the validator is already in the DB
        """
        valid_index: int
        valid_pubkey: Eth2PubKey
        dbeth2 = DBEth2(self.database)
        if self.premium is None:
            tracked_validators = dbeth2.get_validators()
            if len(tracked_validators) >= FREE_VALIDATORS_LIMIT:
                raise PremiumPermissionError(
                    f'Adding validator {validator_index} {public_key} would take you '
                    f'over the free limit of {FREE_VALIDATORS_LIMIT} for tracked validators',
                )

        if validator_index is not None and public_key is not None:
            field = 'validator_index'
            valid_index = validator_index
            valid_pubkey = public_key
            if dbeth2.validator_exists(field=field, arg=valid_index):
                raise InputError(
                    f'Validator {valid_index} already exists in the DB')
        else:  # we are missing one of the 2
            if validator_index is None:
                field = 'public_key'
                arg = public_key
            else:  # we should have valid index
                field = 'validator_index'
                arg = validator_index  # type: ignore

            if dbeth2.validator_exists(field=field, arg=arg):  # type: ignore
                raise InputError(f'Validator {arg} already exists in the DB')

            # at this point we gotta query for one of the two
            result = self.beaconchain._query(
                module='validator',
                endpoint=None,
                encoded_args=arg,  # type: ignore
            )
            if not isinstance(result, dict):
                raise RemoteError(
                    f'Validator data for {arg} could not be found. Likely invalid validator.'
                )

            try:
                valid_index = result['validatorindex']
                valid_pubkey = Eth2PubKey(result['pubkey'])
            except KeyError as e:
                msg = str(e)
                if isinstance(e, KeyError):
                    msg = f'Missing key entry for {msg}.'

                raise RemoteError(
                    f'Failed to query beaconcha.in for validator data due to: {msg}'
                ) from e  # noqa: E501
        # by now we have a valid index and pubkey. Add to DB
        dbeth2.add_validators([
            Eth2Validator(
                index=valid_index,
                public_key=valid_pubkey,
                ownership_proportion=ownership_proportion,
            ),
        ])
Beispiel #13
0
    def get_details(
        self,
        addresses: List[ChecksumEthAddress],
    ) -> List[ValidatorDetails]:
        """Go through the list of eth1 addresses and find all eth2 validators associated
        with them along with their details.



        May raise RemoteError due to beaconcha.in API"""
        indices = []
        index_to_address = {}
        index_to_pubkey = {}
        pubkey_to_index = {}
        result = []
        assert self.beaconchain.db is not None, 'Beaconchain db should be populated'
        address_validators = []

        for address in addresses:
            validators = self.beaconchain.get_eth1_address_validators(address)
            for validator in validators:
                if validator.index is None:
                    # for validators that are so early in the depositing queue that no
                    # validator index is confirmed yet let's return only the most basic info
                    result.append(
                        ValidatorDetails(
                            validator_index=None,
                            public_key=validator.public_key,
                            eth1_depositor=address,
                            performance=DEPOSITING_VALIDATOR_PERFORMANCE,
                        ))
                    continue

                index_to_address[validator.index] = address
                address_validators.append(
                    Eth2Validator(index=validator.index,
                                  public_key=validator.public_key,
                                  ownership_proportion=ONE))  # noqa: E501

        # make sure all validators we deal with are saved in the DB
        dbeth2 = DBEth2(self.database)
        dbeth2.add_validators(address_validators)

        # Also get all manually input validators
        all_validators = dbeth2.get_validators()
        saved_deposits = dbeth2.get_eth2_deposits()
        pubkey_to_deposit = {x.pubkey: x for x in saved_deposits}
        validators_to_query_for_deposits = []
        for v in all_validators:
            index_to_pubkey[v.index] = v.public_key
            pubkey_to_index[v.public_key] = v.index
            indices.append(v.index)
            depositor = index_to_address.get(v.index)
            if depositor is None:
                if v.public_key not in pubkey_to_deposit:
                    validators_to_query_for_deposits.append(v.public_key)

        # Get new deposits if needed, and populate index_to_address
        new_deposits = self._query_and_save_deposits(
            dbeth2, validators_to_query_for_deposits)
        for deposit in saved_deposits + new_deposits:
            index = pubkey_to_index.get(Eth2PubKey(deposit.pubkey))
            if index is None:  # should never happen, unless returned data is off
                log.error(
                    f'At eth2 staking details could not find index for pubkey '
                    f'{deposit.pubkey} at deposit {deposit}.', )
                continue

            index_to_address[index] = deposit.from_address

        # Get current balance of all validator indices
        performance_result = self.beaconchain.get_performance(indices)
        for validator_index, entry in performance_result.items():
            depositor = index_to_address.get(validator_index)
            if depositor is None:  # should never happen, unless returned data is off
                log.error(
                    f'At eth2 staking details could not find depositor for index '
                    f'{validator_index} at index_to_address', )
                continue

            result.append(
                ValidatorDetails(
                    validator_index=validator_index,
                    public_key=index_to_pubkey[validator_index],
                    eth1_depositor=depositor,
                    performance=entry,
                ))

        # Performance call does not return validators that are not active and are still depositing
        depositing_indices = set(index_to_address.keys()) - set(
            performance_result.keys())
        for index in depositing_indices:
            depositor = index_to_address.get(index)
            if depositor is None:  # should never happen, unless returned data is off
                log.error(
                    f'At eth2 staking details could not find depositor for index '
                    f'{index} at index_to_address for depositing indices', )
                continue

            result.append(
                ValidatorDetails(
                    validator_index=index,
                    public_key=index_to_pubkey[index],
                    eth1_depositor=depositor,
                    performance=DEPOSITING_VALIDATOR_PERFORMANCE,
                ))

        return result