github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/consensus/hotstuff/votecollector/staking_vote_processor.go (about)

     1  package votecollector
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/onflow/crypto"
     8  	"github.com/rs/zerolog"
     9  	"go.uber.org/atomic"
    10  
    11  	"github.com/onflow/flow-go/consensus/hotstuff"
    12  	"github.com/onflow/flow-go/consensus/hotstuff/model"
    13  	"github.com/onflow/flow-go/consensus/hotstuff/signature"
    14  	"github.com/onflow/flow-go/consensus/hotstuff/verification"
    15  	"github.com/onflow/flow-go/model/flow"
    16  	msig "github.com/onflow/flow-go/module/signature"
    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.DynamicCommittee
    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.IdentitiesByBlock(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, err := f.committee.QuorumThresholdForView(block.View)
    58  	if err != nil {
    59  		return nil, fmt.Errorf("could not get weight threshold for view %d: %w", block.View, err)
    60  	}
    61  
    62  	return &StakingVoteProcessor{
    63  		log:               log.With().Hex("block_id", block.BlockID[:]).Logger(),
    64  		block:             block,
    65  		stakingSigAggtor:  stakingSigAggtor,
    66  		onQCCreated:       f.onQCCreated,
    67  		minRequiredWeight: minRequiredWeight,
    68  		done:              *atomic.NewBool(false),
    69  		allParticipants:   allParticipants,
    70  	}, nil
    71  }
    72  
    73  /* ****************** StakingVoteProcessor Implementation ******************* */
    74  
    75  // StakingVoteProcessor implements the hotstuff.VerifyingVoteProcessor interface.
    76  // It processes hotstuff votes from a collector cluster, where participants vote
    77  // in favour of a block by proving their staking key signature.
    78  // Concurrency safe.
    79  type StakingVoteProcessor struct {
    80  	log               zerolog.Logger
    81  	block             *model.Block
    82  	stakingSigAggtor  hotstuff.WeightedSignatureAggregator
    83  	onQCCreated       hotstuff.OnQCCreated
    84  	minRequiredWeight uint64
    85  	done              atomic.Bool
    86  	allParticipants   flow.IdentityList
    87  }
    88  
    89  // Block returns block that is part of proposal that we are processing votes for.
    90  func (p *StakingVoteProcessor) Block() *model.Block {
    91  	return p.block
    92  }
    93  
    94  // Status returns status of this vote processor, it's always verifying.
    95  func (p *StakingVoteProcessor) Status() hotstuff.VoteCollectorStatus {
    96  	return hotstuff.VoteCollectorStatusVerifying
    97  }
    98  
    99  // Process performs processing of single vote in concurrent safe way. This
   100  // function is implemented to be called by multiple goroutines at the same time.
   101  // Supports processing of both staking and threshold signatures. Design of this
   102  // function is event driven, as soon as we collect enough weight to create a QC
   103  // we will immediately do this and submit it via callback for further processing.
   104  // Expected error returns during normal operations:
   105  // * VoteForIncompatibleBlockError - submitted vote for incompatible block
   106  // * VoteForIncompatibleViewError - submitted vote for incompatible view
   107  // * model.InvalidVoteError - submitted vote with invalid signature
   108  // All other errors should be treated as exceptions.
   109  func (p *StakingVoteProcessor) Process(vote *model.Vote) error {
   110  	err := EnsureVoteForBlock(vote, p.block)
   111  	if err != nil {
   112  		return fmt.Errorf("received incompatible vote: %w", err)
   113  	}
   114  
   115  	// Vote Processing state machine
   116  	if p.done.Load() {
   117  		return nil
   118  	}
   119  	err = p.stakingSigAggtor.Verify(vote.SignerID, vote.SigData)
   120  	if err != nil {
   121  		if model.IsInvalidSignerError(err) {
   122  			return model.NewInvalidVoteErrorf(vote, "vote %x for view %d is not signed by an authorized consensus participant: %w",
   123  				vote.ID(), vote.View, err)
   124  		}
   125  		if errors.Is(err, model.ErrInvalidSignature) {
   126  			return model.NewInvalidVoteErrorf(vote, "vote %x for view %d has an invalid staking signature: %w",
   127  				vote.ID(), vote.View, err)
   128  		}
   129  		return fmt.Errorf("internal error checking signature validity: %w", err)
   130  	}
   131  
   132  	if p.done.Load() {
   133  		return nil
   134  	}
   135  	totalWeight, err := p.stakingSigAggtor.TrustedAdd(vote.SignerID, vote.SigData)
   136  	if err != nil {
   137  		// we don't expect any errors here during normal operation, as we previously checked
   138  		// for duplicated votes from the same signer and verified the signer+signature
   139  		return fmt.Errorf("unexpected exception adding signature from vote %x to staking aggregator: %w", vote.ID(), err)
   140  	}
   141  
   142  	p.log.Debug().Msgf("processed vote, total weight=(%d), required=(%d)", totalWeight, p.minRequiredWeight)
   143  
   144  	// checking of conditions for building QC are satisfied
   145  	if totalWeight < p.minRequiredWeight {
   146  		return nil
   147  	}
   148  
   149  	// At this point, we have enough signatures to build a QC. Another routine
   150  	// might just be at this point. To avoid duplicate work, only one routine can pass:
   151  	if !p.done.CompareAndSwap(false, true) {
   152  		return nil
   153  	}
   154  	qc, err := p.buildQC()
   155  	if err != nil {
   156  		return fmt.Errorf("internal error constructing QC from votes: %w", err)
   157  	}
   158  
   159  	p.log.Info().
   160  		Uint64("view", qc.View).
   161  		Hex("signers", qc.SignerIndices).
   162  		Msg("new QC has been created")
   163  	p.onQCCreated(qc)
   164  
   165  	return nil
   166  }
   167  
   168  // buildQC performs aggregation of signatures when we have collected enough
   169  // weight for building QC. This function is run only once by single worker.
   170  // Any error should be treated as exception.
   171  func (p *StakingVoteProcessor) buildQC() (*flow.QuorumCertificate, error) {
   172  	stakingSigners, aggregatedStakingSig, err := p.stakingSigAggtor.Aggregate()
   173  	if err != nil {
   174  		return nil, fmt.Errorf("could not aggregate staking signature: %w", err)
   175  	}
   176  
   177  	signerIndices, err := p.signerIndicesFromIdentities(stakingSigners)
   178  	if err != nil {
   179  		return nil, fmt.Errorf("could not encode signer indices: %w", err)
   180  	}
   181  
   182  	return &flow.QuorumCertificate{
   183  		View:          p.block.View,
   184  		BlockID:       p.block.BlockID,
   185  		SignerIndices: signerIndices,
   186  		SigData:       aggregatedStakingSig,
   187  	}, nil
   188  }
   189  
   190  func (p *StakingVoteProcessor) signerIndicesFromIdentities(signerIDs flow.IdentifierList) ([]byte, error) {
   191  	signerIndices, err := msig.EncodeSignersToIndices(p.allParticipants.NodeIDs(), signerIDs)
   192  	if err != nil {
   193  		return nil, fmt.Errorf("could not encode signer identifiers to indices: %w", err)
   194  	}
   195  	return signerIndices, nil
   196  }