github.com/koko1123/flow-go-1@v0.29.6/consensus/hotstuff/votecollector/combined_vote_processor_v3.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/onflow/flow-go/crypto"
    15  	"github.com/koko1123/flow-go-1/model/encoding"
    16  	"github.com/koko1123/flow-go-1/model/flow"
    17  	msig "github.com/koko1123/flow-go-1/module/signature"
    18  )
    19  
    20  /* **************** Base-Factory for CombinedVoteProcessors ***************** */
    21  
    22  // combinedVoteProcessorFactoryBaseV3 is a `votecollector.baseFactory` for creating
    23  // CombinedVoteProcessors, holding all needed dependencies.
    24  // combinedVoteProcessorFactoryBaseV3 is intended to be used for the main consensus.
    25  // CAUTION:
    26  // this base factory only creates the VerifyingVoteProcessor for the given block.
    27  // It does _not_ check the proposer's vote for its own block, i.e. it does _not_
    28  // implement `hotstuff.VoteProcessorFactory`. This base factory should be wrapped
    29  // by `votecollector.VoteProcessorFactory` which adds the logic to verify
    30  // the proposer's vote (decorator pattern).
    31  // nolint:unused
    32  type combinedVoteProcessorFactoryBaseV3 struct {
    33  	committee   hotstuff.Committee
    34  	onQCCreated hotstuff.OnQCCreated
    35  	packer      hotstuff.Packer
    36  }
    37  
    38  // Create creates CombinedVoteProcessorV3 for processing votes for the given block.
    39  // Caller must treat all errors as exceptions
    40  // nolint:unused
    41  func (f *combinedVoteProcessorFactoryBaseV3) Create(log zerolog.Logger, block *model.Block) (hotstuff.VerifyingVoteProcessor, error) {
    42  	allParticipants, err := f.committee.Identities(block.BlockID)
    43  	if err != nil {
    44  		return nil, fmt.Errorf("error retrieving consensus participants at block %v: %w", block.BlockID, err)
    45  	}
    46  
    47  	// message that has to be verified against aggregated signature
    48  	msg := verification.MakeVoteMessage(block.View, block.BlockID)
    49  
    50  	// prepare the staking public keys of participants
    51  	stakingKeys := make([]crypto.PublicKey, 0, len(allParticipants))
    52  	for _, participant := range allParticipants {
    53  		stakingKeys = append(stakingKeys, participant.StakingPubKey)
    54  	}
    55  
    56  	stakingSigAggtor, err := signature.NewWeightedSignatureAggregator(allParticipants, stakingKeys, msg, msig.ConsensusVoteTag)
    57  	if err != nil {
    58  		return nil, fmt.Errorf("could not create aggregator for staking signatures: %w", err)
    59  	}
    60  
    61  	dkg, err := f.committee.DKG(block.BlockID)
    62  	if err != nil {
    63  		return nil, fmt.Errorf("could not get DKG info at block %v: %w", block.BlockID, err)
    64  	}
    65  
    66  	// prepare the random beacon public keys of participants
    67  	beaconKeys := make([]crypto.PublicKey, 0, len(allParticipants))
    68  	for _, participant := range allParticipants {
    69  		pk, err := dkg.KeyShare(participant.NodeID)
    70  		if err != nil {
    71  			return nil, fmt.Errorf("could not get random beacon key share for %x: %w", participant.NodeID, err)
    72  		}
    73  		beaconKeys = append(beaconKeys, pk)
    74  	}
    75  
    76  	rbSigAggtor, err := signature.NewWeightedSignatureAggregator(allParticipants, beaconKeys, msg, msig.RandomBeaconTag)
    77  	if err != nil {
    78  		return nil, fmt.Errorf("could not create aggregator for thershold signatures: %w", err)
    79  	}
    80  
    81  	threshold := msig.RandomBeaconThreshold(int(dkg.Size()))
    82  	randomBeaconInspector, err := signature.NewRandomBeaconInspector(dkg.GroupKey(), beaconKeys, threshold, msg)
    83  	if err != nil {
    84  		return nil, fmt.Errorf("could not create random beacon inspector: %w", err)
    85  	}
    86  
    87  	rbRector := signature.NewRandomBeaconReconstructor(dkg, randomBeaconInspector)
    88  	minRequiredWeight := hotstuff.ComputeWeightThresholdForBuildingQC(allParticipants.TotalWeight())
    89  
    90  	return &CombinedVoteProcessorV3{
    91  		log:               log.With().Hex("block_id", block.BlockID[:]).Logger(),
    92  		block:             block,
    93  		stakingSigAggtor:  stakingSigAggtor,
    94  		rbSigAggtor:       rbSigAggtor,
    95  		rbRector:          rbRector,
    96  		onQCCreated:       f.onQCCreated,
    97  		packer:            f.packer,
    98  		minRequiredWeight: minRequiredWeight,
    99  		done:              *atomic.NewBool(false),
   100  	}, nil
   101  }
   102  
   103  /* ****************** CombinedVoteProcessorV3 Implementation ****************** */
   104  
   105  // CombinedVoteProcessorV3 implements the hotstuff.VerifyingVoteProcessor interface.
   106  // It processes votes from the main consensus committee, where participants vote in
   107  // favour of a block by proving either their staking key signature or their random
   108  // beacon signature. In the former case, the participant only contributes to HotStuff
   109  // progress; while in the latter case, the voter also contributes to running the
   110  // random beacon. Concurrency safe.
   111  type CombinedVoteProcessorV3 struct {
   112  	log               zerolog.Logger
   113  	block             *model.Block
   114  	stakingSigAggtor  hotstuff.WeightedSignatureAggregator
   115  	rbSigAggtor       hotstuff.WeightedSignatureAggregator
   116  	rbRector          hotstuff.RandomBeaconReconstructor
   117  	onQCCreated       hotstuff.OnQCCreated
   118  	packer            hotstuff.Packer
   119  	minRequiredWeight uint64
   120  	done              atomic.Bool
   121  }
   122  
   123  var _ hotstuff.VerifyingVoteProcessor = (*CombinedVoteProcessorV3)(nil)
   124  
   125  // Block returns block that is part of proposal that we are processing votes for.
   126  func (p *CombinedVoteProcessorV3) Block() *model.Block {
   127  	return p.block
   128  }
   129  
   130  // Status returns status of this vote processor, it's always verifying.
   131  func (p *CombinedVoteProcessorV3) Status() hotstuff.VoteCollectorStatus {
   132  	return hotstuff.VoteCollectorStatusVerifying
   133  }
   134  
   135  // Process performs processing of single vote in concurrent safe way. This function is implemented to be
   136  // called by multiple goroutines at the same time. Supports processing of both staking and random beacon signatures.
   137  // Design of this function is event driven: as soon as we collect enough signatures to create a QC we will immediately do so
   138  // and submit it via callback for further processing.
   139  // Expected error returns during normal operations:
   140  // * VoteForIncompatibleBlockError - submitted vote for incompatible block
   141  // * VoteForIncompatibleViewError - submitted vote for incompatible view
   142  // * model.InvalidVoteError - submitted vote with invalid signature
   143  // * model.DuplicatedSignerError - vote from a signer whose vote was previously already processed
   144  // All other errors should be treated as exceptions.
   145  //
   146  // CAUTION: implementation is NOT (yet) BFT
   147  // Explanation: for correctness, we require that no voter can be counted repeatedly. However,
   148  // CombinedVoteProcessorV3 relies on the `VoteCollector`'s `votesCache` filter out all votes but the first for
   149  // every signerID. However, we have the edge case, where we still feed the proposers vote twice into the
   150  // `VerifyingVoteProcessor` (once as part of a cached vote, once as an individual vote). This can be exploited
   151  // by a byzantine proposer to be erroneously counted twice, which would lead to a safety fault.
   152  //
   153  // TODO: (suggestion) I think it would be worth-while to include a second `votesCache` into the `CombinedVoteProcessorV3`.
   154  // Thereby,  `CombinedVoteProcessorV3` inherently guarantees correctness of the QCs it produces without relying on
   155  // external conditions (making the code more modular, less interdependent and thereby easier to maintain). The
   156  // runtime overhead is marginal: For `votesCache` to add 500 votes (concurrently with 20 threads) takes about
   157  // 0.25ms. This runtime overhead is neglectable and a good tradeoff for the gain in maintainability and code clarity.
   158  func (p *CombinedVoteProcessorV3) Process(vote *model.Vote) error {
   159  	err := EnsureVoteForBlock(vote, p.block)
   160  	if err != nil {
   161  		return fmt.Errorf("received incompatible vote %v: %w", vote.ID(), err)
   162  	}
   163  
   164  	// Vote Processing state machine
   165  	if p.done.Load() {
   166  		return nil
   167  	}
   168  	sigType, sig, err := msig.DecodeSingleSig(vote.SigData)
   169  	if err != nil {
   170  		if errors.Is(err, msig.ErrInvalidSignatureFormat) {
   171  			return model.NewInvalidVoteErrorf(vote, "could not decode signature: %w", err)
   172  		}
   173  		return fmt.Errorf("unexpected error decoding vote %v: %w", vote.ID(), err)
   174  	}
   175  
   176  	switch sigType {
   177  
   178  	case encoding.SigTypeStaking:
   179  		err := p.stakingSigAggtor.Verify(vote.SignerID, sig)
   180  		if err != nil {
   181  			if model.IsInvalidSignerError(err) {
   182  				return model.NewInvalidVoteErrorf(vote, "vote %x for view %d is not signed by an authorized consensus participant: %w",
   183  					vote.ID(), vote.View, err)
   184  			}
   185  			if errors.Is(err, model.ErrInvalidSignature) {
   186  				return model.NewInvalidVoteErrorf(vote, "vote %x for view %d has an invalid staking signature: %w",
   187  					vote.ID(), vote.View, err)
   188  			}
   189  			return fmt.Errorf("internal error checking signature validity for vote %v: %w", vote.ID(), err)
   190  		}
   191  		if p.done.Load() {
   192  			return nil
   193  		}
   194  		_, err = p.stakingSigAggtor.TrustedAdd(vote.SignerID, sig)
   195  		if err != nil {
   196  			// we don't expect any errors here during normal operation, as we previously checked
   197  			// for duplicated votes from the same signer and verified the signer+signature
   198  			return fmt.Errorf("adding the signature to staking aggregator failed for vote %v: %w", vote.ID(), err)
   199  		}
   200  
   201  	case encoding.SigTypeRandomBeacon:
   202  		err := p.rbSigAggtor.Verify(vote.SignerID, sig)
   203  		if err != nil {
   204  			if model.IsInvalidSignerError(err) {
   205  				return model.NewInvalidVoteErrorf(vote, "vote %x for view %d is not from an authorized random beacon participant: %w",
   206  					vote.ID(), vote.View, err)
   207  			}
   208  			if errors.Is(err, model.ErrInvalidSignature) {
   209  				return model.NewInvalidVoteErrorf(vote, "vote %x for view %d has an invalid random beacon signature: %w",
   210  					vote.ID(), vote.View, err)
   211  			}
   212  			return fmt.Errorf("internal error checking signature validity for vote %v: %w", vote.ID(), err)
   213  		}
   214  
   215  		if p.done.Load() {
   216  			return nil
   217  		}
   218  		// Add signatures to `rbSigAggtor` and `rbRector`: we don't expect any errors during normal operation,
   219  		// as we previously checked for duplicated votes from the same signer and verified the signer+signature
   220  		_, err = p.rbSigAggtor.TrustedAdd(vote.SignerID, sig)
   221  		if err != nil {
   222  			return fmt.Errorf("unexpected exception adding signature from vote %v to random beacon aggregator: %w", vote.ID(), err)
   223  		}
   224  		_, err = p.rbRector.TrustedAdd(vote.SignerID, sig)
   225  		if err != nil {
   226  			return fmt.Errorf("unexpected exception adding signature from vote %v to random beacon reconstructor: %w", vote.ID(), err)
   227  		}
   228  
   229  	default:
   230  		return model.NewInvalidVoteErrorf(vote, "invalid signature type %d: %w", sigType, model.NewInvalidFormatErrorf(""))
   231  	}
   232  
   233  	// checking of conditions for building QC are satisfied
   234  	if p.stakingSigAggtor.TotalWeight()+p.rbSigAggtor.TotalWeight() < p.minRequiredWeight {
   235  		return nil
   236  	}
   237  	if !p.rbRector.EnoughShares() {
   238  		return nil
   239  	}
   240  
   241  	// At this point, we have enough signatures to build a QC. Another routine
   242  	// might just be at this point. To avoid duplicate work, only one routine can pass:
   243  	if !p.done.CompareAndSwap(false, true) {
   244  		return nil
   245  	}
   246  
   247  	// Our algorithm for checking votes and adding them to the aggregators should
   248  	// guarantee that we are _always_ able to successfully construct a QC when we
   249  	// reach this point. A failure implies that the VoteProcessor's internal state is corrupted.
   250  	qc, err := p.buildQC()
   251  	if err != nil {
   252  		return fmt.Errorf("internal error constructing QC from votes: %w", err)
   253  	}
   254  
   255  	p.log.Info().
   256  		Uint64("view", qc.View).
   257  		Hex("signers", qc.SignerIndices).
   258  		Msg("new qc has been created")
   259  
   260  	p.onQCCreated(qc)
   261  
   262  	return nil
   263  }
   264  
   265  // buildQC performs aggregation and reconstruction of signatures when we have collected enough
   266  // signatures for building a QC. This function is run only once by a single worker.
   267  // Any error should be treated as exception.
   268  func (p *CombinedVoteProcessorV3) buildQC() (*flow.QuorumCertificate, error) {
   269  	// STEP 1: aggregate staking signatures (if there are any)
   270  	// * It is possible that all replicas signed with their random beacon keys.
   271  	//   Per Convention, we represent an empty set of staking signers as
   272  	//   `stakingSigners` and `aggregatedStakingSig` both being zero-length
   273  	//   (here, we use `nil`).
   274  	// * If it has _not collected any_ signatures, `stakingSigAggtor.Aggregate()`
   275  	//   errors with a `model.InsufficientSignaturesError`. We shortcut this case,
   276  	//   and only call `Aggregate`, if the `stakingSigAggtor` has collected signatures
   277  	//   with non-zero weight (i.e. at least one signature was collected).
   278  	var stakingSigners []flow.Identifier // nil (zero value) represents empty set of staking signers
   279  	var aggregatedStakingSig []byte      // nil (zero value) for empty set of staking signers
   280  	if p.stakingSigAggtor.TotalWeight() > 0 {
   281  		var err error
   282  		stakingSigners, aggregatedStakingSig, err = p.stakingSigAggtor.Aggregate()
   283  		if err != nil {
   284  			return nil, fmt.Errorf("unexpected error aggregating staking signatures: %w", err)
   285  		}
   286  	}
   287  
   288  	// STEP 2: reconstruct random beacon group sig and aggregate random beacon sig shares
   289  	// Note: A valid random beacon group sig is required for QC validity. Our logic guarantees
   290  	// that we always collect the minimally required number (non-zero) of signature shares.
   291  	beaconSigners, aggregatedRandomBeaconSig, err := p.rbSigAggtor.Aggregate()
   292  	if err != nil {
   293  		return nil, fmt.Errorf("could not aggregate random beacon signatures: %w", err)
   294  	}
   295  	reconstructedBeaconSig, err := p.rbRector.Reconstruct()
   296  	if err != nil {
   297  		return nil, fmt.Errorf("could not reconstruct random beacon group signature: %w", err)
   298  	}
   299  
   300  	// STEP 3: generate BlockSignatureData and serialize it
   301  	blockSigData := &hotstuff.BlockSignatureData{
   302  		StakingSigners:               stakingSigners,
   303  		RandomBeaconSigners:          beaconSigners,
   304  		AggregatedStakingSig:         aggregatedStakingSig,
   305  		AggregatedRandomBeaconSig:    aggregatedRandomBeaconSig,
   306  		ReconstructedRandomBeaconSig: reconstructedBeaconSig,
   307  	}
   308  	signerIndices, sigData, err := p.packer.Pack(p.block.BlockID, blockSigData)
   309  	if err != nil {
   310  		return nil, fmt.Errorf("could not pack the block sig data: %w", err)
   311  	}
   312  
   313  	return &flow.QuorumCertificate{
   314  		View:          p.block.View,
   315  		BlockID:       p.block.BlockID,
   316  		SignerIndices: signerIndices,
   317  		SigData:       sigData,
   318  	}, nil
   319  }