diff --git a/common/constants.all.go b/common/constants.all.go index fc0b44aff8..cbf664fd5d 100644 --- a/common/constants.all.go +++ b/common/constants.all.go @@ -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 diff --git a/common/constants.devnet.go b/common/constants.devnet.go index d0e8ff8316..c02c73c29f 100644 --- a/common/constants.devnet.go +++ b/common/constants.devnet.go @@ -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"), diff --git a/common/constants.local.go b/common/constants.local.go index d1cdbef1fb..aeec55f669 100644 --- a/common/constants.local.go +++ b/common/constants.local.go @@ -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"), diff --git a/common/constants.mainnet.go b/common/constants.mainnet.go index 842ab4c481..8d00b7b486 100644 --- a/common/constants.mainnet.go +++ b/common/constants.mainnet.go @@ -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"), diff --git a/common/constants.testnet.go b/common/constants.testnet.go index 6af97f7e34..055fd43494 100644 --- a/common/constants.testnet.go +++ b/common/constants.testnet.go @@ -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"), diff --git a/consensus/XDPoS/engines/engine_v2/engine.go b/consensus/XDPoS/engines/engine_v2/engine.go index e974a73fd8..7c59ed1ae4 100644 --- a/consensus/XDPoS/engines/engine_v2/engine.go +++ b/consensus/XDPoS/engines/engine_v2/engine.go @@ -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) } diff --git a/consensus/XDPoS/engines/engine_v2/utils.go b/consensus/XDPoS/engines/engine_v2/utils.go index 0a0f2ad7a6..6fa21a304f 100644 --- a/consensus/XDPoS/engines/engine_v2/utils.go +++ b/consensus/XDPoS/engines/engine_v2/utils.go @@ -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 } diff --git a/consensus/tests/engine_v2_tests/helper.go b/consensus/tests/engine_v2_tests/helper.go index 90e68dc7a9..4586502ceb 100644 --- a/consensus/tests/engine_v2_tests/helper.go +++ b/consensus/tests/engine_v2_tests/helper.go @@ -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 diff --git a/consensus/tests/engine_v2_tests/penalty_test.go b/consensus/tests/engine_v2_tests/penalty_test.go index 3b4b6887ce..85c41849d0 100644 --- a/consensus/tests/engine_v2_tests/penalty_test.go +++ b/consensus/tests/engine_v2_tests/penalty_test.go @@ -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 +} diff --git a/eth/hooks/engine_v2_hooks.go b/eth/hooks/engine_v2_hooks.go index 521c9944d6..5a3a5ae5dc 100644 --- a/eth/hooks/engine_v2_hooks.go +++ b/eth/hooks/engine_v2_hooks.go @@ -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) + } } } diff --git a/params/config.go b/params/config.go index 72fc9b8a64..6d86f96e15 100644 --- a/params/config.go +++ b/params/config.go @@ -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) }