Skip to content

Commit

Permalink
XIN-121 Reward hook (ethereum#57)
Browse files Browse the repository at this point in the history
* v2 Hook Reward, need test

* test reward

* fix RewardHook due to modifying params config directly (ethereum#56)

* more test

* finish test

Co-authored-by: Jerome <[email protected]>
  • Loading branch information
wgr523 and wjrjerome authored Feb 19, 2022
1 parent 9b47146 commit 89acbdd
Show file tree
Hide file tree
Showing 8 changed files with 352 additions and 24 deletions.
37 changes: 24 additions & 13 deletions consensus/XDPoS/engines/engine_v2/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ type XDPoS_v2 struct {
highestTimeoutCert *utils.TimeoutCert
highestCommitBlock *utils.BlockInfo

HookReward func(chain consensus.ChainReader, state *state.StateDB, parentState *state.StateDB, header *types.Header) (error, map[string]interface{})
HookReward func(chain consensus.ChainReader, state *state.StateDB, parentState *state.StateDB, header *types.Header) (map[string]interface{}, error)
HookPenalty func(chain consensus.ChainReader, number *big.Int, parentHash common.Hash, candidates []common.Address) ([]common.Address, error)
}

Expand Down Expand Up @@ -235,13 +235,14 @@ func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) er
// rewards given, and returns the final block.
func (x *XDPoS_v2) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, parentState *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
// set block reward
number := header.Number.Uint64()
rCheckpoint := chain.Config().XDPoS.RewardCheckpoint

// _ = c.CacheData(header, txs, receipts)

if x.HookReward != nil && number%rCheckpoint == 0 {
err, rewards := x.HookReward(chain, state, parentState, header)
isEpochSwitch, _, err := x.IsEpochSwitch(header)
if err != nil {
log.Error("[Finalize] IsEpochSwitch bug!", "err", err)
return nil, err
}
if x.HookReward != nil && isEpochSwitch {
rewards, err := x.HookReward(chain, state, parentState, header)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1385,20 +1386,30 @@ func (x *XDPoS_v2) GetMasternodesByHash(chain consensus.ChainReader, hash common
return epochSwitchInfo.Masternodes
}

// Given hash, get master node from the epoch switch block of the previous `limit` epoch
func (x *XDPoS_v2) GetPreviousPenaltyByHash(chain consensus.ChainReader, hash common.Hash, limit int) []common.Address {
// get epoch switch of the previous `limit` epoch
func (x *XDPoS_v2) getPreviousEpochSwitchInfoByHash(chain consensus.ChainReader, hash common.Hash, limit int) (*utils.EpochSwitchInfo, error) {
epochSwitchInfo, err := x.getEpochSwitchInfo(chain, nil, hash)
if err != nil {
log.Error("[GetMasternodes] Adaptor v2 getEpochSwitchInfo has error, potentially bug", "err", err)
return []common.Address{}
log.Error("[getPreviousEpochSwitchInfoByHash] Adaptor v2 getEpochSwitchInfo has error, potentially bug", "err", err)
return nil, err
}
for i := 0; i < limit; i++ {
epochSwitchInfo, err = x.getEpochSwitchInfo(chain, nil, epochSwitchInfo.EpochSwitchParentBlockInfo.Hash)
if err != nil {
log.Error("[GetMasternodes] Adaptor v2 getEpochSwitchInfo has error, potentially bug", "err", err)
return []common.Address{}
log.Error("[getPreviousEpochSwitchInfoByHash] Adaptor v2 getEpochSwitchInfo has error, potentially bug", "err", err)
return nil, err
}
}
return epochSwitchInfo, nil
}

// Given hash, get master node from the epoch switch block of the previous `limit` epoch
func (x *XDPoS_v2) GetPreviousPenaltyByHash(chain consensus.ChainReader, hash common.Hash, limit int) []common.Address {
epochSwitchInfo, err := x.getPreviousEpochSwitchInfoByHash(chain, hash, limit)
if err != nil {
log.Error("[GetPreviousPenaltyByHash] Adaptor v2 getPreviousEpochSwitchInfoByHash has error, potentially bug", "err", err)
return []common.Address{}
}
header := chain.GetHeaderByHash(epochSwitchInfo.EpochSwitchBlockInfo.Hash)
return common.ExtractAddressFromBytes(header.Penalties)
}
2 changes: 1 addition & 1 deletion consensus/tests/penalty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func TestHookPenaltyV2Comeback(t *testing.T) {
assert.Equal(t, 4, len(penalty))
header6285 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*7 - common.MergeSignRange)
// forcely insert signing tx into cache, to cancel comeback. since no comeback, penalty is 3
tx, err := signingTx(header6285, 0, signer, signFn)
tx, err := signingTxWithSignerFn(header6285, 0, signer, signFn)
assert.Nil(t, err)
adaptor.CacheSigningTxs(header6285.Hash(), []*types.Transaction{tx})
penalty, err = adaptor.EngineV2.HookPenalty(blockchain, big.NewInt(int64(config.XDPoS.Epoch*7)), header6300.ParentHash, masternodes)
Expand Down
163 changes: 163 additions & 0 deletions consensus/tests/reward_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package tests

import (
"encoding/json"
"math/big"
"testing"

"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
"github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/eth/hooks"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/stretchr/testify/assert"
)

func TestHookRewardV2(t *testing.T) {
b, err := json.Marshal(params.TestXDPoSMockChainConfig)
assert.Nil(t, err)
configString := string(b)

var config params.ChainConfig
err = json.Unmarshal([]byte(configString), &config)
assert.Nil(t, err)
// set switch to 1800, so that it covers 901-1799, 1800-2700 two epochs
config.XDPoS.V2.SwitchBlock.SetUint64(1800)

blockchain, _, _, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch)*5, &config, 0)

adaptor := blockchain.Engine().(*XDPoS.XDPoS)
hooks.AttachConsensusV2Hooks(adaptor, blockchain, &config)
assert.NotNil(t, adaptor.EngineV2.HookReward)
// forcely insert signing tx into cache, to give rewards.
header915 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 15)
header916 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 16)
header1799 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*2 - 1)
header1801 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*2 + 1)
tx, err := signingTxWithSignerFn(header915, 0, signer, signFn)
assert.Nil(t, err)
adaptor.CacheSigningTxs(header916.Hash(), []*types.Transaction{tx})
statedb, err := blockchain.StateAt(header1799.Root)
assert.Nil(t, err)
parentState := statedb.Copy()
reward, err := adaptor.EngineV2.HookReward(blockchain, statedb, parentState, header1801)
assert.Nil(t, err)
assert.Zero(t, len(reward))
header2699 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*3 - 1)
header2700 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch * 3)
statedb, err = blockchain.StateAt(header2699.Root)
assert.Nil(t, err)
parentState = statedb.Copy()
reward, err = adaptor.EngineV2.HookReward(blockchain, statedb, parentState, header2700)
assert.Nil(t, err)
owner := state.GetCandidateOwner(parentState, signer)
result := reward["rewards"].(map[common.Address]interface{})
assert.Equal(t, 1, len(result))
for _, x := range result {
r := x.(map[common.Address]*big.Int)
a, _ := big.NewInt(0).SetString("225000000000000000000", 10)
assert.Zero(t, a.Cmp(r[owner]))
b, _ := big.NewInt(0).SetString("25000000000000000000", 10)
assert.Zero(t, b.Cmp(r[common.HexToAddress("0x0000000000000000000000000000000000000068")]))
}
header2685 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*2 + 885)
header2716 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*3 + 16)
header3599 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*4 - 1)
header3600 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch * 4)
tx, err = signingTxWithSignerFn(header2685, 0, signer, signFn)
assert.Nil(t, err)
// signed block hash and block contains tx are in different epoch, we should get same rewards
adaptor.CacheSigningTxs(header2716.Hash(), []*types.Transaction{tx})
statedb, err = blockchain.StateAt(header3599.Root)
assert.Nil(t, err)
parentState = statedb.Copy()
reward, err = adaptor.EngineV2.HookReward(blockchain, statedb, parentState, header3600)
assert.Nil(t, err)
result = reward["rewards"].(map[common.Address]interface{})
assert.Equal(t, 1, len(result))
for _, x := range result {
r := x.(map[common.Address]*big.Int)
a, _ := big.NewInt(0).SetString("225000000000000000000", 10)
assert.Zero(t, a.Cmp(r[owner]))
b, _ := big.NewInt(0).SetString("25000000000000000000", 10)
assert.Zero(t, b.Cmp(r[config.XDPoS.FoudationWalletAddr]))
}
// if no signing tx, then reward will be 0
header4499 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*5 - 1)
header4500 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch * 5)
statedb, err = blockchain.StateAt(header4499.Root)
assert.Nil(t, err)
parentState = statedb.Copy()
reward, err = adaptor.EngineV2.HookReward(blockchain, statedb, parentState, header4500)
assert.Nil(t, err)
result = reward["rewards"].(map[common.Address]interface{})
assert.Equal(t, 0, len(result))
}

