diff --git a/consensus/XDPoS/engines/engine_v2/timeout.go b/consensus/XDPoS/engines/engine_v2/timeout.go index 15f3028d1d..771ad2ab38 100644 --- a/consensus/XDPoS/engines/engine_v2/timeout.go +++ b/consensus/XDPoS/engines/engine_v2/timeout.go @@ -129,7 +129,7 @@ func (x *XDPoS_v2) getTCEpochInfo(chain consensus.ChainReader, timeoutRound type Number: epochSwitchInfo.EpochSwitchBlockInfo.Number, } log.Info("[getTCEpochInfo] Init epochInfo", "number", epochBlockInfo.Number, "round", epochRound, "tcRound", timeoutRound, "tcEpoch", tempTCEpoch) - for epochBlockInfo.Round > timeoutRound { + for epochBlockInfo.Round > timeoutRound && tempTCEpoch > 0 { tempTCEpoch-- epochBlockInfo, err = x.GetBlockByEpochNumber(chain, tempTCEpoch) if err != nil { diff --git a/consensus/XDPoS/engines/engine_v2/vote.go b/consensus/XDPoS/engines/engine_v2/vote.go index b43a281377..6b9ea013fb 100644 --- a/consensus/XDPoS/engines/engine_v2/vote.go +++ b/consensus/XDPoS/engines/engine_v2/vote.go @@ -22,29 +22,22 @@ func (x *XDPoS_v2) VerifyVoteMessage(chain consensus.ChainReader, vote *types.Vo return false, nil } - // If we don't yet have the referenced header locally, defer verification. - // Votes may arrive before the block header itself; avoid logging an error - // for that normal timing condition and let the vote be retried later. - if chain.GetHeaderByHash(vote.ProposedBlockInfo.Hash) == nil { - log.Debug("[VerifyVoteMessage] referenced header not present yet, defer verification", "blockNum", vote.ProposedBlockInfo.Number, "blockHash", vote.ProposedBlockInfo.Hash) - return false, nil - } - - epochInfo, err := x.getEpochSwitchInfo(chain, nil, vote.ProposedBlockInfo.Hash) + snap, err := x.getSnapshot(chain, vote.GapNumber, true) if err != nil { - log.Error("[VerifyVoteMessage] Fail to get epochInfo when verifying vote message!", "blockNum", vote.ProposedBlockInfo.Number, "blockHash", vote.ProposedBlockInfo.Hash, "voteHash", vote.Hash(), "voteGapNumber", vote.GapNumber, "err", err) + log.Error("[VerifyVoteMessage] fail to get snapshot for a vote message", "blockNum", vote.ProposedBlockInfo.Number, "blockHash", vote.ProposedBlockInfo.Hash, "voteHash", vote.Hash(), "err", err) return false, err } verified, signer, err := x.verifyMsgSignature(types.VoteSigHash(&types.VoteForSign{ ProposedBlockInfo: vote.ProposedBlockInfo, GapNumber: vote.GapNumber, - }), vote.Signature, epochInfo.Masternodes) + }), vote.Signature, snap.NextEpochCandidates) + if err != nil { - for i, mn := range epochInfo.Masternodes { + for i, mn := range snap.NextEpochCandidates { log.Warn("[VerifyVoteMessage] Master node list item", "index", i, "Master node", mn.Hex()) } - log.Warn("[VerifyVoteMessage] Error while verifying vote message", "votedBlockNum", vote.ProposedBlockInfo.Number.Uint64(), "votedBlockHash", vote.ProposedBlockInfo.Hash.Hex(), "voteHash", vote.Hash(), "error", err.Error()) + log.Warn("[VerifyVoteMessage] Error while verifying vote message", "votedBlockNum", vote.ProposedBlockInfo.Number.Uint64(), "votedBlockHash", vote.ProposedBlockInfo.Hash.Hex(), "voteHash", vote.Hash(), "err", err) return false, err } vote.SetSigner(signer) @@ -198,7 +191,7 @@ func (x *XDPoS_v2) verifyVotes(chain consensus.ChainReader, votes map[common.Has }) verified, masterNode, err := x.verifyMsgSignature(signedVote, v.Signature, masternodes) if err != nil { - log.Warn("[verifyVotes] error while verifying vote signature", "error", err.Error()) + log.Warn("[verifyVotes] error while verifying vote signature", "err", err) return } diff --git a/consensus/XDPoS/engines/engine_v2/vote_test.go b/consensus/XDPoS/engines/engine_v2/vote_test.go index 6a9f5d9eee..d8c93db792 100644 --- a/consensus/XDPoS/engines/engine_v2/vote_test.go +++ b/consensus/XDPoS/engines/engine_v2/vote_test.go @@ -9,7 +9,6 @@ import ( "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/core/types" - "github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/params" "github.com/stretchr/testify/assert" ) @@ -96,63 +95,6 @@ func (m *MockChainReader) GetBlock(hash common.Hash, number uint64) *types.Block return nil } -// TestVerifyVoteMessage_HeaderNotPresent tests the behavior when a vote arrives -// before its corresponding block header is available. -// -// This test verifies that VerifyVoteMessage handles the normal timing condition -// where votes may arrive before the block header itself, returning (false, nil) -// and logging at Debug level rather than Error level. -func TestVerifyVoteMessage_HeaderNotPresent(t *testing.T) { - // Create a mock chain reader with no headers - mockChain := NewMockChainReader() - - // Capture logs to ensure Debug (and no Error) is emitted - memHandler := newMemoryHandler() - log.SetDefault(log.NewLogger(memHandler)) - defer log.SetDefault(log.NewLogger(log.DiscardHandler())) - - // Create the XDPoS_v2 engine - engine := &XDPoS_v2{ - currentRound: 10, - lock: sync.RWMutex{}, - } - - // Create a vote for a block that doesn't exist in the chain - vote := &types.Vote{ - ProposedBlockInfo: &types.BlockInfo{ - Hash: common.StringToHash("nonexistent-block"), - Round: 10, - Number: big.NewInt(100), - }, - Signature: make([]byte, 65), - GapNumber: 0, - } - - // Call VerifyVoteMessage - verified, err := engine.VerifyVoteMessage(mockChain, vote) - - // Verify the expected behavior: - // 1. Should return false (not verified) - assert.False(t, verified, "Should return false when header is not present") - - // 2. Should return nil error (deferred verification) - assert.NoError(t, err, "Should not error when header is absent; vote will be retried") - - // 3. Should log at Debug level and not emit Error-level logs - records := memHandler.Records() - var hasDebug, hasError bool - for _, rec := range records { - switch rec.Level { - case slog.LevelDebug: - hasDebug = true - case slog.LevelError, log.LevelCrit: - hasError = true - } - } - assert.True(t, hasDebug, "Expected a debug log when header is missing") - assert.False(t, hasError, "Should not emit error-level logs for missing header") -} - // TestVerifyVoteMessage_VoteRoundTooOld tests that votes with rounds below // the current round are rejected immediately func TestVerifyVoteMessage_VoteRoundTooOld(t *testing.T) { diff --git a/consensus/tests/engine_v2_tests/vote_test.go b/consensus/tests/engine_v2_tests/vote_test.go index e45eda220a..93c2a0d9ed 100644 --- a/consensus/tests/engine_v2_tests/vote_test.go +++ b/consensus/tests/engine_v2_tests/vote_test.go @@ -536,6 +536,32 @@ func TestVerifyVoteMsg(t *testing.T) { assert.Nil(t, err) } +func TestVoteMsgMissingSnapshot(t *testing.T) { + blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 915, params.TestXDPoSMockChainConfig, nil) + engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2 + + blockInfo := &types.BlockInfo{ + Hash: currentBlock.Hash(), + Round: types.Round(14), + Number: big.NewInt(915), + } + voteForSign := &types.VoteForSign{ + ProposedBlockInfo: blockInfo, + GapNumber: 450, + } + + signHash, _ := signFn(accounts.Account{Address: signer}, types.VoteSigHash(voteForSign).Bytes()) + voteMsg := &types.Vote{ + ProposedBlockInfo: blockInfo, + Signature: signHash, + GapNumber: 1350, // missing 1350 snapshot + } + engineV2.SetNewRoundFaker(blockchain, types.Round(14), false) + verified, err := engineV2.VerifyVoteMessage(blockchain, voteMsg) + assert.False(t, verified) + assert.NotNil(t, err) +} + func TestVoteMessageHandlerWrongGapNumber(t *testing.T) { blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil) engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2