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