Skip to content

Commit

Permalink
add test and fix issue on happy path (ethereum#46)
Browse files Browse the repository at this point in the history
* add test and fix issue on happy path

* add prepare test

* update comment
  • Loading branch information
liam-lai authored Jan 25, 2022
1 parent 05d315d commit ff0fcd3
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 88 deletions.
53 changes: 28 additions & 25 deletions consensus/XDPoS/engines/engine_v2/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,13 @@ func (x *XDPoS_v2) Initial(chain consensus.ChainReader, header *types.Header, ma

log.Info("[Initial] highest QC for consensus v2 first block", "Block Num", header.Number.String(), "BlockHash", header.Hash())
// Generate new parent blockInfo and put it into QC
parentBlockInfo := &utils.BlockInfo{
Hash: header.ParentHash,
blockInfo := &utils.BlockInfo{
Hash: header.Hash(),
Round: utils.Round(0),
Number: big.NewInt(0).Sub(header.Number, big.NewInt(1)),
Number: header.Number,
}
quorumCert := &utils.QuorumCert{
ProposedBlockInfo: parentBlockInfo,
ProposedBlockInfo: blockInfo,
Signatures: nil,
}
x.currentRound = 1
Expand All @@ -156,7 +156,7 @@ func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) er
highestQC := x.highestQuorumCert
x.lock.RUnlock()

if (highestQC == nil) || (header.ParentHash != highestQC.ProposedBlockInfo.Hash) {
if header.ParentHash != highestQC.ProposedBlockInfo.Hash {
return consensus.ErrNotReadyToPropose
}

Expand All @@ -165,6 +165,12 @@ func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) er
QuorumCert: highestQC,
}

extraBytes, err := extra.EncodeToBytes()
if err != nil {
return err
}
header.Extra = extraBytes

header.Nonce = types.BlockNonce{}

number := header.Number.Uint64()
Expand All @@ -173,40 +179,32 @@ func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) er
if parent == nil {
return consensus.ErrUnknownAncestor
}

// Set the correct difficulty
header.Difficulty = x.calcDifficulty(chain, parent, x.signer)
log.Debug("CalcDifficulty ", "number", header.Number, "difficulty", header.Difficulty)

// TODO: previous round should sit on previous Epoch and x.currentRound should >= Epoch number
isEpochSwitchBlock, _, err := x.IsEpochSwitch(header)
if err != nil {
log.Error("[Prepare] Error while trying to determine if header is an epoch switch during Prepare", "header", header, "Error", err)
return err
}
if isEpochSwitchBlock {
snap, err := x.getSnapshot(chain, number-1)
snap, err := x.getSnapshot(chain, number)
if err != nil {
return err
}
masternodes := snap.NextEpochMasterNodes
//TODO: remove penalty nodes and add comeback nodes
//TODO: remove penalty nodes and add comeback nodes, or change this logic into yourturn function
for _, v := range masternodes {
header.Validators = append(header.Validators, v[:]...)
}
}

extraBytes, err := extra.EncodeToBytes()
if err != nil {
return err
}

header.Extra = extraBytes

// Mix digest is reserved for now, set to empty
header.MixDigest = common.Hash{}

// Ensure the timestamp has the correct delay

