Skip to content

Commit

Permalink
Added block reward
Browse files Browse the repository at this point in the history
fixes: #246
  • Loading branch information
Daniel Graczer committed Mar 25, 2021
1 parent c533f6e commit 0957ff0
Show file tree
Hide file tree
Showing 7 changed files with 1,066 additions and 21 deletions.
691 changes: 691 additions & 0 deletions source/agora/consensus/Reward.d

Large diffs are not rendered by default.

77 changes: 77 additions & 0 deletions source/agora/consensus/data/Params.d
Expand Up @@ -21,6 +21,7 @@ module agora.consensus.data.Params;

import agora.common.Amount;
import agora.consensus.data.Block;
import agora.consensus.Reward;
import agora.crypto.Key;

import core.time;
Expand Down Expand Up @@ -50,6 +51,13 @@ public immutable class ConsensusParams
mixin ROProperty!("PayoutPeriod", "payout_period");
mixin ROProperty!("SlashPenaltyAmount", "slash_penalty_amount");
mixin ROProperty!("GenesisTimestamp", "genesis_timestamp");
mixin ROProperty!("BlockRewardFactorA", "block_reward_factor_a");
mixin ROProperty!("BlockRewardFactorB", "block_reward_factor_b");
mixin ROProperty!("BlockRewardFactorC", "block_reward_factor_c");
mixin ROProperty!("BlockRewardGap", "block_reward_gap");
mixin ROProperty!("BlockRewardDelay", "block_reward_delay");
mixin ROProperty!("ValidatorBlockRewards", "validator_block_rewards");
mixin ROProperty!("FoundationBlockRewards", "foundation_block_rewards");

