diff --git a/consensus/XDPoS/engines/engine_v2/engine.go b/consensus/XDPoS/engines/engine_v2/engine.go index d368c17cd2..ab6a477d34 100644 --- a/consensus/XDPoS/engines/engine_v2/engine.go +++ b/consensus/XDPoS/engines/engine_v2/engine.go @@ -319,41 +319,55 @@ func (x *XDPoS_v2) calcDifficulty(chain consensus.ChainReader, parent *types.Hea return big.NewInt(1) } -// Copy from v1 +// Check if it's my turm to mine a block. Note: The second return value `preIndex` is useless in V2 engine func (x *XDPoS_v2) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (int, int, int, bool, error) { - snap, err := x.GetSnapshot(chain, parent) + x.lock.RLock() + defer x.lock.RUnlock() + + round := x.currentRound + isEpochSwitch, _, err := x.IsEpochSwitchAtRound(round, parent) if err != nil { - log.Error("[YourTurn] Failed while getting snapshot", "parentHash", parent.Hash(), "err", err) + log.Error("[YourTurn]", "Error", err) return 0, -1, -1, false, err } - masternodes := x.GetMasternodes(chain, parent) - if len(masternodes) == 0 { + var masterNodes []common.Address + if isEpochSwitch { + if x.config.XDPoSV2Block.Cmp(parent.Number) == 0 { + // TODO: read v1 master nodes + } else { + // TODO: calc master nodes by smart contract - penalty + // TODO: related to snapshot + } + } else { + // this block and parent belong to the same epoch + masterNodes = x.GetMasternodes(chain, parent) + } + + if len(masterNodes) == 0 { + log.Error("[YourTurn] Fail to find any master nodes from current block round epoch", "Hash", parent.Hash(), "CurrentRound", round, "Number", parent.Number) return 0, -1, -1, false, errors.New("Masternodes not found") } - pre := common.Address{} - // masternode[0] has chance to create block 1 - preIndex := -1 - if parent.Number.Uint64() != 0 { - pre, err = whoIsCreator(snap, parent) - if err != nil { - return 0, 0, 0, false, err - } - preIndex = utils.Position(masternodes, pre) - } - curIndex := utils.Position(masternodes, signer) + leaderIndex := uint64(round) % x.config.Epoch % uint64(len(masterNodes)) + + curIndex := utils.Position(masterNodes, signer) if signer == x.signer { - log.Debug("Masternodes cycle info", "number of masternodes", len(masternodes), "previous", pre, "position", preIndex, "current", signer, "position", curIndex) + log.Debug("[YourTurn] masterNodes cycle info", "number of masternodes", len(masterNodes), "current", signer, "position", curIndex, "parentBlock", parent) } - for i, s := range masternodes { - log.Debug("Masternode:", "index", i, "address", s.String()) + for i, s := range masterNodes { + log.Debug("[YourTurn] Masternode:", "index", i, "address", s.String(), "parentBlockNum", parent.Number) } - if (preIndex+1)%len(masternodes) == curIndex { - return len(masternodes), preIndex, curIndex, true, nil + + if masterNodes[leaderIndex] == signer { + return len(masterNodes), -1, curIndex, true, nil } - return len(masternodes), preIndex, curIndex, false, nil + log.Warn("[YourTurn] Not authorised signer", "signer", signer, "MN", masterNodes, "Hash", parent.Hash(), "masterNodes[leaderIndex]", masterNodes[leaderIndex], "signer", signer) + return len(masterNodes), -1, curIndex, false, nil } func (x *XDPoS_v2) IsAuthorisedAddress(chain consensus.ChainReader, header *types.Header, address common.Address) bool { + x.lock.RLock() + defer x.lock.RUnlock() + var extraField utils.ExtraFields_v2 err := utils.DecodeBytesExtraFields(header.Extra, &extraField) if err != nil { @@ -368,24 +382,16 @@ func (x *XDPoS_v2) IsAuthorisedAddress(chain consensus.ChainReader, header *type log.Error("[IsAuthorisedAddress] Fail to find any master nodes from current block round epoch", "Hash", header.Hash(), "Round", blockRound, "Number", header.Number) return false } - leaderIndex := uint64(blockRound) % x.config.Epoch % uint64(len(masterNodes)) - if masterNodes[leaderIndex] == address { - return true + // leaderIndex := uint64(blockRound) % x.config.Epoch % uint64(len(masterNodes)) + for index, masterNodeAddress := range masterNodes { + if masterNodeAddress == address { + log.Debug("[IsAuthorisedAddress] Found matching master node address", "index", index, "Address", address, "MasterNodes", masterNodes) + return true + } } - log.Warn("Not authorised address", "Address", address, "MN", masterNodes, "Hash", header.Hash(), "masterNodes[leaderIndex]", masterNodes[leaderIndex], "Address", address) - return false -} -// Copy from v1 -func whoIsCreator(snap *SnapshotV2, header *types.Header) (common.Address, error) { - if header.Number.Uint64() == 0 { - return common.Address{}, errors.New("Don't take block 0") - } - m, err := ecrecover(header, snap.sigcache) - if err != nil { - return common.Address{}, err - } - return m, nil + log.Warn("Not authorised address", "Address", address, "MN", masterNodes, "Hash", header.Hash()) + return false } // Copy from v1 @@ -1102,6 +1108,24 @@ func (x *XDPoS_v2) IsEpochSwitch(header *types.Header) (bool, uint64, error) { return parentRound < epochStart, epochNum, nil } +// IsEpochSwitchAtRound() is used by miner to check whether it mines a block in the same epoch with parent +func (x *XDPoS_v2) IsEpochSwitchAtRound(round utils.Round, parentHeader *types.Header) (bool, uint64, error) { + epochNum := x.config.XDPoSV2Block.Uint64()/x.config.Epoch + uint64(round)/x.config.Epoch + // if parent is last v1 block and this is first v2 block, this is treated as epoch switch + if parentHeader.Number.Cmp(x.config.XDPoSV2Block) == 0 { + return true, epochNum, nil + } + var decodedExtraField utils.ExtraFields_v2 + err := utils.DecodeBytesExtraFields(parentHeader.Extra, &decodedExtraField) + if err != nil { + log.Error("[IsEpochSwitch] decode header error", "err", err, "header", parentHeader, "extra", common.Bytes2Hex(parentHeader.Extra)) + return false, 0, err + } + parentRound := decodedExtraField.Round + epochStart := round - round%utils.Round(x.config.Epoch) + return parentRound < epochStart, epochNum, nil +} + // Given header and its hash, get epoch switch info from the epoch switch block of that epoch, // header is allow to be nil. func (x *XDPoS_v2) getEpochSwitchInfo(chain consensus.ChainReader, header *types.Header, hash common.Hash) (*utils.EpochSwitchInfo, error) { diff --git a/consensus/tests/authorised_masternode_test.go b/consensus/tests/authorised_masternode_test.go index d509400394..d888df4201 100644 --- a/consensus/tests/authorised_masternode_test.go +++ b/consensus/tests/authorised_masternode_test.go @@ -87,20 +87,74 @@ func TestIsAuthorisedMNForConsensusV2(t *testing.T) { t.Fatal(err) } - // the first block will start from 1 + // As long as the address is in the master node list, they are all valid isAuthorisedMN := adaptor.IsAuthorisedAddress(blockchain, currentBlock.Header(), common.HexToAddress("xdc03d9e17Ae3fF2c6712E44e25B09Ac5ee91f6c9ff")) assert.True(t, isAuthorisedMN) - // The third address hence not valid - isAuthorisedMN = adaptor.IsAuthorisedAddress(blockchain, currentBlock.Header(), common.HexToAddress("xdc065551F0dcAC6f00CAe11192D462db709bE3758c")) - assert.False(t, isAuthorisedMN) - for blockNum = 12; blockNum < 16; blockNum++ { - blockHeader = createBlock(params.TestXDPoSMockChainConfigWithV2Engine, currentBlock, blockNum, int64(blockNum-10), blockCoinBase, signer, signFn) - currentBlock, err = insertBlock(blockchain, blockHeader) - if err != nil { - t.Fatal(err) - } - } isAuthorisedMN = adaptor.IsAuthorisedAddress(blockchain, currentBlock.Header(), common.HexToAddress("xdc065551F0dcAC6f00CAe11192D462db709bE3758c")) assert.True(t, isAuthorisedMN) + + isAuthorisedMN = adaptor.IsAuthorisedAddress(blockchain, currentBlock.Header(), common.HexToAddress("xdcbanana")) + assert.False(t, isAuthorisedMN) +} + +func TestIsYourTurnConsensusV2(t *testing.T) { + // we skip test for v1 since it's hard to make a real genesis block + blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 10, params.TestXDPoSMockChainConfigWithV2Engine, 0) + + adaptor := blockchain.Engine().(*XDPoS.XDPoS) + blockNum := 11 + blockCoinBase := "0x111000000000000000000000000000000123" + blockHeader := createBlock(params.TestXDPoSMockChainConfigWithV2Engine, currentBlock, blockNum, 1, blockCoinBase, signer, signFn) + // it contains 3 master nodes + // xdc0278C350152e15fa6FFC712a5A73D704Ce73E2E1 + // xdc03d9e17Ae3fF2c6712E44e25B09Ac5ee91f6c9ff + // xdc065551F0dcAC6f00CAe11192D462db709bE3758c + blockHeader.Validators = common.Hex2Bytes("0278c350152e15fa6ffc712a5a73d704ce73e2e103d9e17ae3ff2c6712e44e25b09ac5ee91f6c9ff065551f0dcac6f00cae11192d462db709be3758c") + // block 11 is the first v2 block, and is treated as epoch switch block + currentBlock, err := insertBlock(blockchain, blockHeader) + if err != nil { + t.Fatal(err) + } + + // The first address is valid + numberOfMN, _, curIndex, isYourTurn, err := adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc0278C350152e15fa6FFC712a5A73D704Ce73E2E1")) + assert.Nil(t, err) + assert.Equal(t, 3, numberOfMN) + assert.Equal(t, 0, curIndex) + assert.True(t, isYourTurn) + + // The second and third address are not valid + numberOfMN, _, curIndex, isYourTurn, err = adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc03d9e17Ae3fF2c6712E44e25B09Ac5ee91f6c9ff")) + assert.Nil(t, err) + assert.Equal(t, 3, numberOfMN) + assert.Equal(t, 1, curIndex) + assert.False(t, isYourTurn) + numberOfMN, _, curIndex, isYourTurn, err = adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc065551F0dcAC6f00CAe11192D462db709bE3758c")) + assert.Nil(t, err) + assert.Equal(t, 3, numberOfMN) + assert.Equal(t, 2, curIndex) + assert.False(t, isYourTurn) + + // We continue to grow the chain which will increase the round number + blockNum = 12 + blockHeader = createBlock(params.TestXDPoSMockChainConfigWithV2Engine, currentBlock, blockNum, int64(blockNum-10), blockCoinBase, signer, signFn) + currentBlock, err = insertBlock(blockchain, blockHeader) + if err != nil { + t.Fatal(err) + } + + adaptor.EngineV2.SetNewRoundFaker(1, false) + _, _, curIndex, isYourTurn, _ = adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc0278C350152e15fa6FFC712a5A73D704Ce73E2E1")) + assert.Equal(t, 0, curIndex) + assert.False(t, isYourTurn) + + _, _, curIndex, isYourTurn, _ = adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc03d9e17Ae3fF2c6712E44e25B09Ac5ee91f6c9ff")) + assert.Equal(t, 1, curIndex) + assert.True(t, isYourTurn) + + _, _, curIndex, isYourTurn, _ = adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc065551F0dcAC6f00CAe11192D462db709bE3758c")) + assert.Equal(t, 2, curIndex) + assert.False(t, isYourTurn) + } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 31a78f7e40..724bbe9770 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -967,23 +967,27 @@ func (s *PublicBlockChainAPI) GetCandidates(ctx context.Context, epoch rpc.Epoch func (s *PublicBlockChainAPI) GetPreviousCheckpointFromEpoch(ctx context.Context, epochNum rpc.EpochNumber) (rpc.BlockNumber, rpc.EpochNumber) { var checkpointNumber uint64 - currentCheckpointNumber, epochNumber, err := s.b.GetEngine().(*XDPoS.XDPoS).GetCurrentEpochSwitchBlock(s.chainReader, s.b.CurrentBlock().Number()) - if err != nil { - log.Error("[GetPreviousCheckpointFromEpoch] Error while trying to get current epoch switch block information", "Block", s.b.CurrentBlock(), "Error", err) - } - if epochNum == rpc.LatestEpochNumber { - checkpointNumber = currentCheckpointNumber - epochNum = rpc.EpochNumber(epochNumber) - } else if epochNum < 2 { - checkpointNumber = 0 - } else { - blockNumberBeforeCurrentEpochSwitch := currentCheckpointNumber - 1 - checkpointNumber, _, err = s.b.GetEngine().(*XDPoS.XDPoS).GetCurrentEpochSwitchBlock(s.chainReader, big.NewInt(int64(blockNumberBeforeCurrentEpochSwitch))) + if engine, ok := s.b.GetEngine().(*XDPoS.XDPoS); ok { + currentCheckpointNumber, epochNumber, err := engine.GetCurrentEpochSwitchBlock(s.chainReader, s.b.CurrentBlock().Number()) if err != nil { - log.Error("[GetPreviousCheckpointFromEpoch] Error while trying to get last epoch switch block information", "Number", blockNumberBeforeCurrentEpochSwitch, "Error", err) + log.Error("[GetPreviousCheckpointFromEpoch] Error while trying to get current epoch switch block information", "Block", s.b.CurrentBlock(), "Error", err) } + if epochNum == rpc.LatestEpochNumber { + checkpointNumber = currentCheckpointNumber + epochNum = rpc.EpochNumber(epochNumber) + } else if epochNum < 2 { + checkpointNumber = 0 + } else { + blockNumberBeforeCurrentEpochSwitch := currentCheckpointNumber - 1 + checkpointNumber, _, err = engine.GetCurrentEpochSwitchBlock(s.chainReader, big.NewInt(int64(blockNumberBeforeCurrentEpochSwitch))) + if err != nil { + log.Error("[GetPreviousCheckpointFromEpoch] Error while trying to get last epoch switch block information", "Number", blockNumberBeforeCurrentEpochSwitch, "Error", err) + } + } + return rpc.BlockNumber(checkpointNumber), epochNum + } else { + panic("[GetPreviousCheckpointFromEpoch] Error while trying to get XDPoS consensus engine") } - return rpc.BlockNumber(checkpointNumber), epochNum } // getCandidatesFromSmartContract returns all candidates with their capacities at the current time