func TestHookRewardV2SplitReward(t *testing.T) {
b, err := json.Marshal(params.TestXDPoSMockChainConfig)
assert.Nil(t, err)
configString := string(b)

var config params.ChainConfig
err = json.Unmarshal([]byte(configString), &config)
assert.Nil(t, err)
// set switch to 1800, so that it covers 901-1799, 1800-2700 two epochs
config.XDPoS.V2.SwitchBlock.SetUint64(1800)

blockchain, _, _, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch)*3, &config, 0)

adaptor := blockchain.Engine().(*XDPoS.XDPoS)
hooks.AttachConsensusV2Hooks(adaptor, blockchain, &config)
assert.NotNil(t, adaptor.EngineV2.HookReward)
// forcely insert signing tx into cache, to give rewards.
header915 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 15)
header916 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 16)
// header917 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 17)
header1785 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*2 - 15)
header1799 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*2 - 1)
header1801 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*2 + 1)
tx, err := signingTxWithSignerFn(header915, 0, signer, signFn)
assert.Nil(t, err)
adaptor.CacheSigningTxs(header916.Hash(), []*types.Transaction{tx})
tx2, err := signingTxWithKey(header915, 0, acc1Key)
assert.Nil(t, err)
tx3, err := signingTxWithKey(header1785, 0, acc1Key)
assert.Nil(t, err)
adaptor.CacheSigningTxs(header1799.Hash(), []*types.Transaction{tx2, tx3})

