def get_rewards_for_cycle(self, cycle, expected_reward=False, verbose=False): ############# root = { "delegate_staking_balance": 0, "total_reward_amount": 0, "delegators_balance": {} } uri = self.api['API_URL'] + rewards_split_call.format( self.baking_address, cycle) if verbose: logger.debug("Requesting {}".format(uri)) resp = requests.get(uri, timeout=5) if verbose: logger.debug("Response from tzstats is {}".format(resp)) if resp.status_code != 200: # This means something went wrong. raise ApiProviderException('GET {} {}'.format( uri, resp.status_code)) resp = resp.json()[0] if expected_reward: root["total_reward_amount"] = int( 1e6 * float(resp[idx_income_expected_income])) else: root["total_reward_amount"] = int( 1e6 * (float(resp[idx_income_baking_income]) + float(resp[idx_income_endorsing_income]) + float(resp[idx_income_seed_income]) + float(resp[idx_income_fees_income]) - float(resp[idx_income_lost_accusation_fees]) - float(resp[idx_income_lost_accusation_rewards]) - float(resp[idx_income_lost_revelation_fees]) - float(resp[idx_income_lost_revelation_rewards]))) uri = self.api['API_URL'] + delegators_call.format( cycle - self.preserved_cycles - 2, self.baking_address) if verbose: logger.debug("Requesting {}".format(uri)) resp = requests.get(uri, timeout=5) if verbose: logger.debug("Response from tzstats is {}".format(resp)) if resp.status_code != 200: # This means something went wrong. raise ApiProviderException('GET {} {}'.format( uri, resp.status_code)) resp = resp.json() for delegator in resp: if delegator[idx_delegator_address] == self.baking_address: root["delegate_staking_balance"] = int( 1e6 * (float(delegator[idx_baker_balance]) + float(delegator[idx_baker_delegated]))) else: root["delegators_balance"][ delegator[idx_delegator_address]] = int( 1e6 * float(delegator[idx_delegator_balance])) return root
def __get_delegators_and_delgators_balances(self, cycle, current_level): # calculate the hash of the block for the chosen snapshot of the rewards cycle roll_snapshot, level_snapshot_block = self.__get_roll_snapshot_block_level( cycle, current_level) if level_snapshot_block == "": raise ApiProviderException( "[get_d_d_b] level_snapshot_block is empty. Unable to proceed." ) if roll_snapshot < 0 or roll_snapshot > 15: raise ApiProviderException( "[get_d_d_b] roll_snapshot is outside allowable range: {} Unable to proceed." .format(roll_snapshot)) # construct RPC for getting list of delegates and staking balance get_delegates_request = COMM_DELEGATES.format(self.node_url, level_snapshot_block, self.baking_address) delegate_staking_balance = 0 d_a_len = 0 delegators = {} try: # get RPC response for delegates and staking balance response = self.do_rpc_request(get_delegates_request) delegate_staking_balance = int(response["staking_balance"]) # If roll_snapshot == 15, we need to adjust the baker's staking balance # by subtracting unfrozen rewards due to when the snapshot is taken # within the block context. For more information, see: # https://medium.com/@_MisterWalker_/we-all-were-wrong-baking-bad-and-most-bakers-were-using-wrong-data-to-calculate-staking-rewards-a8c26f5ec62b if roll_snapshot == 15: if cycle >= FIRST_CYCLE_REWARDS_GRANADA: # Since cycle 394, we use an offset of 1589248 blocks (388 cycles pre-Granada of 4096 blocks each) # Cycles start at 0. old_rewards_cycle = (CYCLES_BEFORE_GRANADA + ( (level_snapshot_block - BLOCKS_BEFORE_GRANADA) / self.blocks_per_cycle) - self.preserved_cycles - 1) else: old_rewards_cycle = ((level_snapshot_block / BLOCKS_PER_CYCLE_BEFORE_GRANADA) - self.preserved_cycles - 1) _, unfrozen_rewards = self.__get_unfrozen_rewards( level_snapshot_block, old_rewards_cycle) delegate_staking_balance -= unfrozen_rewards # Remove baker's address from list of delegators delegators_addresses = list( filter(lambda x: x != self.baking_address, response["delegated_contracts"])) d_a_len = len(delegators_addresses) if d_a_len == 0: raise ApiProviderException("[get_d_d_b] No delegators found") # Loop over delegators; get snapshot balance, and current balance for idx, delegator in enumerate(delegators_addresses): # create new dictionary for each delegator d_info = {"staking_balance": 0, "current_balance": 0} get_staking_balance_request = COMM_DELEGATE_BALANCE.format( self.node_url, level_snapshot_block, delegator) d_info["staking_balance"] = int( self.__get_response(delegator, get_staking_balance_request)) sleep( 0.5 ) # Be nice to public RPC since we are now making 2x the amount of RPC calls d_info[ "current_balance"] = self.__get_current_balance_of_delegator( delegator) logger.debug( "Delegator info ({}/{}) fetched: address {}, staked balance {}, current balance {} " .format( idx + 1, d_a_len, delegator, d_info["staking_balance"], d_info["current_balance"], )) if idx % 10 == 0: logger.info("Delegator info ({}/{}) fetched.".format( idx + 1, d_a_len)) # "append" to master dict delegators[delegator] = d_info except ApiProviderException as r: logger.error("[get_d_d_b] RPC API Error: {}".format(str(r))) raise r from r except Exception as e: logger.error("[get_d_d_b] Unexpected error: {}".format(str(e)), exc_info=True) raise e from e # Sanity check. We should have fetched info for all delegates. If we didn't, something went wrong d_len = len(delegators) if d_a_len != d_len: raise ApiProviderException( "[get_d_d_b] Did not collect info for all delegators, {}/{}". format(d_a_len, d_len)) return delegate_staking_balance, delegators
def __get_delegators_and_delgators_balances(self, cycle, current_level): # calculate the hash of the block for the chosen snapshot of the rewards cycle roll_snapshot, level_snapshot_block = self.__get_roll_snapshot_block_level(cycle, current_level) if level_snapshot_block == "": raise ApiProviderException("[get_d_d_b] level_snapshot_block is empty. Unable to proceed.") if roll_snapshot < 0 or roll_snapshot > 15: raise ApiProviderException("[get_d_d_b] roll_snapshot is outside allowable range: {} Unable to proceed.".format(roll_snapshot)) # construct RPC for getting list of delegates and staking balance get_delegates_request = COMM_DELEGATES.format(self.node_url, level_snapshot_block, self.baking_address) delegate_staking_balance = 0 d_a_len = 0 delegators = {} try: # get RPC response for delegates and staking balance response = self.do_rpc_request(get_delegates_request) delegate_staking_balance = int(response["staking_balance"]) # If roll_snapshot == 15, we need to adjust the baker's staking balance # by subtracting unfrozen rewards due to when the snapshot is taken # within the block context if roll_snapshot == 15: old_rewards_cycle = (level_snapshot_block / self.blocks_per_cycle) - self.preserved_cycles - 1 _, unfrozen_rewards = self.__get_unfrozen_rewards(level_snapshot_block, old_rewards_cycle) delegate_staking_balance -= unfrozen_rewards # Remove baker's address from list of delegators delegators_addresses = list(filter(lambda x: x != self.baking_address, response["delegated_contracts"])) d_a_len = len(delegators_addresses) if d_a_len == 0: raise ApiProviderException("[get_d_d_b] No delegators found") # Loop over delegators; get snapshot balance, and current balance for idx, delegator in enumerate(delegators_addresses): # create new dictionary for each delegator d_info = {"staking_balance": 0, "current_balance": 0} get_staking_balance_request = COMM_DELEGATE_BALANCE.format(self.node_url, level_snapshot_block, delegator) staking_balance_response = None while not staking_balance_response: try: staking_balance_response = self.do_rpc_request(get_staking_balance_request, time_out=5) except Exception as e: logger.debug("[get_d_d_b] Fetching delegator {:s} staking balance failed: {:s}, will retry".format(delegator, str(e))) sleep(1.0) # Sleep between failure d_info["staking_balance"] = int(staking_balance_response) sleep(0.5) # Be nice to public RPC since we are now making 2x the amount of RPC calls d_info["current_balance"] = self.__get_current_balance_of_delegator(delegator) logger.debug("Delegator info ({}/{}) fetched: address {}, staked balance {}, current balance {} " .format(idx + 1, d_a_len, delegator, d_info["staking_balance"], d_info["current_balance"])) # "append" to master dict delegators[delegator] = d_info except ApiProviderException as r: logger.error("[get_d_d_b] RPC API Error: {}".format(str(r))) raise r from r except Exception as e: logger.error("[get_d_d_b] Unexpected error: {}".format(str(e)), exc_info=True) raise e from e # Sanity check. We should have fetched info for all delegates. If we didn't, something went wrong d_len = len(delegators) if d_a_len != d_len: raise ApiProviderException("[get_d_d_b] Did not collect info for all delegators, {}/{}".format(d_a_len, d_len)) return delegate_staking_balance, delegators
def get_rewards_for_cycle(self, cycle, expected_reward=False): root = {"delegate_staking_balance": 0, "total_reward_amount": 0, "delegators_balances": {}} # # Get rewards breakdown for cycle # uri = self.api['API_URL'] + rewards_split_call.format(self.baking_address, cycle) sleep(0.5) # be nice to tzstats verbose_logger.debug("Requesting rewards breakdown, {}".format(uri)) resp = requests.get(uri, timeout=5) verbose_logger.debug("Response from tzstats is {}".format(resp.content.decode("utf8"))) if resp.status_code != 200: # This means something went wrong. raise ApiProviderException('GET {} {}'.format(uri, resp.status_code)) resp = resp.json()[0] if expected_reward: root["total_reward_amount"] = int(1e6 * float(resp[idx_income_expected_income])) else: root["total_reward_amount"] = int(1e6 * (float(resp[idx_income_baking_income]) + float(resp[idx_income_endorsing_income]) + float(resp[idx_income_seed_income]) + float(resp[idx_income_fees_income]) - float(resp[idx_income_lost_accusation_fees]) - float(resp[idx_income_lost_accusation_rewards]) - float(resp[idx_income_lost_revelation_fees]) - float(resp[idx_income_lost_revelation_rewards]))) # # Get staking balances of delegators at snapshot block # uri = self.api['API_URL'] + delegators_call.format(cycle - self.preserved_cycles - 2, self.baking_address) sleep(0.5) # be nice to tzstats verbose_logger.debug("Requesting staking balances of delegators, {}".format(uri)) resp = requests.get(uri, timeout=5) verbose_logger.debug("Response from tzstats is {}".format(resp.content.decode("utf8"))) if resp.status_code != 200: # This means something went wrong. raise ApiProviderException('GET {} {}'.format(uri, resp.status_code)) resp = resp.json() for delegator in resp: if delegator[idx_delegator_address] == self.baking_address: root["delegate_staking_balance"] = int( 1e6 * (float(delegator[idx_balance]) + float(delegator[idx_baker_delegated]))) else: delegator_info = {"staking_balance": 0, "current_balance": 0} delegator_info["staking_balance"] = int(1e6 * float(delegator[idx_balance])) root["delegators_balances"][delegator[idx_delegator_address]] = delegator_info # # Get current balance of delegates # # This is done in 2 phases. 1) make a single API call to tzstats, retrieving an array # of arrays with current balance of each delegator who "currently" delegates to delegate. There may # be a case where the delegator has changed delegations and would therefor not be in this array. # Thus, 2) determines which delegators are not in the first result, and makes individual # calls to get their balance. This approach should reduce the overall number of API calls made to tzstats. # # Phase 1 # uri = self.api['API_URL'] + batch_current_balance_call.format(self.baking_address) sleep(0.5) # be nice to tzstats verbose_logger.debug("Requesting current balance of delegators, phase 1, {}".format(uri)) resp = requests.get(uri, timeout=5) verbose_logger.debug("Response from tzstats is {}".format(resp.content.decode("utf8"))) if resp.status_code != 200: # This means something went wrong. raise ApiProviderException('GET {} {}'.format(uri, resp.status_code)) resp = resp.json() # Will use these two lists to determine who has/has not been fetched staked_bal_delegators = root["delegators_balances"].keys() curr_bal_delegators = [] for delegator in resp: delegator_addr = delegator[idx_cb_delegator_address] # If delegator is in this batch, but has no staking balance for this reward cycle, # then they must be a new delegator and are not receiving rewards at this time. # We can ignore them. if delegator_addr not in staked_bal_delegators: continue root["delegators_balances"][delegator_addr]["current_balance"] = int( 1e6 * float(delegator[idx_cb_current_balance])) curr_bal_delegators.append(delegator_addr) # Phase 2 # # Who was not in this result? need_curr_balance_fetch = list(set(staked_bal_delegators) - set(curr_bal_delegators)) # Fetch individual not in original batch if len(need_curr_balance_fetch) > 0: split_addresses = split(need_curr_balance_fetch, 50) for list_address in split_addresses: list_curr_balances = self.__fetch_current_balance(list_address) for d in list_address: root["delegators_balances"][d]["current_balance"] = list_curr_balances[d] curr_bal_delegators.append(d) # All done fetching balances. # Sanity check. n_curr_balance = len(curr_bal_delegators) n_stake_balance = len(staked_bal_delegators) if n_curr_balance != n_stake_balance: raise ApiProviderException('Did not fetch all balances {}/{}'.format(n_curr_balance, n_stake_balance)) return root