github.com/koko1123/flow-go-1@v0.29.6/consensus/hotstuff/votecollector/staking_vote_processor.go (about)

     1  package votecollector
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/rs/zerolog"
     8  	"go.uber.org/atomic"
     9  
    10  	"github.com/koko1123/flow-go-1/consensus/hotstuff"
    11  	"github.com/koko1123/flow-go-1/consensus/hotstuff/model"
    12  	"github.com/koko1123/flow-go-1/consensus/hotstuff/signature"
    13  	"github.com/koko1123/flow-go-1/consensus/hotstuff/verification"
    14  	"github.com/koko1123/flow-go-1/model/flow"
    15  	msig "github.com/koko1123/flow-go-1/module/signature"
    16  	"github.com/onflow/flow-go/crypto"
    17  )
    18  
    19  /* ***************** Base-Factory for StakingVoteProcessor ****************** */
    20  
    21  // stakingVoteProcessorFactoryBase implements a factory for creating StakingVoteProcessor
    22  // holds needed dependencies to initialize StakingVoteProcessor.
    23  // stakingVoteProcessorFactoryBase is used in collector cluster.
    24  // CAUTION:
    25  // this base factory only creates the VerifyingVoteProcessor for the given block.
    26  // It does _not_ check the proposer's vote for its own block, i.e. it does _not_
    27  // implement `hotstuff.VoteProcessorFactory`. This base factory should be wrapped
    28  // by `votecollector.VoteProcessorFactory` which adds the logic to verify
    29  // the proposer's vote (decorator pattern).
    30  type stakingVoteProcessorFactoryBase struct {
    31  	committee   hotstuff.Committee
    32  	onQCCreated hotstuff.OnQCCreated
    33  }
    34  
    35  // Create creates StakingVoteProcessor for processing votes for the given block.
    36  // Caller must treat all errors as exceptions
    37  func (f *stakingVoteProcessorFactoryBase) Create(log zerolog.Logger, block *model.Block) (hotstuff.VerifyingVoteProcessor, error) {
    38  	allParticipants, err := f.committee.Identities(block.BlockID)
    39  	if err != nil {
    40  		return nil, fmt.Errorf("error retrieving consensus participants: %w", err)
    41  	}
    42  
    43  	// message that has to be verified against aggregated signature
    44  	msg := verification.MakeVoteMessage(block.View, block.BlockID)
    45  
    46  	// prepare the staking public keys of participants
    47  	stakingKeys := make([]crypto.PublicKey, 0, len(allParticipants))
    48  	for _, participant := range allParticipants {
    49  		stakingKeys = append(stakingKeys, participant.StakingPubKey)
    50  	}
    51  
    52  	stakingSigAggtor, err := signature.NewWeightedSignatureAggregator(allParticipants, stakingKeys, msg, msig.CollectorVoteTag)
    53  	if err != nil {
    54  		return nil, fmt.Errorf("could not create aggregator for staking signatures: %w", err)
    55  	}
    56  
    57  	minRequiredWeight := hotstuff.ComputeWeightThresholdForBuildingQC(allParticipants.TotalWeight())
    58  
    59  	return &StakingVoteProcessor{
    60  		log:               log,
    61  		block:             block,
    62  		stakingSigAggtor:  stakingSigAggtor,
    63  		onQCCreated:       f.onQCCreated,
    64  		minRequiredWeight: minRequiredWeight,
    65  		done:              *atomic.NewBool(false),
    66  		allParticipants:   allParticipants,
    67  	}, nil
    68  }
    69  
    70  /* ****************** StakingVoteProcessor Implementation ******************* */
    71  
    72  // StakingVoteProcessor implements the hotstuff.VerifyingVoteProcessor interface.
    73  // It processes hotstuff votes from a collector cluster, where participants vote
    74  // in favour of a block by proving their staking key signature.
    75  // Concurrency safe.
    76  type StakingVoteProcessor struct {
    77  	log               zerolog.Logger
    78  	block             *model.Block
    79  	stakingSigAggtor  hotstuff.WeightedSignatureAggregator
    80  	onQCCreated       hotstuff.OnQCCreated
    81  	minRequiredWeight uint64
    82  	done              atomic.Bool
    83  	allParticipants   flow.IdentityList
    84  }
    85  
    86  // Block returns block that is part of proposal that we are processing votes for.
    87  func (p *StakingVoteProcessor) Block() *model.Block {
    88  	return p.block
    89  }
    90  
    91  // Status returns status of this vote processor, it's always verifying.
    92  func (p *StakingVoteProcessor) Status() hotstuff.VoteCollectorStatus {
    93  	return hotstuff.VoteCollectorStatusVerifying
    94  }
    95  
    96  // Process performs processing of single vote in concurrent safe way. This
    97  // function is implemented to be called by multiple goroutines at the same time.
    98  // Supports processing of both staking and threshold signatures. Design of this
    99  // function is event driven, as soon as we collect enough weight to create a QC
   100  // we will immediately do this and submit it via callback for further processing.
   101  // Expected error returns during normal operations:
   102  // * VoteForIncompatibleBlockError - submitted vote for incompatible block
   103  // * VoteForIncompatibleViewError - submitted vote for incompatible view
   104  // * model.InvalidVoteError - submitted vote with invalid signature
   105  // All other errors should be treated as exceptions.
   106  func (p *StakingVoteProcessor) Process(vote *model.Vote) error {
   107  	err := EnsureVoteForBlock(vote, p.block)
   108  	if err != nil {
   109  		return fmt.Errorf("received incompatible vote: %w", err)
   110  	}
   111  
   112  	// Vote Processing state machine
   113  	if p.done.Load() {
   114  		return nil
   115  	}
   116  	err = p.stakingSigAggtor.Verify(vote.SignerID, vote.SigData)
   117  	if err != nil {
   118  		if model.IsInvalidSignerError(err) {
   119  			return model.NewInvalidVoteErrorf(vote, "vote %x for view %d is not signed by an authorized consensus participant: %w",
   120  				vote.ID(), vote.View, err)
   121  		}
   122  		if errors.Is(err, model.ErrInvalidSignature) {
   123  			return model.NewInvalidVoteErrorf(vote, "vote %x for view %d has an invalid staking signature: %w",
   124  				vote.ID(), vote.View, err)
   125  		}
   126  		return fmt.Errorf("internal error checking signature validity: %w", err)
   127  	}
   128  
   129  	if p.done.Load() {
   130  		return nil
   131  	}
   132  	totalWeight, err := p.stakingSigAggtor.TrustedAdd(vote.SignerID, vote.SigData)
   133  	if err != nil {
   134  		// we don't expect any errors here during normal operation, as we previously checked
   135  		// for duplicated votes from the same signer and verified the signer+signature
   136  		return fmt.Errorf("unexpected exception adding signature from vote %x to staking aggregator: %w", vote.ID(), err)
   137  	}
   138  
   139  	// checking of conditions for building QC are satisfied
   140  	if totalWeight < p.minRequiredWeight {
   141  		return nil
   142  	}
   143  
   144  	// At this point, we have enough signatures to build a QC. Another routine
   145  	// might just be at this point. To avoid duplicate work, only one routine can pass:
   146  	if !p.done.CompareAndSwap(false, true) {
   147  		return nil
   148  	}
   149  	qc, err := p.buildQC()
   150  	if err != nil {
   151  		return fmt.Errorf("internal error constructing QC from votes: %w", err)
   152  	}
   153  	p.onQCCreated(qc)
   154  
   155  	return nil
   156  }
   157  
   158  // buildQC performs aggregation of signatures when we have collected enough
   159  // weight for building QC. This function is run only once by single worker.
   160  // Any error should be treated as exception.
   161  func (p *StakingVoteProcessor) buildQC() (*flow.QuorumCertificate, error) {
   162  	stakingSigners, aggregatedStakingSig, err := p.stakingSigAggtor.Aggregate()
   163  	if err != nil {
   164  		return nil, fmt.Errorf("could not aggregate staking signature: %w", err)
   165  	}
   166  
   167  	signerIndices, err := p.signerIndicesFromIdentities(stakingSigners)
   168  	if err != nil {
   169  		return nil, fmt.Errorf("could not encode signer indices: %w", err)
   170  	}
   171  
   172  	return &flow.QuorumCertificate{
   173  		View:          p.block.View,
   174  		BlockID:       p.block.BlockID,
   175  		SignerIndices: signerIndices,
   176  		SigData:       aggregatedStakingSig,
   177  	}, nil
   178  }
   179  
   180  func (p *StakingVoteProcessor) signerIndicesFromIdentities(signerIDs flow.IdentifierList) ([]byte, error) {
   181  	signerIndices, err := msig.EncodeSignersToIndices(p.allParticipants.NodeIDs(), signerIDs)
   182  	if err != nil {
   183  		return nil, fmt.Errorf("could not encode signer identifiers to indices: %w", err)
   184  	}
   185  	return signerIndices, nil
   186  }