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  }