/***************************************************************************
Expand All @@ -71,6 +79,9 @@ public immutable class ConsensusParams
this.Genesis = genesis;
this.CommonsBudgetAddress = commons_budget_address,
this.BlockInterval = block_interval;

if (!config.validator_block_rewards.length)
config.validator_block_rewards = ConsensusConfig.DefaultValidatorBlockRewards;
this.data = config;
}

Expand All @@ -86,6 +97,7 @@ public immutable class ConsensusParams
validator_cycle: validator_cycle,
max_quorum_nodes: max_quorum_nodes,
quorum_threshold: quorum_threshold,
validator_block_rewards: ConsensusConfig.DefaultValidatorBlockRewards,
};
this(GenesisBlock, CommonsBudget.address, config);
}
Expand Down Expand Up @@ -125,6 +137,71 @@ public struct ConsensusConfig

/// The amount of a penalty for slashed validators
public Amount slash_penalty_amount = 10_000.coins;

/// The number of blocks between 2 block reward payout
/// The value of 3 means block reward payout happens at the block 1, 4, 7...
public ushort block_reward_gap = 10;

/// factor 'a' in the block reward reduction function of f(x) = b*e^(a*x) + c
public double block_reward_factor_a = 0.046;

/// factor 'b' in the block reward reduction function of f(x) = b*e^(a*x) + c
public double block_reward_factor_b = 1;

/// factor 'c' in the block reward reduction function of f(x) = b*e^(a*x) + c
public double block_reward_factor_c = 1;

/// The delay (measured in blocks) after which block reward is payed
/// The value of 5 means the block reward payout for confirming blocks
/// [(X-confirmation_payout_gap + 1), (X)] happens at block (X + block_reward_delay)
public ushort block_reward_delay = 5;

/// Block rewards given to the validators for 128 years
public immutable(BlockRewardsTup)[] validator_block_rewards;

/// Block rewards given to the foundation for roughly 6 years
public immutable(BlockRewardsTup)[] foundation_block_rewards =
[
mbr(315_360_000_0000000), mbr(315_360_000_0000000), mbr(315_360_000_0000000),
mbr(315_360_000_0000000), mbr(315_360_000_0000000), mbr(223_200_000_0000000, 22_320_000)
];

/// Default block rewards given to validators
/// Not assigned directly to `validator_block_rewards` here to avoid
/// 'cannot inline default argument' compiler error
private static immutable BlockRewardsTup[] DefaultValidatorBlockRewards =
[
mbr(170_294_400_0000000), mbr(159_548_823_0000000), mbr(149_481_293_0000000), mbr(140_049_023_0000000),
mbr(131_211_930_0000000), mbr(122_932_457_0000000), mbr(115_175_419_0000000), mbr(107_907_850_0000000),
mbr(101_098_865_0000000), mbr(94_719_526_0000000), mbr(88_742_724_0000000), mbr(83_143_058_0000000),
mbr(77_896_731_0000000), mbr(72_981_448_0000000), mbr(68_376_318_0000000), mbr(64_061_773_0000000),
mbr(60_019_475_0000000), mbr(56_232_246_0000000), mbr(52_683_991_0000000), mbr(49_359_631_0000000),
mbr(46_245_039_0000000), mbr(43_326_977_0000000), mbr(40_593_044_0000000), mbr(38_031_623_0000000),
mbr(35_631_828_0000000), mbr(33_383_460_0000000), mbr(31_276_963_0000000), mbr(29_303_387_0000000),
mbr(27_454_343_0000000), mbr(25_721_974_0000000), mbr(24_098_918_0000000), mbr(22_578_276_0000000),
mbr(21_153_587_0000000), mbr(19_818_795_0000000), mbr(18_568_229_0000000), mbr(17_396_574_0000000),
mbr(16_298_850_0000000), mbr(15_270_393_0000000), mbr(14_306_831_0000000), mbr(13_404_070_0000000),
mbr(12_558_273_0000000), mbr(11_765_846_0000000), mbr(11_023_421_0000000), mbr(10_327_843_0000000),
mbr(9_676_156_0000000), mbr(9_065_591_0000000), mbr(8_493_552_0000000), mbr(7_957_609_0000000),
mbr(7_455_484_0000000), mbr(6_985_043_0000000), mbr(6_544_287_0000000), mbr(6_131_342_0000000),
mbr(5_744_454_0000000), mbr(5_381_979_0000000), mbr(5_042_376_0000000), mbr(4_724_203_0000000),
mbr(4_426_105_0000000), mbr(4_146_818_0000000), mbr(3_885_154_0000000), mbr(3_640_001_0000000),
mbr(3_410_317_0000000), mbr(3_195_126_0000000), mbr(2_993_513_0000000), mbr(2_804_623_0000000),
mbr(2_627_651_0000000), mbr(2_461_846_0000000), mbr(2_306_504_0000000), mbr(2_160_963_0000000),
mbr(2_024_606_0000000), mbr(1_896_854_0000000), mbr(1_777_162_0000000), mbr(1_665_023_0000000),
mbr(1_559_960_0000000), mbr(1_461_527_0000000), mbr(1_369_305_0000000), mbr(1_282_901_0000000),
mbr(1_201_950_0000000), mbr(1_126_107_0000000), mbr(1_055_050_0000000), mbr(988_476_0000000),
mbr(926_103_0000000), mbr(867_666_0000000), mbr(812_917_0000000), mbr(761_622_0000000), mbr(713_563_0000000),
mbr(668_537_0000000), mbr(626_353_0000000), mbr(586_830_0000000), mbr(549_801_0000000), mbr(515_108_0000000),
mbr(482_605_0000000), mbr(452_153_0000000), mbr(423_622_0000000), mbr(396_891_0000000), mbr(371_847_0000000),
mbr(348_384_0000000), mbr(326_401_0000000), mbr(305_805_0000000), mbr(286_509_0000000), mbr(268_430_0000000),
mbr(251_492_0000000), mbr(235_623_0000000), mbr(220_755_0000000), mbr(206_825_0000000), mbr(193_775_0000000),
mbr(181_548_0000000), mbr(170_092_0000000), mbr(159_359_0000000), mbr(149_304_0000000), mbr(139_883_0000000),
mbr(131_056_0000000), mbr(122_786_0000000), mbr(115_038_0000000), mbr(107_780_0000000), mbr(100_979_0000000),
mbr(94_607_0000000), mbr(88_637_0000000), mbr(83_044_0000000), mbr(77_804_0000000), mbr(72_895_0000000),
mbr(68_295_0000000), mbr(63_986_0000000), mbr(59_948_0000000), mbr(56_165_0000000), mbr(52_621_0000000),
mbr(49_301_0000000), mbr(46_190_0000000), mbr(43_275_0000000)
];
}

/// Inserts properties functions aliasing `ConsensusConfig`
Expand Down
11 changes: 2 additions & 9 deletions source/agora/consensus/validation/Block.d
Expand Up @@ -107,7 +107,7 @@ public string isInvalidReason (in Block block, Engine engine, Height prev_height
Point delegate (in Height, ulong) nothrow @safe getValidatorAtIndex,
Point delegate (in Point, in Height) nothrow @safe getCommitmentNonce,
ulong prev_time_offset, ulong curr_time_offset, Duration block_time_tolerance,
Transaction[] delegate (in Transaction[] tx_set, in uint[] missing_validators)
Transaction[] delegate (in Transaction[] tx_set, in uint[] missing_validators, Height height)
nothrow @safe getCoinbaseTX,
string file = __FILE__, size_t line = __LINE__) nothrow @safe
{
Expand Down Expand Up @@ -148,13 +148,6 @@ public string isInvalidReason (in Block block, Engine engine, Height prev_height
if (only_coinbase)
return "Block: Must contain other transactions than Coinbase";

auto expected_cb_txs = getCoinbaseTX(block.txs,
block.header.missing_validators);
auto incoming_cb_txs = block.txs.filter!(
tx => tx.type == TxType.Coinbase);
if (!isPermutation(expected_cb_txs, incoming_cb_txs))
return "Invalid Coinbase transaction";

Hash[] merkle_tree;
if (block.header.merkle_root != Block.buildMerkleTree(block.txs, merkle_tree))
return "Block: Merkle root does not match header's";
Expand Down Expand Up @@ -679,7 +672,7 @@ version (unittest)
},
prev_time_offset, (curr_time_offset == ulong.max) ? block.header.time_offset : curr_time_offset,
block_time_tolerance,
(in Transaction[] tx_set, in uint[] missing_validators)
(in Transaction[] tx_set, in uint[] missing_validators, Height height)
{
return (Transaction[]).init;
});
Expand Down
154 changes: 146 additions & 8 deletions source/agora/node/Ledger.d
Expand Up @@ -26,6 +26,7 @@ import agora.common.Config;
import agora.common.ManagedDatabase;
import agora.common.Set;
import agora.common.Types;
import agora.consensus.Reward;
import agora.consensus.data.Block;
import agora.consensus.protocol.Data;
import agora.consensus.data.Enrollment;
Expand Down Expand Up @@ -126,6 +127,9 @@ public class Ledger
/// but less than current time + block_time_offset_tolerance
public Duration block_time_offset_tolerance;

/// Block Reward Calculator
private Reward reward;

/***************************************************************************
Constructor
Expand Down Expand Up @@ -165,6 +169,7 @@ public class Ledger
this.clock = clock;
this.block_time_offset_tolerance = block_time_offset_tolerance;
this.storage.load(params.Genesis);
this.reward = new Reward(params);

// ensure latest checksum can be read
this.last_block = this.storage.readLastBlock();
Expand Down Expand Up @@ -356,6 +361,74 @@ public class Ledger
return true;
}

/***************************************************************************
Returns the public keys of the validators that signed the block at
the specified height, and and also not classified as 'missing' validator
Params:
height = block height
Returns: the public keys of the validators that signed the block at
the specified height, and and also not
classified as 'missing' validator
***************************************************************************/