// TODO: Proper deal with time
// TODO: if timestamp > current time, how to deal with future timestamp
header.Time = new(big.Int).Add(parent.Time, new(big.Int).SetUint64(x.config.Period))
Expand Down Expand Up @@ -337,22 +335,27 @@ func (x *XDPoS_v2) YourTurn(chain consensus.ChainReader, parent *types.Header, s
round := x.currentRound
isEpochSwitch, _, err := x.IsEpochSwitchAtRound(round, parent)
if err != nil {
log.Error("[YourTurn]", "Error", err)
log.Error("[YourTurn] check epoch switch at round failed", "Error", err)
return false, err
}
var masterNodes []common.Address
if isEpochSwitch {
if x.config.XDPoSV2Block.Cmp(parent.Number) == 0 {
snap, err := x.getSnapshot(chain, x.config.XDPoSV2Block.Uint64())
if err != nil {
log.Error("[YourTurn]Cannot find snapshot at gap num of last V1", "err", err, "number", x.config.XDPoSV2Block.Uint64())
log.Error("[YourTurn] Cannot find snapshot at gap num of last V1", "err", err, "number", x.config.XDPoSV2Block.Uint64())
return false, err
}
// the initial snapshot of v1->v2 switch does not need penalty
// the initial snapshot of v1->v2 switch containes penalites node
masterNodes = snap.NextEpochMasterNodes
} else {
// TODO: calc master nodes by smart contract - penalty
// TODO: related to snapshot
snap, err := x.getSnapshot(chain, parent.Number.Uint64()+1)
if err != nil {
log.Error("[YourTurn] Cannot find snapshot at gap block", "err", err, "number", x.config.XDPoSV2Block.Uint64())
return false, err
}
masterNodes = snap.NextEpochMasterNodes
// TODO: calculate master nodes with penalty and comback
}
} else {
// this block and parent belong to the same epoch
Expand Down Expand Up @@ -1137,15 +1140,15 @@ func (x *XDPoS_v2) IsEpochSwitch(header *types.Header) (bool, uint64, error) {
}
parentRound := decodedExtraField.QuorumCert.ProposedBlockInfo.Round
round := decodedExtraField.Round
epochStart := round - round%utils.Round(x.config.Epoch)
epochStartRound := round - round%utils.Round(x.config.Epoch)
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 decodedExtraField.QuorumCert.ProposedBlockInfo.Number.Cmp(x.config.XDPoSV2Block) == 0 {
log.Info("[IsEpochSwitch] true, parent equals XDPoSV2Block", "round", round, "number", header.Number.Uint64(), "hash", header.Hash())
return true, epochNum, nil
}
log.Info("[IsEpochSwitch]", "parent round", parentRound, "round", round, "number", header.Number.Uint64(), "hash", header.Hash())
return parentRound < epochStart, epochNum, nil
return parentRound < epochStartRound, epochNum, nil
}

// IsEpochSwitchAtRound() is used by miner to check whether it mines a block in the same epoch with parent
Expand All @@ -1162,8 +1165,8 @@ func (x *XDPoS_v2) IsEpochSwitchAtRound(round utils.Round, parentHeader *types.H
return false, 0, err
}
parentRound := decodedExtraField.Round
epochStart := round - round%utils.Round(x.config.Epoch)
return parentRound < epochStart, epochNum, nil
epochStartRound := round - round%utils.Round(x.config.Epoch)
return parentRound < epochStartRound, epochNum, nil
}

// Given header and its hash, get epoch switch info from the epoch switch block of that epoch,
Expand Down
9 changes: 9 additions & 0 deletions consensus/XDPoS/engines/engine_v2/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,12 @@ func (s *SnapshotV2) GetMappedMasterNodes() map[common.Address]struct{} {
}
return ms
}

func (s *SnapshotV2) IsMasterNodes(address common.Address) bool {
for _, n := range s.NextEpochMasterNodes {
if n.String() == address.String() {
return true
}
}
return false
}
54 changes: 0 additions & 54 deletions consensus/tests/initialize_v2_test.go

This file was deleted.

155 changes: 155 additions & 0 deletions consensus/tests/mine_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package tests

import (
"fmt"
"math/big"
"testing"
"time"

"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/params"
"github.com/stretchr/testify/assert"
)

func TestYourTurnInitialV2(t *testing.T) {
config := params.TestXDPoSMockChainConfigWithV2EngineEpochSwitch
blockchain, _, parentBlock, _ := PrepareXDCTestBlockChain(t, int(config.XDPoS.Epoch)-1, config)
adaptor := blockchain.Engine().(*XDPoS.XDPoS)

// Insert block 900
t.Logf("Inserting block with propose at 900...")
blockCoinbaseA := "0xaaa0000000000000000000000000000000000900"
//Get from block validator error message
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(900)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
Extra: common.Hex2Bytes("d7830100018358444388676f312e31352e38856c696e757800000000000000000278c350152e15fa6ffc712a5a73d704ce73e2e103d9e17ae3ff2c6712e44e25b09ac5ee91f6c9ff065551f0dcac6f00cae11192d462db709be3758ccef312ee5eea8d7bad5374c6a652150515d744508b61c1a4deb4e4e7bf057e4e3824c11fd2569bcb77a52905cda63b5a58507910bed335e4c9d87ae0ecdfafd400"),
}
block900, err := insertBlock(blockchain, header)
if err != nil {
t.Fatal(err)
}

// YourTurn is called before mine first v2 block
b, err := adaptor.YourTurn(blockchain, block900.Header(), common.HexToAddress("xdc0278C350152e15fa6FFC712a5A73D704Ce73E2E1"))
assert.Nil(t, err)
assert.False(t, b)
b, err = adaptor.YourTurn(blockchain, block900.Header(), common.HexToAddress("xdc03d9e17Ae3fF2c6712E44e25B09Ac5ee91f6c9ff"))
assert.Nil(t, err)
// round=1, so masternode[1] has YourTurn = True
assert.True(t, b)
assert.Equal(t, adaptor.EngineV2.GetCurrentRound(), utils.Round(1))

snap, err := adaptor.EngineV2.GetSnapshot(blockchain, block900.Header())
assert.Nil(t, err)
assert.NotNil(t, snap)
masterNodes := adaptor.EngineV1.GetMasternodesFromCheckpointHeader(block900.Header())
for i := 0; i < len(masterNodes); i++ {
assert.Equal(t, masterNodes[i].Hex(), snap.NextEpochMasterNodes[i].Hex())
}
}

