diff --git a/consensus/XDPoS/engines/engine_v2/engine.go b/consensus/XDPoS/engines/engine_v2/engine.go index 1d703c21ad..37db23d45f 100644 --- a/consensus/XDPoS/engines/engine_v2/engine.go +++ b/consensus/XDPoS/engines/engine_v2/engine.go @@ -53,7 +53,8 @@ type XDPoS_v2 struct { highestTimeoutCert *utils.TimeoutCert highestCommitBlock *utils.BlockInfo - HookReward func(chain consensus.ChainReader, state *state.StateDB, parentState *state.StateDB, header *types.Header) (error, map[string]interface{}) + HookReward func(chain consensus.ChainReader, state *state.StateDB, parentState *state.StateDB, header *types.Header) (error, map[string]interface{}) + HookPenalty func(chain consensus.ChainReader, number *big.Int, parentHash common.Hash, candidates []common.Address) ([]common.Address, error) } func New(config *params.XDPoSConfig, db ethdb.Database, waitPeriodCh chan int) *XDPoS_v2 { @@ -204,15 +205,16 @@ func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) er return err } if isEpochSwitchBlock { - snap, err := x.getSnapshot(chain, number) + masterNodes, penalties, err := x.calcMasternodes(chain, header.Number, header.ParentHash) if err != nil { return err } - masternodes := snap.NextEpochMasterNodes - //TODO: remove penalty nodes and add comeback nodes, or change this logic into yourturn function - for _, v := range masternodes { + for _, v := range masterNodes { header.Validators = append(header.Validators, v[:]...) } + for _, v := range penalties { + header.Penalties = append(header.Penalties, v[:]...) + } } // Mix digest is reserved for now, set to empty @@ -366,16 +368,14 @@ func (x *XDPoS_v2) YourTurn(chain consensus.ChainReader, parent *types.Header, s log.Error("[YourTurn] Cannot find snapshot at gap num of last V1", "err", err, "number", x.config.V2.SwitchBlock.Uint64()) return false, err } - // the initial snapshot of v1->v2 switch containes penalites node + // the initial master nodes of v1->v2 switch contains penalties node masterNodes = snap.NextEpochMasterNodes } else { - snap, err := x.getSnapshot(chain, parent.Number.Uint64()+1) + masterNodes, _, err = x.calcMasternodes(chain, big.NewInt(0).Add(parent.Number, big.NewInt(1)), parent.Hash()) if err != nil { - log.Error("[YourTurn] Cannot find snapshot at gap block", "err", err, "number", x.config.V2.SwitchBlock.Uint64()) + log.Error("[YourTurn] Cannot calcMasternodes at gap num ", "err", err, "parent number", parent.Number) return false, err } - masterNodes = snap.NextEpochMasterNodes - // TODO: calculate master nodes with penalty and comback } } else { // this block and parent belong to the same epoch @@ -1189,6 +1189,13 @@ func (x *XDPoS_v2) SetNewRoundFaker(newRound utils.Round, resetTimer bool) { x.currentRound = newRound } +// for test only +func (x *XDPoS_v2) ProcessQC(chain consensus.ChainReader, qc *utils.QuorumCert) error { + x.lock.Lock() + defer x.lock.Unlock() + return x.processQC(chain, qc) +} + // Utils for test to check currentRound value func (x *XDPoS_v2) GetCurrentRound() utils.Round { x.lock.RLock() @@ -1348,3 +1355,50 @@ func (x *XDPoS_v2) GetCurrentEpochSwitchBlock(chain consensus.ChainReader, block epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(epochSwitchInfo.EpochSwitchBlockInfo.Round)/x.config.Epoch return currentCheckpointNumber, epochNum, nil } + +func (x *XDPoS_v2) calcMasternodes(chain consensus.ChainReader, blockNum *big.Int, parentHash common.Hash) ([]common.Address, []common.Address, error) { + snap, err := x.getSnapshot(chain, blockNum.Uint64()) + if err != nil { + log.Error("[calcMasternodes] Adaptor v2 getSnapshot has error", "err", err) + return nil, nil, err + } + candidates := snap.NextEpochMasterNodes + if x.HookPenalty != nil { + penalties, err := x.HookPenalty(chain, blockNum, parentHash, candidates) + if err != nil { + log.Error("[calcMasternodes] Adaptor v2 HookPenalty has error", "err", err) + return nil, nil, err + } + masternodes := common.RemoveItemFromArray(candidates, penalties) + return masternodes, penalties, nil + } + return candidates, []common.Address{}, nil +} + +// Given hash, get master node from the epoch switch block of the epoch +func (x *XDPoS_v2) GetMasternodesByHash(chain consensus.ChainReader, hash common.Hash) []common.Address { + epochSwitchInfo, err := x.getEpochSwitchInfo(chain, nil, hash) + if err != nil { + log.Error("[GetMasternodes] Adaptor v2 getEpochSwitchInfo has error, potentially bug", "err", err) + return []common.Address{} + } + return epochSwitchInfo.Masternodes +} + +// Given hash, get master node from the epoch switch block of the previous `limit` epoch +func (x *XDPoS_v2) GetPreviousPenaltyByHash(chain consensus.ChainReader, hash common.Hash, limit int) []common.Address { + epochSwitchInfo, err := x.getEpochSwitchInfo(chain, nil, hash) + if err != nil { + log.Error("[GetMasternodes] Adaptor v2 getEpochSwitchInfo has error, potentially bug", "err", err) + return []common.Address{} + } + for i := 0; i < limit; i++ { + epochSwitchInfo, err = x.getEpochSwitchInfo(chain, nil, epochSwitchInfo.EpochSwitchParentBlockInfo.Hash) + if err != nil { + log.Error("[GetMasternodes] Adaptor v2 getEpochSwitchInfo has error, potentially bug", "err", err) + return []common.Address{} + } + } + header := chain.GetHeaderByHash(epochSwitchInfo.EpochSwitchBlockInfo.Hash) + return common.ExtractAddressFromBytes(header.Penalties) +} diff --git a/consensus/tests/adaptor_test.go b/consensus/tests/adaptor_test.go index e53f8df711..89f3bb5e44 100644 --- a/consensus/tests/adaptor_test.go +++ b/consensus/tests/adaptor_test.go @@ -178,7 +178,7 @@ func TestAdaptorGetMasternodesV2(t *testing.T) { adaptor := blockchain.Engine().(*XDPoS.XDPoS) blockNum := 901 blockCoinBase := "0x111000000000000000000000000000000123" - currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 1, blockCoinBase, signer, signFn) + currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 1, blockCoinBase, signer, signFn, nil) // block 901 is the first v2 block, and is treated as epoch switch block blockchain.InsertBlock(currentBlock) @@ -187,7 +187,7 @@ func TestAdaptorGetMasternodesV2(t *testing.T) { masternodes1ByNumber := adaptor.GetMasternodesByNumber(blockchain, currentBlock.NumberU64()) assert.True(t, reflect.DeepEqual(masternodes1, masternodes1ByNumber), "at block number", blockNum) for blockNum = 902; blockNum < 915; blockNum++ { - currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, int64(blockNum-900), blockCoinBase, signer, signFn) + currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, int64(blockNum-900), blockCoinBase, signer, signFn, nil) blockchain.InsertBlock(currentBlock) masternodes2 := adaptor.GetMasternodes(blockchain, currentBlock.Header()) assert.True(t, reflect.DeepEqual(masternodes1, masternodes2), "at block number", blockNum) @@ -209,7 +209,7 @@ func TestGetCurrentEpochSwitchBlock(t *testing.T) { // V2 blockNum := 901 blockCoinBase := "0x111000000000000000000000000000000123" - currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 1, blockCoinBase, signer, signFn) + currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 1, blockCoinBase, signer, signFn, nil) blockchain.InsertBlock(currentBlock) currentCheckpointNumber, epochNum, err = adaptor.GetCurrentEpochSwitchBlock(blockchain, currentBlock.Number()) assert.Nil(t, err) @@ -217,7 +217,7 @@ func TestGetCurrentEpochSwitchBlock(t *testing.T) { assert.Equal(t, uint64(1), epochNum) for blockNum = 902; blockNum < 915; blockNum++ { - currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, int64(blockNum-900), blockCoinBase, signer, signFn) + currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, int64(blockNum-900), blockCoinBase, signer, signFn, nil) blockchain.InsertBlock(currentBlock) currentCheckpointNumber, epochNum, err := adaptor.GetCurrentEpochSwitchBlock(blockchain, currentBlock.Number()) diff --git a/consensus/tests/authorised_masternode_test.go b/consensus/tests/authorised_masternode_test.go index 4d9caf32a6..5c59e5a971 100644 --- a/consensus/tests/authorised_masternode_test.go +++ b/consensus/tests/authorised_masternode_test.go @@ -78,7 +78,7 @@ func TestIsAuthorisedMNForConsensusV2(t *testing.T) { adaptor := blockchain.Engine().(*XDPoS.XDPoS) blockNum := 901 blockCoinBase := "0x111000000000000000000000000000000123" - currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 1, blockCoinBase, signer, signFn) + currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 1, blockCoinBase, signer, signFn, nil) blockchain.InsertBlock(currentBlock) // As long as the address is in the master node list, they are all valid @@ -99,7 +99,7 @@ func TestIsYourTurnConsensusV2(t *testing.T) { adaptor := blockchain.Engine().(*XDPoS.XDPoS) blockNum := 901 blockCoinBase := "0x111000000000000000000000000000000123" - currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 1, blockCoinBase, signer, signFn) + currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 1, blockCoinBase, signer, signFn, nil) blockchain.InsertBlock(currentBlock) // Less then Mine Period @@ -123,7 +123,7 @@ func TestIsYourTurnConsensusV2(t *testing.T) { // We continue to grow the chain which will increase the round number blockNum = 902 - currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, int64(blockNum-900), blockCoinBase, signer, signFn) + currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, int64(blockNum-900), blockCoinBase, signer, signFn, nil) blockchain.InsertBlock(currentBlock) time.Sleep(time.Duration(minePeriod) * time.Second) diff --git a/consensus/tests/penalty_test.go b/consensus/tests/penalty_test.go new file mode 100644 index 0000000000..25d73b9f79 --- /dev/null +++ b/consensus/tests/penalty_test.go @@ -0,0 +1,102 @@ +package tests + +import ( + "math/big" + "reflect" + "testing" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/eth/hooks" + "github.com/XinFinOrg/XDPoSChain/params" + "github.com/stretchr/testify/assert" +) + +func TestHookPenaltyV2Mining(t *testing.T) { + config := params.TestXDPoSMockChainConfig + blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch)*7, config, 0) + adaptor := blockchain.Engine().(*XDPoS.XDPoS) + hooks.AttachConsensusV2Hooks(adaptor, blockchain, config) + assert.NotNil(t, adaptor.EngineV2.HookPenalty) + var extraField utils.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, 4, len(masternodes)) + 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 in penalty. all others are in penalty + assert.Equal(t, 3, len(penalty)) + assert.True(t, reflect.DeepEqual([]common.Address{header901.Coinbase}, common.RemoveItemFromArray(masternodes, penalty))) + // set adaptor round/qc to that of 6299 + err = utils.DecodeBytesExtraFields(header6300.Extra, &extraField) + assert.Nil(t, err) + err = adaptor.EngineV2.ProcessQC(blockchain, extraField.QuorumCert) + assert.Nil(t, err) + headerMining := &types.Header{ + ParentHash: header6300.ParentHash, + Number: header6300.Number, + GasLimit: params.TargetGasLimit, + Time: header6300.Time, + } + err = adaptor.Prepare(blockchain, headerMining) + assert.Nil(t, err) + assert.Equal(t, 3, len(headerMining.Penalties)/common.AddressLength) + // 20 candidates (set by PrepareXDCTestBlockChainForV2Engine) - 3 penalty = 17 + assert.Equal(t, 17, len(headerMining.Validators)/common.AddressLength) +} + +func TestHookPenaltyV2Comeback(t *testing.T) { + config := params.TestXDPoSMockChainConfig + blockchain, _, _, signer, signFn := PrepareXDCTestBlockChainWithPenaltyForV2Engine(t, int(config.XDPoS.Epoch)*7, config) + adaptor := blockchain.Engine().(*XDPoS.XDPoS) + hooks.AttachConsensusV2Hooks(adaptor, blockchain, config) + assert.NotNil(t, adaptor.EngineV2.HookPenalty) + var extraField utils.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, 4, len(masternodes)) + 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 in comeback. so all addresses are in penalty + assert.Equal(t, 4, len(penalty)) + header6285 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*7 - common.MergeSignRange) + // forcely insert signing tx into cache, to cancel comeback. since no comeback, penalty is 3 + tx, err := signingTx(header6285, 0, signer, signFn) + assert.Nil(t, err) + adaptor.CacheSigningTxs(header6285.Hash(), []*types.Transaction{tx}) + penalty, err = adaptor.EngineV2.HookPenalty(blockchain, big.NewInt(int64(config.XDPoS.Epoch*7)), header6300.ParentHash, masternodes) + assert.Nil(t, err) + assert.Equal(t, 3, len(penalty)) +} + +func TestHookPenaltyV2Jump(t *testing.T) { + config := params.TestXDPoSMockChainConfig + end := int(config.XDPoS.Epoch)*7 - common.MergeSignRange + blockchain, _, _, _, _ := PrepareXDCTestBlockChainWithPenaltyForV2Engine(t, int(config.XDPoS.Epoch)*7, config) + adaptor := blockchain.Engine().(*XDPoS.XDPoS) + hooks.AttachConsensusV2Hooks(adaptor, blockchain, config) + assert.NotNil(t, adaptor.EngineV2.HookPenalty) + var extraField utils.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, 4, len(masternodes)) + header6285 := blockchain.GetHeaderByNumber(uint64(end)) + adaptor.EngineV2.SetNewRoundFaker(utils.Round(config.XDPoS.Epoch*7), false) + // round 6285-6300 miss blocks, penalty should work as usual + penalty, err := adaptor.EngineV2.HookPenalty(blockchain, header6285.Number, header6285.ParentHash, masternodes) + assert.Nil(t, err) + assert.Equal(t, 4, len(penalty)) +} diff --git a/consensus/tests/proposed_block_test.go b/consensus/tests/proposed_block_test.go index ec810df1c0..882f7a095d 100644 --- a/consensus/tests/proposed_block_test.go +++ b/consensus/tests/proposed_block_test.go @@ -43,7 +43,7 @@ func TestShouldSendVoteMsgAndCommitGrandGrandParentBlock(t *testing.T) { // Insert another Block, but it won't trigger commit blockNum := 902 blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum) - block902 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 2, blockCoinBase, signer, signFn) + block902 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 2, blockCoinBase, signer, signFn, nil) blockchain.InsertBlock(block902) err = engineV2.ProposedBlockHandler(blockchain, block902.Header()) if err != nil { @@ -60,7 +60,7 @@ func TestShouldSendVoteMsgAndCommitGrandGrandParentBlock(t *testing.T) { // Insert one more Block, but still won't trigger commit blockNum = 903 blockCoinBase = fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum) - block903 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, block902, blockNum, 3, blockCoinBase, signer, signFn) + block903 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, block902, blockNum, 3, blockCoinBase, signer, signFn, nil) blockchain.InsertBlock(block903) err = engineV2.ProposedBlockHandler(blockchain, block903.Header()) if err != nil { @@ -78,7 +78,7 @@ func TestShouldSendVoteMsgAndCommitGrandGrandParentBlock(t *testing.T) { // Insert one more Block, this time will trigger commit blockNum = 904 blockCoinBase = fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum) - block904 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, block903, blockNum, 4, blockCoinBase, signer, signFn) + block904 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, block903, blockNum, 4, blockCoinBase, signer, signFn, nil) blockchain.InsertBlock(block904) err = engineV2.ProposedBlockHandler(blockchain, block904.Header()) if err != nil { @@ -129,7 +129,7 @@ func TestShouldNotCommitIfRoundsNotContinousFor3Rounds(t *testing.T) { // Injecting new block which have gaps in the round number (Round 7 instead of 6) blockNum := 906 blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum) - block906 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 7, blockCoinBase, signer, signFn) + block906 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 7, blockCoinBase, signer, signFn, nil) blockchain.InsertBlock(block906) err = engineV2.ProposedBlockHandler(blockchain, block906.Header()) if err != nil { @@ -150,7 +150,7 @@ func TestShouldNotCommitIfRoundsNotContinousFor3Rounds(t *testing.T) { blockNum = 907 blockCoinBase = fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum) - block907 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, block906, blockNum, 8, blockCoinBase, signer, signFn) + block907 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, block906, blockNum, 8, blockCoinBase, signer, signFn, nil) blockchain.InsertBlock(block907) err = engineV2.ProposedBlockHandler(blockchain, block907.Header()) if err != nil { diff --git a/consensus/tests/test_helper.go b/consensus/tests/test_helper.go index d4616a2273..f3c08ccf26 100644 --- a/consensus/tests/test_helper.go +++ b/consensus/tests/test_helper.go @@ -21,6 +21,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" + "github.com/XinFinOrg/XDPoSChain/contracts" contractValidator "github.com/XinFinOrg/XDPoSChain/contracts/validator/contract" "github.com/XinFinOrg/XDPoSChain/core" . "github.com/XinFinOrg/XDPoSChain/core" @@ -229,6 +230,21 @@ func voteTX(gasLimit uint64, nonce uint64, addr string) (*types.Transaction, err return signedTX, nil } +func signingTx(header *types.Header, nonce uint64, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error)) (*types.Transaction, error) { + tx := contracts.CreateTxSign(header.Number, header.Hash(), nonce, common.HexToAddress(common.BlockSigners)) + s := types.NewEIP155Signer(big.NewInt(chainID)) + h := s.Hash(tx) + sig, err := signFn(accounts.Account{Address: signer}, h[:]) + if err != nil { + return nil, err + } + signedTx, err := tx.WithSignature(s, sig) + if err != nil { + return nil, err + } + return signedTx, nil +} + func UpdateSigner(bc *BlockChain) error { err := bc.UpdateM1() return err @@ -354,8 +370,12 @@ func PrepareXDCTestBlockChainForV2Engine(t *testing.T, numOfBlocks int, chainCon // 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() - block := CreateBlock(blockchain, chainConfig, currentBlock, i, roundNumber, blockCoinBase, signer, signFn) + block := CreateBlock(blockchain, chainConfig, currentBlock, i, roundNumber, blockCoinBase, signer, signFn, nil) err = blockchain.InsertBlock(block) if err != nil { @@ -370,7 +390,7 @@ func PrepareXDCTestBlockChainForV2Engine(t *testing.T, numOfBlocks int, chainCon forkedBlockRoundNumber := roundNumber + int64(numOfForkedBlocks) - forkedBlock := CreateBlock(blockchain, chainConfig, currentForkBlock, i, forkedBlockRoundNumber, forkedBlockCoinBase, signer, signFn) + forkedBlock := CreateBlock(blockchain, chainConfig, currentForkBlock, i, forkedBlockRoundNumber, forkedBlockCoinBase, signer, signFn, nil) blockchain.InsertBlock(forkedBlock) currentForkBlock = forkedBlock @@ -387,7 +407,58 @@ func PrepareXDCTestBlockChainForV2Engine(t *testing.T, numOfBlocks int, chainCon return blockchain, backend, currentBlock, signer, signFn, currentForkBlock } -func CreateBlock(blockchain *BlockChain, chainConfig *params.ChainConfig, startingBlock *types.Block, blockNumber int, roundNumber int64, blockCoinBase string, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error)) *types.Block { +// V2 concensus engine, compared to PrepareXDCTestBlockChainForV2Engine: (1) no forking (2) add penalty +func PrepareXDCTestBlockChainWithPenaltyForV2Engine(t *testing.T, numOfBlocks int, chainConfig *params.ChainConfig) (*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.GetBlockChain() + 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) + } + }() + + // 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 + block := CreateBlock(blockchain, chainConfig, currentBlock, i, roundNumber, blockCoinBase, signer, signFn, signer[:]) + + 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 +} + +func CreateBlock(blockchain *BlockChain, chainConfig *params.ChainConfig, startingBlock *types.Block, blockNumber int, roundNumber int64, blockCoinBase string, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error), penalties []byte) *types.Block { currentBlock := startingBlock merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930" var header *types.Header @@ -442,8 +513,18 @@ func CreateBlock(blockchain *BlockChain, chainConfig *params.ChainConfig, starti for _, v := range masternodesFromV1LastEpoch { header.Validators = append(header.Validators, v[:]...) } + } else if roundNumber%int64(chainConfig.XDPoS.Epoch) == 0 { + // epoch switch blocks, copy the master node list and inject into v2 validators + // Get last master node list from last v1 block + lastv1Block := blockchain.GetBlockByNumber(chainConfig.XDPoS.V2.SwitchBlock.Uint64()) + masternodesFromV1LastEpoch := decodeMasternodesFromHeaderExtra(lastv1Block.Header()) + for _, v := range masternodesFromV1LastEpoch { + header.Validators = append(header.Validators, v[:]...) + } + if penalties != nil { + header.Penalties = penalties + } } - } else { // V1 block header = &types.Header{ @@ -531,6 +612,7 @@ func createBlockFromHeader(bc *BlockChain, customHeader *types.Header, txs []*ty Extra: customHeader.Extra, Validator: customHeader.Validator, Validators: customHeader.Validators, + Penalties: customHeader.Penalties, } var block *types.Block if len(txs) == 0 { diff --git a/consensus/tests/vote_test.go b/consensus/tests/vote_test.go index 8a6c17c35a..6aa9b67626 100644 --- a/consensus/tests/vote_test.go +++ b/consensus/tests/vote_test.go @@ -311,7 +311,7 @@ func TestVoteMessageShallNotThrowErrorIfBlockNotYetExist(t *testing.T) { // Create a new block but don't inject it into the chain yet blockNum := 906 blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum) - block := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 6, blockCoinBase, signer, signFn) + block := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 6, blockCoinBase, signer, signFn, nil) blockInfo := &utils.BlockInfo{ Hash: block.Header().Hash(), diff --git a/eth/hooks/engine_v2_hooks.go b/eth/hooks/engine_v2_hooks.go new file mode 100644 index 0000000000..8aa18b8e49 --- /dev/null +++ b/eth/hooks/engine_v2_hooks.go @@ -0,0 +1,138 @@ +package hooks + +import ( + "math/big" + "time" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS" + "github.com/XinFinOrg/XDPoSChain/core" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/log" + "github.com/XinFinOrg/XDPoSChain/params" +) + +func AttachConsensusV2Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConfig *params.ChainConfig) { + // Hook scans for bad masternodes and decide to penalty them + adaptor.EngineV2.HookPenalty = func(chain consensus.ChainReader, number *big.Int, parentHash common.Hash, candidates []common.Address) ([]common.Address, error) { + start := time.Now() + listBlockHash := make([]common.Hash, chain.Config().XDPoS.Epoch) + + // get list block hash & stats total created block + statMiners := make(map[common.Address]int) + listBlockHash[0] = parentHash + parentNumber := number.Uint64() - 1 + pHash := parentHash + for i := uint64(1); ; i++ { + parentHeader := chain.GetHeader(pHash, parentNumber) + b, _, err := adaptor.EngineV2.IsEpochSwitch(parentHeader) + if err != nil { + log.Error("[HookPenalty]", "err", err) + return []common.Address{}, err + } + if b { + break + } + miner := parentHeader.Coinbase // we can directly use coinbase, since it's verified (Verification is a TODO) + value, exist := statMiners[miner] + if exist { + value = value + 1 + } else { + value = 1 + } + statMiners[miner] = value + pHash = parentHeader.ParentHash + parentNumber-- + listBlockHash[i] = pHash + } + + // add list not miner to penalties + preMasternodes := adaptor.EngineV2.GetMasternodesByHash(chain, parentHash) + penalties := []common.Address{} + for miner, total := range statMiners { + if total < common.MinimunMinerBlockPerEpoch { + log.Debug("Find a node not enough requirement create block", "addr", miner.Hex(), "total", total) + penalties = append(penalties, miner) + } + } + for _, addr := range preMasternodes { + if _, exist := statMiners[addr]; !exist { + log.Debug("Find a node don't create block", "addr", addr.Hex()) + penalties = append(penalties, addr) + } + } + + // get list check penalties signing block & list master nodes wil comeback + // start to calc comeback at v2 block + limitPenaltyEpoch to avoid reading v1 blocks + comebackHeight := (common.LimitPenaltyEpoch+1)*chain.Config().XDPoS.Epoch + chain.Config().XDPoS.V2.SwitchBlock.Uint64() + penComebacks := []common.Address{} + if number.Uint64() > comebackHeight { + pens := adaptor.EngineV2.GetPreviousPenaltyByHash(chain, parentHash, common.LimitPenaltyEpoch) + for _, p := range pens { + for _, addr := range candidates { + if p == 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 { + blockNumber := number.Uint64() - uint64(i) - 1 + bhash := listBlockHash[i] + if blockNumber%common.MergeSignRange == 0 { + mapBlockHash[bhash] = true + } + signData, ok := adaptor.GetCachedSigningTxs(bhash) + if !ok { + block := chain.GetBlock(bhash, blockNumber) + txs := block.Transactions() + signData = adaptor.CacheSigningTxs(bhash, txs) + } + txs := signData.([]*types.Transaction) + // Check signer signed? + for _, tx := range txs { + 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 + } + } + } + } + } else { + break + } + } + + log.Debug("Time Calculated HookPenaltyV2 ", "block", number, "pen comeback nodes", len(penComebacks), "not enough miner", len(penalties), "time", common.PrettyDuration(time.Since(start))) + for _, comeback := range penComebacks { + ok := true + for _, p := range penalties { + if p == comeback { + ok = false + break + } + } + if ok { + penalties = append(penalties, comeback) + } + } + return penalties, nil + } + +}