Skip to content
This repository has been archived by the owner on Aug 30, 2022. It is now read-only.

Prevent overflow within claimrewards in cases of extreme inflation - develop #328

Merged
merged 3 commits into from
Sep 9, 2019

Conversation

arhag
Copy link
Contributor

@arhag arhag commented Sep 2, 2019

Change Description

Now that it is possible to set the inflation rate using the setinflation action, the operators of the network could in theory set the annual inflation rate to an extremely large number. In such a case (and with a large current supply) it is possible for the new_tokens amount calculated within eosio::claimrewards to overflow into a negative number. Then due to how to_per_block_pay and to_per_block_pay are calculated using uint128_t intermediate variables before being cast to int64_t, those two amounts could end up with garbage (possibly negative values).

The following code demonstrates how such values could be computed in these extreme situations:

#include <iostream>
#include <cstdlib>
#include <limits>
#include <cmath>

using uint128_t = unsigned __int128;

int main()
{
   double annual_rate = double(std::numeric_limits<int64_t>::max()) / double(10000);
   std::cout << "annual_rate = " << annual_rate << std::endl;
    
   double continuous_rate = std::log1p(annual_rate);
   std::cout << "continuous_rate = " << continuous_rate << std::endl;
    
   double continous_rate_times_supply = continuous_rate * double((1ll << 62) - 1);
   std::cout << "continous_rate_times_supply = " << continous_rate_times_supply << std::endl;
    
   int64_t useconds_per_year = int64_t(52 * 7 * 24 * 3600) * 1'000'000ll;
   int64_t useconds_per_day   = int64_t(24 * 3600) * 1'000'000ll;
    
   double new_inflation = (continous_rate_times_supply * double(21*useconds_per_day)) / double(useconds_per_year);
   std::cout << "new_inflation = " << new_inflation << std::endl;
    
   int64_t new_tokens       = new_inflation;
   std::cout << "new_tokens = " << new_tokens << std::endl;
    
   int64_t to_producers     = (new_tokens * uint128_t(10000)) / 50000;
   std::cout << "to_producers = " << to_producers << std::endl;
    
   int64_t to_savings       = new_tokens - to_producers;
   std::cout << "to_savings = " << to_savings << std::endl;
    
   int64_t to_per_block_pay = (to_producers * uint128_t(10000)) / 40000;
   std::cout << "to_per_block_pay = " << to_per_block_pay << std::endl;
    
   int64_t to_per_vote_pay  = to_producers - to_per_block_pay;
   std::cout << "to_per_vote_pay = " << to_per_vote_pay << std::endl;
     
   double overflowing_new_inflation = (continous_rate_times_supply * double(22*useconds_per_day)) / double(useconds_per_year);
   std::cout << "overflowing_new_inflation = " << overflowing_new_inflation << std::endl;
    
   int64_t overflowed_new_tokens       = overflowing_new_inflation;
   std::cout << "overflowed_new_tokens = " << overflowed_new_tokens << std::endl;
    
   int64_t overflowed_to_producers     = (overflowed_new_tokens * uint128_t(10000)) / 50000;
   std::cout << "overflowed_to_producers = " << overflowed_to_producers << std::endl;
    
   int64_t overflowed_to_savings       = overflowed_new_tokens - overflowed_to_producers;
   std::cout << "overflowed_to_savings = " << overflowed_to_savings << std::endl;
    
   int64_t overflowed_to_per_block_pay = (overflowed_to_producers * uint128_t(10000)) / 40000;
   std::cout << "overflowed_to_per_block_pay = " << overflowed_to_per_block_pay << std::endl;
    
   int64_t overflowed_to_per_vote_pay  = overflowed_to_producers - overflowed_to_per_block_pay;
   std::cout << "overflowed_to_per_vote_pay = " << overflowed_to_per_vote_pay << std::endl;
}

// Prints:
// annual_rate = 9.22337e+14
// continuous_rate = 34.4579
// continous_rate_times_supply = 1.58909e+20
// new_inflation = 9.16784e+18
// new_tokens = 9167836340973574144
// to_producers = 1833567268194714828
// to_savings = 7334269072778859316
// to_per_block_pay = 458391817048678707
// to_per_vote_pay = 1375175451146036121
// overflowing_new_inflation = 9.6044e+18
// overflowed_new_tokens = -9223372036854775808
// overflowed_to_producers = -1248475638908662454
// overflowed_to_savings = -7974896397946113354
// overflowed_to_per_block_pay = -4178556467576687633
// overflowed_to_per_vote_pay = 2930080828668025179

Since new_tokens is negative, none of the issue or transfer inline actions will execute, but the pervote_bucket and perblock_bucket fields of the global state will be modified with the calculated garbage values.

To prevent this possibility from occurring, the code should check for the overflow and stop proceeding forward if encountered.

So this PR avoids immediately converting the double representation of the computed new tokens amount (called additional_inflation now in the code) into an int64_t. Instead, it first compares additional_inflation against the largest double-precision floating point number that can be represented by a int64_t without any loss of precision. If additional_inflation is not greater than this limit, then it is converted into an int64_t and stored as new_tokens before proceeding forward.

Furthermore, though this should never have occurred anyway as long as the invariant that _gstate4.continuous_rate >= 0 is maintained, extra precaution is taken to ensure that floating-point arithmetic approximations could not possibly lead to a negative value for new_tokens (which if possible could again modify to_per_block_pay and to_per_vote_pay in unintended ways).

This PR also adds a new test case, eosio_system_tests/extreme_inflation, which triggers the above mentioned scenario to ensure the new check protects against possible corruption of the system contract tables.

Deployment Changes

  • Deployment Changes

API Changes

  • API Changes

Documentation Additions

  • Documentation Additions

…dded eosio_system_tests/extreme_inflation test
Copy link
Contributor

@zorba80 zorba80 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When calculating new_tokens, we use the approximation 1 + r_c * dt for exp(r_c * dt) which is valid for small r_c and dt (dt approx 1/365). Now that we allow large inflation rates, this approximation might not be valid anymore (quadratic term might not negligible) and we have to use the full exponential instead. But this can be another PR.

@arhag arhag merged commit 9cc4dcd into develop Sep 9, 2019
@arhag arhag deleted the extreme-inflation-develop branch September 9, 2019 17:19
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants