feat: add TIPEpochHalving, better style

This commit is contained in:
Gerui Wang 2025-03-11 00:31:07 +08:00
parent 7c4668fa91
commit 84ae914350
8 changed files with 183 additions and 78 deletions

View file

@ -62,6 +62,7 @@ type constant struct {
tipXDCXMinerDisable *big.Int
tipXDCXReceiverDisable *big.Int
tipUpgradeReward *big.Int
tipEpochHalving *big.Int
eip1559Block *big.Int
cancunBlock *big.Int
@ -104,6 +105,7 @@ var (
TIPXDCXMinerDisable = MaintnetConstant.tipXDCXMinerDisable
TIPXDCXReceiverDisable = MaintnetConstant.tipXDCXReceiverDisable
TIPUpgradeReward = MaintnetConstant.tipUpgradeReward
TIPEpochHalving = MaintnetConstant.tipEpochHalving
Eip1559Block = MaintnetConstant.eip1559Block
CancunBlock = MaintnetConstant.cancunBlock
@ -170,6 +172,7 @@ func CopyConstans(chainID uint64) {
TIPXDCXMinerDisable = c.tipXDCXMinerDisable
TIPXDCXReceiverDisable = c.tipXDCXReceiverDisable
TIPUpgradeReward = c.tipUpgradeReward
TIPEpochHalving = c.tipEpochHalving
Eip1559Block = c.eip1559Block
CancunBlock = c.cancunBlock

View file

@ -31,6 +31,7 @@ var DevnetConstant = constant{
eip1559Block: big.NewInt(0),
cancunBlock: big.NewInt(1702800),
tipUpgradeReward: big.NewInt(1773000),
tipEpochHalving: big.NewInt(9999999999),
trc21IssuerSMCTestNet: HexToAddress("0x0E2C88753131CE01c7551B726b28BFD04e44003F"),
trc21IssuerSMC: HexToAddress("0x8c0faeb5C6bEd2129b8674F262Fd45c4e9468bee"),

View file

@ -31,6 +31,7 @@ var MaintnetConstant = constant{
eip1559Block: big.NewInt(9999999999),
cancunBlock: big.NewInt(9999999999),
tipUpgradeReward: big.NewInt(9999999999),
tipEpochHalving: big.NewInt(9999999999),
trc21IssuerSMCTestNet: HexToAddress("0x0E2C88753131CE01c7551B726b28BFD04e44003F"),
trc21IssuerSMC: HexToAddress("0x8c0faeb5C6bEd2129b8674F262Fd45c4e9468bee"),

View file

@ -31,6 +31,7 @@ var TestnetConstant = constant{
eip1559Block: big.NewInt(71550000), // Target 14th Feb 2025
cancunBlock: big.NewInt(9999999999),
tipUpgradeReward: big.NewInt(9999999999),
tipEpochHalving: big.NewInt(9999999999),
trc21IssuerSMCTestNet: HexToAddress("0x0E2C88753131CE01c7551B726b28BFD04e44003F"),
trc21IssuerSMC: HexToAddress("0x8c0faeb5C6bEd2129b8674F262Fd45c4e9468bee"),

View file

@ -10,6 +10,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/eth/hooks"
"github.com/XinFinOrg/XDPoSChain/eth/util"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/stretchr/testify/assert"
)
@ -297,3 +298,57 @@ func TestHookRewardAfterUpgrade(t *testing.T) {
}
common.TIPUpgradeReward = backup
}
func TestRewardHalvingVanishing(t *testing.T) {
billion := big.NewInt(1000000000)
epochRewardTotal := big.NewInt(16000)
epochRewardTotal.Mul(epochRewardTotal, billion)
epochReward1 := big.NewInt(10000)
epochReward1.Mul(epochReward1, billion)
epochReward2 := big.NewInt(4000)
epochReward2.Mul(epochReward2, billion)
epochReward3 := big.NewInt(2000)
epochReward3.Mul(epochReward3, billion)
// 45 Billion - 39 Billion XDC (1 XDC = 10^9 wei)
halvingSupply := big.NewInt(6000000000)
halvingSupply.Mul(halvingSupply, billion)
sum := big.NewInt(0)
iterMax := uint64(30000000)
for i := uint64(0); i < iterMax; i++ {
r := new(big.Int).Add(util.RewardHalving(epochReward1, epochRewardTotal, halvingSupply, i), util.RewardHalving(epochReward2, epochRewardTotal, halvingSupply, i))
r.Add(r, util.RewardHalving(epochReward3, epochRewardTotal, halvingSupply, i))
if r.BitLen() == 0 {
t.Log("reward be 0 at i=", i) // reward be 0 at i= 11225088, wich is more than 200 years in the future
break
}
sum.Add(sum, r)
if i == iterMax-1 {
t.Fatal("reward should be 0 at end")
}
}
t.Log("sum", sum) // sum 5999999999982635022, which is less than total, and never reach totoal
assert.True(t, sum.Cmp(halvingSupply) < 0)
}
func TestRewardHalvingSplit(t *testing.T) {
billion := big.NewInt(1000000000)
epochRewardTotal := big.NewInt(16000)
epochRewardTotal.Mul(epochRewardTotal, billion)
epochReward1 := big.NewInt(10000)
epochReward1.Mul(epochReward1, billion)
epochReward2 := big.NewInt(4000)
epochReward2.Mul(epochReward2, billion)
epochReward3 := big.NewInt(2000)
epochReward3.Mul(epochReward3, billion)
// 45 Billion - 39 Billion XDC (1 XDC = 10^9 wei)
halvingSupply := big.NewInt(6000000000)
halvingSupply.Mul(halvingSupply, billion)
i := uint64(50000) // a random number suffice
r1 := util.RewardHalving(epochReward1, epochRewardTotal, halvingSupply, i)
r2 := util.RewardHalving(epochReward2, epochRewardTotal, halvingSupply, i)
r3 := util.RewardHalving(epochReward3, epochRewardTotal, halvingSupply, i)
t.Log(r1, r2, r3)
assert.Equal(t, int64(5), r1.Div(r1, r3).Int64()) // since epochReward1/epochReward3=5
assert.Equal(t, int64(2), r2.Div(r2, r3).Int64()) // since epochReward2/epochReward3=2
}

View file

@ -205,98 +205,80 @@ func AttachConsensusV2Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConf
}
rewardsMap["signers"] = signers[MasterNodeBeneficiary]
rewardSigners := make(map[common.Address]*big.Int)
rewardSignersProtector := make(map[common.Address]*big.Int)
rewardSignersObserver := make(map[common.Address]*big.Int)
if !chain.Config().IsTIPUpgradeReward(header.Number) {
// Get reward inflation.
chainReward := new(big.Int).Mul(new(big.Int).SetUint64(chain.Config().XDPoS.Reward), new(big.Int).SetUint64(params.Ether))
chainReward = util.RewardInflation(chain, chainReward, number, common.BlocksPerYear)
rewardSigners, err = CalculateRewardForSigner(chainReward, signers[MasterNodeBeneficiary])
originalReward := new(big.Int).Mul(new(big.Int).SetUint64(chain.Config().XDPoS.Reward), new(big.Int).SetUint64(params.Ether))
chainReward := util.RewardInflation(chain, originalReward, number, common.BlocksPerYear)
rewardSigners, err := CalculateRewardForSigner(chainReward, signers[MasterNodeBeneficiary])
if err != nil {
log.Error("[HookReward] Fail to calculate reward for masternode", "error", err)
return nil, err
}
// Add reward for coin holders.
voterResults := make(map[common.Address]interface{})
for signer, calcReward := range rewardSigners {
rewards, err := contracts.CalculateRewardForHolders(foundationWalletAddr, parentState, signer, calcReward, number)
if err != nil {
log.Error("[HookReward] Fail to calculate reward for holders.", "error", err)
return nil, err
}
if len(rewards) > 0 {
for holder, reward := range rewards {
stateBlock.AddBalance(holder, reward)
}
}
voterResults[signer] = rewards
}
rewardsMap["rewards"] = voterResults
} else {
rewardsMap["signersProtector"] = signers[ProtectorNodeBeneficiary]
rewardsMap["signersObserver"] = signers[ObserverNodeBeneficiary]
// Masternode rewards
chainReward := new(big.Int).Mul(new(big.Int).SetUint64(currentConfig.MasternodeReward), new(big.Int).SetUint64(params.Ether))
chainReward = util.RewardInflation(chain, chainReward, number, common.BlocksPerYear)
rewardSigners, err = CalculateRewardForSigner(chainReward, signers[MasterNodeBeneficiary])
if err != nil {
log.Error("[HookReward] Fail to calculate reward for masternode", "error", err)
return nil, err
epochRewardTotal := new(big.Int).SetUint64(currentConfig.MasternodeReward + currentConfig.ProtectorReward + currentConfig.ObserverReward)
type rewardWithType struct {
r uint64
t Beneficiary
key string
}
// Protector rewards
chainReward = new(big.Int).Mul(new(big.Int).SetUint64(currentConfig.ProtectorReward), new(big.Int).SetUint64(params.Ether))
chainReward = util.RewardInflation(chain, chainReward, number, common.BlocksPerYear)
rewardSignersProtector, err = CalculateRewardForSigner(chainReward, signers[ProtectorNodeBeneficiary])
if err != nil {
log.Error("[HookReward] Fail to calculate reward for protector", "error", err)
return nil, err
}
// Observer rewards
chainReward = new(big.Int).Mul(new(big.Int).SetUint64(currentConfig.ObserverReward), new(big.Int).SetUint64(params.Ether))
chainReward = util.RewardInflation(chain, chainReward, number, common.BlocksPerYear)
rewardSignersObserver, err = CalculateRewardForSigner(chainReward, signers[ObserverNodeBeneficiary])
if err != nil {
log.Error("[HookReward] Fail to calculate reward for observer", "error", err)
return nil, err
}
}
// Add reward for coin holders.
voterResults := make(map[common.Address]interface{})
for signer, calcReward := range rewardSigners {
rewards, err := contracts.CalculateRewardForHolders(foundationWalletAddr, parentState, signer, calcReward, number)
if err != nil {
log.Error("[HookReward] Fail to calculate reward for holders.", "error", err)
return nil, err
}
if len(rewards) > 0 {
for holder, reward := range rewards {
stateBlock.AddBalance(holder, reward)
for _, rwt := range []rewardWithType{
{currentConfig.MasternodeReward, MasterNodeBeneficiary, "rewards"},
{currentConfig.ProtectorReward, ProtectorNodeBeneficiary, "rewardsProtector"},
{currentConfig.ObserverReward, ObserverNodeBeneficiary, "rewardsObserver"},
} {
originalReward := new(big.Int).Mul(new(big.Int).SetUint64(rwt.r), new(big.Int).SetUint64(params.Ether))
chainReward := new(big.Int)
if !chain.Config().IsTIPEpochHalving(header.Number) {
chainReward = util.RewardInflation(chain, originalReward, number, common.BlocksPerYear)
} else {
halvingSupply := big.NewInt(9000000000) // TODO use config.halvingSupply
_, epochNum, err := adaptor.EngineV2.IsEpochSwitch(header)
if err != nil {
return nil, err
}
epochSinceHalving := epochNum // TODO Minus config.epochHalvingOnset
chainReward = util.RewardHalving(originalReward, epochRewardTotal, halvingSupply, epochSinceHalving)
}
}
voterResults[signer] = rewards
}
rewardsMap["rewards"] = voterResults
voterResultsProtector := make(map[common.Address]interface{})
for signer, calcReward := range rewardSignersProtector {
rewards, err := contracts.CalculateRewardForHolders(foundationWalletAddr, parentState, signer, calcReward, number)
if err != nil {
log.Error("[HookReward] Fail to calculate reward for holders.", "error", err)
return nil, err
}
if len(rewards) > 0 {
for holder, reward := range rewards {
stateBlock.AddBalance(holder, reward)
rewardSigners, err := CalculateRewardForSigner(chainReward, signers[rwt.t])
if err != nil {
log.Error("[HookReward] Fail to calculate reward type 0 for masternode, 1 for protector, 2 for observer", "error", err, "type", rwt.t)
return nil, err
}
}
voterResultsProtector[signer] = rewards
}
if len(voterResultsProtector) > 0 {
rewardsMap["rewardsProtector"] = voterResultsProtector
}
voterResultsObserver := make(map[common.Address]interface{})
for signer, calcReward := range rewardSignersObserver {
rewards, err := contracts.CalculateRewardForHolders(foundationWalletAddr, parentState, signer, calcReward, number)
if err != nil {
log.Error("[HookReward] Fail to calculate reward for holders.", "error", err)
return nil, err
}
if len(rewards) > 0 {
for holder, reward := range rewards {
stateBlock.AddBalance(holder, reward)
// Add reward for coin holders.
voterResults := make(map[common.Address]interface{})
for signer, calcReward := range rewardSigners {
rewards, err := contracts.CalculateRewardForHolders(foundationWalletAddr, parentState, signer, calcReward, number)
if err != nil {
log.Error("[HookReward] Fail to calculate reward for holders.", "error", err)
return nil, err
}
if len(rewards) > 0 {
for holder, reward := range rewards {
stateBlock.AddBalance(holder, reward)
}
}
voterResults[signer] = rewards
}
rewardsMap[rwt.key] = voterResults
}
voterResultsObserver[signer] = rewards
}
if len(voterResultsObserver) > 0 {
rewardsMap["rewardsObserver"] = voterResultsObserver
}
log.Debug("Time Calculated HookReward ", "block", header.Number.Uint64(), "time", common.PrettyDuration(time.Since(start)))
return rewardsMap, nil
@ -474,3 +456,30 @@ func CalculateRewardForSigner(chainReward *big.Int, signers map[common.Address]*
return resultSigners, nil
}
// func TestRewardBeZero(t *testing.T) {
// billion := big.NewInt(1000000000)
// epochRewardTotal := big.NewInt(16000)
// epochRewardTotal.Mul(epochRewardTotal, billion)
// epochReward1 := big.NewInt(10000)
// epochReward1.Mul(epochReward1, billion)
// epochReward2 := big.NewInt(4000)
// epochReward2.Mul(epochReward2, billion)
// epochReward3 := big.NewInt(2000)
// epochReward3.Mul(epochReward3, billion)
// // 45 Billion - 39 Billion XDC (1 XDC = 10^9 wei)
// halvingSupply := big.NewInt(6000000000)
// halvingSupply.Mul(halvingSupply, billion)
// sum := big.NewInt(0)
// for i := uint64(0); i < 30000000; i++ {
// r := new(big.Int).Add(RewardHalving(epochReward1, epochRewardTotal, halvingSupply, i), RewardHalving(epochReward2, epochRewardTotal, halvingSupply, i))
// r.Add(r, RewardHalving(epochReward3, epochRewardTotal, halvingSupply, i))
// if r.BitLen() == 0 {
// t.Log("reward be 0 at i=", i) // reward be 0 at i= 11225088, wich is more than 200 years in the future
// break
// }
// sum.Add(sum, r)
// }
// t.Log("sum", sum) // sum 5999999999982635022, which is less than total, and never reach totoal
// assert.True(t, sum.Cmp(halvingSupply) < 0)
// }

View file

@ -20,3 +20,34 @@ func RewardInflation(chain consensus.ChainReader, chainReward *big.Int, number u
return chainReward
}
// RewardHalving computes the reward for Masternode/Protector/Observer based on epoch total reward, supply after halving is enabled, and epoch after halving is enabled
// The sequence is a geometric sequence in order to make supply be limited
func RewardHalving(epochRewardSingle *big.Int, epochRewardTotal *big.Int, halvingSupply *big.Int, epochSinceHalving uint64) *big.Int {
rt := new(big.Float).SetInt(epochRewardTotal)
hs := new(big.Float).SetInt(halvingSupply)
// zero cause Quo panic so return early
// or epoch reward > halving supply, return early
if halvingSupply.BitLen() == 0 || epochRewardTotal.Cmp(halvingSupply) > 0 {
return big.NewInt(0)
}
quo := new(big.Float).Quo(rt, hs)
// base = 1- reward/supply
base := new(big.Float).Sub(big.NewFloat(1), quo)
r := new(big.Float).SetInt(epochRewardSingle)
result := new(big.Float).Mul(r, FloatPower(base, epochSinceHalving))
resultInt, _ := result.Int(nil)
return resultInt
}
func FloatPower(base *big.Float, exp uint64) *big.Float {
result := big.NewFloat(1)
for exp > 0 {
if exp%2 == 1 {
result.Mul(result, base)
}
base.Mul(base, base)
exp >>= 1 // same as: exp = exp / 2
}
return result
}

View file

@ -764,6 +764,10 @@ func (c *ChainConfig) IsTIPUpgradeReward(num *big.Int) bool {
return isForked(common.TIPUpgradeReward, num)
}
func (c *ChainConfig) IsTIPEpochHalving(num *big.Int) bool {
return isForked(common.TIPEpochHalving, num)
}
// GasTable returns the gas table corresponding to the current phase (homestead or homestead reprice).
//
// The returned GasTable's fields shouldn't, under any circumstances, be changed.