From 0c981ff0d211eea9a19b9e9d20ac315cf58d9a23 Mon Sep 17 00:00:00 2001 From: Max <82761650+MaxMustermann2@users.noreply.github.com> Date: Sat, 2 Sep 2023 22:51:08 +0000 Subject: [PATCH] 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> --- internal/chain/engine.go | 20 ++++++- internal/chain/reward.go | 79 +++++++++++++++------------ internal/configs/sharding/instance.go | 14 +++++ internal/configs/sharding/localnet.go | 12 ++++ internal/configs/sharding/mainnet.go | 4 +- internal/configs/sharding/partner.go | 10 ++++ internal/configs/sharding/testnet.go | 13 +++++ 7 files changed, 114 insertions(+), 38 deletions(-) diff --git a/internal/chain/engine.go b/internal/chain/engine.go index 18096c157..4f3aac9ff 100644 --- a/internal/chain/engine.go +++ b/internal/chain/engine.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 diff --git a/internal/chain/reward.go b/internal/chain/reward.go index f06556412..784338093 100644 --- a/internal/chain/reward.go +++ b/internal/chain/reward.go @@ -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 } diff --git a/internal/configs/sharding/instance.go b/internal/configs/sharding/instance.go index 1bcd79a34..0930c20b0 100644 --- a/internal/configs/sharding/instance.go +++ b/internal/configs/sharding/instance.go @@ -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, diff --git a/internal/configs/sharding/localnet.go b/internal/configs/sharding/localnet.go index ebb494196..fb6050b1d 100644 --- a/internal/configs/sharding/localnet.go +++ b/internal/configs/sharding/localnet.go @@ -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(), + ) ) diff --git a/internal/configs/sharding/mainnet.go b/internal/configs/sharding/mainnet.go index e395912e0..2106b58d8 100644 --- a/internal/configs/sharding/mainnet.go +++ b/internal/configs/sharding/mainnet.go @@ -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 { diff --git a/internal/configs/sharding/partner.go b/internal/configs/sharding/partner.go index 7514656d3..2730726e0 100644 --- a/internal/configs/sharding/partner.go +++ b/internal/configs/sharding/partner.go @@ -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(), +) diff --git a/internal/configs/sharding/testnet.go b/internal/configs/sharding/testnet.go index da0143ef9..93daeaf61 100644 --- a/internal/configs/sharding/testnet.go +++ b/internal/configs/sharding/testnet.go @@ -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(), + ) )