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 blackListHFNumber uint64
maxMasternodesV2 int // Last v1 masternodes maxMasternodesV2 int // Last v1 masternodes
tip2019Block *big.Int tip2019Block *big.Int
tipSigning *big.Int tipSigning *big.Int
tipRandomize *big.Int tipRandomize *big.Int
tipNoHalvingMNReward *big.Int // hard fork no halving masternodes reward tipNoHalvingMNReward *big.Int // hard fork no halving masternodes reward
tipXDCX *big.Int tipXDCX *big.Int
tipXDCXLending *big.Int tipXDCXLending *big.Int
tipXDCXCancellationFee *big.Int tipXDCXCancellationFee *big.Int
tipTRC21Fee *big.Int tipTRC21Fee *big.Int
tipIncreaseMasternodes *big.Int // Upgrade MN Count at Block. tipIncreaseMasternodes *big.Int // Upgrade MN Count at Block.
berlinBlock *big.Int berlinBlock *big.Int
londonBlock *big.Int londonBlock *big.Int
mergeBlock *big.Int mergeBlock *big.Int
shanghaiBlock *big.Int shanghaiBlock *big.Int
blockNumberGas50x *big.Int blockNumberGas50x *big.Int
TIPV2SwitchBlock *big.Int TIPV2SwitchBlock *big.Int
tipXDCXMinerDisable *big.Int tipXDCXMinerDisable *big.Int
tipXDCXReceiverDisable *big.Int tipXDCXReceiverDisable *big.Int
tipUpgradeReward *big.Int tipUpgradeReward *big.Int
tipEpochHalving *big.Int tipUpgradePenalty *big.Int
eip1559Block *big.Int tipEpochHalving *big.Int
cancunBlock *big.Int eip1559Block *big.Int
cancunBlock *big.Int
trc21IssuerSMC Address trc21IssuerSMC Address
xdcxListingSMC Address xdcxListingSMC Address
@ -79,26 +80,27 @@ var (
BlackListHFNumber = MaintnetConstant.blackListHFNumber BlackListHFNumber = MaintnetConstant.blackListHFNumber
MaxMasternodesV2 = MaintnetConstant.maxMasternodesV2 // Last v1 masternodes MaxMasternodesV2 = MaintnetConstant.maxMasternodesV2 // Last v1 masternodes
TIP2019Block = MaintnetConstant.tip2019Block TIP2019Block = MaintnetConstant.tip2019Block
TIPSigning = MaintnetConstant.tipSigning TIPSigning = MaintnetConstant.tipSigning
TIPRandomize = MaintnetConstant.tipRandomize TIPRandomize = MaintnetConstant.tipRandomize
TIPNoHalvingMNReward = MaintnetConstant.tipNoHalvingMNReward TIPNoHalvingMNReward = MaintnetConstant.tipNoHalvingMNReward
TIPXDCX = MaintnetConstant.tipXDCX TIPXDCX = MaintnetConstant.tipXDCX
TIPXDCXLending = MaintnetConstant.tipXDCXLending TIPXDCXLending = MaintnetConstant.tipXDCXLending
TIPXDCXCancellationFee = MaintnetConstant.tipXDCXCancellationFee TIPXDCXCancellationFee = MaintnetConstant.tipXDCXCancellationFee
TIPTRC21Fee = MaintnetConstant.tipTRC21Fee TIPTRC21Fee = MaintnetConstant.tipTRC21Fee
TIPIncreaseMasternodes = MaintnetConstant.tipIncreaseMasternodes TIPIncreaseMasternodes = MaintnetConstant.tipIncreaseMasternodes
BerlinBlock = MaintnetConstant.berlinBlock BerlinBlock = MaintnetConstant.berlinBlock
LondonBlock = MaintnetConstant.londonBlock LondonBlock = MaintnetConstant.londonBlock
MergeBlock = MaintnetConstant.mergeBlock MergeBlock = MaintnetConstant.mergeBlock
ShanghaiBlock = MaintnetConstant.shanghaiBlock ShanghaiBlock = MaintnetConstant.shanghaiBlock
BlockNumberGas50x = MaintnetConstant.blockNumberGas50x BlockNumberGas50x = MaintnetConstant.blockNumberGas50x
TIPXDCXMinerDisable = MaintnetConstant.tipXDCXMinerDisable TIPXDCXMinerDisable = MaintnetConstant.tipXDCXMinerDisable
TIPXDCXReceiverDisable = MaintnetConstant.tipXDCXReceiverDisable TIPXDCXReceiverDisable = MaintnetConstant.tipXDCXReceiverDisable
Eip1559Block = MaintnetConstant.eip1559Block Eip1559Block = MaintnetConstant.eip1559Block
CancunBlock = MaintnetConstant.cancunBlock CancunBlock = MaintnetConstant.cancunBlock
TIPUpgradeReward = MaintnetConstant.tipUpgradeReward TIPUpgradeReward = MaintnetConstant.tipUpgradeReward
TIPEpochHalving = MaintnetConstant.tipEpochHalving TipUpgradePenalty = MaintnetConstant.tipUpgradePenalty
TIPEpochHalving = MaintnetConstant.tipEpochHalving
TRC21IssuerSMC = MaintnetConstant.trc21IssuerSMC TRC21IssuerSMC = MaintnetConstant.trc21IssuerSMC
XDCXListingSMC = MaintnetConstant.xdcxListingSMC XDCXListingSMC = MaintnetConstant.xdcxListingSMC
@ -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,69 +114,130 @@ 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
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 if !chain.Config().IsTIPUpgradePenalty(number) {
mapBlockHash := map[common.Hash]bool{} comebackHeight := (common.LimitPenaltyEpochV2+1)*chain.Config().XDPoS.Epoch + chain.Config().XDPoS.V2.SwitchBlock.Uint64()
startRange := common.RangeReturnSigner - 1 penComebacks := []common.Address{}
// to prevent visiting outside index of listBlockHash if number.Uint64() > comebackHeight {
if startRange >= len(listBlockHash) { pens := adaptor.EngineV2.GetPreviousPenaltyByHash(chain, currentHash, common.LimitPenaltyEpochV2)
startRange = len(listBlockHash) - 1 for _, p := range pens {
} for _, addr := range candidates {
for i := startRange; i >= 0; i-- { if p == addr {
if len(penComebacks) == 0 { log.Info("[HookPenalty] get previous penalty node and add into comeback list", "addr", addr)
break penComebacks = append(penComebacks, p)
}
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 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 { for _, comeback := range penComebacks {
ok := true ok := true
for _, p := range penalties { for _, p := range penalties {
if p == comeback { if p == comeback {
ok = false ok = false
break break
}
}
if ok {
penalties = append(penalties, comeback)
}
} }
} }
if ok { } else { // after penalty upgrade
penalties = append(penalties, comeback) 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 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)
} }