# System Contract Part #1 — Block Producer Rewards

in blockchain •  last year  (edited)

This is eosio.sg, a team comprised by some people who have passion and interest in EOS. We would like to write a series of articles to share about the system contract and wish you can enjoy it. Contact us via Eosio.SG: Telegram, Medium.

In eos network, eosio.system contract enables users to 1) stake tokens, and then vote on producers (or worker proposals), 2) proxy their voting influence to other users, 3) register producers, 4) claim producer rewards, 5) delegate bandwidth and push other necessary actions to keep blockchain system running.

In this series, we will go through this contract and talk about what is going on inside this contract and how to use it.

From this article, questions on Who is getting rewards, How much is the rewards, and How to claim rewards will be discussed successively.

*All codes present are based on commit of 9aa57f9

## TL;DR:

• Block producers get rewards of block production & votes proportion;
• Approx. 1.6 tokens issued on every second;
• For each producer, rewards claim can be approved at most once per 24 hours.

## Who Is Getting Rewards?

21 active Block Producers (BP) and 100 candidates will be determined from voting. BPs are able to claim rewards from both the number of blocks they have produced as well as their votes proportion, i.e. Fixed Rewards & Dynamic Rewards respectively.

BPs have to initiate rewards claim from eos system, some related system level parameters are defined in `global_state_singleton` as follows:

@Global States

parameter namedescription
`payment_per_block`Number of tokens assigned for one block production
`payment_to_eos_bucket`Number of tokens assigned to bucket per second (bucket is used for dynamic rewards claim)
`first_block_time_in_cycle`Mark the start of a cycle
`blocks_per_cycle`Block count of a complete cycle
`last_bucket_fill_time`Last bucket fill time, usually be upon a new cycle
`eos_bucket`Bucket tokens balance, to be transferred to producers as dynamic rewards

## How Much Will Producers Get?

Amount of rewards is related to global parameters(e.g. inflation rate) and BP contribution. Based on the 5% inflation rate, approx. 1.6 tokens are issued system wise.

Producer table includes information of all registered producers, which are widely used in system contract. Some columns related to producer rewards are listed below.

@Producer Table

ColumnDescription
`owner`Producer account name
`total_votes`Number of votes the producer has got
`prefs`Eosio parameters of the producer
`per_block_payments`Fixed rewards available
`last_rewards_claim`Last claim time, used to prevent from claiming too frequent
`last_produced_block_time`Last time produced

### Per Second Payment Calculation

The amount of all rewards giving away is calculated using `system_contract::payment_per_block`, determined by the product of system parameter `max_inflation_rate` (by default is 5%) and median of `percent_of_max_inflation_rate` from 21 active producers.

$annual\_rate = {{max\_inflation\_rate\times percent\_of\_max\_inflation\_rate[median] }\over 10000}$

$continuous\_rate = log1p(annual\_rate)$

$per\_second\_payment = {2\times {{continuous\_rate\times token\_supply.amount }\over blocks\_per\_year}}$

Currently there is no constraints on BPs' parameter `percent_of_max_inflation_rate`. If we assume it to be 100%, based on the 5% inflation rate from code and continuous rate formula, maximum 1.5898 eos tokens are issued per second (might be reduced if `max_inflation_rate` is modified in official release). This amount will be split into 2 "almost equal" parts for Fixed & Dynamic Rewards.

### Update Producer States From Election

After an election, some of parameters related to rewards are being set, which are to be used in rewards calculation.

1. The amount of token calculated in the previous section will be added into `parameters.payment_per_block` for Fixed Rewards and `parameters.payment_to_eos_bucket` for Dynamic Rewards.

``````void system_contract::update_elected_producers(time cycle_time) {
...
global_state_singleton gs( _self, _self );
auto parameters = gs.exists() ? gs.get() :    get_default_parameters();
...
auto half_of_percentage =       parameters.percent_of_max_inflation_rate / 2;
auto other_half_of_percentage = parameters.percent_of_max_inflation_rate - half_of_percentage;
parameters.payment_per_block = payment_per_block(half_of_percentage);
parameters.payment_to_eos_bucket = payment_per_block(other_half_of_percentage);
parameters.blocks_per_cycle = blocks_per_producer * schedule.producers.size();
...
}

