Skip to content

Commit 325adaf

Browse files
committed
Use deterministic randomness for boostrapping
1 parent 759d3bf commit 325adaf

File tree

5 files changed

+55
-18
lines changed

5 files changed

+55
-18
lines changed

cmd/bootstrap/cmd/block.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"fmt"
66
"time"
77

8+
"github.com/onflow/crypto/random"
9+
810
"github.com/onflow/flow-go/cmd/bootstrap/run"
911
"github.com/onflow/flow-go/model/dkg"
1012
"github.com/onflow/flow-go/model/flow"
@@ -56,7 +58,10 @@ func constructRootEpochEvents(
5658
clusterQCs []*flow.QuorumCertificate,
5759
dkgData dkg.ThresholdKeySet,
5860
dkgIndexMap flow.DKGIndexMap,
61+
csprg random.Rand,
5962
) (*flow.EpochSetup, *flow.EpochCommit, error) {
63+
randomSource := make([]byte, flow.EpochSetupRandomSourceLength)
64+
csprg.Read(randomSource)
6065
epochSetup, err := flow.NewEpochSetup(
6166
flow.UntrustedEpochSetup{
6267
Counter: flagEpochCounter,
@@ -67,7 +72,7 @@ func constructRootEpochEvents(
6772
FinalView: firstView + flagNumViewsInEpoch - 1,
6873
Participants: participants.Sort(flow.Canonical[flow.Identity]).ToSkeleton(),
6974
Assignments: assignments,
70-
RandomSource: GenerateRandomSeed(flow.EpochSetupRandomSourceLength),
75+
RandomSource: randomSource,
7176
TargetDuration: flagEpochTimingDuration,
7277
TargetEndTime: rootEpochTargetEndTime(),
7378
},

cmd/bootstrap/cmd/finalize_test.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cmd
22

33
import (
4+
cryptoRand "crypto/rand"
45
"encoding/hex"
56
"math/rand"
67
"path/filepath"
@@ -16,6 +17,7 @@ import (
1617
"github.com/onflow/flow-go/cmd/util/cmd/common"
1718
model "github.com/onflow/flow-go/model/bootstrap"
1819
"github.com/onflow/flow-go/model/flow"
20+
"github.com/onflow/flow-go/state/protocol/prg"
1921
"github.com/onflow/flow-go/utils/unittest"
2022
)
2123

@@ -116,16 +118,23 @@ func TestClusterAssignment(t *testing.T) {
116118
partners := unittest.NodeInfosFixture(partnersLen, unittest.WithRole(flow.RoleCollection))
117119
internals := unittest.NodeInfosFixture(internalLen, unittest.WithRole(flow.RoleCollection))
118120

121+
// use a random seed
122+
seed := make([]byte, 32)
123+
_, err := cryptoRand.Read(seed)
124+
require.NoError(t, err)
125+
prng, err := prg.New(seed, nil, nil)
126+
require.NoError(t, err)
127+
119128
log := zerolog.Nop()
120129
// should not error
121-
_, clusters, err := common.ConstructClusterAssignment(log, model.ToIdentityList(partners), model.ToIdentityList(internals), int(flagCollectionClusters))
130+
_, clusters, err := common.ConstructClusterAssignment(log, model.ToIdentityList(partners), model.ToIdentityList(internals), int(flagCollectionClusters), prng)
122131
require.NoError(t, err)
123132
require.True(t, checkClusterConstraint(clusters, partners, internals))
124133

125134
// unhappy Path
126135
internals = internals[:21] // reduce one internal node
127136
// should error
128-
_, _, err = common.ConstructClusterAssignment(log, model.ToIdentityList(partners), model.ToIdentityList(internals), int(flagCollectionClusters))
137+
_, _, err = common.ConstructClusterAssignment(log, model.ToIdentityList(partners), model.ToIdentityList(internals), int(flagCollectionClusters), prng)
129138
require.Error(t, err)
130139
// revert the flag value
131140
flagCollectionClusters = tmp

cmd/bootstrap/cmd/rootblock.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/onflow/flow-go/cmd"
1414
"github.com/onflow/flow-go/cmd/bootstrap/run"
15+
"github.com/onflow/flow-go/cmd/bootstrap/utils"
1516
"github.com/onflow/flow-go/cmd/util/cmd/common"
1617
model "github.com/onflow/flow-go/model/bootstrap"
1718
"github.com/onflow/flow-go/model/dkg"
@@ -20,6 +21,7 @@ import (
2021
"github.com/onflow/flow-go/module/epochs"
2122
"github.com/onflow/flow-go/state/protocol"
2223
"github.com/onflow/flow-go/state/protocol/inmem"
24+
"github.com/onflow/flow-go/state/protocol/prg"
2325
"github.com/onflow/flow-go/state/protocol/protocol_state"
2426
"github.com/onflow/flow-go/state/protocol/protocol_state/kvstore"
2527
)
@@ -241,8 +243,19 @@ func rootBlock(cmd *cobra.Command, args []string) {
241243
// create flow.IdentityList representation of the participant set
242244
participants := model.ToIdentityList(stakingNodes).Sort(flow.Canonical[flow.Identity])
243245

246+
// use system randomness to create cluster assignment
247+
// TODO(7848): use randomness provided from the command line
248+
var randomSeed [32]byte
249+
_, err = rand.Read(randomSeed[:])
250+
if err != nil {
251+
log.Fatal().Err(err).Msg("unable to generate random seed")
252+
}
253+
csprg, err := prg.New(randomSeed[:], nil, nil)
254+
if err != nil {
255+
log.Fatal().Err(err).Msg("unable to initialize pseudorandom generator")
256+
}
244257
log.Info().Msg("computing collection node clusters")
245-
assignments, clusters, err := common.ConstructClusterAssignment(log, model.ToIdentityList(partnerNodes), model.ToIdentityList(internalNodes), int(flagCollectionClusters))
258+
assignments, clusters, err := common.ConstructClusterAssignment(log, model.ToIdentityList(partnerNodes), model.ToIdentityList(internalNodes), int(flagCollectionClusters), csprg)
246259
if err != nil {
247260
log.Fatal().Err(err).Msg("unable to generate cluster assignment")
248261
}
@@ -263,8 +276,9 @@ func rootBlock(cmd *cobra.Command, args []string) {
263276
}
264277
log.Info().Msg("")
265278

279+
// use the same randomness for the RandomSource of the new epoch
266280
log.Info().Msg("constructing intermediary bootstrapping data")
267-
epochSetup, epochCommit, err := constructRootEpochEvents(headerBody.View, participants, assignments, clusterQCs, randomBeaconData, dkgIndexMap)
281+
epochSetup, epochCommit, err := constructRootEpochEvents(headerBody.View, participants, assignments, clusterQCs, randomBeaconData, dkgIndexMap, csprg)
268282
if err != nil {
269283
log.Fatal().Err(err).Msg("failed to construct root epoch events")
270284
}

cmd/bootstrap/run/epochs.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"github.com/rs/zerolog"
1010
"golang.org/x/exp/slices"
1111

12+
"github.com/onflow/flow-go/state/protocol/prg"
13+
1214
"github.com/onflow/flow-go/cmd/util/cmd/common"
1315
"github.com/onflow/flow-go/fvm/systemcontracts"
1416
"github.com/onflow/flow-go/model/bootstrap"
@@ -174,8 +176,12 @@ func GenerateRecoverTxArgsWithDKG(
174176
}
175177
}
176178

179+
csprg, err := prg.New(epoch.RandomSource(), nil, nil)
180+
if err != nil {
181+
return nil, fmt.Errorf("could not initialize PRNG: %w", err)
182+
}
177183
log.Info().Msgf("partitioning %d partners + %d internal nodes into %d collector clusters", len(partnerCollectors), len(internalCollectors), collectionClusters)
178-
assignments, clusters, err := common.ConstructClusterAssignment(log, partnerCollectors, internalCollectors, collectionClusters)
184+
assignments, clusters, err := common.ConstructClusterAssignment(log, partnerCollectors, internalCollectors, collectionClusters, csprg)
179185
if err != nil {
180186
return nil, fmt.Errorf("unable to generate cluster assignment: %w", err)
181187
}

cmd/util/cmd/common/clusters.go

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77

8+
"github.com/onflow/crypto/random"
89
"github.com/rs/zerolog"
910

1011
"github.com/onflow/cadence"
@@ -21,8 +22,9 @@ import (
2122
// The number of nodes in each cluster is deterministic and only depends on the number of clusters
2223
// and the number of nodes. The repartition of internal and partner nodes is also deterministic
2324
// and only depends on the number of clusters and nodes.
24-
// The identity of internal and partner nodes in each cluster is the non-deterministic and is randomized
25-
// using the system entropy.
25+
// The identity of internal and partner nodes in each cluster is deterministically randomized
26+
// using the provided entropy `randomSource`. Ideally this entropy should be derived from the random beacon of the
27+
// previous epoch, or some other verifiable random source.
2628
// The function guarantees a specific constraint when partitioning the nodes into clusters:
2729
// Each cluster must contain strictly more than 2/3 of internal nodes. If the constraint can't be
2830
// satisfied, an exception is returned.
@@ -33,11 +35,12 @@ import (
3335
// - partnerNodes: identity list of partner nodes.
3436
// - internalNodes: identity list of internal nodes.
3537
// - numCollectionClusters: the number of clusters to generate
38+
// - randomSource: entropy used to randomize the assignment.
3639
// Returns:
3740
// - flow.AssignmentList: the generated assignment list.
3841
// - flow.ClusterList: the generate collection cluster list.
3942
// - error: if any error occurs. Any error returned from this function is irrecoverable.
40-
func ConstructClusterAssignment(log zerolog.Logger, partnerNodes, internalNodes flow.IdentityList, numCollectionClusters int) (flow.AssignmentList, flow.ClusterList, error) {
43+
func ConstructClusterAssignment(log zerolog.Logger, partnerNodes, internalNodes flow.IdentityList, numCollectionClusters int, randomSource random.Rand) (flow.AssignmentList, flow.ClusterList, error) {
4144

4245
partnerCollectors := partnerNodes.Filter(filter.HasRole[flow.Identity](flow.RoleCollection))
4346
internalCollectors := internalNodes.Filter(filter.HasRole[flow.Identity](flow.RoleCollection))
@@ -49,15 +52,15 @@ func ConstructClusterAssignment(log zerolog.Logger, partnerNodes, internalNodes
4952
nCollectors, numCollectionClusters)
5053
}
5154

52-
// shuffle both collector lists based on a non-deterministic algorithm
53-
partnerCollectors, err := partnerCollectors.Shuffle()
54-
if err != nil {
55-
log.Fatal().Err(err).Msg("could not shuffle partners")
56-
}
57-
internalCollectors, err = internalCollectors.Shuffle()
58-
if err != nil {
59-
log.Fatal().Err(err).Msg("could not shuffle internals")
60-
}
55+
//random := rand.New(randomSource)
56+
// shuffle partner nodes in-place using the provided randomness
57+
_ = randomSource.Shuffle(len(partnerCollectors), func(i, j int) {
58+
partnerCollectors[i], partnerCollectors[j] = partnerCollectors[j], partnerCollectors[i]
59+
})
60+
// shuffle internal nodes in-place using the provided randomness
61+
_ = randomSource.Shuffle(len(internalCollectors), func(i, j int) {
62+
internalCollectors[i], internalCollectors[j] = internalCollectors[j], internalCollectors[i]
63+
})
6164

6265
// capture first reference weight to validate that all collectors have equal weight
6366
refWeight := internalCollectors[0].InitialWeight

0 commit comments

Comments
 (0)