@@ -5,14 +5,17 @@ import (
55 "encoding/hex"
66 "fmt"
77 "strconv"
8+ "strings"
89 "time"
910
1011 "github.com/onflow/cadence"
1112 "github.com/spf13/cobra"
1213
1314 "github.com/onflow/flow-go/cmd"
1415 "github.com/onflow/flow-go/cmd/bootstrap/run"
16+ "github.com/onflow/flow-go/cmd/bootstrap/utils"
1517 "github.com/onflow/flow-go/cmd/util/cmd/common"
18+ hotstuff "github.com/onflow/flow-go/consensus/hotstuff/model"
1619 model "github.com/onflow/flow-go/model/bootstrap"
1720 "github.com/onflow/flow-go/model/dkg"
1821 "github.com/onflow/flow-go/model/encodable"
@@ -43,11 +46,15 @@ var (
4346 flagNumViewsInEpoch uint64
4447 flagNumViewsInStakingAuction uint64
4548 flagNumViewsInDKGPhase uint64
49+ flagEpochRandomSeed []byte
4650 // Epoch target end time config
4751 flagUseDefaultEpochTargetEndTime bool
4852 flagEpochTimingRefCounter uint64
4953 flagEpochTimingRefTimestamp uint64
5054 flagEpochTimingDuration uint64
55+
56+ flagIntermediaryClusteringDataPath string
57+ flagRootClusterBlockVotesDir string
5158)
5259
5360// rootBlockCmd represents the rootBlock command
@@ -106,11 +113,13 @@ func addRootBlockCmdFlags() {
106113 rootBlockCmd .Flags ().Uint64Var (& flagEpochExtensionViewCount , "kvstore-epoch-extension-view-count" , 0 , "length of epoch extension in views, default is 100_000 which is approximately 1 day" )
107114 rootBlockCmd .Flags ().StringVar (& flagKVStoreVersion , "kvstore-version" , "default" ,
108115 "protocol state KVStore version to initialize ('default' or an integer equal to a supported protocol version: '0', '1', '2', ...)" )
116+ rootBlockCmd .Flags ().BytesHexVar (& flagEpochRandomSeed , "random-seed" , nil , "random seed" )
109117
110118 cmd .MarkFlagRequired (rootBlockCmd , "root-chain" )
111119 cmd .MarkFlagRequired (rootBlockCmd , "root-parent" )
112120 cmd .MarkFlagRequired (rootBlockCmd , "root-height" )
113121 cmd .MarkFlagRequired (rootBlockCmd , "root-view" )
122+ cmd .MarkFlagRequired (rootBlockCmd , "random-seed" )
114123
115124 // Epoch timing config - these values must be set identically to `EpochTimingConfig` in the FlowEpoch smart contract.
116125 // See https://github.com/onflow/flow-core-contracts/blob/240579784e9bb8d97d91d0e3213614e25562c078/contracts/epochs/FlowEpoch.cdc#L259-L266
@@ -224,16 +233,6 @@ func rootBlock(cmd *cobra.Command, args []string) {
224233 if err != nil {
225234 log .Fatal ().Err (err ).Msgf ("failed to merge node infos" )
226235 }
227- publicInfo , err := model .ToPublicNodeInfoList (stakingNodes )
228- if err != nil {
229- log .Fatal ().Msg ("failed to read public node info" )
230- }
231- err = common .WriteJSON (model .PathNodeInfosPub , flagOutdir , publicInfo )
232- if err != nil {
233- log .Fatal ().Err (err ).Msg ("failed to write json" )
234- }
235- log .Info ().Msgf ("wrote file %s/%s" , flagOutdir , model .PathNodeInfosPub )
236- log .Info ().Msg ("" )
237236
238237 log .Info ().Msg ("running DKG for consensus nodes" )
239238 randomBeaconData , dkgIndexMap := runBeaconKG (model .FilterByRole (stakingNodes , flow .RoleConsensus ))
@@ -242,30 +241,22 @@ func rootBlock(cmd *cobra.Command, args []string) {
242241 // create flow.IdentityList representation of the participant set
243242 participants := model .ToIdentityList (stakingNodes ).Sort (flow .Canonical [flow .Identity ])
244243
245- // use system randomness to create cluster assignment
246- // TODO(7848): use randomness provided from the command line
247- var randomSeed [32 ]byte
248- _ , err = rand .Read (randomSeed [:])
249- if err != nil {
250- log .Fatal ().Err (err ).Msg ("unable to generate random seed" )
251- }
252- clusteringPrg , err := prg .New (randomSeed [:], prg .BootstrapClusterAssignment , nil )
253- if err != nil {
254- log .Fatal ().Err (err ).Msg ("unable to initialize pseudorandom generator" )
255- }
256- log .Info ().Msg ("computing collection node clusters" )
257- assignments , clusters , err := common .ConstructClusterAssignment (log , model .ToIdentityList (partnerNodes ), model .ToIdentityList (internalNodes ), int (flagCollectionClusters ), clusteringPrg )
258- if err != nil {
259- log .Fatal ().Err (err ).Msg ("unable to generate cluster assignment" )
244+ // read the previously created cluster assignment (see cmd/bootstrap/cmd/clustering.go)
245+ clusteringData := readIntermediaryClusteringData ()
246+ if flagEpochCounter != clusteringData .EpochCounter {
247+ log .Fatal ().Msgf ("Epoch counter does not match the one used to generate collector clusters" )
260248 }
249+ clusters := clusteringData .Clusters
250+ log .Info ().Msg ("reading votes for collection node cluster root blocks" )
251+ votes := readClusterBlockVotes (clusteringData .Assignments )
261252 log .Info ().Msg ("" )
262253
263254 log .Info ().Msg ("constructing root blocks for collection node clusters" )
264- clusterBlocks := run .GenerateRootClusterBlocks (flagEpochCounter , clusters )
255+ clusterBlocks := run .GenerateRootClusterBlocks (clusteringData . EpochCounter , clusters )
265256 log .Info ().Msg ("" )
266257
267258 log .Info ().Msg ("constructing root QCs for collection node clusters" )
268- clusterQCs := run .ConstructRootQCsForClusters (log , clusters , internalNodes , clusterBlocks )
259+ clusterQCs := run .ConstructClusterRootQCsFromVotes (log , clusters , internalNodes , clusterBlocks , votes )
269260 log .Info ().Msg ("" )
270261
271262 log .Info ().Msg ("constructing root header" )
@@ -275,13 +266,13 @@ func rootBlock(cmd *cobra.Command, args []string) {
275266 }
276267 log .Info ().Msg ("" )
277268
278- // use the same randomness for the RandomSource of the new epoch
279- randomSourcePrg , err := prg .New (randomSeed [:] , prg .BootstrapEpochRandomSource , nil )
269+ // use provided randomness for the RandomSource of the new epoch
270+ randomSourcePrg , err := prg .New (flagEpochRandomSeed , prg .BootstrapEpochRandomSource , nil )
280271 if err != nil {
281272 log .Fatal ().Err (err ).Msg ("failed to initialize pseudorandom generator" )
282273 }
283274 log .Info ().Msg ("constructing intermediary bootstrapping data" )
284- epochSetup , epochCommit , err := constructRootEpochEvents (headerBody .View , participants , assignments , clusterQCs , randomBeaconData , dkgIndexMap , randomSourcePrg )
275+ epochSetup , epochCommit , err := constructRootEpochEvents (headerBody .View , participants , clusteringData . Assignments , clusterQCs , randomBeaconData , dkgIndexMap , randomSourcePrg )
285276 if err != nil {
286277 log .Fatal ().Err (err ).Msg ("failed to construct root epoch events" )
287278 }
@@ -388,6 +379,53 @@ func validateEpochConfig() error {
388379 return nil
389380}
390381
382+ // readIntermediaryBootstrappingData reads intermediary clustering data file from disk.
383+ // This file needs to be prepared with the clustering bootstrap command
384+ func readIntermediaryClusteringData () * IntermediaryClusteringData {
385+ intermediaryData , err := utils.ReadData [IntermediaryClusteringData ](flagIntermediaryClusteringDataPath )
386+ if err != nil {
387+ log .Fatal ().Err (err ).Msg ("could not read clustering data, was the clustering command run?" )
388+ }
389+ return intermediaryData
390+ }
391+
392+ // readClusterBlockVotes reads votes for root cluster blocks.
393+ // It sorts the votes into the appropriate clusters according to the given assignment list.
394+ func readClusterBlockVotes (al flow.AssignmentList ) [][]* hotstuff.Vote {
395+ votes := make ([][]* hotstuff.Vote , len (al ))
396+ files , err := common .FilesInDir (flagRootClusterBlockVotesDir )
397+ if err != nil {
398+ log .Fatal ().Err (err ).Msg ("could not read cluster block votes" )
399+ }
400+ for _ , f := range files {
401+ // skip files that do not include node-infos
402+ if ! strings .Contains (f , model .FilenameClusterBlockVotePrefix ) {
403+ continue
404+ }
405+
406+ // read file and append to partners
407+ var vote hotstuff.Vote
408+ err = common .ReadJSON (f , & vote )
409+ if err != nil {
410+ log .Fatal ().Err (err ).Msg ("failed to read json" )
411+ }
412+ log .Info ().Msgf ("read vote %v for block %v from signerID %v" , vote .ID (), vote .BlockID , vote .SignerID )
413+
414+ // put the vote in the correct bucket for its cluster
415+ found := false
416+ for i , cluster := range al {
417+ if cluster .Contains (vote .SignerID ) {
418+ votes [i ] = append (votes [i ], & vote )
419+ found = true
420+ }
421+ }
422+ if ! found {
423+ log .Warn ().Msgf ("ignoring vote for block %v from signerID %v not part of the assignment" , vote .BlockID , vote .SignerID )
424+ }
425+ }
426+ return votes
427+ }
428+
391429// generateExecutionStateEpochConfig generates epoch-related configuration used
392430// to generate an empty root execution state. This config is generated in the
393431// `rootblock` alongside the root epoch and root protocol state ID for consistency.
0 commit comments