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 }