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 }