statedb, err := blockchain.StateAt(header1799.Root)
assert.Nil(t, err)
parentState := statedb.Copy()
reward, err := adaptor.EngineV2.HookReward(blockchain, statedb, parentState, header1801)
assert.Nil(t, err)
assert.Zero(t, len(reward))
header2699 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*3 - 1)
header2700 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch * 3)
statedb, err = blockchain.StateAt(header2699.Root)
assert.Nil(t, err)
parentState = statedb.Copy()
reward, err = adaptor.EngineV2.HookReward(blockchain, statedb, parentState, header2700)
assert.Nil(t, err)
result := reward["rewards"].(map[common.Address]interface{})
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 {
r := x.(map[common.Address]*big.Int)
owner := state.GetCandidateOwner(parentState, acc1Addr)
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[common.HexToAddress("0x0000000000000000000000000000000000000068")]))
} else if addr == signer {
r := x.(map[common.Address]*big.Int)
owner := state.GetCandidateOwner(parentState, signer)
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[common.HexToAddress("0x0000000000000000000000000000000000000068")]))
}
}
}
21 changes: 18 additions & 3 deletions consensus/tests/test_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ func voteTX(gasLimit uint64, nonce uint64, addr string) (*types.Transaction, err
return signedTX, nil
}

func signingTx(header *types.Header, nonce uint64, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error)) (*types.Transaction, error) {
func signingTxWithSignerFn(header *types.Header, nonce uint64, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error)) (*types.Transaction, error) {
tx := contracts.CreateTxSign(header.Number, header.Hash(), nonce, common.HexToAddress(common.BlockSigners))
s := types.NewEIP155Signer(big.NewInt(chainID))
h := s.Hash(tx)
Expand All @@ -245,6 +245,21 @@ func signingTx(header *types.Header, nonce uint64, signer common.Address, signFn
return signedTx, nil
}

func signingTxWithKey(header *types.Header, nonce uint64, privateKey *ecdsa.PrivateKey) (*types.Transaction, error) {
tx := contracts.CreateTxSign(header.Number, header.Hash(), nonce, common.HexToAddress(common.BlockSigners))
s := types.NewEIP155Signer(big.NewInt(chainID))
h := s.Hash(tx)
sig, err := crypto.Sign(h[:], privateKey)
if err != nil {
return nil, err
}
signedTx, err := tx.WithSignature(s, sig)
if err != nil {
return nil, err
}
return signedTx, nil
}

func UpdateSigner(bc *BlockChain) error {
err := bc.UpdateM1()
return err
Expand Down Expand Up @@ -534,8 +549,8 @@ func CreateBlock(blockchain *BlockChain, chainConfig *params.ChainConfig, starti
Coinbase: common.HexToAddress(blockCoinBase),
}

// Inject the hardcoded master node list for the last v1 epoch block
if big.NewInt(int64(blockNumber)).Cmp(chainConfig.XDPoS.V2.SwitchBlock) == 0 {
// Inject the hardcoded master node list for the last v1 epoch block and all v1 epoch switch blocks (excluding genesis)
if big.NewInt(int64(blockNumber)).Cmp(chainConfig.XDPoS.V2.SwitchBlock) == 0 || blockNumber%int(chainConfig.XDPoS.Epoch) == 0 {
// reset extra
header.Extra = []byte{}
if len(header.Extra) < utils.ExtraVanity {
Expand Down
2 changes: 1 addition & 1 deletion consensus/tests/vote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQCForFistV2Round(t *te
blockInfo := &utils.BlockInfo{
Hash: currentBlock.Hash(),
Round: utils.Round(1),
Number: big.NewInt(11),
Number: big.NewInt(901),
}
voteSigningHash := utils.VoteSigHash(blockInfo)

Expand Down
10 changes: 5 additions & 5 deletions contracts/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const (
extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal
)

type rewardLog struct {
type RewardLog struct {
Sign uint64 `json:"sign"`
Reward *big.Int `json:"reward"`
}
Expand Down Expand Up @@ -319,13 +319,13 @@ func DecryptRandomizeFromSecretsAndOpening(secrets [][32]byte, opening [32]byte)
}

// Calculate reward for reward checkpoint.
func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header *types.Header, rCheckpoint uint64, totalSigner *uint64) (map[common.Address]*rewardLog, error) {
func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header *types.Header, rCheckpoint uint64, totalSigner *uint64) (map[common.Address]*RewardLog, error) {
// Not reward for singer of genesis block and only calculate reward at checkpoint block.
number := header.Number.Uint64()
prevCheckpoint := number - (rCheckpoint * 2)
startBlockNumber := prevCheckpoint + 1
endBlockNumber := startBlockNumber + rCheckpoint - 1
signers := make(map[common.Address]*rewardLog)
signers := make(map[common.Address]*RewardLog)
mapBlkHash := map[uint64]common.Hash{}

data := make(map[common.Hash][]common.Address)
Expand Down Expand Up @@ -376,7 +376,7 @@ func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header
if exist {
signers[addr].Sign++
} else {
signers[addr] = &rewardLog{1, new(big.Int)}
signers[addr] = &RewardLog{1, new(big.Int)}
}
*totalSigner++
}
Expand All @@ -390,7 +390,7 @@ func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header
}

// Calculate reward for signers.
func CalculateRewardForSigner(chainReward *big.Int, signers map[common.Address]*rewardLog, totalSigner uint64) (map[common.Address]*big.Int, error) {
func CalculateRewardForSigner(chainReward *big.Int, signers map[common.Address]*RewardLog, totalSigner uint64) (map[common.Address]*big.Int, error) {
resultSigners := make(map[common.Address]*big.Int)
// Add reward for signers.
if totalSigner > 0 {
Expand Down
Loading

0 comments on commit 89acbdd

Please sign in to comment.