From e1c489904f30249e193da5e146590009688da49b Mon Sep 17 00:00:00 2001 From: Nicolas Ochem Date: Thu, 28 Oct 2021 15:01:50 -0700 Subject: [PATCH] Refactoring + accusation payout toggle When someone double bakes or endorses, anyone can submit an on-chain proof of equivocation. The bakers that includes the proof gets rewarded with half of the security deposit of the faulty baker. It is important to note that equivocation has been very rare on Tezos so this PR does not change anything to TRD in the vast majority of cases. Out of the discussions leading to PR #415 being merged, it has emerged that TRD pays out accusation rewards inconsistently: RPC reward model will rely on the on-chain calculation of rewards, which includes accusation rewards. Tzstats does not include accusation rewards. Tzkt used to, but #470 changed the tzkt code to not do it. We agreed to make it configurable. This PR does it. It has turned into a major refactoring: the logic to calculate rewards used to be isolated per provider: tzkt, tzstats, rpc would make their own calculations and return the reward amount via the rewards model. It makes more sense to have this calculation done at payment producer level. To make this possible: I extended the model. While it was previously returning only one value, it now returns several values (rewards from baking, endorsing, fees and revelations; accusation rewards; double baking losses). Various providers populate the same fields with custom logic. We end up with the below core logic executed independly of the provider chosen: ``` if rewards_type.isEstimated(): logger.info("Using estimated rewards for payouts calculations") total_estimated_block_reward = reward_model.num_baking_rights * block_reward total_estimated_endorsement_reward = reward_model.num_endorsing_rights * endorsement_reward total_reward_amount = total_estimated_block_reward + total_estimated_endorsement_reward elif rewards_type.isActual(): logger.info("Using actual rewards for payouts calculations") if self.pay_denunciation_rewards: total_reward_amount = reward_model.total_reward_amount else: # omit denunciation rewards total_reward_amount = reward_model.rewards_and_fees - reward_model.equivocation_losses elif rewards_type.isIdeal(): logger.info("Using ideal rewards for payouts calculations") if self.pay_denunciation_rewards: total_reward_amount = reward_model.total_reward_amount + reward_model.offline_losses else: # omit denunciation rewards and double baking loss total_reward_amount = reward_model.rewards_and_fees + reward_model.offline_losses ``` payout_accusation_rewards is the new configuration option. It defaults to false, but you must set it to true if you use RPC provider otherwise you get an error: RPC can't distinguish between an accusation reward and a baking reward unless you parse every block (which is the job of an indexer). In other terms, RPC provider will populate `total_reward_amount` but not `rewards_and_fees` or `accusation_rewards` - it will leave these fields to "None". Extending the model required several changes to tests as well. Specifically tzkt tests continue to be a pain. In PR #501 we had hardcoded the accusation rewards. Now, with our extended model, we can compare apples to apples again, and I removed these hardcoded values. Configuration ------------- I modified configure.py to set the new flag. All flags are currently mandatory, so I did not change that, but anyone upgrading TRD will have to modify their config, which is not great. What does the team think? Should we default to false when the config flag is absent? Testing ------- I did some manual testing with the following baker: ``` baking_address: tz1WnfXMPaNTBmH7DBPwqCWs9cPDJdkGBTZ8 ``` On cycle 78. This baker was rewarded for denunciating a double baking on this cycle. I observed that the rewards were higher when the new setting was set to "True". I also observed that the rewards value was consistent between tzkt and tzstats. --- docs/configuration.rst | 7 + src/calc/calculate_phase0.py | 4 +- src/calc/phased_payment_calculator.py | 4 +- src/configure.py | 21 ++- src/model/baking_conf.py | 4 + src/model/reward_provider_model.py | 22 ++- src/pay/payment_producer.py | 33 ++++- src/rpc/rpc_reward_api.py | 91 +++++++------ src/tzkt/tzkt_reward_api.py | 126 +++++++----------- src/tzstats/tzstats_api_constants.py | 4 + src/tzstats/tzstats_reward_api.py | 12 +- src/tzstats/tzstats_reward_provider_helper.py | 49 +++---- tests/integration/test_phases.py | 2 +- tests/integration/test_tzkt_reward_api.py | 40 +++--- ...xoe3DZ2nfe8FGDnvVj7oKjnMY6_185_actual.json | 6 + ...y7QRuMREGXGxeipb24RsMMzUNe_135_actual.json | 6 + ...68tacosb7Mwj6LkbPSUS1er1_242_expected.json | 8 +- ...hMrPUymqenKfHo8FspppXKpW7h_233_actual.json | 6 + ...78XJRB3NqabfWeM37GnhZMWQ_235_expected.json | 8 +- ...RHW1mEyVZfGYy6QaxrY6Y7WaG5_207_actual.json | 6 + ...KZ5UeqdH14HN42rxvNPQfc9UZg_220_actual.json | 6 + ...BmH7DBPwqCWs9cPDJdkGBTZ8_233_expected.json | 8 +- ...aNTBmH7DBPwqCWs9cPDJdkGBTZ8_74_actual.json | 6 + ...tWxX29N5VedCSmaZGw9LNVxQ_246_expected.json | 8 +- ...9sz8iFi1VYWPfRYeUvUSFAaDao_201_actual.json | 6 + ...z8iFi1VYWPfRYeUvUSFAaDao_232_expected.json | 8 +- ...7cJuiBRMhwQXVvgDnjsxuWhcEA_135_actual.json | 6 + .../test_gas_estimation_oven_kt1.py | 2 +- tests/unit/test_calculatePhase0.py | 2 +- tests/utils.py | 10 +- 30 files changed, 334 insertions(+), 187 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index d3a7ee75..448d4626 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -148,6 +148,13 @@ Available configuration parameters are: delegator_pays_ra_fee : False +**pay_denunciation_rewards** + True/False - Baker may get rewarded for denunciating another baker's equivocation (double baking or double endorsing). The protocol rewards the baker including the denunciation. When True, these rewards will be distributed. When False, they will remain in the baker's account, allowing the baker to reimburse the party at fault if they desire. Must be set to True when using RPC backend as RPC is not able to itemize rewards. + + Example:: + + pay_denunciation_rewards: True + **rules_map** The rules_map is needed to redirect payments. A pre-defined source (left side) is mindelegation. Pre-defined destinations (right side) are: TOF = to founders balance, TOB = to bakers balance, and TOE = to everyone. Variable sources and destinations are PKHs. New since v8.0 PKH: Dexter enables payouts to Dexter liquidity pools. diff --git a/src/calc/calculate_phase0.py b/src/calc/calculate_phase0.py index b72e1d1c..335a4908 100644 --- a/src/calc/calculate_phase0.py +++ b/src/calc/calculate_phase0.py @@ -21,7 +21,7 @@ def __init__(self, reward_provider_model) -> None: def calculate(self, reward_logs=None, total_reward_amount=None): """ :param reward_logs: Nothing is expected. This value is not used. reward_logs are generated from provider object. - :param total_reward_amount: Nothing is expected. This value is not used. total amount is taken from provider object. + :param total_reward_amount: Nothing is expected. This value is not used. total amount is calculated in calling function. :return: tuple (reward_logs, total reward amount) reward_logs is a list of RewardLog objects. Last item is owners_parent record. @@ -66,4 +66,4 @@ def calculate(self, reward_logs=None, total_reward_amount=None): reward_logs.append(owners_rl) - return reward_logs, self.reward_provider_model.total_reward_amount + return reward_logs diff --git a/src/calc/phased_payment_calculator.py b/src/calc/phased_payment_calculator.py index fb0ec670..1b2c99f5 100644 --- a/src/calc/phased_payment_calculator.py +++ b/src/calc/phased_payment_calculator.py @@ -42,10 +42,10 @@ def __init__(self, founders_map, owners_map, service_fee_calculator, min_delegat # owners reward = owners payment = total reward - delegators reward # founders reward = delegators fee = total reward - delegators reward #### - def calculate(self, reward_provider_model): + def calculate(self, reward_provider_model, total_rwrd_amnt): phase0 = CalculatePhase0(reward_provider_model) - rwrd_logs, total_rwrd_amnt = phase0.calculate() + rwrd_logs = phase0.calculate() logger.info("Total rewards before processing is {:,} mutez.".format(total_rwrd_amnt)) if total_rwrd_amnt == 0: diff --git a/src/configure.py b/src/configure.py index b358da0b..7d78f1eb 100644 --- a/src/configure.py +++ b/src/configure.py @@ -20,7 +20,7 @@ from log_config import main_logger, init from model.baking_conf import BakingConf, BAKING_ADDRESS, PAYMENT_ADDRESS, SERVICE_FEE, FOUNDERS_MAP, OWNERS_MAP, \ MIN_DELEGATION_AMT, RULES_MAP, MIN_DELEGATION_KEY, DELEGATOR_PAYS_XFER_FEE, DELEGATOR_PAYS_RA_FEE, \ - REACTIVATE_ZEROED, SPECIALS_MAP, SUPPORTERS_SET, REWARDS_TYPE + REACTIVATE_ZEROED, SPECIALS_MAP, SUPPORTERS_SET, REWARDS_TYPE, PAY_DENUNCIATION_REWARDS from util.address_validator import AddressValidator from util.fee_validator import FeeValidator @@ -43,6 +43,7 @@ 'reactivatezeroed': "If a destination address has 0 balance, should burn fee be paid to reactivate? 1 for Yes, 0 for No. Type enter for Yes", 'delegatorpaysxfrfee': "Who is going to pay for transfer fees: 0 for delegator, 1 for delegate. Type enter for delegator", 'delegatorpaysrafee': "Who is going to pay for 0 balance reactivation/burn fee: 0 for delegator, 1 for delegate. Type enter for delegator", + 'paydenunciationrewards': "If you denounce another baker for baking or endorsing, you will get rewarded. Distribute denunciation rewards to your delegators? 1 for Yes, 0 for No. Type enter for No", 'supporters': "Add supporter address. Supporters do not pay bakery fee. Type enter to skip", 'specials': "Add any addresses with a special fee in form of 'PKH,fee'. Type enter to skip", 'noplugins': "No plugins are enabled by default. If you wish to use the email, twitter, or telegram plugins, please read the documentation and edit the configuration file manually." @@ -312,6 +313,20 @@ def ondelegatorpaysrafee(input): fsm.go() +def onpaydenunciationrewards(input): + try: + if not input: + input = "1" + if input != "0" and input != "1": + raise Exception("Please enter '0' or '1'") + global parser + parser.set(PAY_DENUNCIATION_REWARDS, input != "1") + except Exception as e: + printe("Invalid input: {}".format(str(e))) + return + fsm.go() + + def onreactivatezeroed(input): try: if not input: @@ -344,6 +359,7 @@ def onprefinal(input): 'reactivatezeroed': onreactivatezeroed, 'delegatorpaysxfrfee': ondelegatorpaysxfrfee, 'delegatorpaysrafee': ondelegatorpaysrafee, + 'paydenunciationrewards': onpaydenunciationrewards, 'supporters': onsupporters, 'specials': onspecials, 'prefinal': onprefinal, @@ -364,7 +380,8 @@ def onprefinal(input): {'name': 'go', 'src': 'redirect', 'dst': 'reactivatezeroed'}, {'name': 'go', 'src': 'reactivatezeroed', 'dst': 'delegatorpaysrafee'}, {'name': 'go', 'src': 'delegatorpaysrafee', 'dst': 'delegatorpaysxfrfee'}, - {'name': 'go', 'src': 'delegatorpaysxfrfee', 'dst': 'specials'}, + {'name': 'go', 'src': 'delegatorpaysxfrfee', 'dst': 'paydenunciationrewards'}, + {'name': 'go', 'src': 'paydenunciationrewards', 'dst': 'specials'}, {'name': 'go', 'src': 'specials', 'dst': 'supporters'}, {'name': 'go', 'src': 'supporters', 'dst': 'final'}], 'callbacks': { diff --git a/src/model/baking_conf.py b/src/model/baking_conf.py index d72dbb61..e613ed1e 100644 --- a/src/model/baking_conf.py +++ b/src/model/baking_conf.py @@ -15,6 +15,7 @@ DELEGATOR_PAYS_RA_FEE = 'delegator_pays_ra_fee' PLUGINS_CONF = 'plugins' REWARDS_TYPE = 'rewards_type' +PAY_DENUNCIATION_REWARDS = 'pay_denunciation_rewards' # extensions FULL_SUPPORTERS_SET = "__full_supporters_set" @@ -103,5 +104,8 @@ def get_plugins_conf(self): def get_rewards_type(self): return self.get_attribute(REWARDS_TYPE) + def get_pay_denunciation_rewards(self): + return self.get_attribute(PAY_DENUNCIATION_REWARDS) + def __repr__(self) -> str: return json.dumps(self.__dict__, cls=CustomJsonEncoder, indent=1) diff --git a/src/model/reward_provider_model.py b/src/model/reward_provider_model.py index d26c27f1..8a5c7d6f 100644 --- a/src/model/reward_provider_model.py +++ b/src/model/reward_provider_model.py @@ -1,6 +1,24 @@ class RewardProviderModel: - def __init__(self, delegate_staking_balance, total_reward_amount, delegator_balance_dict) -> None: + def __init__(self, delegate_staking_balance, num_baking_rights, num_endorsing_rights, + total_reward_amount, rewards_and_fees, equivocation_losses, denunciation_rewards, offline_losses, delegator_balance_dict) -> None: super().__init__() self.delegator_balance_dict = delegator_balance_dict - self.total_reward_amount = total_reward_amount self.delegate_staking_balance = delegate_staking_balance + self.num_baking_rights = num_baking_rights + self.num_endorsing_rights = num_endorsing_rights + # rewards that should have been earned, had the baker been online + self.offline_losses = offline_losses + + # total reward as recorded in-protocol + self.total_reward_amount = total_reward_amount + + # When using indexers, the total amount above can be itemized as follows: + + # * baking rewards, fees, revelation rewards + self.rewards_and_fees = rewards_and_fees + + # * losses from double baking/endorsing + self.equivocation_losses = equivocation_losses + + # * rewards from denunciating other people's double baking/endorsing + self.denunciation_rewards = denunciation_rewards diff --git a/src/pay/payment_producer.py b/src/pay/payment_producer.py index ef7e3e3b..ec851d21 100644 --- a/src/pay/payment_producer.py +++ b/src/pay/payment_producer.py @@ -53,6 +53,7 @@ def __init__(self, name, initial_payment_cycle, network_config, payments_dir, ca self.reward_api.set_dexter_contracts_set(dexter_contracts_set) self.rewards_type = baking_cfg.get_rewards_type() + self.pay_denunciation_rewards = baking_cfg.get_pay_denunciation_rewards() self.fee_calc = service_fee_calc self.initial_payment_cycle = initial_payment_cycle @@ -170,6 +171,12 @@ def run(self): # payments should not pass beyond last released reward cycle if pymnt_cycle <= current_cycle - (self.nw_config['NB_FREEZE_CYCLE'] + 1) - self.release_override: if not self.payments_queue.full(): + if (not self.pay_denunciation_rewards) and self.reward_api.name == 'RPC': + logger.info("Error: pay_denunciation_rewards=False requires an indexer since it is not possible to distinguish reward source using RPC") + e = "You must set 'pay_denunciation_rewards' to True when using RPC provider." + logger.error(e) + self.exit() + break # Paying upcoming cycles (-R in [-6, -11] ) if pymnt_cycle >= current_cycle: @@ -192,7 +199,7 @@ def run(self): continue # Break/Repeat loop else: - result = self.try_to_pay(pymnt_cycle, self.rewards_type) + result = self.try_to_pay(pymnt_cycle, self.rewards_type, self.nw_config) if result: # single run is done. Do not continue. @@ -246,7 +253,7 @@ def run(self): return - def try_to_pay(self, pymnt_cycle, rewards_type): + def try_to_pay(self, pymnt_cycle, rewards_type, network_config): try: logger.info("Payment cycle is {:s}".format(str(pymnt_cycle))) @@ -258,17 +265,33 @@ def try_to_pay(self, pymnt_cycle, rewards_type): return True # 1- get reward data + reward_model = self.reward_api.get_rewards_for_cycle_map(pymnt_cycle, rewards_type) + + block_reward = network_config["BLOCK_REWARD"] + endorsement_reward = network_config["ENDORSEMENT_REWARD"] + if rewards_type.isEstimated(): logger.info("Using estimated rewards for payouts calculations") + total_estimated_block_reward = reward_model.num_baking_rights * block_reward + total_estimated_endorsement_reward = reward_model.num_endorsing_rights * endorsement_reward + total_reward_amount = total_estimated_block_reward + total_estimated_endorsement_reward elif rewards_type.isActual(): logger.info("Using actual rewards for payouts calculations") + if self.pay_denunciation_rewards: + total_reward_amount = reward_model.total_reward_amount + else: + # omit denunciation rewards + total_reward_amount = reward_model.rewards_and_fees - reward_model.equivocation_losses elif rewards_type.isIdeal(): logger.info("Using ideal rewards for payouts calculations") - - reward_model = self.reward_api.get_rewards_for_cycle_map(pymnt_cycle, rewards_type) + if self.pay_denunciation_rewards: + total_reward_amount = reward_model.total_reward_amount + reward_model.offline_losses + else: + # omit denunciation rewards and double baking loss + total_reward_amount = reward_model.rewards_and_fees + reward_model.offline_losses # 2- calculate rewards - reward_logs, total_amount = self.payment_calc.calculate(reward_model) + reward_logs, total_amount = self.payment_calc.calculate(reward_model, total_reward_amount) # 3- set cycle info for rl in reward_logs: diff --git a/src/rpc/rpc_reward_api.py b/src/rpc/rpc_reward_api.py index ae5b525a..747c5dcb 100644 --- a/src/rpc/rpc_reward_api.py +++ b/src/rpc/rpc_reward_api.py @@ -76,64 +76,61 @@ def get_rewards_for_cycle_map(self, cycle, rewards_type): .format(cycle, self.preserved_cycles, self.blocks_per_cycle, level_of_first_block_in_preserved_cycles, level_of_last_block_in_unfreeze_cycle)) - # Decide on if paying actual rewards earned, or paying estimated/ideal rewards - if rewards_type.isEstimated(): - - # Determine how many priority 0 baking rights delegate had - nb_blocks = len([r for r in self.__get_baking_rights(cycle, level_of_first_block_in_preserved_cycles) if r["priority"] == 0]) - nb_endorsements = sum([len(r["slots"]) for r in self.__get_endorsement_rights(cycle, level_of_first_block_in_preserved_cycles)]) - - logger.debug("Number of 0 priority blocks: {}, Number of endorsements: {}".format(nb_blocks, nb_endorsements)) - logger.debug("Block reward: {}, Endorsement Reward: {}".format(self.block_reward, self.endorsement_reward)) - - # "ideally", the baker baked every priority 0 block they had rights for, - # and every block they baked contained 32 endorsements - total_block_reward = nb_blocks * self.block_reward - total_endorsement_reward = nb_endorsements * self.endorsement_reward - - logger.info("Estimated rewards for cycle {:d}, {:,} block rewards ({:d} blocks), {:,} endorsing rewards ({:d} slots)".format( - cycle, total_block_reward, nb_blocks, total_endorsement_reward, nb_endorsements)) - - reward_data["total_rewards"] = total_block_reward + total_endorsement_reward - - # Calculate actual rewards - else: - + # Determine how many priority 0 baking rights delegate had + baking_rights = self.__get_baking_rights(cycle, level_of_first_block_in_preserved_cycles) + endorsement_rights = self.__get_endorsement_rights(cycle, level_of_first_block_in_preserved_cycles) + nb_blocks = len([r for r in baking_rights if r["priority"] == 0]) + nb_endorsements = sum([len(r["slots"]) for r in endorsement_rights]) + + total_reward_amount = None + if not rewards_type.isEstimated(): + # Calculate actual rewards if current_level - level_of_last_block_in_unfreeze_cycle >= 0: unfrozen_fees, unfrozen_rewards = self.__get_unfrozen_rewards(level_of_last_block_in_unfreeze_cycle, cycle) - total_actual_rewards = unfrozen_fees + unfrozen_rewards + total_reward_amount = unfrozen_fees + unfrozen_rewards else: frozen_fees, frozen_rewards = self.__get_frozen_rewards(cycle, current_level) - total_actual_rewards = frozen_fees + frozen_rewards - if rewards_type.isActual(): - reward_data["total_rewards"] = total_actual_rewards - elif rewards_type.isIdeal(): - missed_baking_income = 0 - for r in self.__get_baking_rights(cycle, level_of_first_block_in_preserved_cycles): - if r["priority"] == 0: - if self.__get_block_author(r["level"]) != self.baking_address: - logger.warning("Found missed baking slot {}, adding {} mutez reward anyway.".format(r, self.block_reward)) - missed_baking_income += self.block_reward - missed_endorsing_income = 0 - for r in self.__get_endorsement_rights(cycle, level_of_first_block_in_preserved_cycles): - authored_endorsement_slots = self.__get_authored_endorsement_slots_by_level(r["level"] + 1) - if authored_endorsement_slots != r["slots"]: - mutez_to_add = self.endorsement_reward * len(r["slots"]) - logger.warning("Found {} missed endorsement(s) at level {}, adding {} mutez reward anyway.".format(len(r["slots"]), r["level"], mutez_to_add)) - missed_endorsing_income += mutez_to_add - logger.warning("total rewards %s" % (total_actual_rewards + missed_baking_income + missed_endorsing_income)) - reward_data["total_rewards"] = total_actual_rewards + missed_baking_income + missed_endorsing_income + total_reward_amount = frozen_fees + frozen_rewards + + # Without an indexer, it is not possible to itemize rewards + # so setting these values below to "None" + rewards_and_fees = None + equivocation_losses = None + denunciation_rewards = None + + offline_losses = None + if rewards_type.isIdeal(): + # Calculate offline losses + missed_baking_income = 0 + for count, r in enumerate(baking_rights): + if (count % 10 == 0): + logger.info("Verifying bake ({}/{}).".format(count, len(baking_rights))) + if r["priority"] == 0: + if self.__get_block_author(r["level"]) != self.baking_address: + logger.warning("Found missed baking slot {}, adding {} mutez reward anyway.".format(r, self.block_reward)) + missed_baking_income += self.block_reward + missed_endorsing_income = 0 + for count, r in enumerate(endorsement_rights): + if (count % 10 == 0): + logger.info("Verifying endorsement ({}/{}).".format(count, len(endorsement_rights))) + authored_endorsement_slots = self.__get_authored_endorsement_slots_by_level(r["level"] + 1) + if authored_endorsement_slots != r["slots"]: + mutez_to_add = self.endorsement_reward * len(r["slots"]) + logger.warning("Found {} missed endorsement(s) at level {}, adding {} mutez reward anyway.".format(len(r["slots"]), r["level"], mutez_to_add)) + missed_endorsing_income += mutez_to_add + offline_losses = missed_baking_income + missed_endorsing_income # TODO: support Dexter for RPC # _, snapshot_level = self.__get_roll_snapshot_block_level(cycle, current_level) # for delegator in self.dexter_contracts_set: # dxtz.process_original_delegators_map(reward_data["delegators"], delegator, snapshot_level) - reward_model = RewardProviderModel(reward_data["delegate_staking_balance"], reward_data["total_rewards"], + reward_model = RewardProviderModel(reward_data["delegate_staking_balance"], + nb_blocks, nb_endorsements, total_reward_amount, rewards_and_fees, equivocation_losses, denunciation_rewards, offline_losses, reward_data["delegators"]) - logger.debug("delegate_staking_balance = {:d}, total_rewards = {:f}" - .format(reward_data["delegate_staking_balance"], reward_data["total_rewards"])) + logger.debug("delegate_staking_balance = {:d}" + .format(reward_data["delegate_staking_balance"])) logger.debug("delegators = {}".format(reward_data["delegators"])) return reward_model @@ -420,6 +417,8 @@ def __get_delegators_and_delgators_balances(self, cycle, current_level): 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 diff --git a/src/tzkt/tzkt_reward_api.py b/src/tzkt/tzkt_reward_api.py index d1fcfa6f..3bc917a9 100644 --- a/src/tzkt/tzkt_reward_api.py +++ b/src/tzkt/tzkt_reward_api.py @@ -18,98 +18,71 @@ def __init__(self, nw, baking_address, base_url=None): self.baking_address = baking_address self.name = 'tzkt' - def calc_estimated_reward(self, cycle: int, num_blocks: int, num_endorsements: int) -> int: - """ - Calculate estimated rewards (0 priority, 32 endorsements per block) based on baking rights only - :param cycle: Cycle - :param num_blocks: Number of baking rights - :param num_endorsements: Number of endorsement rights - """ - - proto = self.api.get_protocol_by_cycle(cycle) - - block_reward = \ - proto['constants']['blockReward'][0] \ - * proto['constants']['endorsersPerBlock'] - - endorsement_reward = \ - proto['constants']['endorsementReward'][0] - - return num_blocks * block_reward + num_endorsements * endorsement_reward - def get_rewards_for_cycle_map(self, cycle, rewards_type) -> RewardProviderModel: """ Returns reward split in a specified format :param cycle: - :param rewards_type: :return: RewardProviderModel( delegate_staking_balance=5265698993303, - total_reward_amount=2790471275, + num_blocks=333, + num_endorsements=3333, delegators_balances={ 'tz1azSbB91MbdcEquhsmJvjVroLs5t4kpdCn': { 'staking_balance': 1935059821, 'current_balance': 1977503559 } } + ) """ split = self.api.get_reward_split(address=self.baking_address, cycle=cycle, fetch_delegators=True) delegate_staking_balance = split['stakingBalance'] - if rewards_type.isEstimated(): - num_blocks = \ - split['ownBlocks'] \ - + split['missedOwnBlocks'] \ - + split['uncoveredOwnBlocks'] \ - + split['futureBlocks'] - - num_endorsements = \ - split['endorsements'] \ - + split['missedEndorsements'] \ - + split['uncoveredEndorsements'] \ - + split['futureEndorsements'] - - total_reward_amount = self.calc_estimated_reward(cycle, num_blocks, num_endorsements) - else: - # rewards earned (excluding equivocation losses) - total_rewards_and_fees = \ - split['ownBlockRewards'] \ - + split['extraBlockRewards'] \ - + split['endorsementRewards'] \ - + split['ownBlockFees'] \ - + split['extraBlockFees'] \ - + split['revelationRewards'] - # slashing denunciation rewards are not part of any calculation for now: - # + split['doubleBakingRewards'] \ - # + split['doubleEndorsingRewards'] - # Rationale: normally bakers return those funds to the one slashed in case of an honest mistake - # TODO: make it configurable - total_equivocation_losses = split['doubleBakingLostDeposits'] \ - + split['doubleBakingLostRewards'] \ - + split['doubleBakingLostFees'] \ - + split['doubleEndorsingLostDeposits'] \ - + split['doubleEndorsingLostRewards'] \ - + split['doubleEndorsingLostFees'] \ - + split['revelationLostRewards'] \ - + split['revelationLostFees'] - # losses due to being offline or not having enough bond - total_offline_losses = split['missedOwnBlockRewards'] \ - + split['missedExtraBlockRewards'] \ - + split['uncoveredOwnBlockRewards'] \ - + split['uncoveredExtraBlockRewards'] \ - + split['missedEndorsementRewards'] \ - + split['uncoveredEndorsementRewards'] \ - + split['missedOwnBlockFees'] \ - + split['missedExtraBlockFees'] \ - + split['uncoveredOwnBlockFees'] \ - + split['uncoveredExtraBlockFees'] - if rewards_type.isActual(): - total_reward_amount = total_rewards_and_fees - total_equivocation_losses - elif rewards_type.isIdeal(): - total_reward_amount = total_rewards_and_fees + total_offline_losses - - total_reward_amount = max(0, total_reward_amount) + # calculate estimated rewards + num_blocks = \ + split['ownBlocks'] \ + + split['missedOwnBlocks'] \ + + split['uncoveredOwnBlocks'] \ + + split['futureBlocks'] + + num_endorsements = \ + split['endorsements'] \ + + split['missedEndorsements'] \ + + split['uncoveredEndorsements'] \ + + split['futureEndorsements'] + + # rewards earned (excluding equivocation losses) + rewards_and_fees = \ + split['ownBlockRewards'] \ + + split['extraBlockRewards'] \ + + split['endorsementRewards'] \ + + split['ownBlockFees'] \ + + split['extraBlockFees'] \ + + split['revelationRewards'] + denunciation_rewards = \ + split['doubleBakingRewards'] \ + + split['doubleEndorsingRewards'] + equivocation_losses = split['doubleBakingLostDeposits'] \ + + split['doubleBakingLostRewards'] \ + + split['doubleBakingLostFees'] \ + + split['doubleEndorsingLostDeposits'] \ + + split['doubleEndorsingLostRewards'] \ + + split['doubleEndorsingLostFees'] \ + + split['revelationLostRewards'] \ + + split['revelationLostFees'] + total_reward_amount = max(0, rewards_and_fees + denunciation_rewards - equivocation_losses) + # losses due to being offline or not having enough bond + offline_losses = split['missedOwnBlockRewards'] \ + + split['missedExtraBlockRewards'] \ + + split['uncoveredOwnBlockRewards'] \ + + split['uncoveredExtraBlockRewards'] \ + + split['missedEndorsementRewards'] \ + + split['uncoveredEndorsementRewards'] \ + + split['missedOwnBlockFees'] \ + + split['missedExtraBlockFees'] \ + + split['uncoveredOwnBlockFees'] \ + + split['uncoveredExtraBlockFees'] delegators_balances = { item['address']: { @@ -125,7 +98,10 @@ def get_rewards_for_cycle_map(self, cycle, rewards_type) -> RewardProviderModel: # for delegator in self.dexter_contracts_set: # dxtz.process_original_delegators_map(delegators_balances, delegator, snapshot_level) - return RewardProviderModel(delegate_staking_balance, total_reward_amount, delegators_balances) + return RewardProviderModel(delegate_staking_balance, + num_blocks, num_endorsements, total_reward_amount, rewards_and_fees, + equivocation_losses, denunciation_rewards, offline_losses, + delegators_balances) def update_current_balances(self, reward_logs: List[RewardLog]): """ diff --git a/src/tzstats/tzstats_api_constants.py b/src/tzstats/tzstats_api_constants.py index e3e9ca3c..8af63986 100644 --- a/src/tzstats/tzstats_api_constants.py +++ b/src/tzstats/tzstats_api_constants.py @@ -20,6 +20,10 @@ idx_income_lost_revelation_fees = 36 idx_income_lost_revelation_rewards = 37 +# Rights +idx_n_baking_rights = 7 +idx_n_endorsing_rights = 8 + # Cycle Snapshot idx_balance = 0 idx_baker_delegated = 1 diff --git a/src/tzstats/tzstats_reward_api.py b/src/tzstats/tzstats_reward_api.py index f76753cb..4c850b7b 100644 --- a/src/tzstats/tzstats_reward_api.py +++ b/src/tzstats/tzstats_reward_api.py @@ -18,11 +18,17 @@ def __init__(self, nw, baking_address): def get_rewards_for_cycle_map(self, cycle, rewards_type): - root = self.helper.get_rewards_for_cycle(cycle, rewards_type) + root = self.helper.get_rewards_for_cycle(cycle) delegate_staking_balance = root["delegate_staking_balance"] - total_reward_amount = root["total_reward_amount"] + num_baking_rights = root["num_baking_rights"] + num_endorsing_rights = root["num_endorsing_rights"] delegators_balances_dict = root["delegators_balances"] + rewards_and_fees = root["rewards_and_fees"] + equivocation_losses = root["equivocation_losses"] + denunciation_rewards = root["denunciation_rewards"] + total_reward_amount = rewards_and_fees - equivocation_losses + denunciation_rewards + offline_losses = root["offline_losses"] snapshot_level = self.helper.get_snapshot_level(cycle) for delegator in self.dexter_contracts_set: @@ -32,7 +38,7 @@ def get_rewards_for_cycle_map(self, cycle, rewards_type): logger.warning(f"The configured Dexter account {delegator} is not delegated to {self.helper.baking_address} " f"at snapshot level {snapshot_level} corresponding to payout cycle {cycle} or has a zero rewards") - return RewardProviderModel(delegate_staking_balance, total_reward_amount, delegators_balances_dict) + return RewardProviderModel(delegate_staking_balance, num_baking_rights, num_endorsing_rights, total_reward_amount, rewards_and_fees, equivocation_losses, denunciation_rewards, offline_losses, delegators_balances_dict) def update_current_balances(self, reward_logs): self.helper.update_current_balances(reward_logs) diff --git a/src/tzstats/tzstats_reward_provider_helper.py b/src/tzstats/tzstats_reward_provider_helper.py index c65aa813..5bf0ce75 100644 --- a/src/tzstats/tzstats_reward_provider_helper.py +++ b/src/tzstats/tzstats_reward_provider_helper.py @@ -5,11 +5,11 @@ from exception.api_provider import ApiProviderException from log_config import main_logger, verbose_logger -from tzstats.tzstats_api_constants import idx_income_expected_income, idx_income_baking_income, idx_income_endorsing_income, \ +from tzstats.tzstats_api_constants import idx_n_baking_rights, idx_n_endorsing_rights, idx_income_baking_income, idx_income_endorsing_income, \ idx_income_seed_income, idx_income_fees_income, idx_income_lost_accusation_fees, idx_income_lost_accusation_rewards, \ idx_income_lost_revelation_fees, idx_income_lost_revelation_rewards, idx_delegator_address, idx_balance, \ idx_baker_delegated, idx_cb_delegator_address, idx_cb_current_balance, idx_income_missed_baking_income, \ - idx_income_missed_endorsing_income + idx_income_missed_endorsing_income, idx_income_double_baking_income, idx_income_double_endorsing_income from Constants import TZSTATS_PREFIX_API logger = main_logger @@ -43,9 +43,11 @@ def __init__(self, nw, baking_address): self.baking_address = baking_address - def get_rewards_for_cycle(self, cycle, rewards_type): + def get_rewards_for_cycle(self, cycle): - root = {"delegate_staking_balance": 0, "total_reward_amount": 0, "delegators_balances": {}} + root = {"delegate_staking_balance": 0, "num_baking_rights": 0, + "num_endorsing_rights": 0, "rewards_and_fees": 0, + "equivocation_losses": 0, "offline_losses": 0, "delegators_balances": {}} # # Get rewards breakdown for cycle @@ -65,26 +67,25 @@ def get_rewards_for_cycle(self, cycle, rewards_type): raise ApiProviderException('GET {} {}'.format(uri, resp.status_code)) resp = resp.json()[0] - if rewards_type.isEstimated(): - root["total_reward_amount"] = int(1e6 * float(resp[idx_income_expected_income])) - else: - # rewards earned (excluding equivocation losses and equivocation accusation income) - total_rewards_and_fees = (float(resp[idx_income_baking_income]) - + float(resp[idx_income_endorsing_income]) - + float(resp[idx_income_seed_income]) - + float(resp[idx_income_fees_income])) - # losses due to baker double baking, double endorsing or missing nonce - total_equivocation_losses = (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])) - # losses due to being offline or not having enough bond - total_offline_losses = (float(resp[idx_income_missed_baking_income]) - + float(resp[idx_income_missed_endorsing_income])) - if rewards_type.isActual(): - root["total_reward_amount"] = int(1e6 * (total_rewards_and_fees - total_equivocation_losses)) - elif rewards_type.isIdeal(): - root["total_reward_amount"] = int(1e6 * (total_rewards_and_fees + total_offline_losses)) + + root["num_baking_rights"] = resp[idx_n_baking_rights] + root["num_endorsing_rights"] = resp[idx_n_endorsing_rights] + + # rewards earned (excluding equivocation losses and equivocation accusation income) + root["rewards_and_fees"] = 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]))) + # losses due to baker double baking, double endorsing or missing nonce + root["equivocation_losses"] = int(1e6 * (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]))) + root["denunciation_rewards"] = int(1e6 * (float(resp[idx_income_double_baking_income]) + + float(resp[idx_income_double_endorsing_income]))) + # losses due to being offline or not having enough bond + root["offline_losses"] = int(1e6 * (float(resp[idx_income_missed_baking_income]) + + float(resp[idx_income_missed_endorsing_income]))) # # Get staking balances of delegators at snapshot block diff --git a/tests/integration/test_phases.py b/tests/integration/test_phases.py index 22dd173f..d3b278cd 100644 --- a/tests/integration/test_phases.py +++ b/tests/integration/test_phases.py @@ -88,7 +88,7 @@ def test_process_payouts(self): reward_model = rewardApi.get_rewards_for_cycle_map(PAYOUT_CYCLE, RewardsType.ACTUAL) # Calculate rewards - payment_producer.py - reward_logs, total_amount = payment_calc.calculate(reward_model) + reward_logs, total_amount = payment_calc.calculate(reward_model, reward_model.total_reward_amount) # Check total reward amount matches sums of records self.assertTrue(total_amount, sum([rl.amount for rl in reward_logs if rl.payable])) diff --git a/tests/integration/test_tzkt_reward_api.py b/tests/integration/test_tzkt_reward_api.py index bb03ab8f..cd455798 100644 --- a/tests/integration/test_tzkt_reward_api.py +++ b/tests/integration/test_tzkt_reward_api.py @@ -25,6 +25,12 @@ def load_reward_model(address, cycle, suffix) -> Optional[RewardProviderModel]: return RewardProviderModel( delegate_staking_balance=data['delegate_staking_balance'], total_reward_amount=data['total_reward_amount'], + rewards_and_fees=data['rewards_and_fees'], + equivocation_losses=data['equivocation_losses'], + offline_losses=data['offline_losses'], + num_baking_rights=data['num_baking_rights'], + num_endorsing_rights=data['num_endorsing_rights'], + denunciation_rewards=data['denunciation_rewards'], delegator_balance_dict=data['delegator_balance_dict']) else: return None @@ -35,8 +41,14 @@ def store_reward_model(address, cycle, suffix, model: RewardProviderModel): data = dict( delegate_staking_balance=model.delegate_staking_balance, total_reward_amount=model.total_reward_amount, + rewards_and_fees=model.rewards_and_fees, + equivocation_losses=model.equivocation_losses, + offline_losses=model.offline_losses, + num_baking_rights=model.num_baking_rights, + num_endorsing_rights=model.num_endorsing_rights, + denunciation_rewards=model.denunciation_rewards, delegator_balance_dict={ - k: {'staking_balance': v['staking_balance']} + k: {i: v[i] for i in v if i != 'current_balance'} for k, v in model.delegator_balance_dict.items() if v['staking_balance'] > 0 } @@ -65,24 +77,20 @@ def assertBalancesAlmostEqual(self, expected: dict, actual: dict, delta=1): self.assertAlmostEqual(balances['staking_balance'], actual[address]['staking_balance'], delta=delta, msg=address) @parameterized.expand([ - ('tz1ZRWFLgT9sz8iFi1VYWPfRYeUvUSFAaDao', 201, 0), - ('tz1Lhf4J9Qxoe3DZ2nfe8FGDnvVj7oKjnMY6', 185, 0), # double baking (loss) - ('tz1WnfXMPaNTBmH7DBPwqCWs9cPDJdkGBTZ8', 74, 32000000), # double baking (gain) - ('tz1PeZx7FXy7QRuMREGXGxeipb24RsMMzUNe', 135, 0), # double endorsement (loss) - ('tz1gk3TDbU7cJuiBRMhwQXVvgDnjsxuWhcEA', 135, 19328043790), # double endorsement (gain) - ('tz1S1Aew75hMrPUymqenKfHo8FspppXKpW7h', 233, 0), # revelation rewards - ('tz1UUgPwikRHW1mEyVZfGYy6QaxrY6Y7WaG5', 207, 0), # revelation miss + ('tz1ZRWFLgT9sz8iFi1VYWPfRYeUvUSFAaDao', 201), + ('tz1Lhf4J9Qxoe3DZ2nfe8FGDnvVj7oKjnMY6', 185), # double baking (loss) + ('tz1WnfXMPaNTBmH7DBPwqCWs9cPDJdkGBTZ8', 74), # double baking (gain) + ('tz1PeZx7FXy7QRuMREGXGxeipb24RsMMzUNe', 135), # double endorsement (loss) + ('tz1gk3TDbU7cJuiBRMhwQXVvgDnjsxuWhcEA', 135), # double endorsement (gain) + ('tz1S1Aew75hMrPUymqenKfHo8FspppXKpW7h', 233), # revelation rewards + ('tz1UUgPwikRHW1mEyVZfGYy6QaxrY6Y7WaG5', 207), # revelation miss ]) - def test_get_rewards_for_cycle_map(self, address, cycle, hardcoded_denunciation_reward): + def test_get_rewards_for_cycle_map(self, address, cycle): ''' - This test compares the total rewards and balance according to tzkt, + This test compares the total rewards and balance accoring to tzkt, to the total rewards according to rpc. It also compares the balances per delegator. - - Note: double baking and double endorsement gain are taken into account - by the protocol but not by TRD, so there are discrepancies. To resolve, - we are hardcoding the expected gain in hardcoded_denunciation_reward. ''' rpc_rewards = load_reward_model(address, cycle, 'actual') if rpc_rewards is None: @@ -98,10 +106,8 @@ def test_get_rewards_for_cycle_map(self, address, cycle, hardcoded_denunciation_ baking_address=address) tzkt_rewards = tzkt_impl.get_rewards_for_cycle_map(cycle, RewardsType.ACTUAL) - total_reward_amount = rpc_rewards.total_reward_amount - hardcoded_denunciation_reward - self.assertAlmostEqual(rpc_rewards.delegate_staking_balance, tzkt_rewards.delegate_staking_balance, delta=1) - self.assertAlmostEqual(total_reward_amount, tzkt_rewards.total_reward_amount, delta=1) + self.assertAlmostEqual(rpc_rewards.total_reward_amount, tzkt_rewards.total_reward_amount, delta=1) self.assertBalancesAlmostEqual(rpc_rewards.delegator_balance_dict, tzkt_rewards.delegator_balance_dict, delta=1) @parameterized.expand([ diff --git a/tests/integration/tzkt_data/tz1Lhf4J9Qxoe3DZ2nfe8FGDnvVj7oKjnMY6_185_actual.json b/tests/integration/tzkt_data/tz1Lhf4J9Qxoe3DZ2nfe8FGDnvVj7oKjnMY6_185_actual.json index a53a1dda..a184eefe 100644 --- a/tests/integration/tzkt_data/tz1Lhf4J9Qxoe3DZ2nfe8FGDnvVj7oKjnMY6_185_actual.json +++ b/tests/integration/tzkt_data/tz1Lhf4J9Qxoe3DZ2nfe8FGDnvVj7oKjnMY6_185_actual.json @@ -1,6 +1,12 @@ { "delegate_staking_balance": 1870489109620, "total_reward_amount": 0, + "rewards_and_fees": null, + "equivocation_losses": null, + "offline_losses": null, + "num_baking_rights": 13, + "num_endorsing_rights": 413, + "denunciation_rewards": null, "delegator_balance_dict": { "tz1RxTzpUFF5XG4ULs4MRXLQ6q7D5KamQHBk": { "staking_balance": 10485876239 diff --git a/tests/integration/tzkt_data/tz1PeZx7FXy7QRuMREGXGxeipb24RsMMzUNe_135_actual.json b/tests/integration/tzkt_data/tz1PeZx7FXy7QRuMREGXGxeipb24RsMMzUNe_135_actual.json index 18dc2444..f11381fa 100644 --- a/tests/integration/tzkt_data/tz1PeZx7FXy7QRuMREGXGxeipb24RsMMzUNe_135_actual.json +++ b/tests/integration/tzkt_data/tz1PeZx7FXy7QRuMREGXGxeipb24RsMMzUNe_135_actual.json @@ -1,6 +1,12 @@ { "delegate_staking_balance": 4378733698098, "total_reward_amount": 0, + "rewards_and_fees": null, + "equivocation_losses": null, + "offline_losses": null, + "num_baking_rights": 34, + "num_endorsing_rights": 1043, + "denunciation_rewards": null, "delegator_balance_dict": { "KT1XuXmV17f2qqnShcMUzK2QPvKEGwbtuu94": { "staking_balance": 1489322236 diff --git a/tests/integration/tzkt_data/tz1RV1MBbZMR68tacosb7Mwj6LkbPSUS1er1_242_expected.json b/tests/integration/tzkt_data/tz1RV1MBbZMR68tacosb7Mwj6LkbPSUS1er1_242_expected.json index 8a290a77..f3f36753 100644 --- a/tests/integration/tzkt_data/tz1RV1MBbZMR68tacosb7Mwj6LkbPSUS1er1_242_expected.json +++ b/tests/integration/tzkt_data/tz1RV1MBbZMR68tacosb7Mwj6LkbPSUS1er1_242_expected.json @@ -1,6 +1,12 @@ { "delegate_staking_balance": 818730040310, - "total_reward_amount": 372500000, + "total_reward_amount": 330869108, + "rewards_and_fees": 330869108, + "equivocation_losses": 0, + "offline_losses": 40000000, + "num_baking_rights": 4, + "num_endorsing_rights": 170, + "denunciation_rewards": 0, "delegator_balance_dict": { "tz1eNby3LNdPffLAXe4mArWSukQKLF3hmwyy": { "staking_balance": 2889467859 diff --git a/tests/integration/tzkt_data/tz1S1Aew75hMrPUymqenKfHo8FspppXKpW7h_233_actual.json b/tests/integration/tzkt_data/tz1S1Aew75hMrPUymqenKfHo8FspppXKpW7h_233_actual.json index 49806ac8..6869b20a 100644 --- a/tests/integration/tzkt_data/tz1S1Aew75hMrPUymqenKfHo8FspppXKpW7h_233_actual.json +++ b/tests/integration/tzkt_data/tz1S1Aew75hMrPUymqenKfHo8FspppXKpW7h_233_actual.json @@ -1,6 +1,12 @@ { "delegate_staking_balance": 922603814393, "total_reward_amount": 763966172, + "rewards_and_fees": null, + "equivocation_losses": null, + "offline_losses": null, + "num_baking_rights": 13, + "num_endorsing_rights": 199, + "denunciation_rewards": null, "delegator_balance_dict": { "KT1TE5oPuf2UZZA6dBwkgH63b6tg6aLRw6hQ": { "staking_balance": 3920938607 diff --git a/tests/integration/tzkt_data/tz1S8e9GgdZG78XJRB3NqabfWeM37GnhZMWQ_235_expected.json b/tests/integration/tzkt_data/tz1S8e9GgdZG78XJRB3NqabfWeM37GnhZMWQ_235_expected.json index 2e394836..5676948f 100644 --- a/tests/integration/tzkt_data/tz1S8e9GgdZG78XJRB3NqabfWeM37GnhZMWQ_235_expected.json +++ b/tests/integration/tzkt_data/tz1S8e9GgdZG78XJRB3NqabfWeM37GnhZMWQ_235_expected.json @@ -1,6 +1,12 @@ { "delegate_staking_balance": 225687921079, - "total_reward_amount": 187500000, + "total_reward_amount": 187127992, + "rewards_and_fees": 187127992, + "equivocation_losses": 0, + "offline_losses": 0, + "num_baking_rights": 3, + "num_endorsing_rights": 54, + "denunciation_rewards": 0, "delegator_balance_dict": { "tz1LzQgL3wPvPqAtBPiQZvWQkc6uGzrqUdnt": { "staking_balance": 19942072745 diff --git a/tests/integration/tzkt_data/tz1UUgPwikRHW1mEyVZfGYy6QaxrY6Y7WaG5_207_actual.json b/tests/integration/tzkt_data/tz1UUgPwikRHW1mEyVZfGYy6QaxrY6Y7WaG5_207_actual.json index 2396d894..fc7f135d 100644 --- a/tests/integration/tzkt_data/tz1UUgPwikRHW1mEyVZfGYy6QaxrY6Y7WaG5_207_actual.json +++ b/tests/integration/tzkt_data/tz1UUgPwikRHW1mEyVZfGYy6QaxrY6Y7WaG5_207_actual.json @@ -1,6 +1,12 @@ { "delegate_staking_balance": 1193686342228, "total_reward_amount": 477480690, + "rewards_and_fees": null, + "equivocation_losses": null, + "offline_losses": null, + "num_baking_rights": 3, + "num_endorsing_rights": 230, + "denunciation_rewards": null, "delegator_balance_dict": { "KT1TTQW21VUson1pjs8bFvVchaMSMTjM5nxp": { "staking_balance": 2911294874 diff --git a/tests/integration/tzkt_data/tz1V4qCyvPKZ5UeqdH14HN42rxvNPQfc9UZg_220_actual.json b/tests/integration/tzkt_data/tz1V4qCyvPKZ5UeqdH14HN42rxvNPQfc9UZg_220_actual.json index 67ef36c9..2aa125e8 100644 --- a/tests/integration/tzkt_data/tz1V4qCyvPKZ5UeqdH14HN42rxvNPQfc9UZg_220_actual.json +++ b/tests/integration/tzkt_data/tz1V4qCyvPKZ5UeqdH14HN42rxvNPQfc9UZg_220_actual.json @@ -1,6 +1,12 @@ { "delegate_staking_balance": 700889571831, "total_reward_amount": 360483046, + "rewards_and_fees": null, + "equivocation_losses": null, + "offline_losses": null, + "num_baking_rights": 4, + "num_endorsing_rights": 162, + "denunciation_rewards": null, "delegator_balance_dict": { "KT1GdpULFXCMQwtibmmcPrGNrz6rKrACMeyr": { "staking_balance": 16245 diff --git a/tests/integration/tzkt_data/tz1WnfXMPaNTBmH7DBPwqCWs9cPDJdkGBTZ8_233_expected.json b/tests/integration/tzkt_data/tz1WnfXMPaNTBmH7DBPwqCWs9cPDJdkGBTZ8_233_expected.json index 1b7ddeca..d02faffe 100644 --- a/tests/integration/tzkt_data/tz1WnfXMPaNTBmH7DBPwqCWs9cPDJdkGBTZ8_233_expected.json +++ b/tests/integration/tzkt_data/tz1WnfXMPaNTBmH7DBPwqCWs9cPDJdkGBTZ8_233_expected.json @@ -1,6 +1,12 @@ { "delegate_staking_balance": 699269618812, - "total_reward_amount": 202500000, + "total_reward_amount": 206948690, + "rewards_and_fees": 206948690, + "equivocation_losses": 0, + "offline_losses": 1250000, + "num_baking_rights": 1, + "num_endorsing_rights": 130, + "denunciation_rewards": 0, "delegator_balance_dict": { "KT1R24ygjvkkoib9Zsd3ww7Zpo2aNGUxSGXr": { "staking_balance": 51308 diff --git a/tests/integration/tzkt_data/tz1WnfXMPaNTBmH7DBPwqCWs9cPDJdkGBTZ8_74_actual.json b/tests/integration/tzkt_data/tz1WnfXMPaNTBmH7DBPwqCWs9cPDJdkGBTZ8_74_actual.json index e3620155..d062efc1 100644 --- a/tests/integration/tzkt_data/tz1WnfXMPaNTBmH7DBPwqCWs9cPDJdkGBTZ8_74_actual.json +++ b/tests/integration/tzkt_data/tz1WnfXMPaNTBmH7DBPwqCWs9cPDJdkGBTZ8_74_actual.json @@ -1,6 +1,12 @@ { "delegate_staking_balance": 1080140743094, "total_reward_amount": 797163164, + "rewards_and_fees": null, + "equivocation_losses": null, + "offline_losses": null, + "num_baking_rights": 9, + "num_endorsing_rights": 315, + "denunciation_rewards": null, "delegator_balance_dict": { "KT1XWdbmBYQmuFgLm1dULXANVcoGQTmpZ1XJ": { "staking_balance": 21306498848 diff --git a/tests/integration/tzkt_data/tz1YKh8T79LAtWxX29N5VedCSmaZGw9LNVxQ_246_expected.json b/tests/integration/tzkt_data/tz1YKh8T79LAtWxX29N5VedCSmaZGw9LNVxQ_246_expected.json index a4a77e5d..45a01324 100644 --- a/tests/integration/tzkt_data/tz1YKh8T79LAtWxX29N5VedCSmaZGw9LNVxQ_246_expected.json +++ b/tests/integration/tzkt_data/tz1YKh8T79LAtWxX29N5VedCSmaZGw9LNVxQ_246_expected.json @@ -1,6 +1,12 @@ { "delegate_staking_balance": 59743831158, - "total_reward_amount": 55000000, + "total_reward_amount": 53757200, + "rewards_and_fees": 53757200, + "equivocation_losses": 0, + "offline_losses": 1250000, + "num_baking_rights": 1, + "num_endorsing_rights": 12, + "denunciation_rewards": 0, "delegator_balance_dict": { "KT1Fd17ajP529Bb19KkttGjBPyAFZfR9PHGg": { "staking_balance": 420364 diff --git a/tests/integration/tzkt_data/tz1ZRWFLgT9sz8iFi1VYWPfRYeUvUSFAaDao_201_actual.json b/tests/integration/tzkt_data/tz1ZRWFLgT9sz8iFi1VYWPfRYeUvUSFAaDao_201_actual.json index 681e178e..3e482460 100644 --- a/tests/integration/tzkt_data/tz1ZRWFLgT9sz8iFi1VYWPfRYeUvUSFAaDao_201_actual.json +++ b/tests/integration/tzkt_data/tz1ZRWFLgT9sz8iFi1VYWPfRYeUvUSFAaDao_201_actual.json @@ -1,6 +1,12 @@ { "delegate_staking_balance": 338305261295, "total_reward_amount": 170428078, + "rewards_and_fees": null, + "equivocation_losses": null, + "offline_losses": null, + "num_baking_rights": 2, + "num_endorsing_rights": 71, + "denunciation_rewards": null, "delegator_balance_dict": { "tz1ZpoDnbZBMDtRfWvKtDyEb2eYR8MVLrTfP": { "staking_balance": 734388 diff --git a/tests/integration/tzkt_data/tz1ZRWFLgT9sz8iFi1VYWPfRYeUvUSFAaDao_232_expected.json b/tests/integration/tzkt_data/tz1ZRWFLgT9sz8iFi1VYWPfRYeUvUSFAaDao_232_expected.json index de4bbaad..3fb3dc3c 100644 --- a/tests/integration/tzkt_data/tz1ZRWFLgT9sz8iFi1VYWPfRYeUvUSFAaDao_232_expected.json +++ b/tests/integration/tzkt_data/tz1ZRWFLgT9sz8iFi1VYWPfRYeUvUSFAaDao_232_expected.json @@ -1,6 +1,12 @@ { "delegate_staking_balance": 342580974952, - "total_reward_amount": 172500000, + "total_reward_amount": 165021555, + "rewards_and_fees": 165021555, + "equivocation_losses": 0, + "offline_losses": 6250000, + "num_baking_rights": 2, + "num_endorsing_rights": 74, + "denunciation_rewards": 0, "delegator_balance_dict": { "tz1S7BFYFFYz3MGwe59zcW5qi1Mbo3XNG9Vd": { "staking_balance": 1896223 diff --git a/tests/integration/tzkt_data/tz1gk3TDbU7cJuiBRMhwQXVvgDnjsxuWhcEA_135_actual.json b/tests/integration/tzkt_data/tz1gk3TDbU7cJuiBRMhwQXVvgDnjsxuWhcEA_135_actual.json index 5fb8d710..583eea2c 100644 --- a/tests/integration/tzkt_data/tz1gk3TDbU7cJuiBRMhwQXVvgDnjsxuWhcEA_135_actual.json +++ b/tests/integration/tzkt_data/tz1gk3TDbU7cJuiBRMhwQXVvgDnjsxuWhcEA_135_actual.json @@ -1,6 +1,12 @@ { "delegate_staking_balance": 8926515975361, "total_reward_amount": 24540583402, + "rewards_and_fees": null, + "equivocation_losses": null, + "offline_losses": null, + "num_baking_rights": 63, + "num_endorsing_rights": 2121, + "denunciation_rewards": null, "delegator_balance_dict": { "KT1XboC76dhG5CSmcjUnBZRq1FWMGiVuE6ad": { "staking_balance": 17269997311 diff --git a/tests/regression/test_gas_estimation_oven_kt1.py b/tests/regression/test_gas_estimation_oven_kt1.py index 21aa25c2..11831ca8 100644 --- a/tests/regression/test_gas_estimation_oven_kt1.py +++ b/tests/regression/test_gas_estimation_oven_kt1.py @@ -103,7 +103,7 @@ def test_batch_payer_total_payout_amount(): reward_model = rewardApi.get_rewards_for_cycle_map(PAYOUT_CYCLE, RewardsType.ACTUAL) # Calculate rewards - payment_producer.py - reward_logs, total_amount = payment_calc.calculate(reward_model) + reward_logs, total_amount = payment_calc.calculate(reward_model, reward_model.total_reward_amount) # Check total reward amount matches sums of records assert total_amount == sum([rl.amount for rl in reward_logs if rl.payable]) diff --git a/tests/unit/test_calculatePhase0.py b/tests/unit/test_calculatePhase0.py index edd997b8..e6f2d920 100644 --- a/tests/unit/test_calculatePhase0.py +++ b/tests/unit/test_calculatePhase0.py @@ -20,7 +20,7 @@ def test_calculate(self): model = api.get_rewards_for_cycle_map(CYCLE, REWARDS_TYPE) phase0 = CalculatePhase0(model) - reward_data, total_rewards = phase0.calculate() + reward_data = phase0.calculate() delegate_staking_balance = int(model.delegate_staking_balance) diff --git a/tests/utils.py b/tests/utils.py index f148b82b..91ca4da3 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -131,5 +131,13 @@ def mock_request_get(url, timeout): {"kind": "contract", "contract": "tz1gtHbmBF3TSebsgJfJPvUB2e9x8EDeNm6V", "change": "14626175032"} ] }) + if path == "/chains/main/blocks/178177/helpers/baking_rights": + return MagicMock(status_code=HTTPStatus.OK, json=lambda: []) + if path == "/chains/main/blocks/178177/helpers/endorsing_rights": + return MagicMock(status_code=HTTPStatus.OK, json=lambda: []) + if path == "/chains/main/blocks/196609/helpers/baking_rights": + return MagicMock(status_code=HTTPStatus.OK, json=lambda: []) + if path == "/chains/main/blocks/196609/helpers/endorsing_rights": + return MagicMock(status_code=HTTPStatus.OK, json=lambda: []) - raise MagicMock(status_code=HTTPStatus.NOT_FOUND, json=lambda: {"Not Found"}) + raise Exception("Mocked URL not found")