``````
2. Corresponding tokens are issued to `eosio` account for further giveaway.

``````    ...
auto issue_quantity = parameters.blocks_per_cycle * (parameters.payment_per_block + parameters.payment_to_eos_bucket);
INLINE_ACTION_SENDER(eosio::token, issue)( N(eosio.token), {{N(eosio),N(active)}},
{N(eosio), issue_quantity, std::string("producer pay")} );

set_blockchain_parameters( parameters );
gs.set( parameters, _self );
}
``````

### Init New Cycle

Rewards rules are set upon every new cycle with the action of `system_contract::onblock`, inside this action, some global states and producer table will be updated accordingly.

1. Update cycle

``````void system_contract::onblock(const block_header& header) {
// update parameters if it's a new cycle
...
}

bool system_contract::update_cycle(time block_time) {
...

static const uint32_t slots_per_cycle = parameters.blocks_per_cycle;
const uint32_t time_slots = block_time - parameters.first_block_time_in_cycle;
if (time_slots >= slots_per_cycle) {
time beginning_of_cycle = block_time - (time_slots % slots_per_cycle);
update_elected_producers(beginning_of_cycle);
...
}

``````
1. Modify producer table with Fixed Rewards that is to be claimed.

``````void system_contract::onblock(const block_header& header) {
...
//            const system_token_type block_payment = parameters.payment_per_block;
const asset block_payment = parameters.payment_per_block;
auto prod = producers_tbl.find(producer);
if ( prod != producers_tbl.end() ) {
producers_tbl.modify( prod, 0, [&](auto& p) {
p.per_block_payments += block_payment;
});
}
``````
2. Fill `eos_bucket` for Dynamic Rewards.

``````void system_contract::onblock(const block_header& header) {
...
const uint32_t num_of_payments = header.timestamp - parameters.last_bucket_fill_time;
//            const system_token_type to_eos_bucket = num_of_payments * parameters.payment_to_eos_bucket;
const asset to_eos_bucket = num_of_payments * parameters.payment_to_eos_bucket;
parameters.eos_bucket += to_eos_bucket;
gs.set( parameters, _self );
}
``````

## How To Claim Rewards

BPs can claim rewards periodically (at most once a day), by pushing `claimrewards` actions. Fixed & Dynamic Rewards are calculated & transferred from `eosio` to the claimer.

### BP Validation

1. Check whether the account name is the same with the push sender, which means producers cannot claim rewards on behalf of others.
2. Check whether the push sender is among the producer list, and being active (activity check might be removed in the coming versions).
3. Check whether producer is claiming too frequent, no more than once a day.
``````void system_contract::claimrewards(const account_name& owner) {
require_auth(owner);
eosio_assert(current_sender() == account_name(), "claimrewards can not be part of a deferred transaction");
producers_table producers_tbl( _self, _self );
auto prod = producers_tbl.find(owner);
eosio_assert(prod != producers_tbl.end(), "account name is not in producer list");
eosio_assert(prod->active(), "producer is not active"); // QUESTION: Why do we want to prevent inactive producers from claiming their earned rewards?
if( prod->last_rewards_claim > 0 ) {
eosio_assert(now() >= prod->last_rewards_claim + seconds_per_day, "already claimed rewards within a day");
}
...
}
``````

### Fixed Rewards Calculation

This amount is set at every beginning of a new cycle mentioned above.

``````void system_contract::claimrewards(const account_name& owner) {
...
//            system_token_type rewards = prod->per_block_payments;
eosio::asset rewards = prod->per_block_payments;
...
}
``````

Dynamic rewards are calculated based on votes proportion, the number total of votes is obtained by iterating the producer table.

1. Define a pointer index to the end of the producer table. This calculation will loop from newer registered producer to older.

``````void system_contract::claimrewards(const account_name& owner) {
...
auto idx = producers_tbl.template get_index<N(prototalvote)>();
auto itr = --idx.end();
...
``````
2. Loop from the tail to head, sum active producers' votes up into `total_producer_votes`. Which is all valid votes across the network. `num_of_payed_producers = 121`, defined in the system contract, consists of 21 BPs and 100 candidates.

``````void system_contract::claimrewards(const account_name& owner) {
...
bool is_among_payed_producers = false;
uint128_t total_producer_votes = 0;
uint32_t n = 0;
while( n < num_of_payed_producers ) {
if( !is_among_payed_producers ) {
if( itr->owner == owner )
is_among_payed_producers = true;
}
if( itr->active() ) {
++n;
}
if( itr == idx.begin() ) {
break;
}
--itr;
}

...
}
``````

### Dynamic Rewards Calculation

1. Get global states parameters.
2. Calculate Dynamic Rewards by
$gs.eos\_bucket.amount\times {producer\_votes \over total\_producer\_votes}$
3. Add dynamic rewards to `rewards`, this amount will be the final one transferred to the BP.
4. Deduct this dynamic rewards from the global `gs.eos_bucket`, the remaining amount will be claimed by others.
``````   ...
if (is_among_payed_producers && total_producer_votes > 0) {
global_state_singleton gs( _self, _self );
if( gs.exists() ) {
auto parameters = gs.get();
//                  auto share_of_eos_bucket = system_token_type( static_cast<uint64_t>( (prod->total_votes * parameters.eos_bucket.quantity) / total_producer_votes ) ); // This will be improved in the future when total_votes becomes a double type.
auto share_of_eos_bucket = eosio::asset( static_cast<int64_t>( (prod->total_votes * parameters.eos_bucket.amount) / total_producer_votes ) );
rewards += share_of_eos_bucket;
parameters.eos_bucket -= share_of_eos_bucket;
gs.set( parameters, _self );
}
}
...
}
``````

### Transfer Rewards To The Producer

1. Update producer table, set `last_reward_claim` to the current time, and reset block production rewards `per_block_payments` to be 0.

``````void system_contract::claimrewards(const account_name& owner) {
...
//            eosio_assert( rewards > system_token_type(), "no rewards available to claim" );
eosio_assert( rewards > asset(0, S(4,EOS)), "no rewards available to claim" );

producers_tbl.modify( prod, 0, [&](auto& p) {
p.last_rewards_claim = now();
p.per_block_payments.amount = 0;
});
...
}
``````
2. Push an inline action, transferring corresponding amount from `eosio` account to the push sending producer.

``````void system_contract::claimrewards(const account_name& owner) {
...

INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)}, { N(eosio), owner, rewards, std::string("producer claiming rewards") } );
}
``````

