diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d568dc50938..3306363dd0fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,10 +42,8 @@ jobs: script: go run build/ci.go test -coverage $(go list ./... | grep "github.com/XinFinOrg/XDPoSChain/[d-i].*") - name: J-N tests script: go run build/ci.go test -coverage $(go list ./... | grep "github.com/XinFinOrg/XDPoSChain/[j-n].*") - - name: O-R tests - script: go run build/ci.go test -coverage $(go list ./... | grep "github.com/XinFinOrg/XDPoSChain/[o-r].*") - - name: S tests - script: go run build/ci.go test -coverage $(go list ./... | grep "github.com/XinFinOrg/XDPoSChain/s.*") + - name: O-S tests + script: go run build/ci.go test -coverage $(go list ./... | grep "github.com/XinFinOrg/XDPoSChain/[o-s].*") - name: T-Z tests script: go run build/ci.go test -coverage $(go list ./... | grep "github.com/XinFinOrg/XDPoSChain/[t-z].*") steps: diff --git a/consensus/XDPoS/engines/engine_v2/epochSwitch.go b/consensus/XDPoS/engines/engine_v2/epochSwitch.go index b6c4a0c16808..40a684e335e3 100644 --- a/consensus/XDPoS/engines/engine_v2/epochSwitch.go +++ b/consensus/XDPoS/engines/engine_v2/epochSwitch.go @@ -40,7 +40,6 @@ func (x *XDPoS_v2) getEpochSwitchInfo(chain consensus.ChainReader, header *types log.Debug("[getEpochSwitchInfo] header doesn't provide, get header by hash", "hash", hash.Hex()) h = chain.GetHeaderByHash(hash) if h == nil { - log.Warn("[getEpochSwitchInfo] can not find header from db", "hash", hash.Hex()) return nil, fmt.Errorf("[getEpochSwitchInfo] can not find header from db hash %v", hash.Hex()) } } @@ -152,7 +151,7 @@ func (x *XDPoS_v2) IsEpochSwitch(header *types.Header) (bool, uint64, error) { log.Info("[IsEpochSwitch] true, parent equals V2.SwitchBlock", "round", round, "number", header.Number.Uint64(), "hash", header.Hash()) return true, epochNum, nil } - log.Debug("[IsEpochSwitch]", "is", parentRound < epochStartRound, "parentRound", parentRound, "round", round, "number", header.Number.Uint64(), "epochNum", epochNum, "hash", header.Hash()) + log.Debug("[IsEpochSwitch]", "is", parentRound < epochStartRound, "parentRound", parentRound, "round", round, "number", header.Number.Uint64(), "epochNum", epochNum, "hash", header.Hash().Hex()) // if isEpochSwitch, add to cache if parentRound < epochStartRound { x.round2epochBlockInfo.Add(round, &types.BlockInfo{ diff --git a/consensus/XDPoS/engines/engine_v2/timeout.go b/consensus/XDPoS/engines/engine_v2/timeout.go index 2f077b2a0018..875dd22c9c99 100644 --- a/consensus/XDPoS/engines/engine_v2/timeout.go +++ b/consensus/XDPoS/engines/engine_v2/timeout.go @@ -95,7 +95,7 @@ func (x *XDPoS_v2) verifyTC(chain consensus.ChainReader, timeoutCert *types.Time snap, err := x.getSnapshot(chain, timeoutCert.GapNumber, true) if err != nil { - log.Error("[verifyTC] Fail to get snapshot when verifying TC!", "TCGapNumber", timeoutCert.GapNumber) + log.Error("[verifyTC] Fail to get snapshot when verifying TC!", "tcGapNumber", timeoutCert.GapNumber) return fmt.Errorf("[verifyTC] Unable to get snapshot, %s", err) } if snap == nil || len(snap.NextEpochCandidates) == 0 { @@ -110,15 +110,42 @@ func (x *XDPoS_v2) verifyTC(chain consensus.ChainReader, timeoutCert *types.Time } } - epochInfo, err := x.getEpochSwitchInfo(chain, chain.CurrentHeader(), chain.CurrentHeader().Hash()) + epochSwitchInfo, err := x.getEpochSwitchInfo(chain, (chain.CurrentHeader()), (chain.CurrentHeader()).Hash()) if err != nil { - log.Error("[verifyTC] Error when getting epoch switch Info", "error", err) + log.Error("[verifyTC] Error when getting epoch switch info", "error", err) + return fmt.Errorf("fail on verifyTC due to failure in getting epoch switch info, %s", err) + } + + epochRound := epochSwitchInfo.EpochSwitchBlockInfo.Round + tempTCEpoch := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(epochRound)/x.config.Epoch + + epochBlockInfo := &types.BlockInfo{ + Hash: epochSwitchInfo.EpochSwitchBlockInfo.Hash, + Round: epochRound, + Number: epochSwitchInfo.EpochSwitchBlockInfo.Number, + } + log.Info("[verifyTC] Init epochInfo", "number", epochBlockInfo.Number, "round", epochRound, "tcRound", timeoutCert.Round, "tcEpoch", tempTCEpoch) + for epochBlockInfo.Round > timeoutCert.Round { + tempTCEpoch-- + epochBlockInfo, err = x.GetBlockByEpochNumber(chain, tempTCEpoch) + if err != nil { + log.Error("[verifyTC] Error when getting epoch block info by tc round", "error", err) + return fmt.Errorf("fail on verifyTC due to failure in getting epoch block info tc round, %s", err) + } + log.Debug("[verifyTC] Loop to get right epochInfo", "number", epochBlockInfo.Number, "round", epochBlockInfo.Round, "tcRound", timeoutCert.Round, "tcEpoch", tempTCEpoch) + } + tcEpoch := tempTCEpoch + log.Info("[verifyTC] Final TC epochInfo", "number", epochBlockInfo.Number, "round", epochBlockInfo.Round, "tcRound", timeoutCert.Round, "tcEpoch", tcEpoch) + + epochInfo, err := x.getEpochSwitchInfo(chain, nil, epochBlockInfo.Hash) + if err != nil { + log.Error("[verifyTC] Error when getting epoch switch info", "error", err) return fmt.Errorf("fail on verifyTC due to failure in getting epoch switch info, %s", err) } certThreshold := x.config.V2.Config(uint64(timeoutCert.Round)).CertThreshold if float64(len(signatures)) < float64(epochInfo.MasternodesLen)*certThreshold { - log.Warn("[verifyTC] Invalid TC Signature is nil or empty", "timeoutCert.Round", timeoutCert.Round, "timeoutCert.GapNumber", timeoutCert.GapNumber, "Signatures len", len(timeoutCert.Signatures), "CertThreshold", float64(epochInfo.MasternodesLen)*certThreshold) + log.Warn("[verifyTC] Invalid TC Signature is less or empty", "tcRound", timeoutCert.Round, "tcGapNumber", timeoutCert.GapNumber, "tcSignLen", len(timeoutCert.Signatures), "certThreshold", float64(epochInfo.MasternodesLen)*certThreshold) return utils.ErrInvalidTCSignatures } @@ -138,14 +165,14 @@ func (x *XDPoS_v2) verifyTC(chain consensus.ChainReader, timeoutCert *types.Time defer wg.Done() verified, _, err := x.verifyMsgSignature(signedTimeoutObj, sig, snap.NextEpochCandidates) if err != nil || !verified { - log.Error("[verifyTC] Error or verification failure", "Signature", sig, "Error", err) + log.Error("[verifyTC] Error or verification failure", "signature", sig, "error", err) mutex.Lock() // Lock before accessing haveError if haveError == nil { if err != nil { - log.Error("[verifyTC] Error while verfying TC message signatures", "timeoutCert.Round", timeoutCert.Round, "timeoutCert.GapNumber", timeoutCert.GapNumber, "Signatures len", len(signatures), "Error", err) + log.Error("[verifyTC] Error while verfying TC message signatures", "tcRound", timeoutCert.Round, "tcGapNumber", timeoutCert.GapNumber, "tcSignLen", len(signatures), "error", err) haveError = fmt.Errorf("error while verifying TC message signatures, %s", err) } else { - log.Warn("[verifyTC] Signature not verified doing TC verification", "timeoutCert.Round", timeoutCert.Round, "timeoutCert.GapNumber", timeoutCert.GapNumber, "Signatures len", len(signatures)) + log.Warn("[verifyTC] Signature not verified doing TC verification", "tcRound", timeoutCert.Round, "tcGapNumber", timeoutCert.GapNumber, "tcSignLen", len(signatures)) haveError = errors.New("fail to verify TC due to signature mis-match") } } @@ -219,6 +246,8 @@ func (x *XDPoS_v2) sendTimeout(chain consensus.ChainReader) error { Signature: signedHash, GapNumber: gapNumber, } + + timeoutMsg.SetSigner(x.signer) log.Warn("[sendTimeout] Timeout message generated, ready to send!", "timeoutMsgRound", timeoutMsg.Round, "timeoutMsgGapNumber", timeoutMsg.GapNumber, "whosTurn", x.whosTurn) err = x.timeoutHandler(chain, timeoutMsg) if err != nil { diff --git a/consensus/tests/engine_v2_tests/sync_info_test.go b/consensus/tests/engine_v2_tests/sync_info_test.go index 8219b37a4e15..ebe2e4db6873 100644 --- a/consensus/tests/engine_v2_tests/sync_info_test.go +++ b/consensus/tests/engine_v2_tests/sync_info_test.go @@ -1,9 +1,11 @@ package engine_v2_tests import ( + "fmt" "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" @@ -100,3 +102,176 @@ func TestSkipVerifySyncInfoIfBothQcTcNotQualified(t *testing.T) { assert.False(t, verified) assert.Nil(t, err) } + +func TestVerifySyncInfoIfTCRoundIsAtNextEpoch(t *testing.T) { + blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil) + engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2 + + // Make the Highest QC in syncInfo point to an old block to simulate it's no longer qualified + parentBlock := blockchain.GetBlockByNumber(903) + var extraField types.ExtraFields_v2 + err := utils.DecodeBytesExtraFields(parentBlock.Extra(), &extraField) + if err != nil { + t.Fatal("Fail to decode extra data", err) + } + + highestTC := &types.TimeoutCert{ + Round: types.Round(899), + Signatures: []types.Signature{}, + } + + timeoutForSign := &types.TimeoutForSign{ + Round: types.Round(900), + GapNumber: 450, + } + + // Sign from acc 1, 2, 3 and voter + acc1SignedHash := SignHashByPK(acc1Key, types.TimeoutSigHash(timeoutForSign).Bytes()) + acc2SignedHash := SignHashByPK(acc2Key, types.TimeoutSigHash(timeoutForSign).Bytes()) + acc3SignedHash := SignHashByPK(acc3Key, types.TimeoutSigHash(timeoutForSign).Bytes()) + voterSignedHash := SignHashByPK(voterKey, types.TimeoutSigHash(timeoutForSign).Bytes()) + + var signatures []types.Signature + signatures = append(signatures, acc1SignedHash, acc2SignedHash, acc3SignedHash, voterSignedHash) + + syncInfoTC := &types.TimeoutCert{ + Round: timeoutForSign.Round, + Signatures: signatures, + GapNumber: timeoutForSign.GapNumber, + } + + syncInfoMsg := &types.SyncInfo{ + HighestQuorumCert: extraField.QuorumCert, + HighestTimeoutCert: syncInfoTC, + } + + engineV2.SetPropertiesFaker(syncInfoMsg.HighestQuorumCert, highestTC) + + verified, err := engineV2.VerifySyncInfoMessage(blockchain, syncInfoMsg) + assert.True(t, verified) + assert.Nil(t, err) +} + +func TestVerifySyncInfoIfTcUseDifferentEpoch(t *testing.T) { + config := params.TestXDPoSMockChainConfig + blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 1349, config, nil) + adaptor := blockchain.Engine().(*XDPoS.XDPoS) + x := adaptor.EngineV2 + + // Insert block 1350 + t.Logf("Inserting block with propose at 1350...") + blockCoinbaseA := "0xaaa0000000000000000000000000000000001350" + // NOTE: voterAddr never exist in the Masternode list, but all acc1,2,3 already does + tx, err := voteTX(37117, 0, signer.String()) + if err != nil { + t.Fatal(err) + } + //Get from block validator error message + merkleRoot := "8a355a8636d1aae24d5a63df0318534e09110891d6ab7bf20587da64725083be" + header := &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(1350)), + ParentHash: currentBlock.Hash(), + Coinbase: common.HexToAddress(blockCoinbaseA), + } + + header.Extra = generateV2Extra(450, currentBlock, signer, signFn, nil) + + parentBlock, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, config) + assert.Nil(t, err) + err = blockchain.InsertBlock(parentBlock) + assert.Nil(t, err) + // 1350 is a gap block, need to update the snapshot + err = blockchain.UpdateM1() + assert.Nil(t, err) + t.Logf("Inserting block from 1351 to 1799...") + for i := 1351; i <= 1799; i++ { + blockCoinbase := fmt.Sprintf("0xaaa000000000000000000000000000000000%4d", i) + //Get from block validator error message + header = &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(i)), + ParentHash: parentBlock.Hash(), + Coinbase: common.HexToAddress(blockCoinbase), + } + + header.Extra = generateV2Extra(int64(i)-900, parentBlock, signer, signFn, nil) + + block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, config) + if err != nil { + t.Fatal(err) + } + err = blockchain.InsertBlock(block) + assert.Nil(t, err) + parentBlock = block + } + t.Logf("build epoch block with new set of masternodes") + blockCoinbase := fmt.Sprintf("0xaaa0000000000000000000000000000000001800") + //Get from block validator error message + header = &types.Header{ + Root: common.HexToHash(merkleRoot), + Number: big.NewInt(int64(1800)), + ParentHash: parentBlock.Hash(), + Coinbase: common.HexToAddress(blockCoinbase), + } + + header.Extra = generateV2Extra(900, parentBlock, signer, signFn, nil) + validators := []byte{} + + snap, err := x.GetSnapshot(blockchain, parentBlock.Header()) + assert.Nil(t, err) + + for _, v := range snap.NextEpochCandidates { + validators = append(validators, v[:]...) + } + // set up 1 more masternode to make it difference + validators = append(validators, voterAddr[:]...) + header.Validators = validators + block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, config) + if err != nil { + t.Fatal(err) + } + err = blockchain.InsertBlock(block) + assert.Nil(t, err) + parentBlock = block + + var extraField types.ExtraFields_v2 + err = utils.DecodeBytesExtraFields(parentBlock.Extra(), &extraField) + if err != nil { + t.Fatal("Fail to decode extra data", err) + } + + timeoutForSign := &types.TimeoutForSign{ + Round: types.Round(899), + GapNumber: 450, + } + + // Sign from acc 1, 2, 3 and voter + acc1SignedHash := SignHashByPK(acc1Key, types.TimeoutSigHash(timeoutForSign).Bytes()) + acc2SignedHash := SignHashByPK(acc2Key, types.TimeoutSigHash(timeoutForSign).Bytes()) + acc3SignedHash := SignHashByPK(acc3Key, types.TimeoutSigHash(timeoutForSign).Bytes()) + voterSignedHash := SignHashByPK(voterKey, types.TimeoutSigHash(timeoutForSign).Bytes()) + + var signatures []types.Signature + signatures = append(signatures, acc1SignedHash, acc2SignedHash, acc3SignedHash, voterSignedHash) + + newTC := &types.TimeoutCert{ + Round: timeoutForSign.Round, + Signatures: signatures, + GapNumber: timeoutForSign.GapNumber, + } + + syncInfoMsg := &types.SyncInfo{ + HighestQuorumCert: extraField.QuorumCert, + HighestTimeoutCert: newTC, + } + + x.SetPropertiesFaker(syncInfoMsg.HighestQuorumCert, &types.TimeoutCert{ + Round: types.Round(898), + Signatures: []types.Signature{}, + }) + + verified, err := x.VerifySyncInfoMessage(blockchain, syncInfoMsg) + assert.True(t, verified) + assert.Nil(t, err) +} diff --git a/core/types/consensus_v2.go b/core/types/consensus_v2.go index 8dae6aaa0728..f69892532dea 100644 --- a/core/types/consensus_v2.go +++ b/core/types/consensus_v2.go @@ -21,7 +21,7 @@ type BlockInfo struct { // Vote message in XDPoS 2.0 type Vote struct { - signer common.Address //field not exported + signer common.Address //field not exported ProposedBlockInfo *BlockInfo `json:"proposedBlockInfo"` Signature Signature `json:"signature"` GapNumber uint64 `json:"gapNumber"` @@ -81,9 +81,9 @@ func (s *SyncInfo) Hash() common.Hash { // Quorum Certificate struct in XDPoS 2.0 type QuorumCert struct { - ProposedBlockInfo *BlockInfo `json:"proposedBlockInfo"` + ProposedBlockInfo *BlockInfo `json:"proposedBlockInfo"` Signatures []Signature `json:"signatures"` - GapNumber uint64 `json:"gapNumber"` + GapNumber uint64 `json:"gapNumber"` } // Timeout Certificate struct in XDPoS 2.0 diff --git a/params/config.go b/params/config.go index a97f07bfb336..ba9b45212a7a 100644 --- a/params/config.go +++ b/params/config.go @@ -88,7 +88,7 @@ var ( SwitchRound: 0, CertThreshold: 0.45, TimeoutSyncThreshold: 3, - TimeoutPeriod: 20, + TimeoutPeriod: 60, MinePeriod: 2, }, 900000: { @@ -96,7 +96,7 @@ var ( SwitchRound: 900000, CertThreshold: 0.667, TimeoutSyncThreshold: 3, - TimeoutPeriod: 30, + TimeoutPeriod: 60, MinePeriod: 2, }, } diff --git a/params/version.go b/params/version.go index 0162660a4c65..6dad86457668 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 2 // Major version component of the current release - VersionMinor = 3 // Minor version component of the current release - VersionPatch = 0 // Patch version component of the current release - VersionMeta = "stable" // Version metadata to append to the version string + VersionMajor = 2 // Major version component of the current release + VersionMinor = 4 // Minor version component of the current release + VersionPatch = 6 // Patch version component of the current release + VersionMeta = "beta1" // Version metadata to append to the version string ) // Version holds the textual version string.