diff --git a/cmd/bootstrap/cmd/clusters.go b/cmd/bootstrap/cmd/clusters.go index 078c74c08f2..30ad8eabf43 100644 --- a/cmd/bootstrap/cmd/clusters.go +++ b/cmd/bootstrap/cmd/clusters.go @@ -13,7 +13,7 @@ import ( // Construct cluster assignment with internal and partner nodes uniformly // distributed across clusters. This function will produce the same cluster // assignments for the same partner and internal lists, and the same seed. -func constructClusterAssignment(partnerNodes, internalNodes []model.NodeInfo, seed int64) (flow.AssignmentList, flow.ClusterList) { +func constructClusterAssignment(partnerNodes, internalNodes []model.NodeInfo) (flow.AssignmentList, flow.ClusterList) { partners := model.ToIdentityList(partnerNodes).Filter(filter.HasRole(flow.RoleCollection)) internals := model.ToIdentityList(internalNodes).Filter(filter.HasRole(flow.RoleCollection)) @@ -26,11 +26,16 @@ func constructClusterAssignment(partnerNodes, internalNodes []model.NodeInfo, se nCollectors, flagCollectionClusters) } - // deterministically shuffle both collector lists based on the input seed - // by using a different seed each spork, we will have different clusters - // even with the same collectors - partners = partners.DeterministicShuffle(seed) - internals = internals.DeterministicShuffle(seed) + // shuffle both collector lists based on a non-deterministic algorithm + var err error + partners, err = partners.Shuffle() + if err != nil { + log.Fatal().Err(err).Msg("could not shuffle partners") + } + internals, err = internals.Shuffle() + if err != nil { + log.Fatal().Err(err).Msg("could not shuffle internals") + } identifierLists := make([]flow.IdentifierList, nClusters) diff --git a/cmd/bootstrap/cmd/constraints.go b/cmd/bootstrap/cmd/constraints.go index e50867341e5..ac25c534f49 100644 --- a/cmd/bootstrap/cmd/constraints.go +++ b/cmd/bootstrap/cmd/constraints.go @@ -37,7 +37,7 @@ func checkConstraints(partnerNodes, internalNodes []model.NodeInfo) { // check collection committee Byzantine threshold for each cluster // for checking Byzantine constraints, the seed doesn't matter - _, clusters := constructClusterAssignment(partnerNodes, internalNodes, 0) + _, clusters := constructClusterAssignment(partnerNodes, internalNodes) partnerCOLCount := uint(0) internalCOLCount := uint(0) for _, cluster := range clusters { diff --git a/cmd/bootstrap/cmd/finalize.go b/cmd/bootstrap/cmd/finalize.go index 5d1eb74106a..a688e21928f 100644 --- a/cmd/bootstrap/cmd/finalize.go +++ b/cmd/bootstrap/cmd/finalize.go @@ -1,7 +1,7 @@ package cmd import ( - "encoding/binary" + "crypto/rand" "encoding/hex" "encoding/json" "fmt" @@ -48,9 +48,6 @@ var ( flagNumViewsInStakingAuction uint64 flagNumViewsInDKGPhase uint64 flagEpochCommitSafetyThreshold uint64 - - // this flag is used to seed the DKG, clustering and cluster QC generation - flagBootstrapRandomSeed []byte ) // PartnerWeights is the format of the JSON file specifying partner node weights. @@ -101,7 +98,6 @@ func addFinalizeCmdFlags() { 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().Uint64Var(&flagEpochCommitSafetyThreshold, "epoch-commit-safety-threshold", 500, "defines epoch commitment deadline") - finalizeCmd.Flags().BytesHexVar(&flagBootstrapRandomSeed, "random-seed", GenerateRandomSeed(flow.EpochSetupRandomSourceLength), "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") @@ -143,14 +139,6 @@ func finalize(cmd *cobra.Command, args []string) { log.Fatal().Err(err).Msg("invalid or unsafe epoch commit threshold config") } - if len(flagBootstrapRandomSeed) != flow.EpochSetupRandomSourceLength { - log.Error().Int("expected", flow.EpochSetupRandomSourceLength).Int("actual", len(flagBootstrapRandomSeed)).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("") @@ -195,8 +183,7 @@ func finalize(cmd *cobra.Command, args []string) { log.Info().Msg("") log.Info().Msg("computing collection node clusters") - clusterAssignmentSeed := binary.BigEndian.Uint64(flagBootstrapRandomSeed) - assignments, clusters := constructClusterAssignment(partnerNodes, internalNodes, int64(clusterAssignmentSeed)) + assignments, clusters := constructClusterAssignment(partnerNodes, internalNodes) log.Info().Msg("") log.Info().Msg("constructing root blocks for collection node clusters") @@ -211,7 +198,6 @@ func finalize(cmd *cobra.Command, args []string) { if flagRootCommit == "0000000000000000000000000000000000000000000000000000000000000000" { generateEmptyExecutionState( block.Header.ChainID, - flagBootstrapRandomSeed, assignments, clusterQCs, dkgData, @@ -587,7 +573,6 @@ func loadRootProtocolSnapshot(path string) (*inmem.Snapshot, error) { // 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, @@ -606,6 +591,10 @@ func generateEmptyExecutionState( log.Fatal().Err(err).Msg("invalid genesis token supply") } + randomSource := make([]byte, flow.EpochSetupRandomSourceLength) + if _, err = rand.Read(randomSource); err != nil { + log.Fatal().Err(err).Msg("failed to generate a random source") + } cdcRandomSource, err := cadence.NewString(hex.EncodeToString(randomSource)) if err != nil { log.Fatal().Err(err).Msg("invalid random source") diff --git a/cmd/bootstrap/cmd/finalize_test.go b/cmd/bootstrap/cmd/finalize_test.go index 816760540da..7ce723709d0 100644 --- a/cmd/bootstrap/cmd/finalize_test.go +++ b/cmd/bootstrap/cmd/finalize_test.go @@ -2,23 +2,19 @@ package cmd import ( "encoding/hex" - "os" "path/filepath" "regexp" "strings" "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" utils "github.com/onflow/flow-go/cmd/bootstrap/utils" model "github.com/onflow/flow-go/model/bootstrap" - "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/unittest" ) -const finalizeHappyPathLogs = "^deterministic bootstrapping random seed" + - "collecting partner network and staking keys" + +const finalizeHappyPathLogs = "collecting partner network and staking keys" + `read \d+ partner node configuration files` + `read \d+ weights for partner nodes` + "generating internal private networking and staking keys" + @@ -52,7 +48,6 @@ const finalizeHappyPathLogs = "^deterministic bootstrapping random seed" + var finalizeHappyPathRegex = regexp.MustCompile(finalizeHappyPathLogs) func TestFinalize_HappyPath(t *testing.T) { - deterministicSeed := GenerateRandomSeed(flow.EpochSetupRandomSourceLength) rootCommit := unittest.StateCommitmentFixture() rootParent := unittest.StateCommitmentFixture() chainName := "main" @@ -72,9 +67,6 @@ func TestFinalize_HappyPath(t *testing.T) { flagRootParent = hex.EncodeToString(rootParent[:]) flagRootHeight = rootHeight - // set deterministic bootstrapping seed - flagBootstrapRandomSeed = deterministicSeed - // rootBlock will generate DKG and place it into bootDir/public-root-information rootBlock(nil, nil) @@ -100,227 +92,3 @@ func TestFinalize_HappyPath(t *testing.T) { assert.FileExists(t, snapshotPath) }) } - -func TestFinalize_Deterministic(t *testing.T) { - deterministicSeed := GenerateRandomSeed(flow.EpochSetupRandomSourceLength) - rootCommit := unittest.StateCommitmentFixture() - rootParent := unittest.StateCommitmentFixture() - chainName := "main" - rootHeight := uint64(1000) - epochCounter := uint64(0) - - utils.RunWithSporkBootstrapDir(t, func(bootDir, partnerDir, partnerWeights, internalPrivDir, configPath string) { - - flagOutdir = bootDir - - flagConfig = configPath - flagPartnerNodeInfoDir = partnerDir - flagPartnerWeights = partnerWeights - flagInternalNodePrivInfoDir = internalPrivDir - - flagRootCommit = hex.EncodeToString(rootCommit[:]) - flagRootParent = hex.EncodeToString(rootParent[:]) - flagRootChain = chainName - flagRootHeight = rootHeight - flagEpochCounter = epochCounter - flagNumViewsInEpoch = 100_000 - flagNumViewsInStakingAuction = 50_000 - flagNumViewsInDKGPhase = 2_000 - flagEpochCommitSafetyThreshold = 1_000 - - // set deterministic bootstrapping seed - flagBootstrapRandomSeed = deterministicSeed - - // rootBlock will generate DKG and place it into model.PathRootDKGData - rootBlock(nil, nil) - - flagRootBlock = filepath.Join(bootDir, model.PathRootBlockData) - flagDKGDataPath = filepath.Join(bootDir, model.PathRootDKGData) - flagRootBlockVotesDir = filepath.Join(bootDir, model.DirnameRootBlockVotes) - - hook := zeroLoggerHook{logs: &strings.Builder{}} - log = log.Hook(hook) - - finalize(nil, nil) - require.Regexp(t, finalizeHappyPathRegex, hook.logs.String()) - hook.logs.Reset() - - // check if root protocol snapshot exists - snapshotPath := filepath.Join(bootDir, model.PathRootProtocolStateSnapshot) - assert.FileExists(t, snapshotPath) - - // read snapshot - _, err := utils.ReadRootProtocolSnapshot(bootDir) - require.NoError(t, err) - - // delete snapshot file - err = os.Remove(snapshotPath) - require.NoError(t, err) - - finalize(nil, nil) - require.Regexp(t, finalizeHappyPathRegex, hook.logs.String()) - hook.logs.Reset() - - // check if root protocol snapshot exists - assert.FileExists(t, snapshotPath) - - // read snapshot - _, err = utils.ReadRootProtocolSnapshot(bootDir) - require.NoError(t, err) - - // ATTENTION: we can't use next statement because QC generation is not deterministic - // assert.Equal(t, firstSnapshot, secondSnapshot) - // Meaning we don't have a guarantee that with same input arguments we will get same QC. - // This doesn't mean that QC is invalid, but it will result in different structures, - // different QC => different service events => different result => different seal - // We need to use a different mechanism for comparing. - // ToDo: Revisit if this test case is valid at all. - }) -} - -func TestFinalize_SameSeedDifferentStateCommits(t *testing.T) { - deterministicSeed := GenerateRandomSeed(flow.EpochSetupRandomSourceLength) - rootCommit := unittest.StateCommitmentFixture() - rootParent := unittest.StateCommitmentFixture() - chainName := "main" - rootHeight := uint64(1000) - epochCounter := uint64(0) - - utils.RunWithSporkBootstrapDir(t, func(bootDir, partnerDir, partnerWeights, internalPrivDir, configPath string) { - - flagOutdir = bootDir - - flagConfig = configPath - flagPartnerNodeInfoDir = partnerDir - flagPartnerWeights = partnerWeights - flagInternalNodePrivInfoDir = internalPrivDir - - flagRootCommit = hex.EncodeToString(rootCommit[:]) - flagRootParent = hex.EncodeToString(rootParent[:]) - flagRootChain = chainName - flagRootHeight = rootHeight - flagEpochCounter = epochCounter - flagNumViewsInEpoch = 100_000 - flagNumViewsInStakingAuction = 50_000 - flagNumViewsInDKGPhase = 2_000 - flagEpochCommitSafetyThreshold = 1_000 - - // set deterministic bootstrapping seed - flagBootstrapRandomSeed = deterministicSeed - - // rootBlock will generate DKG and place it into bootDir/public-root-information - rootBlock(nil, nil) - - flagRootBlock = filepath.Join(bootDir, model.PathRootBlockData) - flagDKGDataPath = filepath.Join(bootDir, model.PathRootDKGData) - flagRootBlockVotesDir = filepath.Join(bootDir, model.DirnameRootBlockVotes) - - hook := zeroLoggerHook{logs: &strings.Builder{}} - log = log.Hook(hook) - - finalize(nil, nil) - require.Regexp(t, finalizeHappyPathRegex, hook.logs.String()) - hook.logs.Reset() - - // check if root protocol snapshot exists - snapshotPath := filepath.Join(bootDir, model.PathRootProtocolStateSnapshot) - assert.FileExists(t, snapshotPath) - - // read snapshot - snapshot1, err := utils.ReadRootProtocolSnapshot(bootDir) - require.NoError(t, err) - - // delete snapshot file - err = os.Remove(snapshotPath) - require.NoError(t, err) - - // change input state commitments - rootCommit2 := unittest.StateCommitmentFixture() - rootParent2 := unittest.StateCommitmentFixture() - flagRootCommit = hex.EncodeToString(rootCommit2[:]) - flagRootParent = hex.EncodeToString(rootParent2[:]) - - finalize(nil, nil) - require.Regexp(t, finalizeHappyPathRegex, hook.logs.String()) - hook.logs.Reset() - - // check if root protocol snapshot exists - assert.FileExists(t, snapshotPath) - - // read snapshot - snapshot2, err := utils.ReadRootProtocolSnapshot(bootDir) - require.NoError(t, err) - - // current epochs - currentEpoch1 := snapshot1.Epochs().Current() - currentEpoch2 := snapshot2.Epochs().Current() - - // check dkg - dkg1, err := currentEpoch1.DKG() - require.NoError(t, err) - dkg2, err := currentEpoch2.DKG() - require.NoError(t, err) - assert.Equal(t, dkg1, dkg2) - - // check clustering - clustering1, err := currentEpoch1.Clustering() - require.NoError(t, err) - clustering2, err := currentEpoch2.Clustering() - require.NoError(t, err) - assert.Equal(t, clustering1, clustering2) - - // verify random sources are same - randomSource1, err := currentEpoch1.RandomSource() - require.NoError(t, err) - randomSource2, err := currentEpoch2.RandomSource() - require.NoError(t, err) - assert.Equal(t, randomSource1, randomSource2) - assert.Equal(t, randomSource1, deterministicSeed) - assert.Equal(t, flow.EpochSetupRandomSourceLength, len(randomSource1)) - }) -} - -func TestFinalize_InvalidRandomSeedLength(t *testing.T) { - rootCommit := unittest.StateCommitmentFixture() - rootParent := unittest.StateCommitmentFixture() - chainName := "main" - rootHeight := uint64(12332) - epochCounter := uint64(2) - - // set random seed with smaller length - deterministicSeed, err := hex.DecodeString("a12354a343234aa44bbb43") - require.NoError(t, err) - - // invalid length execution logs - expectedLogs := regexp.MustCompile("random seed provided length is not valid") - - utils.RunWithSporkBootstrapDir(t, func(bootDir, partnerDir, partnerWeights, internalPrivDir, configPath string) { - - flagOutdir = bootDir - - flagConfig = configPath - flagPartnerNodeInfoDir = partnerDir - flagPartnerWeights = partnerWeights - flagInternalNodePrivInfoDir = internalPrivDir - - flagRootCommit = hex.EncodeToString(rootCommit[:]) - flagRootParent = hex.EncodeToString(rootParent[:]) - flagRootChain = chainName - flagRootHeight = rootHeight - flagEpochCounter = epochCounter - flagNumViewsInEpoch = 100_000 - flagNumViewsInStakingAuction = 50_000 - flagNumViewsInDKGPhase = 2_000 - flagEpochCommitSafetyThreshold = 1_000 - - // set deterministic bootstrapping seed - flagBootstrapRandomSeed = deterministicSeed - - hook := zeroLoggerHook{logs: &strings.Builder{}} - log = log.Hook(hook) - - finalize(nil, nil) - assert.Regexp(t, expectedLogs, hook.logs.String()) - hook.logs.Reset() - }) -} diff --git a/cmd/bootstrap/cmd/machine_account_test.go b/cmd/bootstrap/cmd/machine_account_test.go index 5fab682e561..7a1627ca3ac 100644 --- a/cmd/bootstrap/cmd/machine_account_test.go +++ b/cmd/bootstrap/cmd/machine_account_test.go @@ -31,6 +31,7 @@ func TestMachineAccountHappyPath(t *testing.T) { flagRole = "consensus" flagAddress = "189.123.123.42:3869" addr, err := flow.Mainnet.Chain().AddressAtIndex(uint64(rand.Intn(1_000_000))) + t.Logf("address is %s", addr) require.NoError(t, err) flagMachineAccountAddress = addr.HexWithPrefix() diff --git a/cmd/bootstrap/cmd/rootblock.go b/cmd/bootstrap/cmd/rootblock.go index dd530f562d6..7060fdf1a4b 100644 --- a/cmd/bootstrap/cmd/rootblock.go +++ b/cmd/bootstrap/cmd/rootblock.go @@ -1,7 +1,6 @@ package cmd import ( - "encoding/hex" "time" "github.com/spf13/cobra" @@ -58,8 +57,6 @@ func addRootBlockCmdFlags() { cmd.MarkFlagRequired(rootBlockCmd, "root-chain") cmd.MarkFlagRequired(rootBlockCmd, "root-parent") cmd.MarkFlagRequired(rootBlockCmd, "root-height") - - rootBlockCmd.Flags().BytesHexVar(&flagBootstrapRandomSeed, "random-seed", GenerateRandomSeed(flow.EpochSetupRandomSourceLength), "The seed used to for DKG, Clustering and Cluster QC generation") } func rootBlock(cmd *cobra.Command, args []string) { @@ -74,14 +71,6 @@ func rootBlock(cmd *cobra.Command, args []string) { } } - if len(flagBootstrapRandomSeed) != flow.EpochSetupRandomSourceLength { - log.Error().Int("expected", flow.EpochSetupRandomSourceLength).Int("actual", len(flagBootstrapRandomSeed)).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("") diff --git a/cmd/bootstrap/cmd/rootblock_test.go b/cmd/bootstrap/cmd/rootblock_test.go index 09bc7d10305..a2ccb177e79 100644 --- a/cmd/bootstrap/cmd/rootblock_test.go +++ b/cmd/bootstrap/cmd/rootblock_test.go @@ -13,12 +13,10 @@ import ( "github.com/onflow/flow-go/cmd/bootstrap/utils" model "github.com/onflow/flow-go/model/bootstrap" - "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/unittest" ) -const rootBlockHappyPathLogs = "^deterministic bootstrapping random seed" + - "collecting partner network and staking keys" + +const rootBlockHappyPathLogs = "collecting partner network and staking keys" + `read \d+ partner node configuration files` + `read \d+ weights for partner nodes` + "generating internal private networking and staking keys" + @@ -42,7 +40,6 @@ const rootBlockHappyPathLogs = "^deterministic bootstrapping random seed" + var rootBlockHappyPathRegex = regexp.MustCompile(rootBlockHappyPathLogs) func TestRootBlock_HappyPath(t *testing.T) { - deterministicSeed := GenerateRandomSeed(flow.EpochSetupRandomSourceLength) rootParent := unittest.StateCommitmentFixture() chainName := "main" rootHeight := uint64(12332) @@ -60,9 +57,6 @@ func TestRootBlock_HappyPath(t *testing.T) { flagRootChain = chainName flagRootHeight = rootHeight - // set deterministic bootstrapping seed - flagBootstrapRandomSeed = deterministicSeed - hook := zeroLoggerHook{logs: &strings.Builder{}} log = log.Hook(hook) @@ -77,7 +71,6 @@ func TestRootBlock_HappyPath(t *testing.T) { } func TestRootBlock_Deterministic(t *testing.T) { - deterministicSeed := GenerateRandomSeed(flow.EpochSetupRandomSourceLength) rootParent := unittest.StateCommitmentFixture() chainName := "main" rootHeight := uint64(1000) @@ -95,9 +88,6 @@ func TestRootBlock_Deterministic(t *testing.T) { flagRootChain = chainName flagRootHeight = rootHeight - // set deterministic bootstrapping seed - flagBootstrapRandomSeed = deterministicSeed - hook := zeroLoggerHook{logs: &strings.Builder{}} log = log.Hook(hook) diff --git a/cmd/bootstrap/cmd/seal.go b/cmd/bootstrap/cmd/seal.go index 91533377a0e..1a34c394e13 100644 --- a/cmd/bootstrap/cmd/seal.go +++ b/cmd/bootstrap/cmd/seal.go @@ -41,7 +41,7 @@ func constructRootResultAndSeal( DKGPhase3FinalView: firstView + flagNumViewsInStakingAuction + flagNumViewsInDKGPhase*3 - 1, Participants: participants.Sort(order.Canonical), Assignments: assignments, - RandomSource: flagBootstrapRandomSeed, + RandomSource: GenerateRandomSeed(flow.EpochSetupRandomSourceLength), } qcsWithSignerIDs := make([]*flow.QuorumCertificateWithSignerIDs, 0, len(clusterQCs)) diff --git a/cmd/bootstrap/utils/file.go b/cmd/bootstrap/utils/file.go index b1c0585ba0e..fc5f35c7122 100644 --- a/cmd/bootstrap/utils/file.go +++ b/cmd/bootstrap/utils/file.go @@ -35,7 +35,7 @@ func ReadRootProtocolSnapshot(bootDir string) (*inmem.Snapshot, error) { func ReadRootBlock(rootBlockDataPath string) (*flow.Block, error) { bytes, err := io.ReadFile(rootBlockDataPath) if err != nil { - return nil, fmt.Errorf("could not read root block: %w", err) + return nil, fmt.Errorf("could not read root block file: %w", err) } var encodable flow.Block