For the time being rewards are distributed from a super user `eosio`, which means additional tokens will be created and assigned to the same account who is able to issue them upon the whole network launch.

## Conclusion

1. Block producers get Fixed Rewards from blocks they are to produce, and Dynamic Rewards from proportion of votes they possess.

2. Payment per block might fluctuate based on BPs' parameters. Based on current configuration, 1.5898 eos tokens will become "visible" every second, which likely to be reduced upon release.

3. Producers have to initiate rewards claim from the system, no producer is able to claim more than once a day.

4. Some imperfect implementations (lack of constraints, etc) from the current code, we assume this is a stopgap for easy test and look forward to an improvement in the coming versions.

In the following articles, we are going to talk about some detailed implementation about voting process, including producer registration, producer voting and proxy related stuff.

Stay tuned with Eosio.SG: Telegram, Medium.

Sort Order:
·  last year

Great article @eosiosg! Note that the payment algorithm changed and the number of BPs that are paid is now based on whether they earn at least 100 tokens per day. It would be great if you’d update the article.

https://github.com/EOSIO/eos/blob/slim/contracts/eosio.system/producer_pay.cpp#L77

·  last year

Thanks @gleehokie! yup it has been changed, while we can't update the content of this article since it was posted more than 7 days already, we would like to post a new article to make the update in the future :)