LIP: 0042
Title: Define state transitions of Reward module
Author: Iker Alustiza <[email protected]>
Mehmet Egemen Albayrak <[email protected]>
Discussions-To: https://research.lisk.com/t/define-state-transitions-of-reward-module
Status: Active
Type: Standards Track
Created: 2021-08-03
Updated: 2024-01-04
Requires: 0042, 0051, 0058
The Reward module provides the base reward system for a blockchain developed with the Lisk SDK. In this LIP, we specify the protocol logic that this module injects during the block lifecycle as well as the functions that can be called from off-chain services.
This LIP is licensed under the Creative Commons Zero 1.0 Universal.
The goal of this LIP is to modularize the rewards assignment logic when a new block is generated. For this purpose, we specify the Reward module, which will make it easier to customize this logic and replace it if necessary.
The Reward module induces specific logic after the application of a block:
- After applying the block, a certain quantity of the native token of the blockchain is minted and assigned to the block generator. The exact amount assigned to the block generator, i.e., the reward, is calculated depending on the rules of the Random module and the property
impliesMaxPrevotes
of the block header.
In this section, we specify the protocol logic in the lifecycle of a block injected by the Reward module as well as the functions that can be called from off-chain services. It depends on the token and random modules.
Name | Type | Validation | Description |
---|---|---|---|
BlockHeader | object | see LIP 0055 | The header of a block. |
Block | object | see LIP 0055 | The block object. |
BlockAssets | object | see LIP 0055 | An object representing the block assets property of a block. |
Name | Type | Value | Description |
---|---|---|---|
MODULE_NAME_REWARD |
string | "reward" | The module name of the Reward module. |
EVENT_NAME_REWARD_MINTED |
string | "rewardMinted" | Name of the event during minting of the rewards. |
REWARD_NO_REDUCTION |
uint32 | 0 | Return code for no block reward reduction. |
REWARD_REDUCTION_SEED_REVEAL |
uint32 | 1 | Return code for block reward reduction because of the failed seed reveal. |
REWARD_REDUCTION_MAX_PREVOTES |
uint32 | 2 | Return code for block reward reduction because the block header does not imply the maximal number of prevotes. |
REWARD_REDUCTION_NO_ACCOUNT |
uint32 | 3 | Return code for block reward reduction because the TOKEN_ID_REWARD account is not initialized. |
Configurable Constants | Suggested Value | ||
TOKEN_ID_REWARD |
bytes | specified as part of module configuration | The token ID of the token used for the reward system of the blockchain. |
REWARD_REDUCTION_FACTOR_BFT |
uint32 | 4 | The reduction factor for the block reward in case the block header does not imply the maximal number of prevotes. |
The Reward module triggers the minting of rewards in the fungible token identified by the value of TOKEN_ID_REWARD
, which denotes a token ID. The value of TOKEN_ID_REWARD
is set according to the initial configuration of the Reward module.
As part of the Reward module configuration, the module has to define certain reward brackets, i.e., the values of the default block reward depending on the height of the block. For this LIP, we assume the reward brackets are given by the function getDefaultRewardAtHeight(height)
, which returns a 64-bit unsigned integer value, the default block reward, given the block height height
as input.
As an example, consider the reward brackets for the Lisk Mainchain:
Heights | Default Reward |
---|---|
From 1,451,520 to 4,451,519 | 5 × 108 |
From 4,451,520 to 7,451,519 | 4 × 108 |
From 7,451,520 to 10,451,519 | 3 × 108 |
From 10,451,520 to 13,451,519 | 2 × 108 |
From 13,451,520 onwards | 1 × 108 |
This corresponds to default rewards of 5 LSK, 4 LSK, 3 LSK, 2 LSK, and 1 LSK respectively.
Calling a function fct
from another module (named module
) is represented by module.fct(required inputs)
.
This module does not define any state store.
This module does not define any command.
The event is emitted when the reward is minted. In case of a reward reduction, it provides information about the reason for the reduction. This event has name = EVENT_NAME_REWARD_MINTED
.
generatorAddress
: the address of the block generator that obtains the reward.
rewardMintedDataSchema = {
"type": "object",
"required": ["amount", "reduction"],
"properties": {
"amount": {
"dataType": "uint64",
"fieldNumber": 1
},
"reduction": {
"dataType": "uint32",
"fieldNumber": 2
}
}
}
amount
: the amount of rewards minted.reduction
: an integer indicating whether the reward was reduced and for which reason. Allowed values are:REWARD_NO_REDUCTION
,REWARD_REDUCTION_SEED_REVEAL
,REWARD_REDUCTION_MAX_PREVOTES
, andREWARD_REDUCTION_NO_ACCOUNT
.
The Reward module has the following internal function.
This function is used to retrieve the reward of a block at a given height.
amount
: amount of block reward to be minted.reduction
: an integer indicating whether the reward is reduced. Possible values areREWARD_NO_REDUCTION
,REWARD_REDUCTION_SEED_REVEAL
,REWARD_REDUCTION_MAX_PREVOTES
, andREWARD_REDUCTION_NO_ACCOUNT
.
def getBlockReward(blockHeader: BlockHeader, blockAssets: BlockAssets) -> tuple[uint64, uint32]:
if Random.isSeedRevealValid(blockHeader.generatorAddress, blockAssets) == False:
return (0, REWARD_REDUCTION_SEED_REVEAL)
defaultReward = getDefaultRewardAtHeight(blockHeader.height)
if blockHeader.impliesMaxPrevotes == False:
return (defaultReward // REWARD_REDUCTION_FACTOR_BFT, REWARD_REDUCTION_MAX_PREVOTES)
return (defaultReward, REWARD_NO_REDUCTION)
This module does not define any specific logic for other modules.
This section specifies the non-trivial or recommended endpoints of the Reward module and does not include all endpoints.
This function is used to retrieve the expected default reward at a given height.
The height of a block as a 32-bit unsigned integer.
The default reward of the block as a 64-bit unsigned integer.
Before the transactions of a block b
are executed, the following logic is executed:
def beforeTransactionsExecute(b: Block) -> None:
(ctx.blockReward, ctx.reduction) = getBlockReward(b.header, b.blockAssets)
Here, we calculate the reward for the block generator; then this value will be stored in the context in memory for the afterTransactionsExecute
in order to mint this reward and assign it to the generator. The reason for calculating the reward here is to make sure that during the call to getBlockReward
function, the subsequent call to isSeedRevealValid
function of Random module takes the value from the validatorReveals
array. Therefore, if we calculate the reward in afterTransactionsExecute
, there might be a case where validatorReveals
array are being updated by Random module before calculating the reward and then it might not produce a correct result.
The following function assigns block rewards after all transactions in the block are executed.
def afterTransactionsExecute(b: Block) -> None:
if ctx.blockReward > 0:
if Token.userSubstoreExists(b.header.generatorAddress, TOKEN_ID_REWARD):
Token.mint(b.header.generatorAddress, TOKEN_ID_REWARD, ctx.blockReward)
else:
ctx.blockReward = 0
ctx.reduction = REWARD_REDUCTION_NO_ACCOUNT
emitEvent(
module = MODULE_NAME_REWARD,
name = EVENT_NAME_REWARD_MINTED,
data = {
"amount": ctx.blockReward,
"reduction": ctx.reduction
},
topics = [b.header.generatorAddress]
)
where TOKEN_ID_REWARD
is the token ID of the token used for the reward system of the blockchain.
This LIP defines the interface for the Reward module but does not introduce any change to the protocol, hence it is a backward compatible change.