def get_eth2_balances( beaconchain: 'BeaconChain', addresses: List[ChecksumEthAddress], ) -> Dict[ChecksumEthAddress, Balance]: """May Raise RemoteError from beaconcha.in api""" address_to_validators = {} index_to_address = {} validator_indices = [] usd_price = Inquirer().find_usd_price(A_ETH) balance_mapping: Dict[ChecksumEthAddress, Balance] = defaultdict(Balance) # Map eth1 addresses to validators for address in addresses: validators = beaconchain.get_eth1_address_validators(address) if len(validators) == 0: continue address_to_validators[address] = validators for validator in validators: validator_indices.append(validator.validator_index) index_to_address[validator.validator_index] = address # Get current balance of all validator indices performance = beaconchain.get_performance(validator_indices) for validator_index, entry in performance.items(): amount = from_gwei(entry.balance) balance_mapping[index_to_address[validator_index]] += Balance(amount, amount * usd_price) # The performance call does not return validators that are not active and are still depositing depositing_indices = set(index_to_address.keys()) - set(performance.keys()) for index in depositing_indices: balance_mapping[index_to_address[index]] += Balance( amount=FVal('32'), usd_value=FVal('32') * usd_price, ) return balance_mapping
def _serialize_gwei_with_price(value: int, eth_usd_price: FVal) -> Dict[str, str]: normalized_value = from_gwei(value) return { 'amount': str(normalized_value), 'usd_value': str(normalized_value * eth_usd_price), }
def _get_eth2_staking_deposits_onchain( ethereum: 'EthereumManager', addresses: List[ChecksumEthAddress], has_premium: bool, msg_aggregator: MessagesAggregator, from_ts: Timestamp, to_ts: Timestamp, ) -> List[Eth2Deposit]: events = ETH2_DEPOSIT.get_logs( ethereum=ethereum, event_name='DepositEvent', argument_filters={}, from_block=ETH2_DEPOSIT.deployed_block, to_block='latest', ) transactions = ethereum.transactions.query( addresses=addresses, from_ts=from_ts, to_ts=to_ts, with_limit=False, recent_first=False, ) deposits: List[Eth2Deposit] = [] for transaction in transactions: if transaction.to_address != ETH2_DEPOSIT.address: continue tx_hash = '0x' + transaction.tx_hash.hex() for event in events: # Now find the corresponding event. If no event is found the transaction # probably failed or was something other than a deposit if event['transactionHash'] == tx_hash: decoded_data = decode_event_data(event['data'], EVENT_ABI) amount = int.from_bytes(decoded_data[2], byteorder='little') usd_price = ZERO if has_premium: # won't show this to non-premium so don't bother usd_price = query_usd_price_zero_if_error( asset=A_ETH, time=transaction.timestamp, location='Eth2 staking query', msg_aggregator=msg_aggregator, ) normalized_amount = from_gwei(FVal(amount)) deposits.append( Eth2Deposit( from_address=transaction.from_address, pubkey='0x' + decoded_data[0].hex(), withdrawal_credentials='0x' + decoded_data[1].hex(), value=Balance(normalized_amount, usd_price * normalized_amount), validator_index=int.from_bytes(decoded_data[4], byteorder='little'), tx_hash=tx_hash, log_index=event['logIndex'], timestamp=Timestamp(transaction.timestamp), )) break return deposits
def get_validator_deposits( self, indices_or_pubkeys: Union[List[int], List[Eth2PubKey]], ) -> List[Eth2Deposit]: """Get the deposits of all the validators given from the list of indices or pubkeys Queries in chunks of 100 due to api limitations May raise: - RemoteError due to problems querying beaconcha.in API """ chunks = _calculate_query_chunks(indices_or_pubkeys) data = [] for chunk in chunks: result = self._query( module='validator', endpoint='deposits', encoded_args=','.join(str(x) for x in chunk), ) if isinstance(result, list): data.extend(result) else: data.append(result) deposits = [] for entry in data: try: amount = from_gwei(FVal(entry['amount'])) timestamp = entry['block_ts'] usd_price = query_usd_price_zero_if_error( asset=A_ETH, time=timestamp, location=f'Eth2 staking deposit at time {timestamp}', msg_aggregator=self.msg_aggregator, ) deposits.append( Eth2Deposit( from_address=deserialize_ethereum_address( entry['from_address']), pubkey=entry['publickey'], withdrawal_credentials=entry['withdrawal_credentials'], value=Balance( amount=amount, usd_value=amount * usd_price, ), tx_hash=hexstring_to_bytes(entry['tx_hash']), tx_index=entry['tx_index'], timestamp=entry['block_ts'], )) except (DeserializationError, KeyError) as e: msg = str(e) if isinstance(e, KeyError): msg = f'Missing key entry for {msg}.' raise RemoteError( f'Beaconchai.in deposits response processing error. {msg}', ) from e return deposits
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
def get_balances( self, addresses: List[ChecksumEthAddress], ) -> Dict[ChecksumEthAddress, Balance]: """May Raise RemoteError from beaconcha.in api""" address_to_validators = {} index_to_address = {} validator_indices = [] usd_price = Inquirer().find_usd_price(A_ETH) balance_mapping: Dict[ChecksumEthAddress, Balance] = defaultdict(Balance) # Map eth1 addresses to validators for address in addresses: validators = self.beaconchain.get_eth1_address_validators(address) if len(validators) == 0: continue address_to_validators[address] = validators for validator in validators: if validator.validator_index is not None: validator_indices.append(validator.validator_index) index_to_address[validator.validator_index] = address else: # Validator is early in depositing, and no index is known yet. # Simply count 32 ETH balance for them balance_mapping[address] += Balance( amount=FVal('32'), usd_value=FVal('32') * usd_price, ) # Get current balance of all validator indices performance = self.beaconchain.get_performance(validator_indices) for validator_index, entry in performance.items(): amount = from_gwei(entry.balance) balance_mapping[index_to_address[validator_index]] += Balance( amount, amount * usd_price) # noqa: E501 # Performance call does not return validators that are not active and are still depositing # So for them let's just count 32 ETH depositing_indices = set(index_to_address.keys()) - set( performance.keys()) for index in depositing_indices: balance_mapping[index_to_address[index]] += Balance( amount=FVal('32'), usd_value=FVal('32') * usd_price, ) return balance_mapping
def _get_eth2_staking_deposits_onchain( self, addresses: List[ChecksumEthAddress], msg_aggregator: MessagesAggregator, from_ts: Timestamp, to_ts: Timestamp, ) -> List[Eth2Deposit]: from_block = max( ETH2_DEPOSIT.deployed_block, self.ethereum.get_blocknumber_by_time(from_ts), ) to_block = self.ethereum.get_blocknumber_by_time(to_ts) events = ETH2_DEPOSIT.get_logs( ethereum=self.ethereum, event_name='DepositEvent', argument_filters={}, from_block=from_block, to_block=to_block, ) transactions = self.ethereum.transactions.query( addresses=addresses, from_ts=from_ts, to_ts=to_ts, with_limit=False, recent_first=False, ) deposits: List[Eth2Deposit] = [] for transaction in transactions: if transaction.to_address != ETH2_DEPOSIT.address: continue tx_hash = '0x' + transaction.tx_hash.hex() for event in events: # Now find the corresponding event. If no event is found the transaction # probably failed or was something other than a deposit if event['transactionHash'] == tx_hash: decoded_data = decode_event_data(event['data'], EVENT_ABI) # all pylint ignores below due to https://github.com/PyCQA/pylint/issues/4114 amount = int.from_bytes(decoded_data[2], byteorder='little') # pylint: disable=unsubscriptable-object # noqa: E501 usd_price = query_usd_price_zero_if_error( asset=A_ETH, time=transaction.timestamp, location='Eth2 staking query', msg_aggregator=msg_aggregator, ) normalized_amount = from_gwei(FVal(amount)) deposits.append( Eth2Deposit( from_address=transaction.from_address, pubkey='0x' + decoded_data[0].hex(), # pylint: disable=unsubscriptable-object # noqa: E501 withdrawal_credentials='0x' + decoded_data[1].hex(), # pylint: disable=unsubscriptable-object # noqa: E501 value=Balance(normalized_amount, usd_price * normalized_amount), deposit_index=int.from_bytes(decoded_data[4], byteorder='little'), # pylint: disable=unsubscriptable-object # noqa: E501 tx_hash=tx_hash, log_index=event['logIndex'], timestamp=Timestamp(transaction.timestamp), )) break return deposits