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

@ -42,27 +42,28 @@ type constant struct {
blackListHFNumber uint64
maxMasternodesV2 int // Last v1 masternodes
tip2019Block *big.Int
tipSigning *big.Int
tipRandomize *big.Int
tipNoHalvingMNReward *big.Int // hard fork no halving masternodes reward
tipXDCX *big.Int
tipXDCXLending *big.Int
tipXDCXCancellationFee *big.Int
tipTRC21Fee *big.Int
tipIncreaseMasternodes *big.Int // Upgrade MN Count at Block.
berlinBlock *big.Int
londonBlock *big.Int
mergeBlock *big.Int
shanghaiBlock *big.Int
blockNumberGas50x *big.Int
TIPV2SwitchBlock *big.Int
tipXDCXMinerDisable *big.Int
tipXDCXReceiverDisable *big.Int
tipUpgradeReward *big.Int
tipEpochHalving *big.Int
eip1559Block *big.Int
cancunBlock *big.Int
tip2019Block *big.Int
tipSigning *big.Int
tipRandomize *big.Int
tipNoHalvingMNReward *big.Int // hard fork no halving masternodes reward
tipXDCX *big.Int
tipXDCXLending *big.Int
tipXDCXCancellationFee *big.Int
tipTRC21Fee *big.Int
tipIncreaseMasternodes *big.Int // Upgrade MN Count at Block.
berlinBlock *big.Int
londonBlock *big.Int
mergeBlock *big.Int
shanghaiBlock *big.Int
blockNumberGas50x *big.Int
TIPV2SwitchBlock *big.Int
tipXDCXMinerDisable *big.Int
tipXDCXReceiverDisable *big.Int
tipUpgradeReward *big.Int
tipUpgradePenalty *big.Int
tipEpochHalving *big.Int
eip1559Block *big.Int
cancunBlock *big.Int
trc21IssuerSMC Address
xdcxListingSMC Address
@ -79,26 +80,27 @@ var (
BlackListHFNumber = MaintnetConstant.blackListHFNumber
MaxMasternodesV2 = MaintnetConstant.maxMasternodesV2 // Last v1 masternodes
TIP2019Block = MaintnetConstant.tip2019Block
TIPSigning = MaintnetConstant.tipSigning
TIPRandomize = MaintnetConstant.tipRandomize
TIPNoHalvingMNReward = MaintnetConstant.tipNoHalvingMNReward
TIPXDCX = MaintnetConstant.tipXDCX
TIPXDCXLending = MaintnetConstant.tipXDCXLending
TIPXDCXCancellationFee = MaintnetConstant.tipXDCXCancellationFee
TIPTRC21Fee = MaintnetConstant.tipTRC21Fee
TIPIncreaseMasternodes = MaintnetConstant.tipIncreaseMasternodes
BerlinBlock = MaintnetConstant.berlinBlock
LondonBlock = MaintnetConstant.londonBlock
MergeBlock = MaintnetConstant.mergeBlock
ShanghaiBlock = MaintnetConstant.shanghaiBlock
BlockNumberGas50x = MaintnetConstant.blockNumberGas50x
TIPXDCXMinerDisable = MaintnetConstant.tipXDCXMinerDisable
TIPXDCXReceiverDisable = MaintnetConstant.tipXDCXReceiverDisable
Eip1559Block = MaintnetConstant.eip1559Block
CancunBlock = MaintnetConstant.cancunBlock
TIPUpgradeReward = MaintnetConstant.tipUpgradeReward
TIPEpochHalving = MaintnetConstant.tipEpochHalving
TIP2019Block = MaintnetConstant.tip2019Block
TIPSigning = MaintnetConstant.tipSigning
TIPRandomize = MaintnetConstant.tipRandomize
TIPNoHalvingMNReward = MaintnetConstant.tipNoHalvingMNReward
TIPXDCX = MaintnetConstant.tipXDCX
TIPXDCXLending = MaintnetConstant.tipXDCXLending
TIPXDCXCancellationFee = MaintnetConstant.tipXDCXCancellationFee
TIPTRC21Fee = MaintnetConstant.tipTRC21Fee
TIPIncreaseMasternodes = MaintnetConstant.tipIncreaseMasternodes
BerlinBlock = MaintnetConstant.berlinBlock
LondonBlock = MaintnetConstant.londonBlock
MergeBlock = MaintnetConstant.mergeBlock
ShanghaiBlock = MaintnetConstant.shanghaiBlock
BlockNumberGas50x = MaintnetConstant.blockNumberGas50x
TIPXDCXMinerDisable = MaintnetConstant.tipXDCXMinerDisable
TIPXDCXReceiverDisable = MaintnetConstant.tipXDCXReceiverDisable
Eip1559Block = MaintnetConstant.eip1559Block
CancunBlock = MaintnetConstant.cancunBlock
TIPUpgradeReward = MaintnetConstant.tipUpgradeReward
TipUpgradePenalty = MaintnetConstant.tipUpgradePenalty
TIPEpochHalving = MaintnetConstant.tipEpochHalving
TRC21IssuerSMC = MaintnetConstant.trc21IssuerSMC
XDCXListingSMC = MaintnetConstant.xdcxListingSMC
@ -158,6 +160,7 @@ func CopyConstants(chainID uint64) {
Eip1559Block = c.eip1559Block
CancunBlock = c.cancunBlock
TIPUpgradeReward = c.tipUpgradeReward
TipUpgradePenalty = c.tipUpgradePenalty
TIPEpochHalving = c.tipEpochHalving
TRC21IssuerSMC = c.trc21IssuerSMC

View file

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

View file

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

View file

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

View file

@ -29,6 +29,7 @@ var TestnetConstant = constant{
eip1559Block: big.NewInt(71550000), // Target 14th Feb 2025
cancunBlock: big.NewInt(71551800),
tipUpgradeReward: big.NewInt(9999999999),
tipUpgradePenalty: big.NewInt(9999999999),
tipEpochHalving: big.NewInt(9999999999),
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
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 {
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)
}

View file

@ -246,28 +246,28 @@ func (x *XDPoS_v2) getBlockByEpochNumberInCache(chain consensus.ChainReader, est
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
for start < end {
header := chain.GetHeaderByNumber((start + end) / 2)
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)
if err != nil {
return nil, err
return nil, nil, err
}
if epochNum == targetEpochNum {
_, round, _, err := x.getExtraFields(header)
if err != nil {
return nil, err
return nil, nil, err
}
if isEpochSwitch {
return &types.BlockInfo{
Hash: header.Hash(),
Round: round,
Number: header.Number,
}, nil
}, header, nil
} else {
end = header.Number.Uint64()
// trick to shorten the search
@ -287,7 +287,7 @@ func (x *XDPoS_v2) binarySearchBlockByEpochNumber(chain consensus.ChainReader, t
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) {
@ -337,5 +337,6 @@ func (x *XDPoS_v2) GetBlockByEpochNumber(chain consensus.ChainReader, targetEpoc
}
}
// 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
}
// 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
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

View file

@ -1,6 +1,7 @@
package engine_v2_tests
import (
"encoding/json"
"math/big"
"testing"
@ -39,7 +40,7 @@ func TestHookPenaltyV2Mining(t *testing.T) {
}
}
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)
assert.Nil(t, err)
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
assert.Equal(t, 2, len(penalty))
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)
assert.Nil(t, err)
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(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
parentHash := currentHash
var round types.Round
// check and wait the latest block is already in the disk
// sometimes blocks are yet inserted into block
for timeout := 0; ; timeout++ {
parentHeader := chain.GetHeader(parentHash, parentNumber)
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
}
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)
}
currentConfig := chain.Config().XDPoS.V2.Config(uint64(round))
// add list not miner to penalties
preMasternodes := adaptor.EngineV2.GetMasternodesByHash(chain, currentHash)
penalties := []common.Address{}
minimunMinerBlockPerEpoch := common.MinimunMinerBlockPerEpoch
if chain.Config().IsTIPUpgradePenalty(number) {
minimunMinerBlockPerEpoch = currentConfig.MinimumMinerBlockPerEpoch
}
for miner, total := range statMiners {
if total < common.MinimunMinerBlockPerEpoch {
log.Info("[HookPenalty] Find a node does not create enough block", "addr", miner.Hex(), "total", total, "require", common.MinimunMinerBlockPerEpoch)
if total < minimunMinerBlockPerEpoch {
log.Info("[HookPenalty] Find a node does not create enough block", "addr", miner.Hex(), "total", total, "require", minimunMinerBlockPerEpoch)
penalties = append(penalties, miner)
}
}
@ -101,69 +114,130 @@ func AttachConsensusV2Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConf
// get list check penalties signing block & list master nodes wil comeback
// start to calc comeback at v2 block + limitPenaltyEpochV2 to avoid reading v1 blocks
comebackHeight := (common.LimitPenaltyEpochV2+1)*chain.Config().XDPoS.Epoch + chain.Config().XDPoS.V2.SwitchBlock.Uint64()
penComebacks := []common.Address{}
if number.Uint64() > comebackHeight {
pens := adaptor.EngineV2.GetPreviousPenaltyByHash(chain, currentHash, common.LimitPenaltyEpochV2)
for _, p := range pens {
for _, addr := range candidates {
if p == addr {
log.Info("[HookPenalty] get previous penalty node and add into comeback list", "addr", addr)
penComebacks = append(penComebacks, p)
break
}
}
}
}
// Loop for each block to check missing sign. with comeback nodes
mapBlockHash := map[common.Hash]bool{}
startRange := common.RangeReturnSigner - 1
// to prevent visiting outside index of listBlockHash
if startRange >= len(listBlockHash) {
startRange = len(listBlockHash) - 1
}
for i := startRange; i >= 0; i-- {
if len(penComebacks) == 0 {
break
}
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] {
for j, addr := range penComebacks {
if from == addr {
// Remove it from dupSigners.
penComebacks = append(penComebacks[:j], penComebacks[j+1:]...)
if !chain.Config().IsTIPUpgradePenalty(number) {
comebackHeight := (common.LimitPenaltyEpochV2+1)*chain.Config().XDPoS.Epoch + chain.Config().XDPoS.V2.SwitchBlock.Uint64()
penComebacks := []common.Address{}
if number.Uint64() > comebackHeight {
pens := adaptor.EngineV2.GetPreviousPenaltyByHash(chain, currentHash, common.LimitPenaltyEpochV2)
for _, p := range pens {
for _, addr := range candidates {
if p == addr {
log.Info("[HookPenalty] get previous penalty node and add into comeback list", "addr", addr)
penComebacks = append(penComebacks, p)
break
}
}
}
}
}
// Loop for each block to check missing sign. with comeback nodes
mapBlockHash := map[common.Hash]bool{}
startRange := common.RangeReturnSigner - 1
// to prevent visiting outside index of listBlockHash
if startRange >= len(listBlockHash) {
startRange = len(listBlockHash) - 1
}
for i := startRange; i >= 0; i-- {
if len(penComebacks) == 0 {
break
}
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] {
for j, addr := range penComebacks {
if from == addr {
// Remove it from dupSigners.
penComebacks = append(penComebacks[:j], penComebacks[j+1:]...)
break
}
}
}
}
}
for _, comeback := range penComebacks {
ok := true
for _, p := range penalties {
if p == comeback {
ok = false
break
for _, comeback := range penComebacks {
ok := true
for _, p := range penalties {
if p == comeback {
ok = false
break
}
}
if ok {
penalties = append(penalties, comeback)
}
}
}
if ok {
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)
}
}
}

View file

@ -199,6 +199,8 @@ var (
MasternodeReward: 500, // double as Reward
ProtectorReward: 400,
ObserverReward: 300.125,
LimitPenaltyEpoch: 1,
MinimumSigningTx: 2,
},
}
@ -484,6 +486,10 @@ type V2Config struct {
ProtectorReward float64 `json:"protectorReward"` // Block reward per protector - 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"`
}
@ -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- TimeoutPeriod: %v\n", prefix, c.TimeoutPeriod)
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
}
@ -786,6 +800,10 @@ func (c *ChainConfig) IsTIPUpgradeReward(num *big.Int) bool {
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 {
return isForked(common.TIPEpochHalving, num)
}