func TestUpdateMasterNodes(t *testing.T) {
config := params.TestXDPoSMockChainConfigWithV2EngineEpochSwitch
blockchain, backend, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch+config.XDPoS.Gap)-1, config, 0)
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
x := adaptor.EngineV2
snap, err := x.GetSnapshot(blockchain, currentBlock.Header())

assert.Nil(t, err)
assert.Equal(t, int(snap.Number), 450)

// Insert block 1350
t.Logf("Inserting block with propose at 1350...")
blockCoinbaseA := "0xaaa0000000000000000000000000000000001350"
tx, err := voteTX(37117, 0, acc1Addr.String())
if err != nil {
t.Fatal(err)
}
//Get from block validator error message
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
header := &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(1350)),
ParentHash: currentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbaseA),
}
// insert header validator
err = generateSignature(backend, adaptor, header)
if err != nil {
t.Fatal(err)
}
parentBlock, err := insertBlockTxs(blockchain, header, []*types.Transaction{tx})
assert.Nil(t, err)

t.Logf("Inserting block from 1351 to 1800...")
for i := 1351; i <= 1800; i++ {
blockCoinbase := fmt.Sprintf("0xaaa000000000000000000000000000000000%4d", i)
//Get from block validator error message
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
header = &types.Header{
Root: common.HexToHash(merkleRoot),
Number: big.NewInt(int64(i)),
ParentHash: parentBlock.Hash(),
Coinbase: common.HexToAddress(blockCoinbase),
}
err = generateSignature(backend, adaptor, header)
if err != nil {
t.Fatal(err)
}
block, err := insertBlock(blockchain, header)
if err != nil {
t.Fatal(err)
}
parentBlock = block
}

snap, err = x.GetSnapshot(blockchain, parentBlock.Header())

assert.Nil(t, err)
assert.False(t, snap.IsMasterNodes(acc3Addr))
assert.True(t, snap.IsMasterNodes(acc1Addr))
assert.Equal(t, int(snap.Number), 1350)
}

func TestPrepare(t *testing.T) {
config := params.TestXDPoSMockChainConfigWithV2EngineEpochSwitch
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch), config, 0)
adaptor := blockchain.Engine().(*XDPoS.XDPoS)

adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc0278C350152e15fa6FFC712a5A73D704Ce73E2E1"))

tstamp := time.Now().Unix()
header901 := &types.Header{
ParentHash: currentBlock.Hash(),
Number: big.NewInt(int64(901)),
GasLimit: params.TargetGasLimit,
Time: big.NewInt(tstamp),
}

err := adaptor.Prepare(blockchain, header901)
assert.Nil(t, err)

snap, err := adaptor.EngineV2.GetSnapshot(blockchain, currentBlock.Header())
if err != nil {
t.Fatal(err)
}

validators := []byte{}
for _, v := range snap.NextEpochMasterNodes {
validators = append(validators, v[:]...)
}
assert.Equal(t, validators, header901.Validators)

var decodedExtraField utils.ExtraFields_v2
err = utils.DecodeBytesExtraFields(header901.Extra, &decodedExtraField)
assert.Nil(t, err)
assert.Equal(t, utils.Round(1), decodedExtraField.Round)
assert.Equal(t, utils.Round(0), decodedExtraField.QuorumCert.ProposedBlockInfo.Round)
}
12 changes: 8 additions & 4 deletions consensus/tests/test_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,10 @@ func PrepareXDCTestBlockChain(t *testing.T, numOfBlocks int, chainConfig *params
currentBlock := blockchain.Genesis()

go func() {
checkpointChanMsg := <-core.CheckpointCh
log.Info("[V1] Got a message from core CheckpointChan!", "msg", checkpointChanMsg)
for range core.CheckpointCh {
checkpointChanMsg := <-core.CheckpointCh
log.Info("[V1] Got a message from core CheckpointChan!", "msg", checkpointChanMsg)
}
}()

// Insert initial blocks
Expand Down Expand Up @@ -290,8 +292,10 @@ func PrepareXDCTestBlockChainForV2Engine(t *testing.T, numOfBlocks int, chainCon
var currentForkBlock *types.Block

go func() {
checkpointChanMsg := <-core.CheckpointCh
log.Info("[V2] Got a message from core CheckpointChan!", "msg", checkpointChanMsg)
for range core.CheckpointCh {
checkpointChanMsg := <-core.CheckpointCh
log.Info("[V2] Got a message from core CheckpointChan!", "msg", checkpointChanMsg)
}
}()

var masternodesFromV1LastEpoch []common.Address
Expand Down
Loading

0 comments on commit ff0fcd3

Please sign in to comment.