Skip to content

Commit

Permalink
Add v2 yourturn (ethereum#38)
Browse files Browse the repository at this point in the history
* Add v2 yourturn

* add isEpochSwitchByRound into YourTurn

Co-authored-by: Gerui Wang <[email protected]>
  • Loading branch information
wjrjerome and wgr523 authored Jan 16, 2022
1 parent f8d3f9f commit 38c3582
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 62 deletions.
100 changes: 62 additions & 38 deletions consensus/XDPoS/engines/engine_v2/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
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
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
}
preIndex = utils.Position(masternodes, pre)
} 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")
}
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 {
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
74 changes: 64 additions & 10 deletions consensus/tests/authorised_masternode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.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)

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)
}
// 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)
}
isAuthorisedMN = adaptor.IsAuthorisedAddress(blockchain, currentBlock.Header(), common.HexToAddress("xdc065551F0dcAC6f00CAe11192D462db709bE3758c"))
assert.True(t, isAuthorisedMN)

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)

}
32 changes: 18 additions & 14 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 38c3582

Please sign in to comment.