示例#1
0
class ValidatorService:
    def __init__(self, user_id: int) -> None:
        self.user_svc = UserService()
        self.user_id = user_id
        cur_validators = self.user_svc.get_all_validators(user_id)
        self.indices = [validator.indice for validator in cur_validators]

    def create(self, indices: List[str]) -> List[ValidatorDT]:
        resp = requests.get(f'{config.BEACON_RPC_URI}/validators',
                            params={'indices': indices})
        resp = resp.json()
        index_to_pubkey = {}
        for val in resp['validatorList']:
            index_to_pubkey[val['index']] = b64_to_hex(
                val['validator']['publicKey'])
        validators_to_add = [
            Validator(indice=i,
                      pubkey=index_to_pubkey[i],
                      user_id=self.user_id) for i in indices
        ]
        with SessionManager.session() as session:
            session.add_all(validators_to_add)

        return [ValidatorDT.from_model(m) for m in validators_to_add]

    def remove(self, indice: str) -> None:
        with SessionManager.session() as session:
            session.query(Validator).filter_by(
                indice=indice,
                user_id=self.user_id).delete(synchronize_session=False)

    def get(self) -> List[ValidatorDT]:
        with SessionManager.session() as session:
            rows = session.query(Validator).filter_by(
                user_id=self.user_id).all()

        return [ValidatorDT.from_model(m) for m in rows]

    def info(self) -> Dict:
        """Retrieve a validator total balance, balance over time,
        avg rating and rating per index.
        """
        activation_epoch = self._get_activation_epoch()
        balances = self._get_balance(activation_epoch)
        validator_perf = self._get_validator_performance(activation_epoch)
        balance_overtime = self._get_balance_ovetime(activation_epoch,
                                                     balances['total'])

        info_validator = {}
        for i in self.indices:
            info_validator[i] = {
                'balance': balances['validators'][i],
                'rating': validator_perf['validators'][i],
            }

        return {
            'global': {
                'balance': balances['total'],
                'rating': validator_perf['avg'],
                'overtime': balance_overtime,
            },
            'validators': info_validator,
        }

    def _get_balance_ovetime(self, activation_epoch: Dict[str, str],
                             total_balance: str) -> Dict[str, str]:
        num_validators = len(activation_epoch)
        cur_epoch = int(self._get_current_epoch())
        earliest_epoch = cur_epoch
        overtime = {}
        for epoch in activation_epoch.values():
            if int(epoch) < int(earliest_epoch):
                earliest_epoch = int(epoch)

        if cur_epoch == earliest_epoch:
            increment = int(cur_epoch) // 6
            step = 0
            for _ in range(6):
                overtime[step] = 0
                step += increment
            return overtime

        for _ in range(6):
            distance_epoch = cur_epoch - earliest_epoch
            increment = int(distance_epoch) // 6
            balance_increment = (float(total_balance) -
                                 (num_validators * 32.00)) / 6

            step = earliest_epoch
            step_balance = 32 * num_validators
            for _ in range(6):
                overtime[step] = step_balance
                step_balance += balance_increment
                step += increment
            return overtime

    def _get_validator_performance(self, activation_epoch: Dict[str,
                                                                str]) -> Dict:
        """GET eth/v1alpha1/validators/performance
        Retrieve the inclusion distance for each validators assigned to current_user
        """
        cur_epoch = int(self._get_current_epoch())
        filter_active_indices = [
            i for i in self.indices
            if int(activation_epoch[i]) <= int(cur_epoch)
        ]
        if len(filter_active_indices) == 0:
            return {i: '?' for i in self.indices}
        resp = requests.get(f'{config.BEACON_RPC_URI}/validators/performance',
                            params={'indices': self.indices})
        resp = resp.json()
        validator_perf = {i: '?' for i in self.indices}
        sum_distance = 0
        for n, inclusion_dist in enumerate(resp['inclusionDistances']):
            distance_int = int(inclusion_dist)
            validator_perf[filter_active_indices[n]] = distance_int
            sum_distance += distance_int
        avg_perf = int(round(sum_distance // len(filter_active_indices)))
        return {'validators': validator_perf, 'avg': avg_perf}

    def _get_balance(self, activation_epoch: Dict[str, str]) -> Dict:
        """GET eth/v1alpha1/validator/balances
        Retrieve:
        a) the overall balance for all validators assigned to current_user over time
        b) balance for each individual validators
        """
        # last_n_epoch = self._get_last_n_epoch()
        cur_epoch = int(self._get_current_epoch())
        balances = {}
        balance_per_index = {i: '0' for i in self.indices}
        # for epoch in last_n_epoch:
        total_balance_epoch, balance_per_index = self._balance_for_epoch(
            activation_epoch, cur_epoch)
        balances['total'] = total_balance_epoch
        balances['validators'] = balance_per_index
        return balances

    def _balance_for_epoch(self, activation_epoch: Dict[str, str],
                           epoch: str) -> Tuple[str, Dict[str, str]]:
        filter_active_indices = [
            i for i in self.indices if int(activation_epoch[i]) <= int(epoch)
        ]
        if len(filter_active_indices) == 0:
            return '0', {i: '0' for i in self.indices}

        total_balance = 0
        resp = requests.get(
            f'{config.BEACON_RPC_URI}/validators/balances',
            params={
                'indices': filter_active_indices,
                'epoch': epoch
            },
        )
        resp = resp.json()
        balance_per_index = {}
        for json in resp['balances']:
            total_balance += int(json['balance'])
            balance_per_index[json['index']] = gwei_to_ether(json['balance'])
        return gwei_to_ether(total_balance), balance_per_index

    def _get_current_epoch(self) -> str:
        """Calculate the current epoch from genesis"""
        sec_since_genesis = int(time.time()) - config.EPOCH_GENESIS
        current_epoch = int(sec_since_genesis / config.SEC_PER_EPOCH)
        return str(current_epoch)

    def _get_last_n_epoch(self) -> List[str]:
        """Fetch last 7 days epoch"""
        pointer_epoch = int(self._get_current_epoch())
        last_n_epoch = [pointer_epoch]
        for _ in range(6):
            pointer_epoch = max(1, pointer_epoch - config.EPOCH_PER_DAY)
            last_n_epoch = [pointer_epoch] + last_n_epoch
            if pointer_epoch == 0:
                break

        return last_n_epoch

    def _get_activation_epoch(self) -> Dict[str, str]:
        """GET /eth/v1alpha1/validators
        Retrieve activation epoch for each validator, None if not activated yet
        """
        resp = requests.get(f'{config.BEACON_RPC_URI}/validators',
                            params={'indices': self.indices})
        resp = resp.json()
        validator_activation_epoch = {}
        for val in resp['validatorList']:
            validator_activation_epoch[
                val['index']] = val['validator']['activationEpoch']
        return validator_activation_epoch