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 }