private Point[] getRewardEligibleValidators (Height height) nothrow @safe
{
Point[] keys;
Block block;
try
{
block = this.storage.readBlock(height);
}
catch (Exception e) {
log.error("unable to read block at height: {}", height);
return null;
}

immutable validator_cnt = enroll_man.getCountOfValidators(height);
foreach (i; 0 .. validator_cnt)
{
auto validator_pkey = enroll_man.getValidatorAtIndex(height, i);
if (block.header.validators[i] && !block.header.missing_validators.canFind(i))
keys ~= validator_pkey;
}

return keys;
}

/***************************************************************************
Returns a map indexed by the public keys of validators who signed blocks
and also not classified as 'missing' validator at least once between
`start_height` and `end_height`
The value corresponding to the public key shows how many blocks the
validator signed between `start_height` and `end_height`
Params:
start_height = starting block height(inclusive)
end_height = ending block height(inclusive)
Returns: a map indexed by public keys of validators who signed blocks
and also not classified as 'missing' validator at least once
between `start_height` and `end_height`
***************************************************************************/

private ushort[Point] getRewardEligibleValidators (Height start_height, Height end_height) nothrow @safe
{
ushort[Point] signed_cnt_keys_map;

foreach (height; start_height .. end_height + 1)
foreach (point; getRewardEligibleValidators(height))
signed_cnt_keys_map[point]++;

return signed_cnt_keys_map;
}

