Consecutive penalty upgrade (#1053)

* feat: penalty upgrade, consecutive epochs penalty
can be unpenalized

* feat: use binary search inside penalty hook

* style: modification on style

* feat: in penaltyHook change startRange

* fix: add lastPenalty condition in HookPenalty V2
This commit is contained in:
wgr523 2025-06-25 16:12:56 +08:00 committed by GitHub
parent 217a01e927
commit 4ec4a5390f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 389 additions and 109 deletions

View file

@ -60,6 +60,7 @@ type constant struct {
tipXDCXMinerDisable *big.Int tipXDCXMinerDisable *big.Int
tipXDCXReceiverDisable *big.Int tipXDCXReceiverDisable *big.Int
tipUpgradeReward *big.Int tipUpgradeReward *big.Int
tipUpgradePenalty *big.Int
tipEpochHalving *big.Int tipEpochHalving *big.Int
eip1559Block *big.Int eip1559Block *big.Int
cancunBlock *big.Int cancunBlock *big.Int
@ -98,6 +99,7 @@ var (
Eip1559Block = MaintnetConstant.eip1559Block Eip1559Block = MaintnetConstant.eip1559Block
CancunBlock = MaintnetConstant.cancunBlock CancunBlock = MaintnetConstant.cancunBlock
TIPUpgradeReward = MaintnetConstant.tipUpgradeReward TIPUpgradeReward = MaintnetConstant.tipUpgradeReward
TipUpgradePenalty = MaintnetConstant.tipUpgradePenalty
TIPEpochHalving = MaintnetConstant.tipEpochHalving TIPEpochHalving = MaintnetConstant.tipEpochHalving
TRC21IssuerSMC = MaintnetConstant.trc21IssuerSMC TRC21IssuerSMC = MaintnetConstant.trc21IssuerSMC
@ -158,6 +160,7 @@ func CopyConstants(chainID uint64) {
Eip1559Block = c.eip1559Block Eip1559Block = c.eip1559Block
CancunBlock = c.cancunBlock CancunBlock = c.cancunBlock
TIPUpgradeReward = c.tipUpgradeReward TIPUpgradeReward = c.tipUpgradeReward
TipUpgradePenalty = c.tipUpgradePenalty
TIPEpochHalving = c.tipEpochHalving TIPEpochHalving = c.tipEpochHalving
TRC21IssuerSMC = c.trc21IssuerSMC TRC21IssuerSMC = c.trc21IssuerSMC

View file

@ -29,6 +29,7 @@ var DevnetConstant = constant{
eip1559Block: big.NewInt(32400), eip1559Block: big.NewInt(32400),
cancunBlock: big.NewInt(43200), cancunBlock: big.NewInt(43200),
tipUpgradeReward: big.NewInt(243000), tipUpgradeReward: big.NewInt(243000),
tipUpgradePenalty: big.NewInt(9999999999),
tipEpochHalving: big.NewInt(9999999999), tipEpochHalving: big.NewInt(9999999999),
trc21IssuerSMC: HexToAddress("0x8c0faeb5C6bEd2129b8674F262Fd45c4e9468bee"), trc21IssuerSMC: HexToAddress("0x8c0faeb5C6bEd2129b8674F262Fd45c4e9468bee"),

View file

@ -29,6 +29,7 @@ var localConstant = constant{
eip1559Block: big.NewInt(0), eip1559Block: big.NewInt(0),
cancunBlock: big.NewInt(0), cancunBlock: big.NewInt(0),
tipUpgradeReward: big.NewInt(0), tipUpgradeReward: big.NewInt(0),
tipUpgradePenalty: big.NewInt(0),
tipEpochHalving: big.NewInt(0), tipEpochHalving: big.NewInt(0),
trc21IssuerSMC: HexToAddress("0x8c0faeb5C6bEd2129b8674F262Fd45c4e9468bee"), trc21IssuerSMC: HexToAddress("0x8c0faeb5C6bEd2129b8674F262Fd45c4e9468bee"),

View file

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

View file

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

View file

@ -1089,12 +1089,25 @@ func (x *XDPoS_v2) GetMasternodesByHash(chain consensus.ChainReader, hash common
// Given hash, get master node from the epoch switch block of the previous `limit` epoch // 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 { func (x *XDPoS_v2) GetPreviousPenaltyByHash(chain consensus.ChainReader, hash common.Hash, limit int) []common.Address {
epochSwitchInfo, err := x.getPreviousEpochSwitchInfoByHash(chain, hash, limit) currentEpochSwitchInfo, err := x.getEpochSwitchInfo(chain, nil, hash)
if err != nil {
log.Error("[GetPreviousPenaltyByHash] Adaptor v2 getPreviousEpochSwitchInfoByHash has error, potentially bug", "err", err)
return []common.Address{}
}
if limit == 0 {
return currentEpochSwitchInfo.Penalties
}
epochNum := x.config.V2.SwitchEpoch + uint64(currentEpochSwitchInfo.EpochSwitchBlockInfo.Round)/x.config.Epoch
if epochNum < uint64(limit) {
// avoid negative number
log.Error("[GetPreviousPenaltyByHash] Adaptor v2 getPreviousEpochSwitchInfoByHash has error, too large limit", "limit", limit)
return []common.Address{}
}
_, header, err := x.binarySearchBlockByEpochNumber(chain, epochNum-uint64(limit), currentEpochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64()-x.config.Epoch*uint64(limit), currentEpochSwitchInfo.EpochSwitchParentBlockInfo.Number.Uint64())
if err != nil { if err != nil {
log.Error("[GetPreviousPenaltyByHash] Adaptor v2 getPreviousEpochSwitchInfoByHash has error, potentially bug", "err", err) log.Error("[GetPreviousPenaltyByHash] Adaptor v2 getPreviousEpochSwitchInfoByHash has error, potentially bug", "err", err)
return []common.Address{} return []common.Address{}
} }
header := chain.GetHeaderByHash(epochSwitchInfo.EpochSwitchBlockInfo.Hash)
return common.ExtractAddressFromBytes(header.Penalties) return common.ExtractAddressFromBytes(header.Penalties)
} }

View file

@ -246,28 +246,28 @@ func (x *XDPoS_v2) getBlockByEpochNumberInCache(chain consensus.ChainReader, est
return nil return nil
} }
func (x *XDPoS_v2) binarySearchBlockByEpochNumber(chain consensus.ChainReader, targetEpochNum uint64, start, end uint64) (*types.BlockInfo, error) { func (x *XDPoS_v2) binarySearchBlockByEpochNumber(chain consensus.ChainReader, targetEpochNum uint64, start, end uint64) (*types.BlockInfo, *types.Header, error) {
// `end` must be larger than the target and `start` could be the target // `end` must be larger than the target and `start` could be the target
for start < end { for start < end {
header := chain.GetHeaderByNumber((start + end) / 2) header := chain.GetHeaderByNumber((start + end) / 2)
if header == nil { if header == nil {
return nil, errors.New("header nil in binary search") return nil, nil, errors.New("header nil in binary search")
} }
isEpochSwitch, epochNum, err := x.IsEpochSwitch(header) isEpochSwitch, epochNum, err := x.IsEpochSwitch(header)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
if epochNum == targetEpochNum { if epochNum == targetEpochNum {
_, round, _, err := x.getExtraFields(header) _, round, _, err := x.getExtraFields(header)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
if isEpochSwitch { if isEpochSwitch {
return &types.BlockInfo{ return &types.BlockInfo{
Hash: header.Hash(), Hash: header.Hash(),
Round: round, Round: round,
Number: header.Number, Number: header.Number,
}, nil }, header, nil
} else { } else {
end = header.Number.Uint64() end = header.Number.Uint64()
// trick to shorten the search // trick to shorten the search
@ -287,7 +287,7 @@ func (x *XDPoS_v2) binarySearchBlockByEpochNumber(chain consensus.ChainReader, t
start = nextStart start = nextStart
} }
} }
return nil, errors.New("no epoch switch header in binary search (all rounds in this epoch are missed, which is very rare)") return nil, nil, errors.New("no epoch switch header in binary search (all rounds in this epoch are missed, which is very rare)")
} }
func (x *XDPoS_v2) GetBlockByEpochNumber(chain consensus.ChainReader, targetEpochNum uint64) (*types.BlockInfo, error) { func (x *XDPoS_v2) GetBlockByEpochNumber(chain consensus.ChainReader, targetEpochNum uint64) (*types.BlockInfo, error) {
@ -337,5 +337,6 @@ func (x *XDPoS_v2) GetBlockByEpochNumber(chain consensus.ChainReader, targetEpoc
} }
} }
// else, we use binary search // else, we use binary search
return x.binarySearchBlockByEpochNumber(chain, targetEpochNum, estBlockNum.Uint64(), epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64()) blockInfo, _, err = x.binarySearchBlockByEpochNumber(chain, targetEpochNum, estBlockNum.Uint64(), epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64())
return blockInfo, err
} }

View file

@ -578,6 +578,68 @@ func PrepareXDCTestBlockChainWithPenaltyForV2Engine(t *testing.T, numOfBlocks in
return blockchain, backend, currentBlock, signer, signFn return blockchain, backend, currentBlock, signer, signFn
} }
// V2 concensus engine, compared to PrepareXDCTestBlockChainForV2Engine: (1) no forking (2) add penalty
func PrepareXDCTestBlockChainWithPenaltyCustomized(t *testing.T, numOfBlocks int, chainConfig *params.ChainConfig, penaltyOrNot []bool) (*core.BlockChain, *backends.SimulatedBackend, *types.Block, common.Address, func(account accounts.Account, hash []byte) ([]byte, error)) {
// Preparation
var err error
signer, signFn, err := backends.SimulateWalletAddressAndSignFn()
if err != nil {
t.Fatal("Error while creating simulated wallet for generating singer address and signer fn: ", err)
}
backend := getCommonBackend(t, chainConfig)
blockchain := backend.BlockChain()
blockchain.Client = backend
// Authorise
blockchain.Engine().(*XDPoS.XDPoS).Authorize(signer, signFn)
currentBlock := blockchain.Genesis()
go func() {
for range core.CheckpointCh {
checkpointChanMsg := <-core.CheckpointCh
log.Info("[V2] Got a message from core CheckpointChan!", "msg", checkpointChanMsg)
}
}()
penaltyCnt := 0
// Insert initial blocks
for i := 1; i <= numOfBlocks; i++ {
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i)
// for v2 blocks, fill in correct coinbase
if int64(i) > chainConfig.XDPoS.V2.SwitchBlock.Int64() {
blockCoinBase = signer.Hex()
}
roundNumber := int64(i) - chainConfig.XDPoS.V2.SwitchBlock.Int64()
// use signer itself as penalty
penalty := signer[:]
if roundNumber%int64(chainConfig.XDPoS.Epoch) != 0 {
penalty = nil
} else {
if len(penaltyOrNot) > penaltyCnt && !penaltyOrNot[penaltyCnt] {
penalty = nil
t.Log("nilnil penalty for block", i)
}
penaltyCnt++
}
block := CreateBlock(blockchain, chainConfig, currentBlock, i, roundNumber, blockCoinBase, signer, signFn, penalty, nil, "")
err = blockchain.InsertBlock(block)
if err != nil {
t.Fatal(err)
}
currentBlock = block
}
// Update Signer as there is no previous signer assigned
err = UpdateSigner(blockchain)
if err != nil {
t.Fatal(err)
}
return blockchain, backend, currentBlock, signer, signFn
}
// V2 concensus engine, compared to PrepareXDCTestBlockChainForV2Engine: (1) no forking (2) 128 masternode candidates // V2 concensus engine, compared to PrepareXDCTestBlockChainForV2Engine: (1) no forking (2) 128 masternode candidates
func PrepareXDCTestBlockChainWith128Candidates(t *testing.T, numOfBlocks int, chainConfig *params.ChainConfig) (*core.BlockChain, *backends.SimulatedBackend, *types.Block, common.Address, func(account accounts.Account, hash []byte) ([]byte, error)) { func PrepareXDCTestBlockChainWith128Candidates(t *testing.T, numOfBlocks int, chainConfig *params.ChainConfig) (*core.BlockChain, *backends.SimulatedBackend, *types.Block, common.Address, func(account accounts.Account, hash []byte) ([]byte, error)) {
// Preparation // Preparation

View file

@ -1,6 +1,7 @@
package engine_v2_tests package engine_v2_tests
import ( import (
"encoding/json"
"math/big" "math/big"
"testing" "testing"
@ -39,7 +40,7 @@ func TestHookPenaltyV2Mining(t *testing.T) {
} }
} }
assert.True(t, contains) assert.True(t, contains)
// set adaptor round/qc to that of 6299 // set adaptor round/qc to that of 2099
err = utils.DecodeBytesExtraFields(header2100.Extra, &extraField) err = utils.DecodeBytesExtraFields(header2100.Extra, &extraField)
assert.Nil(t, err) assert.Nil(t, err)
err = adaptor.EngineV2.ProcessQCFaker(blockchain, extraField.QuorumCert) err = adaptor.EngineV2.ProcessQCFaker(blockchain, extraField.QuorumCert)
@ -84,7 +85,7 @@ func TestHookPenaltyV2Comeback(t *testing.T) {
// miner (coinbase) is in comeback. so all addresses are in penalty // miner (coinbase) is in comeback. so all addresses are in penalty
assert.Equal(t, 2, len(penalty)) assert.Equal(t, 2, len(penalty))
header2085 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*3 - common.MergeSignRange) header2085 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*3 - common.MergeSignRange)
// forcely insert signing tx into cache, to cancel comeback. since no comeback, penalty is 3 // forcely insert signing tx into cache, to cancel comeback. since no comeback, penalty is 1
tx, err := signingTxWithSignerFn(header2085, 0, signer, signFn) tx, err := signingTxWithSignerFn(header2085, 0, signer, signFn)
assert.Nil(t, err) assert.Nil(t, err)
adaptor.CacheSigningTxs(header2085.Hash(), []*types.Transaction{tx}) adaptor.CacheSigningTxs(header2085.Hash(), []*types.Transaction{tx})
@ -151,3 +152,107 @@ func TestGetPenalties(t *testing.T) {
assert.Equal(t, 1, len(penalty2699)) assert.Equal(t, 1, len(penalty2699))
assert.Equal(t, 1, len(penalty1801)) assert.Equal(t, 1, len(penalty1801))
} }
// TestHookPenaltyParolee tests that a penalty stays enough epoch, it will not be penalty.
// but if it does not stays enough, it will still be penalty.
func TestHookPenaltyParolee(t *testing.T) {
// set upgrade number to 0
backup := common.TipUpgradePenalty
common.TipUpgradePenalty = big.NewInt(0)
config := params.TestXDPoSMockChainConfig
blockchain, _, _, signer, signFn := PrepareXDCTestBlockChainWithPenaltyForV2Engine(t, int(config.XDPoS.Epoch)*4, config)
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
hooks.AttachConsensusV2Hooks(adaptor, blockchain, config)
assert.NotNil(t, adaptor.EngineV2.HookPenalty)
var extraField types.ExtraFields_v2
// 901 is the first v2 block
header901 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 1)
err := utils.DecodeBytesExtraFields(header901.Extra, &extraField)
assert.Nil(t, err)
masternodes := adaptor.GetMasternodesFromCheckpointHeader(header901)
assert.Equal(t, 5, len(masternodes))
header2700 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch * 3)
header2685 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*3 - common.MergeSignRange)
// forcely insert signing tx into cache, to cancel comeback. since no comeback, penalty is 1
tx, err := signingTxWithSignerFn(header2685, 0, signer, signFn)
assert.Nil(t, err)
adaptor.CacheSigningTxs(header2685.Hash(), []*types.Transaction{tx})
penalty, err := adaptor.EngineV2.HookPenalty(blockchain, big.NewInt(int64(config.XDPoS.Epoch*3)), header2700.ParentHash, masternodes)
assert.Nil(t, err)
// 2700 not trigger parole yet
assert.Equal(t, 1, len(penalty))
header3600 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch * 4)
penalty, err = adaptor.EngineV2.HookPenalty(blockchain, big.NewInt(int64(config.XDPoS.Epoch*4)), header3600.ParentHash, masternodes)
assert.Nil(t, err)
// miner (coinbase) is in comeback. so all addresses are in penalty
assert.Equal(t, 2, len(penalty))
header3585 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*4 - common.MergeSignRange)
// forcely insert signing tx into cache, to cancel comeback
tx, err = signingTxWithSignerFn(header3585, 0, signer, signFn)
assert.Nil(t, err)
adaptor.CacheSigningTxs(header3585.Hash(), []*types.Transaction{tx})
penalty, err = adaptor.EngineV2.HookPenalty(blockchain, big.NewInt(int64(config.XDPoS.Epoch*4)), header3600.ParentHash, masternodes)
assert.Nil(t, err)
assert.Equal(t, 2, len(penalty))
header3570 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*4 - common.MergeSignRange*2)
// forcely insert signing tx into cache, to cancel comeback. since no comeback, penalty is 1
tx, err = signingTxWithSignerFn(header3570, 0, signer, signFn)
assert.Nil(t, err)
adaptor.CacheSigningTxs(header3570.Hash(), []*types.Transaction{tx})
penalty, err = adaptor.EngineV2.HookPenalty(blockchain, big.NewInt(int64(config.XDPoS.Epoch*4)), header3600.ParentHash, masternodes)
assert.Nil(t, err)
assert.Equal(t, 1, len(penalty))
common.TipUpgradePenalty = backup
}
// TestHookPenaltyParoleePerformance tests penalty hook performance
func TestHookPenaltyParoleePerformance(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)
config.XDPoS.V2.AllConfigs[900].LimitPenaltyEpoch = 4
// set upgrade number to 0
backup := common.TipUpgradePenalty
common.TipUpgradePenalty = big.NewInt(0)
// 900 1800 2700 3600(not) 4500 5400 has penalty except 3600
penaltyOrNot := []bool{true, true, true, false, true, true}
blockchain, _, _, signer, signFn := PrepareXDCTestBlockChainWithPenaltyCustomized(t, int(config.XDPoS.Epoch)*7, &config, penaltyOrNot)
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
hooks.AttachConsensusV2Hooks(adaptor, blockchain, &config)
assert.NotNil(t, adaptor.EngineV2.HookPenalty)
var extraField types.ExtraFields_v2
// 901 is the first v2 block
header901 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 1)
err = utils.DecodeBytesExtraFields(header901.Extra, &extraField)
assert.Nil(t, err)
masternodes := adaptor.GetMasternodesFromCheckpointHeader(header901)
assert.Equal(t, 5, len(masternodes))
header6285 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*7 - common.MergeSignRange)
// forcely insert signing tx into cache, to cancel comeback
tx, err := signingTxWithSignerFn(header6285, 0, signer, signFn)
assert.Nil(t, err)
adaptor.CacheSigningTxs(header6285.Hash(), []*types.Transaction{tx})
header6270 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*7 - common.MergeSignRange*2)
// forcely insert signing tx into cache, to cancel comeback
tx, err = signingTxWithSignerFn(header6270, 0, signer, signFn)
assert.Nil(t, err)
adaptor.CacheSigningTxs(header6270.Hash(), []*types.Transaction{tx})
header6300 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch * 7)
penalty, err := adaptor.EngineV2.HookPenalty(blockchain, big.NewInt(int64(config.XDPoS.Epoch*7)), header6300.ParentHash, masternodes)
assert.Nil(t, err)
// miner (coinbase) is not parolee since one epoch it is not penalty. so it is in penalty. plus another one, total 2.
assert.Equal(t, 2, len(penalty))
common.TipUpgradePenalty = backup
}

