Skip to content

Commit

Permalink
remove deterministic bootstrap seed
Browse files Browse the repository at this point in the history
  • Loading branch information
Tarak Ben Youssef committed May 17, 2023
1 parent d07e2a8 commit b7d2185
Show file tree
Hide file tree
Showing 9 changed files with 23 additions and 281 deletions.
17 changes: 11 additions & 6 deletions cmd/bootstrap/cmd/clusters.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion cmd/bootstrap/cmd/constraints.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
23 changes: 6 additions & 17 deletions cmd/bootstrap/cmd/finalize.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cmd

import (
"encoding/binary"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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("")
Expand Down Expand Up @@ -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")
Expand All @@ -211,7 +198,6 @@ func finalize(cmd *cobra.Command, args []string) {
if flagRootCommit == "0000000000000000000000000000000000000000000000000000000000000000" {
generateEmptyExecutionState(
block.Header.ChainID,
flagBootstrapRandomSeed,
assignments,
clusterQCs,
dkgData,
Expand Down Expand Up @@ -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,
Expand All @@ -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")
Expand Down
234 changes: 1 addition & 233 deletions cmd/bootstrap/cmd/finalize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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" +
Expand Down Expand Up @@ -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"
Expand All @@ -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)

Expand All @@ -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()
})
}
1 change: 1 addition & 0 deletions cmd/bootstrap/cmd/machine_account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
Loading

0 comments on commit b7d2185

Please sign in to comment.