github.com/onflow/flow-go@v0.33.17/cmd/bootstrap/run/qc.go (about) 1 package run 2 3 import ( 4 "fmt" 5 6 "github.com/rs/zerolog" 7 8 "github.com/onflow/flow-go/consensus/hotstuff" 9 "github.com/onflow/flow-go/consensus/hotstuff/committees" 10 "github.com/onflow/flow-go/consensus/hotstuff/model" 11 hotstuffSig "github.com/onflow/flow-go/consensus/hotstuff/signature" 12 "github.com/onflow/flow-go/consensus/hotstuff/validator" 13 "github.com/onflow/flow-go/consensus/hotstuff/verification" 14 "github.com/onflow/flow-go/consensus/hotstuff/votecollector" 15 "github.com/onflow/flow-go/crypto" 16 "github.com/onflow/flow-go/model/bootstrap" 17 "github.com/onflow/flow-go/model/dkg" 18 "github.com/onflow/flow-go/model/flow" 19 "github.com/onflow/flow-go/module/local" 20 ) 21 22 type Participant struct { 23 bootstrap.NodeInfo 24 RandomBeaconPrivKey crypto.PrivateKey 25 } 26 27 type ParticipantData struct { 28 Participants []Participant 29 Lookup map[flow.Identifier]flow.DKGParticipant 30 GroupKey crypto.PublicKey 31 } 32 33 func (pd *ParticipantData) Identities() flow.IdentityList { 34 nodes := make([]bootstrap.NodeInfo, 0, len(pd.Participants)) 35 for _, participant := range pd.Participants { 36 nodes = append(nodes, participant.NodeInfo) 37 } 38 return bootstrap.ToIdentityList(nodes) 39 } 40 41 // GenerateRootQC generates QC for root block, caller needs to provide votes for root QC and 42 // participantData to build the QC. 43 // NOTE: at the moment, we require private keys for one node because we we re-using the full business logic, 44 // which assumes that only consensus participants construct QCs, which also have produce votes. 45 // 46 // TODO: modularize QC construction code (and code to verify QC) to be instantiated without needing private keys. 47 // It returns (qc, nil, nil) if a QC can be constructed with enough votes, and there is no invalid votes 48 // It returns (qc, invalidVotes, nil) if there are some invalid votes, but a QC can still be constructed 49 // It returns (nil, invalidVotes, err) if no qc can be constructed with not enough votes or running any any exception 50 func GenerateRootQC(block *flow.Block, votes []*model.Vote, participantData *ParticipantData, identities flow.IdentityList) ( 51 *flow.QuorumCertificate, // the constructed QC 52 []error, // return invalid votes error 53 error, // exception or could not construct qc 54 ) { 55 // create consensus committee's state 56 committee, err := committees.NewStaticCommittee(identities, flow.Identifier{}, participantData.Lookup, participantData.GroupKey) 57 if err != nil { 58 return nil, nil, err 59 } 60 61 // STEP 1: create VoteProcessor 62 var createdQC *flow.QuorumCertificate 63 hotBlock := model.GenesisBlockFromFlow(block.Header) 64 processor, err := votecollector.NewBootstrapCombinedVoteProcessor(zerolog.Logger{}, committee, hotBlock, func(qc *flow.QuorumCertificate) { 65 createdQC = qc 66 }) 67 if err != nil { 68 return nil, nil, fmt.Errorf("could not CombinedVoteProcessor processor: %w", err) 69 } 70 71 invalidVotes := make([]error, 0, len(votes)) 72 // STEP 2: feed the votes into the vote processor to create QC 73 for _, vote := range votes { 74 err := processor.Process(vote) 75 76 // in case there are invalid votes, we continue process more votes, 77 // so that finalizing block won't be interrupted by any invalid vote. 78 // if no enough votes are collected, finalize will fail and exit anyway, because 79 // no QC will be built. 80 if err != nil { 81 if model.IsInvalidVoteError(err) { 82 invalidVotes = append(invalidVotes, err) 83 continue 84 } 85 return nil, invalidVotes, fmt.Errorf("fail to process vote %v for block %v from signer %v: %w", 86 vote.ID(), 87 vote.BlockID, 88 vote.SignerID, 89 err) 90 } 91 } 92 93 if createdQC == nil { 94 return nil, invalidVotes, fmt.Errorf("QC is not created, total number of votes %v, expect to have 2/3 votes of %v participants", 95 len(votes), len(identities)) 96 } 97 98 // STEP 3: validate constructed QC 99 val, err := createValidator(committee) 100 if err != nil { 101 return nil, invalidVotes, err 102 } 103 err = val.ValidateQC(createdQC) 104 105 return createdQC, invalidVotes, err 106 } 107 108 // GenerateRootBlockVotes generates votes for root block based on participantData 109 func GenerateRootBlockVotes(block *flow.Block, participantData *ParticipantData) ([]*model.Vote, error) { 110 hotBlock := model.GenesisBlockFromFlow(block.Header) 111 n := len(participantData.Participants) 112 fmt.Println("Number of staked consensus nodes: ", n) 113 114 votes := make([]*model.Vote, 0, n) 115 for _, p := range participantData.Participants { 116 fmt.Println("generating votes from consensus participants: ", p.NodeID, p.Address, p.StakingPubKey().String()) 117 118 // create the participant's local identity 119 keys, err := p.PrivateKeys() 120 if err != nil { 121 return nil, fmt.Errorf("could not get private keys for participant: %w", err) 122 } 123 me, err := local.New(p.Identity(), keys.StakingKey) 124 if err != nil { 125 return nil, err 126 } 127 128 // create signer and use it to generate vote 129 beaconStore := hotstuffSig.NewStaticRandomBeaconSignerStore(p.RandomBeaconPrivKey) 130 vote, err := verification.NewCombinedSigner(me, beaconStore).CreateVote(hotBlock) 131 if err != nil { 132 return nil, err 133 } 134 votes = append(votes, vote) 135 } 136 return votes, nil 137 } 138 139 // createValidator creates validator that can validate votes and QC 140 func createValidator(committee hotstuff.DynamicCommittee) (hotstuff.Validator, error) { 141 packer := hotstuffSig.NewConsensusSigDataPacker(committee) 142 verifier := verification.NewCombinedVerifier(committee, packer) 143 144 hotstuffValidator := validator.New(committee, verifier) 145 return hotstuffValidator, nil 146 } 147 148 // GenerateQCParticipantData generates QC participant data used to create the 149 // random beacon and staking signatures on the QC. 150 // 151 // allNodes must be in the same order that was used when running the DKG. 152 func GenerateQCParticipantData(allNodes, internalNodes []bootstrap.NodeInfo, dkgData dkg.DKGData) (*ParticipantData, error) { 153 154 // stakingNodes can include external validators, so it can be longer than internalNodes 155 if len(allNodes) < len(internalNodes) { 156 return nil, fmt.Errorf("need at least as many staking public keys as private keys (pub=%d, priv=%d)", len(allNodes), len(internalNodes)) 157 } 158 159 // length of DKG participants needs to match stakingNodes, since we run DKG for external and internal validators 160 if len(allNodes) != len(dkgData.PrivKeyShares) { 161 return nil, fmt.Errorf("need exactly the same number of staking public keys as DKG private participants") 162 } 163 164 qcData := &ParticipantData{} 165 166 participantLookup := make(map[flow.Identifier]flow.DKGParticipant) 167 168 // the index here is important - we assume allNodes is in the same order as the DKG 169 for i := 0; i < len(allNodes); i++ { 170 // assign a node to a DGKdata entry, using the canonical ordering 171 node := allNodes[i] 172 participantLookup[node.NodeID] = flow.DKGParticipant{ 173 KeyShare: dkgData.PubKeyShares[i], 174 Index: uint(i), 175 } 176 } 177 178 // the QC will be signed by everyone in internalNodes 179 for _, node := range internalNodes { 180 181 if node.NodeID == flow.ZeroID { 182 return nil, fmt.Errorf("node id cannot be zero") 183 } 184 185 if node.Weight == 0 { 186 return nil, fmt.Errorf("node (id=%s) cannot have 0 weight", node.NodeID) 187 } 188 189 dkgParticipant, ok := participantLookup[node.NodeID] 190 if !ok { 191 return nil, fmt.Errorf("nonexistannt node id (%x) in participant lookup", node.NodeID) 192 } 193 dkgIndex := dkgParticipant.Index 194 195 qcData.Participants = append(qcData.Participants, Participant{ 196 NodeInfo: node, 197 RandomBeaconPrivKey: dkgData.PrivKeyShares[dkgIndex], 198 }) 199 } 200 201 qcData.Lookup = participantLookup 202 qcData.GroupKey = dkgData.PubGroupKey 203 204 return qcData, nil 205 }