Skip to content

Commit 4ef0352

Browse files
authored
Merge pull request #7978 from onflow/tim/7838-bootstrapping-randomness
Update randomness used during bootstrapping
2 parents 759d3bf + 961d38a commit 4ef0352

File tree

7 files changed

+62
-13
lines changed

7 files changed

+62
-13
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, prg.BootstrapClusterAssignment, 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: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/onflow/flow-go/module/epochs"
2121
"github.com/onflow/flow-go/state/protocol"
2222
"github.com/onflow/flow-go/state/protocol/inmem"
23+
"github.com/onflow/flow-go/state/protocol/prg"
2324
"github.com/onflow/flow-go/state/protocol/protocol_state"
2425
"github.com/onflow/flow-go/state/protocol/protocol_state/kvstore"
2526
)
@@ -241,8 +242,19 @@ func rootBlock(cmd *cobra.Command, args []string) {
241242
// create flow.IdentityList representation of the participant set
242243
participants := model.ToIdentityList(stakingNodes).Sort(flow.Canonical[flow.Identity])
243244

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+
}
244256
log.Info().Msg("computing collection node clusters")
245-
assignments, clusters, err := common.ConstructClusterAssignment(log, model.ToIdentityList(partnerNodes), model.ToIdentityList(internalNodes), int(flagCollectionClusters))
257+
assignments, clusters, err := common.ConstructClusterAssignment(log, model.ToIdentityList(partnerNodes), model.ToIdentityList(internalNodes), int(flagCollectionClusters), clusteringPrg)
246258
if err != nil {
247259
log.Fatal().Err(err).Msg("unable to generate cluster assignment")
248260
}
@@ -263,8 +275,13 @@ func rootBlock(cmd *cobra.Command, args []string) {
263275
}
264276
log.Info().Msg("")
265277

278+
// use the same randomness for the RandomSource of the new epoch
279+
randomSourcePrg, err := prg.New(randomSeed[:], prg.BootstrapEpochRandomSource, nil)
280+
if err != nil {
281+
log.Fatal().Err(err).Msg("failed to initialize pseudorandom generator")
282+
}
266283
log.Info().Msg("constructing intermediary bootstrapping data")
267-
epochSetup, epochCommit, err := constructRootEpochEvents(headerBody.View, participants, assignments, clusterQCs, randomBeaconData, dkgIndexMap)
284+
epochSetup, epochCommit, err := constructRootEpochEvents(headerBody.View, participants, assignments, clusterQCs, randomBeaconData, dkgIndexMap, randomSourcePrg)
268285
if err != nil {
269286
log.Fatal().Err(err).Msg("failed to construct root epoch events")
270287
}

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+
rng, err := prg.New(epoch.RandomSource(), prg.BootstrapClusterAssignment, 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, rng)
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: 14 additions & 6 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,12 +52,17 @@ 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()
55+
// shuffle partner nodes in-place using the provided randomness
56+
err := randomSource.Shuffle(len(partnerCollectors), func(i, j int) {
57+
partnerCollectors[i], partnerCollectors[j] = partnerCollectors[j], partnerCollectors[i]
58+
})
5459
if err != nil {
5560
log.Fatal().Err(err).Msg("could not shuffle partners")
5661
}
57-
internalCollectors, err = internalCollectors.Shuffle()
62+
// shuffle internal nodes in-place using the provided randomness
63+
err = randomSource.Shuffle(len(internalCollectors), func(i, j int) {
64+
internalCollectors[i], internalCollectors[j] = internalCollectors[j], internalCollectors[i]
65+
})
5866
if err != nil {
5967
log.Fatal().Err(err).Msg("could not shuffle internals")
6068
}

integration/localnet/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ bootstrap-ci:
9494
# Creates a version of localnet configured with short epochs
9595
.PHONY: bootstrap-short-epochs
9696
bootstrap-short-epochs:
97-
$(MAKE) -e EPOCHLEN=200 STAKINGLEN=5 DKGLEN=50 FINALIZATIONSAFETYTHRESHOLD=20 bootstrap
97+
$(MAKE) -e EPOCHLEN=200 STAKINGLEN=5 DKGLEN=50 FINALIZATION_SAFETY_THRESHOLD=20 bootstrap
9898

9999
# Starts the network - must have been bootstrapped first. Builds fresh images.
100100
.PHONY: start

state/protocol/prg/customizers.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ var (
2525
// ExecutionRandomSourceHistory is the customizer for Flow's transaction execution environment
2626
// (used for the source of randomness history core-contract)
2727
ExecutionRandomSourceHistory = customizerFromIndices(1, 1)
28+
// BootstrapClusterAssignment is the customizer for assignment of collectors to clusters during bootstrapping
29+
BootstrapClusterAssignment = customizerFromIndices(4, 0)
30+
// BootstrapEpochRandomSource is the customizer for the RandomSource of the new epoch during bootstrapping
31+
BootstrapEpochRandomSource = customizerFromIndices(4, 1)
2832
//
2933
// clusterLeaderSelectionPrefix is the prefix used for CollectorClusterLeaderSelection
3034
clusterLeaderSelectionPrefix = []uint16{3}

0 commit comments

Comments
 (0)