-
Notifications
You must be signed in to change notification settings - Fork 176
/
qc.go
190 lines (159 loc) · 6.52 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
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/mocks"
"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.
func GenerateRootQC(block *flow.Block, votes []*model.Vote, participantData *ParticipantData, identities flow.IdentityList) (*flow.QuorumCertificate, error) {
// create consensus committee's state
committee, err := committees.NewStaticCommittee(identities, flow.Identifier{}, participantData.Lookup, participantData.GroupKey)
if err != nil {
return 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, fmt.Errorf("could not CombinedVoteProcessor processor: %w", err)
}
// STEP 2: feed the votes into the vote processor to create QC
for _, vote := range votes {
err := processor.Process(vote)
if err != nil {
return nil, fmt.Errorf("fail to process vote %v for block %v from signer %v: %w",
vote.ID(),
block.ID(),
vote.SignerID,
err)
}
}
if createdQC == nil {
return nil, 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, err
}
err = val.ValidateQC(createdQC, hotBlock)
return createdQC, 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.Committee) (hotstuff.Validator, error) {
packer := hotstuffSig.NewConsensusSigDataPacker(committee)
verifier := verification.NewCombinedVerifier(committee, packer)
forks := &mocks.ForksReader{}
hotstuffValidator := validator.New(committee, forks, 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
}