Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 25 additions & 16 deletions consensus/tests/engine_v2_tests/reward_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,21 +145,22 @@ func TestHookRewardV2SplitReward(t *testing.T) {
assert.Equal(t, 2, len(result))
// two signing account, 3 txs, reward is split by 1:2 (total reward is 250...000)
for addr, x := range result {
if addr == acc1Addr {
switch addr {
case acc1Addr:
r := x.(map[common.Address]*big.Int)
owner := state.GetCandidateOwner(parentState, addr)
a, _ := big.NewInt(0).SetString("149999999999999999999", 10)
assert.Zero(t, a.Cmp(r[owner]))
b, _ := big.NewInt(0).SetString("16666666666666666666", 10)
assert.Zero(t, b.Cmp(r[config.XDPoS.FoudationWalletAddr]))
} else if addr == signer {
case signer:
r := x.(map[common.Address]*big.Int)
owner := state.GetCandidateOwner(parentState, addr)
a, _ := big.NewInt(0).SetString("74999999999999999999", 10)
assert.Zero(t, a.Cmp(r[owner]))
b, _ := big.NewInt(0).SetString("8333333333333333333", 10)
assert.Zero(t, b.Cmp(r[config.XDPoS.FoudationWalletAddr]))
} else {
default:
assert.Fail(t, "wrong reward")
}
}
Expand Down Expand Up @@ -227,21 +228,22 @@ func TestHookRewardAfterUpgrade(t *testing.T) {
assert.Equal(t, 2, len(result))
// two signing account, both get fixed reward
for addr, x := range result {
if addr == acc1Addr {
switch addr {
case acc1Addr:
r := x.(map[common.Address]*big.Int)
owner := state.GetCandidateOwner(parentState, addr)
a, _ := big.NewInt(0).SetString("450000000000000000000", 10)
assert.Zero(t, a.Cmp(r[owner]), "real reward is", r[owner])
b, _ := big.NewInt(0).SetString("50000000000000000000", 10)
assert.Zero(t, b.Cmp(r[config.XDPoS.FoudationWalletAddr]), "real reward is", r[config.XDPoS.FoudationWalletAddr])
} else if addr == signer {
case signer:
r := x.(map[common.Address]*big.Int)
owner := state.GetCandidateOwner(parentState, addr)
a, _ := big.NewInt(0).SetString("450000000000000000000", 10)
assert.Zero(t, a.Cmp(r[owner]), "real reward is", r[owner])
b, _ := big.NewInt(0).SetString("50000000000000000000", 10)
assert.Zero(t, b.Cmp(r[config.XDPoS.FoudationWalletAddr]), "real reward is", r[config.XDPoS.FoudationWalletAddr])
} else {
default:
assert.Fail(t, "wrong reward")
}
}
Expand All @@ -266,21 +268,22 @@ func TestHookRewardAfterUpgrade(t *testing.T) {
// 2 protector both get fixed reward
assert.Equal(t, 2, len(resultProtector))
for addr, x := range resultProtector {
if addr == protector1Addr {
switch addr {
case protector1Addr:
r := x.(map[common.Address]*big.Int)
owner := state.GetCandidateOwner(parentState, addr)
a, _ := big.NewInt(0).SetString("360000000000000000000", 10)
assert.Zero(t, a.Cmp(r[owner]), "real reward is", r[owner])
b, _ := big.NewInt(0).SetString("40000000000000000000", 10)
assert.Zero(t, b.Cmp(r[config.XDPoS.FoudationWalletAddr]), "real reward is", r[config.XDPoS.FoudationWalletAddr])
} else if addr == protector2Addr {
case protector2Addr:
r := x.(map[common.Address]*big.Int)
owner := state.GetCandidateOwner(parentState, addr)
a, _ := big.NewInt(0).SetString("360000000000000000000", 10)
assert.Zero(t, a.Cmp(r[owner]), "real reward is", r[owner])
b, _ := big.NewInt(0).SetString("40000000000000000000", 10)
assert.Zero(t, b.Cmp(r[config.XDPoS.FoudationWalletAddr]), "real reward is", r[config.XDPoS.FoudationWalletAddr])
} else {
default:
assert.Fail(t, "wrong reward")
}

Expand All @@ -297,11 +300,17 @@ func TestHookRewardAfterUpgrade(t *testing.T) {
b, _ := big.NewInt(0).SetString("30012500000000000000", 10) // this value tests the float64 reward
assert.Zero(t, b.Cmp(r[config.XDPoS.FoudationWalletAddr]), "real reward is", r[config.XDPoS.FoudationWalletAddr])
}
totalMinted := state.GetTotalMinted(statedb).Big()
totalExpect, _ := big.NewInt(0).SetString("2100125000000000000000", 10)
assert.Zero(t, totalMinted.Cmp(totalExpect), "statedb records wrong total minted")
lastEpochNum := state.GetLastEpochNum(statedb).Big().Int64()
assert.Equal(t, 3, int(lastEpochNum))
epochNum := uint64(3)
totalMinted := state.GetPostTotalMinted(statedb, epochNum).Big()
expectMinted, _ := big.NewInt(0).SetString("2100125000000000000000", 10)
assert.Zero(t, totalMinted.Cmp(expectMinted), "statedb records wrong total minted")
blockNum := state.GetPostRewardBlock(statedb, epochNum).Big().Int64()
assert.Equal(t, 2700, int(blockNum))
onsetBlock := state.GetMintedRecordOnsetBlock(statedb).Big().Int64()
assert.Equal(t, 2700, int(onsetBlock))
totalBurned := state.GetPostTotalBurned(statedb, epochNum).Big().Int64()
// since no EIP 1559, so no burned
assert.Zero(t, totalBurned, "statedb records wrong total burned")
common.TIPUpgradeReward = backup
}

Expand Down Expand Up @@ -366,9 +375,9 @@ func TestFinalizeAfterUpgrade(t *testing.T) {
assert.Nil(t, err)

// the recorded reward cannot be zero
minted := state.GetTotalMinted(statedbAfterFinalize)
epochNum := uint64(3)
minted := state.GetPostTotalMinted(statedbAfterFinalize, epochNum)
assert.False(t, minted.IsZero())
t.Log("total minted", minted)

common.TIPUpgradeReward = backup
}
Expand Down
65 changes: 48 additions & 17 deletions core/state/statedb_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,34 +153,65 @@ func GetVoterCap(statedb *StateDB, candidate, voter common.Address) *big.Int {
return ret.Big()
}

func IncrementMintedRecordNonce(statedb *StateDB) {
nonce := statedb.GetNonce(common.MintedRecordAddressBinary)
statedb.SetNonce(common.MintedRecordAddressBinary, nonce+1)
}

var (
slotMintedRecordTotalMinted uint64 = 0
slotMintedRecordLastEpochNum uint64 = 1
// Storage slot locations (32-byte keys) within MintedRecord SMC
slotMintedRecordOnsetEpoch = common.HexToHash("0000000000000000000000000000000000000000000000000000000000000001")
slotMintedRecordOnsetBlock = common.HexToHash("0000000000000000000000000000000000000000000000000000000000000002")
slotMintedRecordPostTotalMintedBase, _ = new(big.Int).SetString("0100000000000000000000000000000000000000000000000000000000000000", 16)
slotMintedRecordPostTotalBurnedBase, _ = new(big.Int).SetString("0200000000000000000000000000000000000000000000000000000000000000", 16)
slotMintedRecordPostRewardBlockBase, _ = new(big.Int).SetString("0300000000000000000000000000000000000000000000000000000000000000", 16)
Copy link
Collaborator

@gzliudan gzliudan Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add 0x prefix in string ? I think it's more clear

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

common.HexToHash can. done.
new(big.Int).SetString cannot.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/ethereum/go-ethereum/blob/master/crypto/secp256k1/curve.go#L267-L271:

func init() {
	// See SEC 2 section 2.7.1
	// curve parameters taken from:
	// http://www.secg.org/sec2-v2.pdf
	theCurve.P, _ = new(big.Int).SetString("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 0)
	theCurve.N, _ = new(big.Int).SetString("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 0)
	theCurve.B, _ = new(big.Int).SetString("0x0000000000000000000000000000000000000000000000000000000000000007", 0)
	theCurve.Gx, _ = new(big.Int).SetString("0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 0)
	theCurve.Gy, _ = new(big.Int).SetString("0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 0)
	theCurve.BitSize = 256
}

)

func GetTotalMinted(statedb *StateDB) common.Hash {
hash := GetLocSimpleVariable(slotMintedRecordTotalMinted)
totalMinted := statedb.GetState(common.MintedRecordAddressBinary, hash)
return totalMinted
func GetMintedRecordOnsetEpoch(statedb *StateDB) common.Hash {
return statedb.GetState(common.MintedRecordAddressBinary, slotMintedRecordOnsetEpoch)
}

func PutTotalMinted(statedb *StateDB, value common.Hash) {
hash := GetLocSimpleVariable(slotMintedRecordTotalMinted)
func PutMintedRecordOnsetEpoch(statedb *StateDB, value common.Hash) {
statedb.SetState(common.MintedRecordAddressBinary, slotMintedRecordOnsetEpoch, value)
}

func GetMintedRecordOnsetBlock(statedb *StateDB) common.Hash {
return statedb.GetState(common.MintedRecordAddressBinary, slotMintedRecordOnsetBlock)
}

func PutMintedRecordOnsetBlock(statedb *StateDB, value common.Hash) {
statedb.SetState(common.MintedRecordAddressBinary, slotMintedRecordOnsetBlock, value)
}

func GetPostTotalMinted(statedb *StateDB, epoch uint64) common.Hash {
hash := common.BigToHash(new(big.Int).Add(slotMintedRecordPostTotalMintedBase, new(big.Int).SetUint64(epoch)))
v := statedb.GetState(common.MintedRecordAddressBinary, hash)
return v
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not use return statedb.GetState(common.MintedRecordAddressBinary, hash)

}

func PutPostTotalMinted(statedb *StateDB, epoch uint64, value common.Hash) {
hash := common.BigToHash(new(big.Int).Add(slotMintedRecordPostTotalMintedBase, new(big.Int).SetUint64(epoch)))
statedb.SetState(common.MintedRecordAddressBinary, hash, value)
}

func GetLastEpochNum(statedb *StateDB) common.Hash {
hash := GetLocSimpleVariable(slotMintedRecordLastEpochNum)
totalMinted := statedb.GetState(common.MintedRecordAddressBinary, hash)
return totalMinted
func GetPostTotalBurned(statedb *StateDB, epoch uint64) common.Hash {
hash := common.BigToHash(new(big.Int).Add(slotMintedRecordPostTotalBurnedBase, new(big.Int).SetUint64(epoch)))
v := statedb.GetState(common.MintedRecordAddressBinary, hash)
return v
}

func PutLastEpochNum(statedb *StateDB, value common.Hash) {
hash := GetLocSimpleVariable(slotMintedRecordLastEpochNum)
func PutPostTotalBurned(statedb *StateDB, epoch uint64, value common.Hash) {
hash := common.BigToHash(new(big.Int).Add(slotMintedRecordPostTotalBurnedBase, new(big.Int).SetUint64(epoch)))
statedb.SetState(common.MintedRecordAddressBinary, hash, value)
}

func IncrementMintedRecordNonce(statedb *StateDB) {
nonce := statedb.GetNonce(common.MintedRecordAddressBinary)
statedb.SetNonce(common.MintedRecordAddressBinary, nonce+1)
func GetPostRewardBlock(statedb *StateDB, epoch uint64) common.Hash {
hash := common.BigToHash(new(big.Int).Add(slotMintedRecordPostRewardBlockBase, new(big.Int).SetUint64(epoch)))
v := statedb.GetState(common.MintedRecordAddressBinary, hash)
return v
}

func PutPostRewardBlock(statedb *StateDB, epoch uint64, value common.Hash) {
hash := common.BigToHash(new(big.Int).Add(slotMintedRecordPostRewardBlockBase, new(big.Int).SetUint64(epoch)))
statedb.SetState(common.MintedRecordAddressBinary, hash, value)
}
61 changes: 44 additions & 17 deletions eth/hooks/engine_v2_hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,8 @@ func AttachConsensusV2Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConf
return nil, err
}
currentConfig := chain.Config().XDPoS.V2.Config(uint64(round))
// Get signers/signing tx count
signers, err := GetSigningTxCount(adaptor, chain, header, parentState, currentConfig)
// Get signers/signing tx count, and burned tokens in one epoch
signers, burnedInOneEpoch, err := GetSigningTxCount(adaptor, chain, header, parentState, currentConfig)

log.Debug("Time Get Signers", "block", header.Number.Uint64(), "time", common.PrettyDuration(time.Since(start)))
if err != nil {
Expand Down Expand Up @@ -361,24 +361,45 @@ func AttachConsensusV2Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConf
rewardsMap[rwt.key] = rewardResults
}
// record the total reward into state db
totalMinted := state.GetTotalMinted(stateBlock).Big()
lastEpochNum := state.GetLastEpochNum(stateBlock)
if lastEpochNum.IsZero() {
// if `lastEpochNum` is zero, the total minted has not included tokens before TIPUpgradeReward
// calculate the tokens before TIPUpgradeReward and set to totalMinted
// for now no-do
totalMinted := big.NewInt(0)
totalBurned := big.NewInt(0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new(big.Int) is better than big.NewInt(0)


nonce := stateBlock.GetNonce(common.MintedRecordAddressBinary)
if nonce == 0 {
// initialize MintedRecordAddress
state.PutMintedRecordOnsetEpoch(stateBlock, common.Uint64ToHash(epochNum))
state.PutMintedRecordOnsetBlock(stateBlock, common.Uint64ToHash(number))
} else {
epochNumIter := epochNum
for epochNumIter > 0 {
totalMinted = state.GetPostTotalMinted(stateBlock, epochNumIter-1).Big()
totalBurned = state.GetPostTotalBurned(stateBlock, epochNumIter-1).Big()
if totalMinted.BitLen() != 0 || totalBurned.BitLen() != 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should use Sign() to compare with 0

// if previous epoch has non-zero total minted or non-zero total burned, break the loop
break
}
epochNumIter--
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can decrease epochNumIter early:

				for epochNumIter > 0 {
					epochNumIter--
					totalMinted = state.GetPostMinted(stateBlock, epochNumIter).Big()
					totalBurned = state.GetPostBurned(stateBlock, epochNumIter).Big()
					if totalMinted.Sign() != 0 || totalBurned.Sign() != 0 {
						// if previous epoch has non-zero total minted or non-zero total burned, break the loop
						break
					}
				}

}
totalMinted.Add(totalMinted, rewardSum)
bigPower256 := new(big.Int).Lsh(big.NewInt(1), 256)
bigMaxU256 := new(big.Int).Sub(bigPower256, big.NewInt(1))
Copy link
Collaborator

@gzliudan gzliudan Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use MaxUint256 in abi package, or define a package level variable:

MaxUint256 = new(big.Int).Sub(new(big.Int).Lsh(common.Big1, 256), common.Big1)

No need to create bigPower256 and bigMaxU256 each time.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or use math.MaxBig256

// if overflow, set to maxU256 and log a warning
if totalMinted.Cmp(bigMaxU256) >= 0 {
if totalMinted.Cmp(bigMaxU256) > 0 {
totalMinted.Set(bigMaxU256)
Copy link
Collaborator

@gzliudan gzliudan Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think totalMinted and totalBurned will be greater than bigMaxU256 one day.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that will be very long time in the future. this PR can skip that.

log.Warn("[HookReward] total minted overflow max u256")
}
log.Debug("[HookReward] total minted in hook", "value", totalMinted)
state.PutTotalMinted(stateBlock, common.BigToHash(totalMinted))
state.PutLastEpochNum(stateBlock, common.Uint64ToHash(epochNum))
state.PutPostTotalMinted(stateBlock, epochNum, common.BigToHash(totalMinted))
state.PutPostRewardBlock(stateBlock, epochNum, common.Uint64ToHash(number))
// Record total burned into statedb
totalBurned.Add(totalBurned, burnedInOneEpoch)
// if overflow, set to maxU256 and log a warning
if totalBurned.Cmp(bigMaxU256) > 0 {
totalBurned.Set(bigMaxU256)
log.Warn("[HookReward] total burned overflow max u256")
}
state.PutPostTotalBurned(stateBlock, epochNum, common.BigToHash(totalBurned))
// Increment nonce so that statedb does not treat it as empty account
state.IncrementMintedRecordNonce(stateBlock)
}
Expand All @@ -388,7 +409,7 @@ func AttachConsensusV2Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConf
}

// get signing transaction sender count
func GetSigningTxCount(c *XDPoS.XDPoS, chain consensus.ChainReader, header *types.Header, parentState *state.StateDB, currentConfig *params.V2Config) (map[Beneficiary]map[common.Address]*RewardLog, error) {
func GetSigningTxCount(c *XDPoS.XDPoS, chain consensus.ChainReader, header *types.Header, parentState *state.StateDB, currentConfig *params.V2Config) (map[Beneficiary]map[common.Address]*RewardLog, *big.Int, error) {
// header should be a new epoch switch block
number := header.Number.Uint64()
rewardEpochCount := 2
Expand All @@ -400,9 +421,11 @@ func GetSigningTxCount(c *XDPoS.XDPoS, chain consensus.ChainReader, header *type

mapBlkHash := map[uint64]common.Hash{}

burnedInOneEpoch := big.NewInt(0)

// prevent overflow
if number == 0 {
return signers, nil
return signers, burnedInOneEpoch, nil
}

data := make(map[common.Hash][]common.Address)
Expand All @@ -417,11 +440,15 @@ func GetSigningTxCount(c *XDPoS.XDPoS, chain consensus.ChainReader, header *type
h = chain.GetHeader(parentHash, i)
if h == nil {
log.Error("[GetSigningTxCount] fail to get header", "number", i, "hash", parentHash)
return nil, fmt.Errorf("fail to get header in GetSigningTxCount at number: %v, hash: %v", i, parentHash)
return nil, burnedInOneEpoch, fmt.Errorf("fail to get header in GetSigningTxCount at number: %v, hash: %v", i, parentHash)
}
if epochCount == 0 && h.BaseFee != nil {
// add burned for the first epoch during loop
burnedInOneEpoch.Add(burnedInOneEpoch, new(big.Int).Mul(h.BaseFee, new(big.Int).SetUint64(h.GasUsed)))
}
isEpochSwitch, _, err := c.IsEpochSwitch(h)
if err != nil {
return nil, err
return nil, burnedInOneEpoch, err
}
if isEpochSwitch && i != chain.Config().XDPoS.V2.SwitchBlock.Uint64()+1 {
epochCount += 1
Expand Down Expand Up @@ -490,7 +517,7 @@ func GetSigningTxCount(c *XDPoS.XDPoS, chain consensus.ChainReader, header *type
}
// prevent overflow
if i == 0 {
return signers, nil
return signers, burnedInOneEpoch, nil
}
}

Expand Down Expand Up @@ -535,7 +562,7 @@ func GetSigningTxCount(c *XDPoS.XDPoS, chain consensus.ChainReader, header *type

log.Info("Calculate reward at checkpoint", "startBlock", startBlockNumber, "endBlock", endBlockNumber)

return signers, nil
return signers, burnedInOneEpoch, nil
}

// Calculate reward for signers.
Expand Down
Loading