/***************************************************************************
Add a validated block to the Ledger.
Expand Down Expand Up @@ -555,17 +628,21 @@ public class Ledger
tot_fee = Total fee amount (incl. data)
tot_data_fee = Total data fee amount
missing_validators = MPVs
height = the height for which the coinbase transaction should be returned
Returns:
List of expected Coinbase TXs
One or zero Coinbase TX
***************************************************************************/

public Transaction[] getCoinbaseTX (in Amount tot_fee, in Amount tot_data_fee,
in uint[] missing_validators) nothrow @safe
in uint[] missing_validators, in Height height) nothrow @safe
{
const next_height = this.getBlockHeight() + 1;

////////
// calculating transaction fee reward
///////
UTXO[] stakes;
this.enroll_man.getValidatorStakes(&this.utxo_set.peekUTXO, stakes,
missing_validators);
Expand All @@ -579,17 +656,77 @@ public class Ledger
[],
);

ulong[Point] pkey_to_output_ind;
// pay the commons budget
if (commons_fee > Amount(0))
{
coinbase_tx.outputs ~= Output(commons_fee,
this.params.CommonsBudgetAddress);
pkey_to_output_ind[this.params.CommonsBudgetAddress.data] = pkey_to_output_ind.length;
}

// pay the validator for the past blocks
if (auto payouts = this.fee_man.getAccumulatedFees(next_height))
foreach (pair; payouts.byKeyValue())
if (pair.value > Amount(0))
{
coinbase_tx.outputs ~= Output(pair.value, pair.key);
pkey_to_output_ind[pair.key.data] = pkey_to_output_ind.length;
}

