Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 92 additions & 11 deletions consensus/parlia/parlia.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,15 @@ func getVoteAttestationFromHeader(header *types.Header, chainConfig *params.Chai
return &attestation, nil
}

// getVoteAttestation returns the vote attestation extracted from the header's extra field if exists.
func (p *Parlia) getVoteAttestation(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) (*types.VoteAttestation, error) {
epochLength, err := p.epochLength(chain, header, parents)
if err != nil {
return nil, err
}
return getVoteAttestationFromHeader(header, chain.Config(), epochLength)
}

// getParent returns the parent of a given block.
func (p *Parlia) getParent(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) (*types.Header, error) {
var parent *types.Header
Expand Down Expand Up @@ -465,11 +474,7 @@ func trimParents(parents []*types.Header) []*types.Header {
// verifyVoteAttestation checks whether the vote attestation in the header is valid.
func (p *Parlia) verifyVoteAttestation(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error {
// === Step 1: Extract attestation ===
epochLength, err := p.epochLength(chain, header, parents)
if err != nil {
return err
}
attestation, err := getVoteAttestationFromHeader(header, chain.Config(), epochLength)
attestation, err := p.getVoteAttestation(chain, header, parents)
if err != nil {
return err
}
Expand Down Expand Up @@ -1291,11 +1296,7 @@ func (p *Parlia) distributeFinalityReward(chain consensus.ChainHeaderReader, sta
if head == nil {
return fmt.Errorf("header is nil at height %d", height)
}
epochLength, err := p.epochLength(chain, head, nil)
if err != nil {
return err
}
voteAttestation, err := getVoteAttestationFromHeader(head, chain.Config(), epochLength)
voteAttestation, err := p.getVoteAttestation(chain, head, nil)
if err != nil {
return err
}
Expand Down Expand Up @@ -1632,7 +1633,12 @@ func (p *Parlia) VerifyVote(chain consensus.ChainHeaderReader, vote *types.VoteE
return errors.New("unexpected error when getting the highest justified number and hash")
}
if vote.Data.SourceNumber != justifiedBlockNumber || vote.Data.SourceHash != justifiedBlockHash {
return errors.New("vote source block mismatch")
// It's possible the local chain hasn't reached the justified parent of the target header yet
// due to slight lag (mini lagging). In that case, allow the vote if it points to
// the parent of the target header.
if vote.Data.SourceNumber+1 != targetNumber || vote.Data.SourceHash != header.ParentHash {
return errors.New("vote source block mismatch")
}
}

number := header.Number.Uint64()
Expand Down Expand Up @@ -2418,6 +2424,81 @@ func (p *Parlia) BlockInterval(chain consensus.ChainHeaderReader, header *types.
return snap.BlockInterval, nil
}

func (p *Parlia) loadAttestations(chain consensus.ChainHeaderReader, hdr *types.Header) (att, patt *types.VoteAttestation, err error) {
att, err = p.getVoteAttestation(chain, hdr, nil)
if err != nil {
return nil, nil, err
}

parent := chain.GetHeaderByHash(hdr.ParentHash)
if parent == nil {
return nil, nil, errUnknownBlock
}

patt, err = p.getVoteAttestation(chain, parent, nil)
return
}

func (p *Parlia) skipVoting(chain consensus.ChainHeaderReader, curHead *types.Header) bool {
if p.GetAncestorGenerationDepth(curHead) != 3 {
return false
}

att, patt, err := p.loadAttestations(chain, curHead)
if err != nil {
return false
}

if att == nil && patt != nil {
latestJustified, _, err := p.GetJustifiedNumberAndHash(chain, []*types.Header{curHead})
if err != nil {
return false
}
return latestJustified+2 != curHead.Number.Uint64()
}

return false
}

// VoteTarget determines which block a validator should vote for.
// Returns nil if no vote is needed. (Refer BEP-590)
func (p *Parlia) VoteTarget(chain consensus.ChainHeaderReader, curHead *types.Header) (*types.Header, error) {
if p.GetAncestorGenerationDepth(curHead) != 3 {
return curHead, nil
}

// --- Rule 1: Skip voting ---
if p.skipVoting(chain, curHead) {
return nil, nil
}

// --- Rule 2: Vote for latestJustified + 1 ---
att, parentAtt, err := p.loadAttestations(chain, curHead)
if err != nil {
return nil, err
}
if att != nil && parentAtt == nil {
latestJustified, _, err := p.GetJustifiedNumberAndHash(chain, []*types.Header{curHead})
if err != nil {
return nil, err
}
target := chain.GetHeaderByNumber(latestJustified + 1)
if target != nil && p.skipVoting(chain, target) {
latestFinalized := p.GetFinalizedHeader(chain, curHead)
needAdvanceFinalized := latestFinalized != nil &&
latestFinalized.Number.Uint64()+1 != latestJustified
canAssemble := latestJustified+2 >= curHead.Number.Uint64()

if needAdvanceFinalized && canAssemble {
return target, nil
}
}
}

// --- Rule 3: Vote for current head as fallback ---
return curHead, nil
}

func (p *Parlia) NextProposalBlock(chain consensus.ChainHeaderReader, header *types.Header, proposer common.Address) (uint64, uint64, error) {
snap, err := p.snapshot(chain, header.Number.Uint64(), header.Hash(), nil)
if err != nil {
Expand Down
5 changes: 0 additions & 5 deletions consensus/parlia/parlia_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,11 +335,6 @@ func (v *MockValidator) Produce(attestation uint64) (*MockBlock, error) {
}

func (v *MockValidator) Vote(block *MockBlock) bool {
// Rule 3: The block should be the latest block of canonical chain
if block != v.head {
return false
}

// Rule 1: No double vote
if _, ok := v.voteRecords[block.blockNumber]; ok {
return false
Expand Down
40 changes: 25 additions & 15 deletions core/vote/vote_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,22 @@ func (voteManager *VoteManager) loop() {
}

curHead := cHead.Header
voteTarget := curHead
if p, ok := voteManager.engine.(*parlia.Parlia); ok {
var err error
voteTarget, err = p.VoteTarget(voteManager.chain, curHead)
if err != nil {
log.Debug("Get VoteTarget", "err", err)
continue
}
if voteTarget == nil {
log.Debug("skip voting", "blockNumber", curHead.Number.Uint64())
continue
}
}

// Check if cur validator is within the validatorSet at curHead
if !voteManager.engine.IsActiveValidatorAt(voteManager.chain, curHead,
// Check if cur validator is within the validatorSet at voteTarget
if !voteManager.engine.IsActiveValidatorAt(voteManager.chain, voteTarget,
func(bLSPublicKey *types.BLSPublicKey) bool {
return bytes.Equal(voteManager.signer.PubKey[:], bLSPublicKey[:])
}) {
Expand All @@ -164,16 +177,16 @@ func (voteManager *VoteManager) loop() {

// Vote for curBlockHeader block.
vote := &types.VoteData{
TargetNumber: curHead.Number.Uint64(),
TargetHash: curHead.Hash(),
TargetNumber: voteTarget.Number.Uint64(),
TargetHash: voteTarget.Hash(),
}
voteMessage := &types.VoteEnvelope{
Data: vote,
}

// Put Vote into journal and VotesPool if we are active validator and allow to sign it.
if ok, sourceNumber, sourceHash := voteManager.UnderRules(curHead); ok {
log.Debug("curHead is underRules for voting")
if ok, sourceNumber, sourceHash := voteManager.UnderRules(curHead, voteTarget); ok {
log.Debug("voteTarget is underRules for voting")
if sourceHash == (common.Hash{}) {
log.Debug("sourceHash is empty")
continue
Expand Down Expand Up @@ -209,7 +222,7 @@ func (voteManager *VoteManager) loop() {

log.Debug("vote manager produced vote", "votedBlockNumber", voteMessage.Data.TargetNumber, "votedBlockHash", voteMessage.Data.TargetHash, "voteMessageHash", voteMessage.Hash())
voteManager.pool.PutVote(voteMessage)
voteManager.chain.GetBlockStats(curHead.Hash()).SendVoteTime.Store(time.Now().UnixMilli())
voteManager.chain.GetBlockStats(voteTarget.Hash()).SendVoteTime.Store(time.Now().UnixMilli())
votesManagerCounter.Inc(1)
}

Expand Down Expand Up @@ -267,15 +280,14 @@ func (voteManager *VoteManager) loop() {
// UnderRules checks if the produced header under the following rules:
// A validator must not publish two distinct votes for the same height. (Rule 1)
// A validator must not vote within the span of its other votes . (Rule 2)
// Validators always vote for their canonical chain’s latest block. (Rule 3)
func (voteManager *VoteManager) UnderRules(header *types.Header) (bool, uint64, common.Hash) {
sourceNumber, sourceHash, err := voteManager.engine.GetJustifiedNumberAndHash(voteManager.chain, []*types.Header{header})
func (voteManager *VoteManager) UnderRules(curHeader, targetHeader *types.Header) (bool, uint64, common.Hash) {
sourceNumber, sourceHash, err := voteManager.engine.GetJustifiedNumberAndHash(voteManager.chain, []*types.Header{curHeader})
if err != nil {
log.Error("failed to get the highest justified number and hash at cur header", "curHeader's BlockNumber", header.Number, "curHeader's BlockHash", header.Hash())
log.Error("failed to get the highest justified number and hash at cur header", "curHeader's BlockNumber", curHeader.Number, "curHeader's BlockHash", curHeader.Hash())
return false, 0, common.Hash{}
}

targetNumber := header.Number.Uint64()
targetNumber := targetHeader.Number.Uint64()

voteDataBuffer := voteManager.journal.voteDataBuffer
//Rule 1: A validator must not publish two distinct votes for the same height.
Expand Down Expand Up @@ -318,8 +330,6 @@ func (voteManager *VoteManager) UnderRules(header *types.Header) (bool, uint64,
}
}

// Rule 3: Validators always vote for their canonical chain’s latest block.
// Since the header subscribed to is the canonical chain, so this rule is satisfied by default.
log.Debug("All three rules check passed")
log.Debug("All two rules check passed")
return true, sourceNumber, sourceHash
}
Loading