fix(consensus): fix private chain initialization (#1987)

* revert: use masternodes from snapshot to verify vote

* fix underflow during chain initialization

* add previously removed test

* rename snapshot > snap for consistency
This commit is contained in:
Wanwiset Peerapatanapokin 2026-02-19 01:47:03 +07:00 committed by GitHub
parent 296d612167
commit e324a78d94
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 34 additions and 73 deletions

View file

@ -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 {

View file

@ -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
}

View file

@ -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) {

View file

@ -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