From ff0fcd3951bd11e6d258379fca33bcf8b5ff9d6d Mon Sep 17 00:00:00 2001 From: Liam Date: Tue, 25 Jan 2022 10:51:17 +0300 Subject: [PATCH] add test and fix issue on happy path (#46) * add test and fix issue on happy path * add prepare test * update comment --- consensus/XDPoS/engines/engine_v2/engine.go | 53 +++--- consensus/XDPoS/engines/engine_v2/snapshot.go | 9 + consensus/tests/initialize_v2_test.go | 54 ------ consensus/tests/mine_test.go | 155 ++++++++++++++++++ consensus/tests/test_helper.go | 12 +- core/blockchain.go | 5 - 6 files changed, 200 insertions(+), 88 deletions(-) delete mode 100644 consensus/tests/initialize_v2_test.go create mode 100644 consensus/tests/mine_test.go diff --git a/consensus/XDPoS/engines/engine_v2/engine.go b/consensus/XDPoS/engines/engine_v2/engine.go index 21b4841b0dbb..2b91f3aced7b 100644 --- a/consensus/XDPoS/engines/engine_v2/engine.go +++ b/consensus/XDPoS/engines/engine_v2/engine.go @@ -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 @@ -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 } @@ -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() @@ -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)) @@ -337,7 +335,7 @@ 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 @@ -345,14 +343,19 @@ func (x *XDPoS_v2) YourTurn(chain consensus.ChainReader, parent *types.Header, s 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 @@ -1137,7 +1140,7 @@ 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 { @@ -1145,7 +1148,7 @@ func (x *XDPoS_v2) IsEpochSwitch(header *types.Header) (bool, uint64, error) { 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 @@ -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, diff --git a/consensus/XDPoS/engines/engine_v2/snapshot.go b/consensus/XDPoS/engines/engine_v2/snapshot.go index acdd78062f85..9a9237d15c68 100644 --- a/consensus/XDPoS/engines/engine_v2/snapshot.go +++ b/consensus/XDPoS/engines/engine_v2/snapshot.go @@ -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 +} diff --git a/consensus/tests/initialize_v2_test.go b/consensus/tests/initialize_v2_test.go deleted file mode 100644 index 6dbfbd44017e..000000000000 --- a/consensus/tests/initialize_v2_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package tests - -import ( - "math/big" - "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/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()) - } -} diff --git a/consensus/tests/mine_test.go b/consensus/tests/mine_test.go new file mode 100644 index 000000000000..3ed64bdad0e7 --- /dev/null +++ b/consensus/tests/mine_test.go @@ -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) +} diff --git a/consensus/tests/test_helper.go b/consensus/tests/test_helper.go index 1552bab40c06..cbf691b304ce 100644 --- a/consensus/tests/test_helper.go +++ b/consensus/tests/test_helper.go @@ -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 @@ -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 diff --git a/core/blockchain.go b/core/blockchain.go index 2882ce25f4d9..602f425a35d6 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2052,7 +2052,6 @@ func (bc *BlockChain) insertBlock(block *types.Block) ([]interface{}, []*types.L } if isEpochSwithBlock { CheckpointCh <- 1 - } } // Append a single chain head event if we've progressed the chain @@ -2480,16 +2479,12 @@ func (bc *BlockChain) UpdateM1() error { // if can't get anything, request from contracts stateDB, err := bc.State() if err != nil { - candidates, err = validator.GetCandidates(opts) if err != nil { - return err } } else { - candidates = state.GetCandidates(stateDB) - } var ms []utils.Masternode