mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-27 00:46:18 +00:00
Previously, vote verification would log errors and fail when the referenced block header was not yet available locally, especially when nodes in the same round processed votes before receiving the block header. This commit changes the logic to defer verification and log at debug level if the header is missing, preventing unnecessary error logs and aligning with upstream geth behavior for out-of-order message arrival during consensus voting.
This commit is contained in:
parent
2855f1b48c
commit
84ac794e22
2 changed files with 190 additions and 0 deletions
|
|
@ -22,6 +22,14 @@ 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)
|
||||
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)
|
||||
|
|
|
|||
182
consensus/XDPoS/engines/engine_v2/vote_test.go
Normal file
182
consensus/XDPoS/engines/engine_v2/vote_test.go
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"math/big"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// memoryHandler captures log records for inspection in tests.
|
||||
type memoryHandler struct {
|
||||
mu sync.Mutex
|
||||
attrs []slog.Attr
|
||||
records []slog.Record
|
||||
}
|
||||
|
||||
func newMemoryHandler() *memoryHandler {
|
||||
return &memoryHandler{}
|
||||
}
|
||||
|
||||
func (h *memoryHandler) Enabled(_ context.Context, _ slog.Level) bool { return true }
|
||||
|
||||
func (h *memoryHandler) Handle(_ context.Context, r slog.Record) error {
|
||||
clone := r.Clone()
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
h.records = append(h.records, clone)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *memoryHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
return &memoryHandler{attrs: append(append([]slog.Attr{}, h.attrs...), attrs...)}
|
||||
}
|
||||
|
||||
func (h *memoryHandler) WithGroup(_ string) slog.Handler { return h }
|
||||
|
||||
func (h *memoryHandler) Records() []slog.Record {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
out := make([]slog.Record, len(h.records))
|
||||
copy(out, h.records)
|
||||
return out
|
||||
}
|
||||
|
||||
// MockChainReader is a mock implementation of consensus.ChainReader
|
||||
type MockChainReader struct {
|
||||
headers map[common.Hash]*types.Header
|
||||
}
|
||||
|
||||
// NewMockChainReader creates a new mock chain reader
|
||||
func NewMockChainReader() *MockChainReader {
|
||||
return &MockChainReader{
|
||||
headers: make(map[common.Hash]*types.Header),
|
||||
}
|
||||
}
|
||||
|
||||
// AddHeader adds a header to the mock chain
|
||||
func (m *MockChainReader) AddHeader(header *types.Header) {
|
||||
m.headers[header.Hash()] = header
|
||||
}
|
||||
|
||||
// Config implements consensus.ChainReader
|
||||
func (m *MockChainReader) Config() *params.ChainConfig {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CurrentHeader implements consensus.ChainReader
|
||||
func (m *MockChainReader) CurrentHeader() *types.Header {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetHeader implements consensus.ChainReader
|
||||
func (m *MockChainReader) GetHeader(hash common.Hash, number uint64) *types.Header {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetHeaderByNumber implements consensus.ChainReader
|
||||
func (m *MockChainReader) GetHeaderByNumber(number uint64) *types.Header {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetHeaderByHash implements consensus.ChainReader
|
||||
func (m *MockChainReader) GetHeaderByHash(hash common.Hash) *types.Header {
|
||||
return m.headers[hash]
|
||||
}
|
||||
|
||||
// GetBlock implements consensus.ChainReader
|
||||
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) {
|
||||
mockChain := NewMockChainReader()
|
||||
|
||||
engine := &XDPoS_v2{
|
||||
currentRound: 10,
|
||||
lock: sync.RWMutex{},
|
||||
}
|
||||
|
||||
// Create a vote with a round number less than current round
|
||||
vote := &types.Vote{
|
||||
ProposedBlockInfo: &types.BlockInfo{
|
||||
Hash: common.StringToHash("some-block"),
|
||||
Round: 5, // Less than currentRound (10)
|
||||
Number: big.NewInt(50),
|
||||
},
|
||||
Signature: make([]byte, 65),
|
||||
GapNumber: 0,
|
||||
}
|
||||
|
||||
verified, err := engine.VerifyVoteMessage(mockChain, vote)
|
||||
|
||||
// Should reject the vote without error
|
||||
assert.False(t, verified, "Should return false for vote with round < currentRound")
|
||||
assert.NoError(t, err, "Should not return an error for old round votes")
|
||||
}
|
||||
Loading…
Reference in a new issue