-
Notifications
You must be signed in to change notification settings - Fork 166
/
finalize.go
602 lines (509 loc) · 21 KB
/
finalize.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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
package cmd
import (
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"path/filepath"
"strings"
"github.com/onflow/cadence"
"github.com/spf13/cobra"
"github.com/onflow/flow-go/cmd"
"github.com/onflow/flow-go/cmd/bootstrap/run"
"github.com/onflow/flow-go/cmd/bootstrap/utils"
hotstuff "github.com/onflow/flow-go/consensus/hotstuff/model"
"github.com/onflow/flow-go/fvm"
model "github.com/onflow/flow-go/model/bootstrap"
"github.com/onflow/flow-go/model/dkg"
"github.com/onflow/flow-go/model/encodable"
"github.com/onflow/flow-go/model/flow"
"github.com/onflow/flow-go/model/flow/order"
"github.com/onflow/flow-go/module/epochs"
"github.com/onflow/flow-go/state/protocol/badger"
"github.com/onflow/flow-go/state/protocol/inmem"
"github.com/onflow/flow-go/utils/io"
)
var (
flagConfig string
flagInternalNodePrivInfoDir string
flagCollectionClusters uint
flagPartnerNodeInfoDir string
flagPartnerStakes string
flagDKGDataPath string
flagRootBlock string
flagRootBlockVotesDir string
flagRootCommit string
flagProtocolVersion uint
flagServiceAccountPublicKeyJSON string
flagGenesisTokenSupply string
flagEpochCounter uint64
flagNumViewsInEpoch uint64
flagNumViewsInStakingAuction uint64
flagNumViewsInDKGPhase uint64
// this flag is used to seed the DKG, clustering and cluster QC generation
flagBootstrapRandomSeed []byte
)
// PartnerStakes ...
type PartnerStakes map[flow.Identifier]uint64
// finalizeCmd represents the finalize command
var finalizeCmd = &cobra.Command{
Use: "finalize",
Short: "Finalize the bootstrapping process",
Long: `Finalize the bootstrapping process, which includes running the DKG for the generation of the random beacon
keys and generating the root block, QC, execution result and block seal.`,
Run: finalize,
}
func init() {
rootCmd.AddCommand(finalizeCmd)
addFinalizeCmdFlags()
}
func addFinalizeCmdFlags() {
// required parameters for network configuration and generation of root node identities
finalizeCmd.Flags().StringVar(&flagConfig, "config", "",
"path to a JSON file containing multiple node configurations (fields Role, Address, Stake)")
finalizeCmd.Flags().StringVar(&flagInternalNodePrivInfoDir, "internal-priv-dir", "", "path to directory "+
"containing the output from the `keygen` command for internal nodes")
finalizeCmd.Flags().StringVar(&flagPartnerNodeInfoDir, "partner-dir", "", "path to directory "+
"containing one JSON file starting with node-info.pub.<NODE_ID>.json for every partner node (fields "+
" in the JSON file: Role, Address, NodeID, NetworkPubKey, StakingPubKey)")
finalizeCmd.Flags().StringVar(&flagPartnerStakes, "partner-stakes", "", "path to a JSON file containing "+
"a map from partner node's NodeID to their stake")
finalizeCmd.Flags().StringVar(&flagDKGDataPath, "dkg-data", "", "path to a JSON file containing data as output from DKG process")
cmd.MarkFlagRequired(finalizeCmd, "config")
cmd.MarkFlagRequired(finalizeCmd, "internal-priv-dir")
cmd.MarkFlagRequired(finalizeCmd, "partner-dir")
cmd.MarkFlagRequired(finalizeCmd, "partner-stakes")
cmd.MarkFlagRequired(finalizeCmd, "dkg-data")
// required parameters for generation of root block, root execution result and root block seal
finalizeCmd.Flags().StringVar(&flagRootBlock, "root-block", "",
"path to a JSON file containing root block")
finalizeCmd.Flags().StringVar(&flagRootBlockVotesDir, "root-block-votes-dir", "", "path to directory with votes for root block")
finalizeCmd.Flags().StringVar(&flagRootCommit, "root-commit", "0000000000000000000000000000000000000000000000000000000000000000", "state commitment of root execution state")
finalizeCmd.Flags().Uint64Var(&flagEpochCounter, "epoch-counter", 0, "epoch counter for the epoch beginning with the root block")
finalizeCmd.Flags().Uint64Var(&flagNumViewsInEpoch, "epoch-length", 4000, "length of each epoch measured in views")
finalizeCmd.Flags().Uint64Var(&flagNumViewsInStakingAuction, "epoch-staking-phase-length", 100, "length of the epoch staking phase measured in views")
finalizeCmd.Flags().Uint64Var(&flagNumViewsInDKGPhase, "epoch-dkg-phase-length", 1000, "length of each DKG phase measured in views")
finalizeCmd.Flags().BytesHexVar(&flagBootstrapRandomSeed, "random-seed", GenerateRandomSeed(), "The seed used to for DKG, Clustering and Cluster QC generation")
finalizeCmd.Flags().UintVar(&flagProtocolVersion, "protocol-version", flow.DefaultProtocolVersion, "major software version used for the duration of this spork")
cmd.MarkFlagRequired(finalizeCmd, "root-block")
cmd.MarkFlagRequired(finalizeCmd, "root-block-votes-dir")
cmd.MarkFlagRequired(finalizeCmd, "root-commit")
cmd.MarkFlagRequired(finalizeCmd, "epoch-counter")
cmd.MarkFlagRequired(finalizeCmd, "epoch-length")
cmd.MarkFlagRequired(finalizeCmd, "epoch-staking-phase-length")
cmd.MarkFlagRequired(finalizeCmd, "epoch-dkg-phase-length")
cmd.MarkFlagRequired(finalizeCmd, "protocol-version")
// optional parameters to influence various aspects of identity generation
finalizeCmd.Flags().UintVar(&flagCollectionClusters, "collection-clusters", 2, "number of collection clusters")
// these two flags are only used when setup a network from genesis
finalizeCmd.Flags().StringVar(&flagServiceAccountPublicKeyJSON, "service-account-public-key-json",
"{\"PublicKey\":\"ABCDEFGHIJK\",\"SignAlgo\":2,\"HashAlgo\":1,\"SeqNumber\":0,\"Weight\":1000}",
"encoded json of public key for the service account")
finalizeCmd.Flags().StringVar(&flagGenesisTokenSupply, "genesis-token-supply", "10000000.00000000",
"genesis flow token supply")
}
func finalize(cmd *cobra.Command, args []string) {
actualSeedLength := len(flagBootstrapRandomSeed)
if actualSeedLength != randomSeedBytes {
log.Error().Int("expected", randomSeedBytes).Int("actual", actualSeedLength).Msg("random seed provided length is not valid")
return
}
log.Info().Str("seed", hex.EncodeToString(flagBootstrapRandomSeed)).Msg("deterministic bootstrapping random seed")
log.Info().Msg("")
log.Info().Msg("collecting partner network and staking keys")
partnerNodes := readPartnerNodeInfos()
log.Info().Msg("")
log.Info().Msg("generating internal private networking and staking keys")
internalNodes := readInternalNodeInfos()
log.Info().Msg("")
log.Info().Msg("checking constraints on consensus/cluster nodes")
checkConstraints(partnerNodes, internalNodes)
log.Info().Msg("")
log.Info().Msg("assembling network and staking keys")
stakingNodes := mergeNodeInfos(internalNodes, partnerNodes)
log.Info().Msg("")
// create flow.IdentityList representation of participant set
participants := model.ToIdentityList(stakingNodes).Sort(order.Canonical)
log.Info().Msg("reading root block data")
block := readRootBlock()
log.Info().Msg("")
log.Info().Msg("reading root block votes")
votes := readRootBlockVotes()
log.Info().Msg("")
log.Info().Msg("reading dkg data")
dkgData := readDKGData()
log.Info().Msg("")
log.Info().Msg("constructing root QC")
rootQC := constructRootQC(
block,
votes,
model.FilterByRole(stakingNodes, flow.RoleConsensus),
model.FilterByRole(internalNodes, flow.RoleConsensus),
dkgData,
)
log.Info().Msg("")
log.Info().Msg("computing collection node clusters")
clusterAssignmentSeed := binary.BigEndian.Uint64(flagBootstrapRandomSeed)
assignments, clusters := constructClusterAssignment(partnerNodes, internalNodes, int64(clusterAssignmentSeed))
log.Info().Msg("")
log.Info().Msg("constructing root blocks for collection node clusters")
clusterBlocks := run.GenerateRootClusterBlocks(flagEpochCounter, clusters)
log.Info().Msg("")
log.Info().Msg("constructing root QCs for collection node clusters")
clusterQCs := constructRootQCsForClusters(clusters, internalNodes, clusterBlocks)
log.Info().Msg("")
// if no root commit is specified, bootstrap an empty execution state
if flagRootCommit == "0000000000000000000000000000000000000000000000000000000000000000" {
generateEmptyExecutionState(
block.Header.ChainID,
getRandomSource(flagBootstrapRandomSeed),
assignments,
clusterQCs,
dkgData,
participants,
)
}
log.Info().Msg("constructing root execution result and block seal")
result, seal := constructRootResultAndSeal(flagRootCommit, block, participants, assignments, clusterQCs, dkgData)
log.Info().Msg("")
// construct serializable root protocol snapshot
log.Info().Msg("constructing root protocol snapshot")
snapshot, err := inmem.SnapshotFromBootstrapStateWithProtocolVersion(block, result, seal, rootQC, flagProtocolVersion)
if err != nil {
log.Fatal().Err(err).Msg("unable to generate root protocol snapshot")
}
// validate the generated root snapshot is valid
verifyResultID := true
err = badger.IsValidRootSnapshot(snapshot, verifyResultID)
if err != nil {
log.Fatal().Err(err).Msg("the generated root snapshot is invalid")
}
// write snapshot to disk
writeJSON(model.PathRootProtocolStateSnapshot, snapshot.Encodable())
log.Info().Msg("")
// read snapshot and verify consistency
rootSnapshot, err := loadRootProtocolSnapshot(model.PathRootProtocolStateSnapshot)
if err != nil {
log.Fatal().Err(err).Msg("unable to load seralized root protocol")
}
savedResult, savedSeal, err := rootSnapshot.SealedResult()
if err != nil {
log.Fatal().Err(err).Msg("could not load sealed result")
}
if savedSeal.ID() != seal.ID() {
log.Fatal().Msgf("inconsistent seralization of the root seal: %v != %v", savedSeal.ID(), seal.ID())
}
if savedResult.ID() != result.ID() {
log.Fatal().Msgf("inconsistent seralization of the root result: %v != %v", savedResult.ID(), result.ID())
}
if savedSeal.ResultID != savedResult.ID() {
log.Fatal().Msgf("mismatch saved seal's resultID %v and result %v", savedSeal.ResultID, savedResult.ID())
}
log.Info().Msg("saved result and seal are matching")
err = badger.IsValidRootSnapshot(rootSnapshot, verifyResultID)
if err != nil {
log.Fatal().Err(err).Msg("saved snapshot is invalid")
}
log.Info().Msgf("saved root snapshot is valid")
// copy files only if the directories differ
log.Info().Str("private_dir", flagInternalNodePrivInfoDir).Str("output_dir", flagOutdir).Msg("attempting to copy private key files")
if flagInternalNodePrivInfoDir != flagOutdir {
log.Info().Msg("copying internal private keys to output folder")
err := copyDir(flagInternalNodePrivInfoDir, filepath.Join(flagOutdir, model.DirPrivateRoot))
if err != nil {
log.Error().Err(err).Msg("could not copy private key files")
}
} else {
log.Info().Msg("skipping copy of private keys to output dir")
}
log.Info().Msg("")
// print count of all nodes
roleCounts := nodeCountByRole(stakingNodes)
log.Info().Msg(fmt.Sprintf("created keys for %d %s nodes", roleCounts[flow.RoleConsensus], flow.RoleConsensus.String()))
log.Info().Msg(fmt.Sprintf("created keys for %d %s nodes", roleCounts[flow.RoleCollection], flow.RoleCollection.String()))
log.Info().Msg(fmt.Sprintf("created keys for %d %s nodes", roleCounts[flow.RoleVerification], flow.RoleVerification.String()))
log.Info().Msg(fmt.Sprintf("created keys for %d %s nodes", roleCounts[flow.RoleExecution], flow.RoleExecution.String()))
log.Info().Msg(fmt.Sprintf("created keys for %d %s nodes", roleCounts[flow.RoleAccess], flow.RoleAccess.String()))
log.Info().Msg("🌊 🏄 🤙 Done – ready to flow!")
}
// readRootBlockVotes reads votes for root block
func readRootBlockVotes() []*hotstuff.Vote {
var votes []*hotstuff.Vote
files, err := filesInDir(flagRootBlockVotesDir)
if err != nil {
log.Fatal().Err(err).Msg("could not read root block votes")
}
for _, f := range files {
// skip files that do not include node-infos
if !strings.Contains(f, model.FilenameRootBlockVotePrefix) {
continue
}
// read file and append to partners
var vote hotstuff.Vote
readJSON(f, &vote)
votes = append(votes, &vote)
log.Info().Msgf("read vote %v for block %v from signerID %v", vote.ID(), vote.BlockID, vote.SignerID)
}
return votes
}
// readPartnerNodeInfos returns a list of partner nodes after gathering stake
// and public key information from configuration files
func readPartnerNodeInfos() []model.NodeInfo {
partners := readPartnerNodes()
log.Info().Msgf("read %v partner node configuration files", len(partners))
var stakes PartnerStakes
readJSON(flagPartnerStakes, &stakes)
log.Info().Msgf("read %v stakes for partner nodes", len(stakes))
var nodes []model.NodeInfo
for _, partner := range partners {
// validate every single partner node
nodeID := validateNodeID(partner.NodeID)
networkPubKey := validateNetworkPubKey(partner.NetworkPubKey)
stakingPubKey := validateStakingPubKey(partner.StakingPubKey)
stake, valid := validateStake(stakes[partner.NodeID])
if !valid {
log.Error().Msgf("stakes: %v", stakes)
log.Fatal().Msgf("partner node id %v has no stake", nodeID)
}
node := model.NewPublicNodeInfo(
nodeID,
partner.Role,
partner.Address,
stake,
networkPubKey.PublicKey,
stakingPubKey.PublicKey,
)
nodes = append(nodes, node)
}
return nodes
}
// readPartnerNodes reads the partner node information
func readPartnerNodes() []model.NodeInfoPub {
var partners []model.NodeInfoPub
files, err := filesInDir(flagPartnerNodeInfoDir)
if err != nil {
log.Fatal().Err(err).Msg("could not read partner node infos")
}
for _, f := range files {
// skip files that do not include node-infos
if !strings.Contains(f, model.PathPartnerNodeInfoPrefix) {
continue
}
// read file and append to partners
var p model.NodeInfoPub
readJSON(f, &p)
partners = append(partners, p)
}
return partners
}
// readInternalNodeInfos returns a list of internal nodes after collecting stakes
// from configuration files
func readInternalNodeInfos() []model.NodeInfo {
privInternals := readInternalNodes()
log.Info().Msgf("read %v internal private node-info files", len(privInternals))
stakes := internalStakesByAddress()
log.Info().Msgf("read %v stakes for internal nodes", len(stakes))
var nodes []model.NodeInfo
for _, internal := range privInternals {
// check if address is valid format
validateAddressFormat(internal.Address)
// validate every single internal node
nodeID := validateNodeID(internal.NodeID)
stake, valid := validateStake(stakes[internal.Address])
if !valid {
log.Error().Msgf("stakes: %v", stakes)
log.Fatal().Msgf("internal node %v has no stake. Did you forget to update the node address?", internal)
}
node := model.NewPrivateNodeInfo(
nodeID,
internal.Role,
internal.Address,
stake,
internal.NetworkPrivKey,
internal.StakingPrivKey,
)
nodes = append(nodes, node)
}
return nodes
}
// readInternalNodes reads our internal node private infos generated by
// `keygen` command and returns it
func readInternalNodes() []model.NodeInfoPriv {
var internalPrivInfos []model.NodeInfoPriv
// get files in internal priv node infos directory
files, err := filesInDir(flagInternalNodePrivInfoDir)
if err != nil {
log.Fatal().Err(err).Msg("could not read partner node infos")
}
// for each of the files
for _, f := range files {
// skip files that do not include node-infos
if !strings.Contains(f, model.PathPrivNodeInfoPrefix) {
continue
}
// read file and append to partners
var p model.NodeInfoPriv
readJSON(f, &p)
internalPrivInfos = append(internalPrivInfos, p)
}
return internalPrivInfos
}
// internalStakesByAddress returns a mapping of node address by stake for internal nodes
func internalStakesByAddress() map[string]uint64 {
// read json
var configs []model.NodeConfig
readJSON(flagConfig, &configs)
log.Info().Interface("config", configs).Msgf("read internal node configurations")
stakes := make(map[string]uint64)
for _, config := range configs {
if _, ok := stakes[config.Address]; !ok {
stakes[config.Address] = config.Stake
} else {
log.Error().Msgf("duplicate internal node address %s", config.Address)
}
}
return stakes
}
// mergeNodeInfos merges the internal and partner nodes and checks if there are no
// duplicate addresses or node Ids.
//
// IMPORTANT: node infos are returned in the canonical ordering, meaning this
// is safe to use as the input to the DKG and protocol state.
func mergeNodeInfos(internalNodes, partnerNodes []model.NodeInfo) []model.NodeInfo {
nodes := append(internalNodes, partnerNodes...)
// test for duplicate Addresses
addressLookup := make(map[string]struct{})
for _, node := range nodes {
if _, ok := addressLookup[node.Address]; ok {
log.Fatal().Str("address", node.Address).Msg("duplicate node address")
}
}
// test for duplicate node IDs
idLookup := make(map[flow.Identifier]struct{})
for _, node := range nodes {
if _, ok := idLookup[node.NodeID]; ok {
log.Fatal().Str("NodeID", node.NodeID.String()).Msg("duplicate node ID")
}
}
// sort nodes using the canonical ordering
nodes = model.Sort(nodes, order.Canonical)
return nodes
}
// readRootBlock reads root block data from disc, this file needs to be prepared with
// rootblock command
func readRootBlock() *flow.Block {
rootBlock, err := utils.ReadRootBlock(flagRootBlock)
if err != nil {
log.Fatal().Err(err).Msg("could not read root block data")
}
return rootBlock
}
func readDKGData() dkg.DKGData {
encodableDKG, err := utils.ReadDKGData(flagDKGDataPath)
if err != nil {
log.Fatal().Err(err).Msg("could not read DKG data")
}
dkgData := dkg.DKGData{
PrivKeyShares: nil,
PubGroupKey: encodableDKG.GroupKey.PublicKey,
PubKeyShares: nil,
}
for _, pubKey := range encodableDKG.PubKeyShares {
dkgData.PubKeyShares = append(dkgData.PubKeyShares, pubKey.PublicKey)
}
for _, privKey := range encodableDKG.PrivKeyShares {
dkgData.PrivKeyShares = append(dkgData.PrivKeyShares, privKey.PrivateKey)
}
return dkgData
}
// Validation utility methods ------------------------------------------------
func validateNodeID(nodeID flow.Identifier) flow.Identifier {
if nodeID == flow.ZeroID {
log.Fatal().Msg("NodeID must not be zero")
}
return nodeID
}
func validateNetworkPubKey(key encodable.NetworkPubKey) encodable.NetworkPubKey {
if key.PublicKey == nil {
log.Fatal().Msg("NetworkPubKey must not be nil")
}
return key
}
func validateStakingPubKey(key encodable.StakingPubKey) encodable.StakingPubKey {
if key.PublicKey == nil {
log.Fatal().Msg("StakingPubKey must not be nil")
}
return key
}
func validateStake(stake uint64) (uint64, bool) {
return stake, stake > 0
}
// loadRootProtocolSnapshot loads the root protocol snapshot from disk
func loadRootProtocolSnapshot(path string) (*inmem.Snapshot, error) {
data, err := io.ReadFile(filepath.Join(flagOutdir, path))
if err != nil {
return nil, err
}
var snapshot inmem.EncodableSnapshot
err = json.Unmarshal(data, &snapshot)
if err != nil {
return nil, err
}
return inmem.SnapshotFromEncodable(snapshot), nil
}
// generateEmptyExecutionState generates a new empty execution state with the
// given configuration. Sets the flagRootCommit variable for future reads.
func generateEmptyExecutionState(
chainID flow.ChainID,
randomSource []byte,
assignments flow.AssignmentList,
clusterQCs []*flow.QuorumCertificate,
dkgData dkg.DKGData,
identities flow.IdentityList,
) (commit flow.StateCommitment) {
log.Info().Msg("generating empty execution state")
var serviceAccountPublicKey flow.AccountPublicKey
err := serviceAccountPublicKey.UnmarshalJSON([]byte(flagServiceAccountPublicKeyJSON))
if err != nil {
log.Fatal().Err(err).Msg("unable to parse the service account public key json")
}
cdcInitialTokenSupply, err := cadence.NewUFix64(flagGenesisTokenSupply)
if err != nil {
log.Fatal().Err(err).Msg("invalid genesis token supply")
}
cdcRandomSource, err := cadence.NewString(hex.EncodeToString(randomSource))
if err != nil {
log.Fatal().Err(err).Msg("invalid random source")
}
epochConfig := epochs.EpochConfig{
EpochTokenPayout: cadence.UFix64(0),
RewardCut: cadence.UFix64(0),
CurrentEpochCounter: cadence.UInt64(flagEpochCounter),
NumViewsInEpoch: cadence.UInt64(flagNumViewsInEpoch),
NumViewsInStakingAuction: cadence.UInt64(flagNumViewsInStakingAuction),
NumViewsInDKGPhase: cadence.UInt64(flagNumViewsInDKGPhase),
NumCollectorClusters: cadence.UInt16(flagCollectionClusters),
FLOWsupplyIncreasePercentage: cadence.UFix64(0),
RandomSource: cdcRandomSource,
CollectorClusters: assignments,
ClusterQCs: clusterQCs,
DKGPubKeys: dkgData.PubKeyShares,
}
commit, err = run.GenerateExecutionState(
filepath.Join(flagOutdir, model.DirnameExecutionState),
serviceAccountPublicKey,
chainID.Chain(),
fvm.WithInitialTokenSupply(cdcInitialTokenSupply),
fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation),
fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee),
fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW),
fvm.WithEpochConfig(epochConfig),
fvm.WithIdentities(identities),
)
if err != nil {
log.Fatal().Err(err).Msg("unable to generate execution state")
}
flagRootCommit = hex.EncodeToString(commit[:])
log.Info().Msg("")
return
}