View file

@ -45,11 +45,19 @@ func AttachConsensusV2Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConf
parentNumber := number.Uint64() - 1 parentNumber := number.Uint64() - 1
parentHash := currentHash parentHash := currentHash
var round types.Round
// check and wait the latest block is already in the disk // check and wait the latest block is already in the disk
// sometimes blocks are yet inserted into block // sometimes blocks are yet inserted into block
for timeout := 0; ; timeout++ { for timeout := 0; ; timeout++ {
parentHeader := chain.GetHeader(parentHash, parentNumber) parentHeader := chain.GetHeader(parentHash, parentNumber)
if parentHeader != nil { // found the latest block in the disk if parentHeader != nil { // found the latest block in the disk
// extract round number from the lastest block
r, err := adaptor.EngineV2.GetRoundNumber(parentHeader)
if err != nil {
log.Error("[V2 Hook Penalty] Fail to get round", "error", err)
return nil, err
}
round = r
break break
} }
log.Info("[V2 Hook Penalty] parentHeader is nil, wait block to be writen in disk", "parentNumber", parentNumber) log.Info("[V2 Hook Penalty] parentHeader is nil, wait block to be writen in disk", "parentNumber", parentNumber)
@ -83,12 +91,17 @@ func AttachConsensusV2Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConf
listBlockHash = append(listBlockHash, parentHash) listBlockHash = append(listBlockHash, parentHash)
} }
currentConfig := chain.Config().XDPoS.V2.Config(uint64(round))
// add list not miner to penalties // add list not miner to penalties
preMasternodes := adaptor.EngineV2.GetMasternodesByHash(chain, currentHash) preMasternodes := adaptor.EngineV2.GetMasternodesByHash(chain, currentHash)
penalties := []common.Address{} penalties := []common.Address{}
minimunMinerBlockPerEpoch := common.MinimunMinerBlockPerEpoch
if chain.Config().IsTIPUpgradePenalty(number) {
minimunMinerBlockPerEpoch = currentConfig.MinimumMinerBlockPerEpoch
}
for miner, total := range statMiners { for miner, total := range statMiners {
if total < common.MinimunMinerBlockPerEpoch { if total < minimunMinerBlockPerEpoch {
log.Info("[HookPenalty] Find a node does not create enough block", "addr", miner.Hex(), "total", total, "require", common.MinimunMinerBlockPerEpoch) log.Info("[HookPenalty] Find a node does not create enough block", "addr", miner.Hex(), "total", total, "require", minimunMinerBlockPerEpoch)
penalties = append(penalties, miner) penalties = append(penalties, miner)
} }
} }
@ -101,6 +114,8 @@ func AttachConsensusV2Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConf
// get list check penalties signing block & list master nodes wil comeback // get list check penalties signing block & list master nodes wil comeback
// start to calc comeback at v2 block + limitPenaltyEpochV2 to avoid reading v1 blocks // start to calc comeback at v2 block + limitPenaltyEpochV2 to avoid reading v1 blocks
if !chain.Config().IsTIPUpgradePenalty(number) {
comebackHeight := (common.LimitPenaltyEpochV2+1)*chain.Config().XDPoS.Epoch + chain.Config().XDPoS.V2.SwitchBlock.Uint64() comebackHeight := (common.LimitPenaltyEpochV2+1)*chain.Config().XDPoS.Epoch + chain.Config().XDPoS.V2.SwitchBlock.Uint64()
penComebacks := []common.Address{} penComebacks := []common.Address{}
if number.Uint64() > comebackHeight { if number.Uint64() > comebackHeight {
@ -114,8 +129,6 @@ func AttachConsensusV2Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConf
} }
} }
} }
}
// Loop for each block to check missing sign. with comeback nodes // Loop for each block to check missing sign. with comeback nodes
mapBlockHash := map[common.Hash]bool{} mapBlockHash := map[common.Hash]bool{}
startRange := common.RangeReturnSigner - 1 startRange := common.RangeReturnSigner - 1
@ -166,6 +179,67 @@ func AttachConsensusV2Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConf
penalties = append(penalties, comeback) penalties = append(penalties, comeback)
} }
} }
}
} else { // after penalty upgrade
comebackHeight := (uint64(currentConfig.LimitPenaltyEpoch)+1)*chain.Config().XDPoS.Epoch + chain.Config().XDPoS.V2.SwitchBlock.Uint64()
if number.Uint64() > comebackHeight {
// penParolees record those who stayed enough epoch of LimitPenaltyEpoch
penParoleeMap := map[common.Address]int{}
// lastPenalty record the last epoch penalties
lastPenalty := []common.Address{}
for i := 0; i <= currentConfig.LimitPenaltyEpoch; i++ {
pens := adaptor.EngineV2.GetPreviousPenaltyByHash(chain, currentHash, i)
for _, p := range pens {
penParoleeMap[p]++
}
if i == 0 {
// record the last epoch penalties
lastPenalty = pens
}
}
// Loop for each block to check missing sign. with comeback nodes
mapBlockHash := map[common.Hash]bool{}
txSignerMap := map[common.Address]int{}
startRange := int(chain.Config().XDPoS.Epoch) - 1
// to prevent visiting outside index of listBlockHash
if startRange >= len(listBlockHash) {
startRange = len(listBlockHash) - 1
}
for i := startRange; i >= 0; i-- {
blockNumber := number.Uint64() - uint64(i) - 1
bhash := listBlockHash[i]
if blockNumber%common.MergeSignRange == 0 {
mapBlockHash[bhash] = true
}
signingTxs, ok := adaptor.GetCachedSigningTxs(bhash)
if !ok {
block := chain.GetBlock(bhash, blockNumber)
txs := block.Transactions()
signingTxs = adaptor.CacheSigningTxs(bhash, txs)
}
// Check signer signed?
for _, tx := range signingTxs {
blkHash := common.BytesToHash(tx.Data()[len(tx.Data())-32:])
from := *tx.From()
if mapBlockHash[blkHash] {
txSignerMap[from]++
}
}
}
// check addr in lastPenalty, and if they does not meet condition, add them to penalty
for _, p := range lastPenalty {
if penParoleeMap[p] == currentConfig.LimitPenaltyEpoch+1 {
// check if this node signs enough
if txSignerMap[p] >= currentConfig.MinimumSigningTx {
continue
}
}
// reaches here means that the node should still stays in penalty list
penalties = append(penalties, p)
}
}
}
for i, p := range penalties { for i, p := range penalties {
log.Info("[HookPenalty] Final penalty list", "index", i, "addr", p) log.Info("[HookPenalty] Final penalty list", "index", i, "addr", p)

View file

@ -199,6 +199,8 @@ var (
MasternodeReward: 500, // double as Reward MasternodeReward: 500, // double as Reward
ProtectorReward: 400, ProtectorReward: 400,
ObserverReward: 300.125, ObserverReward: 300.125,
LimitPenaltyEpoch: 1,
MinimumSigningTx: 2,
}, },
} }
@ -484,6 +486,10 @@ type V2Config struct {
ProtectorReward float64 `json:"protectorReward"` // Block reward per protector - unit Ether ProtectorReward float64 `json:"protectorReward"` // Block reward per protector - unit Ether
ObserverReward float64 `json:"observerReward"` // Block reward per observer - unit Ether ObserverReward float64 `json:"observerReward"` // Block reward per observer - unit Ether
MinimumMinerBlockPerEpoch int `json:"minimumMinerBlockPerEpoch"` // Minimum block per epoch for a miner to not be penalized
LimitPenaltyEpoch int `json:"limitPenaltyEpoch"` // Epochs in a row that a penalty node needs to be penalized
MinimumSigningTx int `json:"minimumSigningTx"` // Signing txs that a node needs to produce to get out of penalty, after `LimitPenaltyEpoch`
ExpTimeoutConfig ExpTimeoutConfig `json:"expTimeoutConfig"` ExpTimeoutConfig ExpTimeoutConfig `json:"expTimeoutConfig"`
} }
@ -541,6 +547,14 @@ func (c *V2Config) Description(name string, indent int) string {
banner += fmt.Sprintf("%s- TimeoutSyncThreshold: %v\n", prefix, c.TimeoutSyncThreshold) banner += fmt.Sprintf("%s- TimeoutSyncThreshold: %v\n", prefix, c.TimeoutSyncThreshold)
banner += fmt.Sprintf("%s- TimeoutPeriod: %v\n", prefix, c.TimeoutPeriod) banner += fmt.Sprintf("%s- TimeoutPeriod: %v\n", prefix, c.TimeoutPeriod)
banner += fmt.Sprintf("%s- CertThreshold: %v", prefix, c.CertThreshold) banner += fmt.Sprintf("%s- CertThreshold: %v", prefix, c.CertThreshold)
banner += fmt.Sprintf("%s- MasternodeReward: %v", prefix, c.MasternodeReward)
banner += fmt.Sprintf("%s- ProtectorReward: %v", prefix, c.ProtectorReward)
banner += fmt.Sprintf("%s- ObserverReward: %v", prefix, c.ObserverReward)
banner += fmt.Sprintf("%s- MinimumMinerBlockPerEpoch: %v", prefix, c.MinimumMinerBlockPerEpoch)
banner += fmt.Sprintf("%s- LimitPenaltyEpoch: %v", prefix, c.LimitPenaltyEpoch)
banner += fmt.Sprintf("%s- MinimumSigningTx: %v", prefix, c.MinimumSigningTx)
banner += fmt.Sprintf("%s- ExpTimeoutBase: %v", prefix, c.ExpTimeoutConfig.Base)
banner += fmt.Sprintf("%s- ExpTimeoutMaxExponent: %v", prefix, c.ExpTimeoutConfig.MaxExponent)
return banner return banner
} }
@ -786,6 +800,10 @@ func (c *ChainConfig) IsTIPUpgradeReward(num *big.Int) bool {
return isForked(common.TIPUpgradeReward, num) return isForked(common.TIPUpgradeReward, num)
} }
func (c *ChainConfig) IsTIPUpgradePenalty(num *big.Int) bool {
return isForked(common.TipUpgradePenalty, num)
}
func (c *ChainConfig) IsTIPEpochHalving(num *big.Int) bool { func (c *ChainConfig) IsTIPEpochHalving(num *big.Int) bool {
return isForked(common.TIPEpochHalving, num) return isForked(common.TIPEpochHalving, num)
} }