diff --git a/consensus/parlia/parlia.go b/consensus/parlia/parlia.go index d5fe807763..89bbc82a8b 100644 --- a/consensus/parlia/parlia.go +++ b/consensus/parlia/parlia.go @@ -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 @@ -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 } @@ -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 } @@ -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() @@ -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 { diff --git a/consensus/parlia/parlia_test.go b/consensus/parlia/parlia_test.go index d726ae6a27..a6bd41b58e 100644 --- a/consensus/parlia/parlia_test.go +++ b/consensus/parlia/parlia_test.go @@ -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 diff --git a/core/vote/vote_manager.go b/core/vote/vote_manager.go index ad83b4e51f..533c3adccf 100644 --- a/core/vote/vote_manager.go +++ b/core/vote/vote_manager.go @@ -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[:]) }) { @@ -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 @@ -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) } @@ -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. @@ -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 }