////////
// calculating block reward
///////
if (reward.isPayoutTime(height))
{
immutable period_end = Height(height - params.BlockRewardDelay);
immutable period_start = Height(period_end - params.BlockRewardGap + 1);
Amount remainder;

// calculate the penalty for missing singatures
auto pkey_sigcounts = getRewardEligibleValidators(period_start, period_end);
double total_actual_sigcount = pkey_sigcounts.byValue().fold!((a, b) => a + b)(0);
log.trace("Total actual signatures count: {}", total_actual_sigcount);
double total_expected_sigcount = iota(period_start.value, period_end.value + 1)
.map!(height => this.enroll_man.getCountOfValidators(Height(height))).fold!((a, b) => a + b)(cast(size_t) 0);
log.trace("Total expected signatures count: {}", total_expected_sigcount);
immutable comply_perc = (total_actual_sigcount / total_expected_sigcount) * 100;

// calculate the validator block reward for each validator
auto each_validator_reward = this.reward.getEachValidatorReward(
height, getRewardEligibleValidators(period_start, period_end), remainder, comply_perc);

// calculate the foundation block reward
Amount foundation_reward = this.reward.getTotalCommonsBudgetReward(height, comply_perc);
foundation_reward.mustAdd(remainder);
each_validator_reward.update(
Point(params.CommonsBudgetAddress[]),
{
auto amount = Amount(foundation_reward);
return amount;
},
(ref Amount amount)
{
amount.mustAdd(foundation_reward);
return amount;
}
);

// merge the block reward with fee reward
foreach (ref pkey_reward; each_validator_reward.byKeyValue())
if (pkey_reward.value != Amount(0))
{
if (auto pkey_ind_found = (pkey_reward.key in pkey_to_output_ind))
coinbase_tx.outputs[*pkey_ind_found].value.mustAdd(pkey_reward.value);
else
coinbase_tx.outputs ~= Output(pkey_reward.value, PublicKey(pkey_reward.key[]));
}

}

// This method returns an array of coinbase transaction even if the array
// contains at most one element. Returning an array as opposed to a
// Nullable!Transaction greatly simplifies the calling code
return coinbase_tx.outputs.length > 0 ? [coinbase_tx] : [];
}

Expand All @@ -610,19 +747,20 @@ public class Ledger
Params:
tx_set = Transaction set to generate the CoinBase TX for
missing_validators = MPVs
height = the height for which the coinbase transaction should be returned
Returns:
List of expected Coinbase TXs
One or zero Coinbase TX
***************************************************************************/

public Transaction[] getCoinbaseTX (in Transaction[] tx_set,
in uint[] missing_validators) nothrow @safe
in uint[] missing_validators, Height height) nothrow @safe
{
Amount tot_fee, tot_data_fee;
this.fee_man.getTXSetFees(tx_set, &this.utxo_set.peekUTXO, tot_fee,
tot_data_fee);
return this.getCoinbaseTX(tot_fee, tot_data_fee, missing_validators);
return this.getCoinbaseTX(tot_fee, tot_data_fee, missing_validators, height);
}

/***************************************************************************
Expand Down Expand Up @@ -920,7 +1058,7 @@ public class Ledger
}

auto expected_cb_txs = this.getCoinbaseTX(tot_fee,
tot_data_fee, data.missing_validators);
tot_data_fee, data.missing_validators, expect_height);
auto excepted_cb_hashes = expected_cb_txs.map!(tx => tx.hashFull());
assert(expected_cb_txs.length <= 1);

Expand Down Expand Up @@ -1069,7 +1207,7 @@ public class ValidatingLedger : Ledger
// Dont append a CB TX to an empty TX set
if (pre_cb_len > 0)
data.tx_set ~= this.getCoinbaseTX(tot_fee, tot_data_fee,
data.missing_validators).map!(tx => tx.hashFull()).array;
data.missing_validators, next_height).map!(tx => tx.hashFull()).array;
// No more than 1 CB per block
assert(data.tx_set.length - pre_cb_len <= 1);
}
Expand Down Expand Up @@ -1920,7 +2058,7 @@ unittest
const Transaction[] empty_tx_set;
const uint[] empty_mpvs;

auto cb_tx_set = ledger.getCoinbaseTX(empty_tx_set, empty_mpvs);
auto cb_tx_set = ledger.getCoinbaseTX(empty_tx_set, empty_mpvs, Height(1));
data.tx_set ~= cb_tx_set.map!(tx => tx.hashFull()).array;
assert(data.tx_set.length == 1);
// Coinbase only nomination, Should not validate
Expand Down

0 comments on commit 0957ff0

Please sign in to comment.