HIP-30: Emission split (#4497)

* HIP-30: sharding configuration boilerplate

* update comments

* goimports

* HIP-30: minimum validator commission of 7%

Based on #4495, which must be merged before this PR. This PR should be
rebased with dev after #4495 is merged to retain atomicity of changes by
pull request.

* goimports

* HIP-30: Emission split implementation

Note that the allocated split of the emission goes directly to the
recipient (and not via the Reward). This is because rewards are indexed
by validator and not by delegator, and the recipient may/may not have
any delegations which we can reward. Even if one was guaranteed to
exist, it would mess up the math of the validator.

* set up mainnet recipient of emission split

* HIP-30: Emission split addresses for non mainnet

* update test

* update test

* Update mainnet.go

---------

Co-authored-by: Casey Gardiner <117784577+ONECasey@users.noreply.github.com>
pull/4491/merge
Max 1 year ago committed by GitHub
parent 62feec962b
commit 0c981ff0d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 20
      internal/chain/engine.go
  2. 79
      internal/chain/reward.go
  3. 14
      internal/configs/sharding/instance.go
  4. 12
      internal/configs/sharding/localnet.go
  5. 4
      internal/configs/sharding/mainnet.go
  6. 10
      internal/configs/sharding/partner.go
  7. 13
      internal/configs/sharding/testnet.go

@ -6,6 +6,7 @@ import (
"sort"
"time"
"github.com/harmony-one/harmony/common/denominations"
"github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/numeric"
@ -312,7 +313,7 @@ func (e *engineImpl) Finalize(
// Accumulate block rewards and commit the final state root
// Header seems complete, assemble into a block and return
payout, err := AccumulateRewardsAndCountSigs(
remainder, payout, err := AccumulateRewardsAndCountSigs(
chain, state, header, beacon, sigsReady,
)
if err != nil {
@ -332,6 +333,23 @@ func (e *engineImpl) Finalize(
// TODO: make the viewID fetch from caller of the block proposal.
header.SetViewID(new(big.Int).SetUint64(viewID()))
// Add the emission recovery split to the balance
if chain.Config().IsHIP30(header.Epoch()) {
// convert to ONE - note that numeric.Dec
// is designed for staking decimals and not
// ONE balances so we use big.Int for this math
remainderOne := new(big.Int).Div(
remainder.Int, big.NewInt(denominations.One),
)
// this goes directly to the balance (on shard 0, of course)
// because the reward mechanism isn't built to handle
// rewards not obtained from any delegations
state.AddBalance(
shard.Schedule.InstanceForEpoch(header.Epoch()).
HIP30RecoveryAddress(),
remainderOne,
)
}
// Finalize the state root
header.SetRoot(state.IntermediateRoot(chain.Config().IsS3(header.Epoch())))
return types.NewBlock(header, txs, receipts, outcxs, incxs, stks), payout, nil

@ -139,11 +139,11 @@ func lookupDelegatorShares(
func accumulateRewardsAndCountSigsBeforeStaking(
bc engine.ChainReader, state *state.DB,
header *block.Header, sigsReady chan bool,
) (reward.Reader, error) {
) (numeric.Dec, reward.Reader, error) {
parentHeader := bc.GetHeaderByHash(header.ParentHash())
if parentHeader == nil {
return network.EmptyPayout, errors.Errorf(
return numeric.ZeroDec(), network.EmptyPayout, errors.Errorf(
"cannot find parent block header in DB at parent hash %s",
header.ParentHash().Hex(),
)
@ -151,11 +151,11 @@ func accumulateRewardsAndCountSigsBeforeStaking(
if parentHeader.Number().Cmp(common.Big0) == 0 {
// Parent is an epoch block,
// which is not signed in the usual manner therefore rewards nothing.
return network.EmptyPayout, nil
return numeric.ZeroDec(), network.EmptyPayout, nil
}
parentShardState, err := bc.ReadShardState(parentHeader.Epoch())
if err != nil {
return nil, errors.Wrapf(
return numeric.ZeroDec(), nil, errors.Wrapf(
err, "cannot read shard state at epoch %v", parentHeader.Epoch(),
)
}
@ -163,7 +163,7 @@ func accumulateRewardsAndCountSigsBeforeStaking(
// Block here until the commit sigs are ready or timeout.
// sigsReady signal indicates that the commit sigs are already populated in the header object.
if err := waitForCommitSigs(sigsReady); err != nil {
return network.EmptyPayout, err
return numeric.ZeroDec(), network.EmptyPayout, err
}
_, signers, _, err := availability.BallotResult(
@ -171,7 +171,7 @@ func accumulateRewardsAndCountSigsBeforeStaking(
)
if err != nil {
return network.EmptyPayout, err
return numeric.ZeroDec(), network.EmptyPayout, err
}
totalAmount := big.NewInt(0)
@ -194,12 +194,12 @@ func accumulateRewardsAndCountSigsBeforeStaking(
Int64("block-reward", stakingReward.PreStakedBlocks.Int64()).
Int64("total-amount-paid-out", totalAmount.Int64()).
Msg("Total paid out was not equal to block-reward")
return nil, errors.Wrapf(
return numeric.ZeroDec(), nil, errors.Wrapf(
network.ErrPayoutNotEqualBlockReward, "payout "+totalAmount.String(),
)
}
return network.NewPreStakingEraRewarded(totalAmount), nil
return numeric.ZeroDec(), network.NewPreStakingEraRewarded(totalAmount), nil
}
// getDefaultStakingReward returns the static default reward based on the the block production interval and the chain.
@ -240,14 +240,14 @@ func getDefaultStakingReward(bc engine.ChainReader, epoch *big.Int, blockNum uin
func AccumulateRewardsAndCountSigs(
bc engine.ChainReader, state *state.DB,
header *block.Header, beaconChain engine.ChainReader, sigsReady chan bool,
) (reward.Reader, error) {
) (numeric.Dec, reward.Reader, error) {
blockNum := header.Number().Uint64()
epoch := header.Epoch()
isBeaconChain := bc.CurrentHeader().ShardID() == shard.BeaconChainShardID
if blockNum == 0 {
err := waitForCommitSigs(sigsReady) // wait for commit signatures, or timeout and return err.
return network.EmptyPayout, err
return numeric.ZeroDec(), network.EmptyPayout, err
}
// Pre-staking era
@ -258,12 +258,12 @@ func AccumulateRewardsAndCountSigs(
// Rewards are accumulated only in the beaconchain, so just wait for commit sigs and return.
if !isBeaconChain {
err := waitForCommitSigs(sigsReady)
return network.EmptyPayout, err
return numeric.ZeroDec(), network.EmptyPayout, err
}
defaultReward := getDefaultStakingReward(bc, epoch, blockNum)
if defaultReward.IsNegative() { // TODO: Figure out whether that's possible.
return network.EmptyPayout, nil
return numeric.ZeroDec(), network.EmptyPayout, nil
}
// Handle rewards on pre-aggregated rewards era.
@ -275,12 +275,12 @@ func AccumulateRewardsAndCountSigs(
// Wait for commit signatures, or timeout and return err.
if err := waitForCommitSigs(sigsReady); err != nil {
return network.EmptyPayout, err
return numeric.ZeroDec(), network.EmptyPayout, err
}
// Only do reward distribution at the 63th block in the modulus.
if blockNum%RewardFrequency != RewardFrequency-1 {
return network.EmptyPayout, nil
return numeric.ZeroDec(), network.EmptyPayout, nil
}
return distributeRewardAfterAggregateEpoch(bc, state, header, beaconChain, defaultReward)
@ -300,7 +300,16 @@ func waitForCommitSigs(sigsReady chan bool) error {
}
func distributeRewardAfterAggregateEpoch(bc engine.ChainReader, state *state.DB, header *block.Header, beaconChain engine.ChainReader,
defaultReward numeric.Dec) (reward.Reader, error) {
rewardToDistribute numeric.Dec) (numeric.Dec, reward.Reader, error) {
epoch := header.Epoch()
defaultReward := rewardToDistribute
remainingReward := numeric.ZeroDec()
if bc.Config().IsHIP30(epoch) {
fractionToRecovery := shard.Schedule.InstanceForEpoch(epoch).HIP30EmissionFraction()
fractionToValidators := numeric.OneDec().Sub(fractionToRecovery)
defaultReward = rewardToDistribute.Mul(fractionToValidators)
remainingReward = rewardToDistribute.Mul(fractionToRecovery)
}
newRewards, payouts :=
big.NewInt(0), []reward.Payout{}
@ -331,7 +340,7 @@ func distributeRewardAfterAggregateEpoch(bc engine.ChainReader, state *state.DB,
if cxLinks := curHeader.CrossLinks(); len(cxLinks) > 0 {
crossLinks := types.CrossLinks{}
if err := rlp.DecodeBytes(cxLinks, &crossLinks); err != nil {
return network.EmptyPayout, err
return numeric.ZeroDec(), network.EmptyPayout, err
}
allCrossLinks = append(allCrossLinks, crossLinks...)
}
@ -346,7 +355,7 @@ func distributeRewardAfterAggregateEpoch(bc engine.ChainReader, state *state.DB,
payables, _, err := processOneCrossLink(bc, state, cxLink, defaultReward, i)
if err != nil {
return network.EmptyPayout, err
return numeric.ZeroDec(), network.EmptyPayout, err
}
allPayables = append(allPayables, payables...)
@ -385,29 +394,29 @@ func distributeRewardAfterAggregateEpoch(bc engine.ChainReader, state *state.DB,
for _, addr := range allAddresses {
snapshot, err := bc.ReadValidatorSnapshot(addr)
if err != nil {
return network.EmptyPayout, err
return numeric.ZeroDec(), network.EmptyPayout, err
}
due := allValidatorPayable[addr]
newRewards.Add(newRewards, due)
shares, err := lookupDelegatorShares(snapshot)
if err != nil {
return network.EmptyPayout, err
return numeric.ZeroDec(), network.EmptyPayout, err
}
if err := state.AddReward(snapshot.Validator, due, shares); err != nil {
return network.EmptyPayout, err
return numeric.ZeroDec(), network.EmptyPayout, err
}
}
utils.Logger().Debug().Int64("elapsed time", time.Now().Sub(startTimeLocal).Milliseconds()).Msg("After Chain Reward (AddReward)")
utils.Logger().Debug().Int64("elapsed time", time.Now().Sub(startTime).Milliseconds()).Msg("After Chain Reward")
return network.NewStakingEraRewardForRound(
return remainingReward, network.NewStakingEraRewardForRound(
newRewards, payouts,
), nil
}
func distributeRewardBeforeAggregateEpoch(bc engine.ChainReader, state *state.DB, header *block.Header, beaconChain engine.ChainReader,
defaultReward numeric.Dec, sigsReady chan bool) (reward.Reader, error) {
defaultReward numeric.Dec, sigsReady chan bool) (numeric.Dec, reward.Reader, error) {
newRewards, payouts :=
big.NewInt(0), []reward.Payout{}
@ -417,7 +426,7 @@ func distributeRewardBeforeAggregateEpoch(bc engine.ChainReader, state *state.DB
startTime := time.Now()
crossLinks := types.CrossLinks{}
if err := rlp.DecodeBytes(cxLinks, &crossLinks); err != nil {
return network.EmptyPayout, err
return numeric.ZeroDec(), network.EmptyPayout, err
}
utils.Logger().Debug().Int64("elapsed time", time.Now().Sub(startTime).Milliseconds()).Msg("Decode Cross Links")
@ -427,7 +436,7 @@ func distributeRewardBeforeAggregateEpoch(bc engine.ChainReader, state *state.DB
payables, _, err := processOneCrossLink(bc, state, cxLink, defaultReward, i)
if err != nil {
return network.EmptyPayout, err
return numeric.ZeroDec(), network.EmptyPayout, err
}
allPayables = append(allPayables, payables...)
@ -461,17 +470,17 @@ func distributeRewardBeforeAggregateEpoch(bc engine.ChainReader, state *state.DB
payable.EcdsaAddress,
)
if err != nil {
return network.EmptyPayout, err
return numeric.ZeroDec(), network.EmptyPayout, err
}
due := resultsHandle[bucket][payThem].payout
newRewards.Add(newRewards, due)
shares, err := lookupDelegatorShares(snapshot)
if err != nil {
return network.EmptyPayout, err
return numeric.ZeroDec(), network.EmptyPayout, err
}
if err := state.AddReward(snapshot.Validator, due, shares); err != nil {
return network.EmptyPayout, err
return numeric.ZeroDec(), network.EmptyPayout, err
}
payouts = append(payouts, reward.Payout{
Addr: payable.EcdsaAddress,
@ -487,14 +496,14 @@ func distributeRewardBeforeAggregateEpoch(bc engine.ChainReader, state *state.DB
// Block here until the commit sigs are ready or timeout.
// sigsReady signal indicates that the commit sigs are already populated in the header object.
if err := waitForCommitSigs(sigsReady); err != nil {
return network.EmptyPayout, err
return numeric.ZeroDec(), network.EmptyPayout, err
}
startTime := time.Now()
// Take care of my own beacon chain committee, _ is missing, for slashing
parentE, members, payable, missing, err := ballotResultBeaconchain(beaconChain, header)
if err != nil {
return network.EmptyPayout, errors.Wrapf(err, "shard 0 block %d reward error with bitmap %x", header.Number(), header.LastCommitBitmap())
return numeric.ZeroDec(), network.EmptyPayout, errors.Wrapf(err, "shard 0 block %d reward error with bitmap %x", header.Number(), header.LastCommitBitmap())
}
subComm := shard.Committee{ShardID: shard.BeaconChainShardID, Slots: members}
@ -505,13 +514,13 @@ func distributeRewardBeforeAggregateEpoch(bc engine.ChainReader, state *state.DB
payable,
missing,
); err != nil {
return network.EmptyPayout, err
return numeric.ZeroDec(), network.EmptyPayout, err
}
votingPower, err := lookupVotingPower(
parentE, &subComm,
)
if err != nil {
return network.EmptyPayout, err
return numeric.ZeroDec(), network.EmptyPayout, err
}
allSignersShare := numeric.ZeroDec()
@ -528,7 +537,7 @@ func distributeRewardBeforeAggregateEpoch(bc engine.ChainReader, state *state.DB
if !voter.IsHarmonyNode {
snapshot, err := bc.ReadValidatorSnapshot(voter.EarningAccount)
if err != nil {
return network.EmptyPayout, err
return numeric.ZeroDec(), network.EmptyPayout, err
}
due := defaultReward.Mul(
voter.OverallPercent.Quo(allSignersShare),
@ -537,10 +546,10 @@ func distributeRewardBeforeAggregateEpoch(bc engine.ChainReader, state *state.DB
shares, err := lookupDelegatorShares(snapshot)
if err != nil {
return network.EmptyPayout, err
return numeric.ZeroDec(), network.EmptyPayout, err
}
if err := state.AddReward(snapshot.Validator, due, shares); err != nil {
return network.EmptyPayout, err
return numeric.ZeroDec(), network.EmptyPayout, err
}
payouts = append(payouts, reward.Payout{
Addr: voter.EarningAccount,
@ -551,7 +560,7 @@ func distributeRewardBeforeAggregateEpoch(bc engine.ChainReader, state *state.DB
}
utils.Logger().Debug().Int64("elapsed time", time.Now().Sub(startTime).Milliseconds()).Msg("Beacon Chain Reward")
return network.NewStakingEraRewardForRound(
return numeric.ZeroDec(), network.NewStakingEraRewardForRound(
newRewards, payouts,
), nil
}

@ -108,6 +108,20 @@ func NewInstance(
"emission split must be within [0, 1]",
)
}
if !emissionFractionToRecovery.Equal(numeric.ZeroDec()) {
if recoveryAddress == (ethCommon.Address{}) {
return nil, errors.Errorf(
"have non-zero emission split but no target address",
)
}
}
if recoveryAddress != (ethCommon.Address{}) {
if emissionFractionToRecovery.Equal(numeric.ZeroDec()) {
return nil, errors.Errorf(
"have target address but no emission split",
)
}
}
return instance{
numShards: numShards,

@ -22,6 +22,9 @@ var feeCollectorsLocalnet = FeeCollectors{
mustAddress("0x1563915e194D8CfBA1943570603F7606A3115508"): numeric.MustNewDecFromStr("0.5"),
}
// pk: 0x3333333333333333333333333333333333333333333333333333333333333333
var hip30CollectionAddressLocalnet = mustAddress("0x5CbDd86a2FA8Dc4bDdd8a8f69dBa48572EeC07FB")
type localnetSchedule struct{}
const (
@ -36,6 +39,8 @@ const (
func (ls localnetSchedule) InstanceForEpoch(epoch *big.Int) Instance {
switch {
case params.LocalnetChainConfig.IsHIP30(epoch):
return localnetV4
case params.LocalnetChainConfig.IsFeeCollectEpoch(epoch):
return localnetV3_2
case params.LocalnetChainConfig.IsSixtyPercent(epoch):
@ -203,4 +208,11 @@ var (
numeric.ZeroDec(), ethCommon.Address{},
localnetReshardingEpoch, LocalnetSchedule.BlocksPerEpoch(),
)
localnetV4 = MustNewInstance(
2, 9, 6, 0, numeric.MustNewDecFromStr("0.68"),
genesis.LocalHarmonyAccountsV2, genesis.LocalFnAccountsV2,
emptyAllowlist, feeCollectorsLocalnet,
numeric.MustNewDecFromStr("0.25"), hip30CollectionAddressLocalnet,
localnetReshardingEpoch, LocalnetSchedule.BlocksPerEpoch(),
)
)

@ -54,8 +54,8 @@ var (
mustAddress("0xbdFeE8587d347Cd8df002E6154763325265Fa84c"): numeric.MustNewDecFromStr("0.5"),
}
hip30CollectionAddress = ethCommon.Address{}
// hip30CollectionAddress = mustAddress("0xMustAddress")
// Emission DAO
hip30CollectionAddress = mustAddress("0xD8194284df879f465ed61DBA6fa8300940cacEA3")
)
func mustAddress(addrStr string) ethCommon.Address {

@ -42,6 +42,8 @@ const (
func (ps partnerSchedule) InstanceForEpoch(epoch *big.Int) Instance {
switch {
case params.PartnerChainConfig.IsHIP30(epoch):
return partnerV4
case params.PartnerChainConfig.IsFeeCollectEpoch(epoch):
return partnerV3
case epoch.Cmp(feeCollectEpochV1) >= 0:
@ -121,3 +123,11 @@ var partnerV3 = MustNewInstance(
feeCollectorsDevnet[1], numeric.ZeroDec(), ethCommon.Address{},
partnerReshardingEpoch, PartnerSchedule.BlocksPerEpoch(),
)
var partnerV4 = MustNewInstance(
2, 5, 4, 0,
numeric.MustNewDecFromStr("0.9"), genesis.TNHarmonyAccounts,
genesis.TNFoundationalAccounts, emptyAllowlist,
feeCollectorsDevnet[1], numeric.MustNewDecFromStr("0.25"),
hip30CollectionAddressTestnet, partnerReshardingEpoch,
PartnerSchedule.BlocksPerEpoch(),
)

@ -21,6 +21,8 @@ var feeCollectorsTestnet = FeeCollectors{
mustAddress("0xb41B6B8d9e68fD44caC8342BC2EEf4D59531d7d7"): numeric.MustNewDecFromStr("0.5"),
}
var hip30CollectionAddressTestnet = mustAddress("0x58dB8BeCe892F343350D125ff22B242784a8BA38")
type testnetSchedule struct{}
const (
@ -40,6 +42,8 @@ const (
func (ts testnetSchedule) InstanceForEpoch(epoch *big.Int) Instance {
switch {
case params.TestnetChainConfig.IsHIP30(epoch):
return testnetV5
case params.TestnetChainConfig.IsFeeCollectEpoch(epoch):
return testnetV4
case epoch.Cmp(shardReductionEpoch) >= 0:
@ -157,4 +161,13 @@ var (
feeCollectorsTestnet, numeric.ZeroDec(), ethCommon.Address{},
testnetReshardingEpoch, TestnetSchedule.BlocksPerEpoch(),
)
testnetV5 = MustNewInstance(
2, 30, 8, 0.15,
numeric.MustNewDecFromStr("0.90"), genesis.TNHarmonyAccountsV1,
genesis.TNFoundationalAccounts, emptyAllowlist,
feeCollectorsTestnet, numeric.MustNewDecFromStr("0.25"),
hip30CollectionAddressTestnet, testnetReshardingEpoch,
TestnetSchedule.BlocksPerEpoch(),
)
)

Loading…
Cancel
Save