Skip to content

Commit 6c89f62

Browse files
authored
Merge pull request #8210 from onflow/bastian/update-cadence-vm
[Cadence VM] Merge master
2 parents 6642dd1 + 7448deb commit 6c89f62

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1591
-284
lines changed

cmd/bootstrap/README.md

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,22 @@ The bootstrapping will generate the following information:
3232
- public networking key
3333
- weight
3434

35+
36+
#### Collector clusters
37+
_Each cluster_ of collector nodes needs to have its own root Block and root QC
38+
* Root clustering: assignment of collector nodes to clusters
39+
* For each cluster:
40+
* Root `cluster.Block`
41+
* Root QC: votes from collector nodes for the respective root `cluster.Block`
42+
43+
3544
#### Root Block for main consensus
3645
* Root Block
3746
* Root QC: votes from consensus nodes for the root block (required to start consensus)
3847
* Root Execution Result: execution result for the initial execution state
3948
* Root Block Seal: block seal for the initial execution result
4049

4150

42-
#### Root Blocks for Collector clusters
43-
_Each cluster_ of collector nodes needs to have its own root Block and root QC
44-
* Root `ClusterBlockProposal`
45-
* Root QC from cluster for their respective `ClusterBlockProposal`
46-
47-
4851
# Usage
4952

5053
`go run ./cmd/bootstrap` prints usage information
@@ -97,6 +100,8 @@ Each input is a config file specified as a command line parameter:
97100
* folder containing the `<NodeID>.node-info.pub.json` files for _all_ partner nodes (see `.example_files/partner-node-infos`)
98101
* `json` containing the weight value for all partner nodes (see `./example_files/partner-weights.json`).
99102
Format: ```<NodeID>: <weight value>```
103+
* random seed for the new collector node clustering and epoch RandomSource (min 32 bytes in hex encoding)
104+
Provided seeds should be derived from a verifiable random source, such as the previous epoch's RandomSource.
100105

101106
#### Example
102107
```bash
@@ -121,6 +126,19 @@ go run . keygen \
121126

122127
```
123128

129+
```bash
130+
go run . cluster-assignment \
131+
--epoch-counter 0 \
132+
--collection-clusters 1 \
133+
--clustering-random-seed 00000000000000000000000000000000000000000000000000000000deadbeef \
134+
--config ./bootstrap-example/node-config.json \
135+
-o ./bootstrap-example \
136+
--partner-dir ./example_files/partner-node-infos \
137+
--partner-weights ./example_files/partner-weights.json \
138+
--internal-priv-dir ./bootstrap-example/keys
139+
140+
```
141+
124142
```bash
125143
go run . rootblock \
126144
--root-chain bench \
@@ -131,15 +149,19 @@ go run . rootblock \
131149
--epoch-length 30000 \
132150
--epoch-staking-phase-length 20000 \
133151
--epoch-dkg-phase-length 2000 \
152+
--random-seed 00000000000000000000000000000000000000000000000000000000deadbeef \
134153
--collection-clusters 1 \
135154
--protocol-version=0 \
136155
--use-default-epoch-timing \
137-
--epoch-commit-safety-threshold=1000 \
156+
--kvstore-finalization-safety-threshold=1000 \
157+
--kvstore-epoch-extension-view-count=2000 \
138158
--config ./bootstrap-example/node-config.json \
139159
-o ./bootstrap-example \
140160
--partner-dir ./example_files/partner-node-infos \
141161
--partner-weights ./example_files/partner-weights.json \
142-
--internal-priv-dir ./bootstrap-example/keys
162+
--internal-priv-dir ./bootstrap-example/keys \
163+
--intermediary-clustering-data ./bootstrap-example/public-root-information/root-clustering.json \
164+
--cluster-votes-dir ./bootstrap-example/public-root-information/root-block-votes/
143165
```
144166

145167
```bash
@@ -187,14 +209,6 @@ go run . finalize \
187209
* file `dkg-data.pub.json`
188210
- REQUIRED at NODE START by all nodes
189211

190-
* file `<ClusterID>.root-cluster-block.json`
191-
- root `ClusterBlockProposal` for collector cluster with ID `<ClusterID>`
192-
- REQUIRED at NODE START by all collectors of the respective cluster
193-
- file can be made accessible to all nodes at boot up (or recovery after crash)
194-
* file `<ClusterID>.root-cluster-qc.json`
195-
- root Quorum Certificate for `ClusterBlockProposal` for collector cluster with ID `<ClusterID>`
196-
- REQUIRED at NODE START by all collectors of the respective cluster
197-
- file can be made accessible to all nodes at boot up (or recovery after crash)
198212

199213
## Generating networking key for Observer
200214

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+
rng random.Rand,
5962
) (*flow.EpochSetup, *flow.EpochCommit, error) {
63+
randomSource := make([]byte, flow.EpochSetupRandomSourceLength)
64+
rng.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/clustering.go

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"path/filepath"
6+
7+
"github.com/spf13/cobra"
8+
9+
"github.com/onflow/flow-go/cmd"
10+
"github.com/onflow/flow-go/cmd/bootstrap/run"
11+
"github.com/onflow/flow-go/cmd/util/cmd/common"
12+
hotstuff "github.com/onflow/flow-go/consensus/hotstuff/model"
13+
model "github.com/onflow/flow-go/model/bootstrap"
14+
"github.com/onflow/flow-go/model/flow"
15+
cluster2 "github.com/onflow/flow-go/state/cluster"
16+
"github.com/onflow/flow-go/state/protocol/prg"
17+
)
18+
19+
var (
20+
flagClusteringRandomSeed []byte
21+
)
22+
23+
// clusterAssignmentCmd represents the clusterAssignment command
24+
var clusterAssignmentCmd = &cobra.Command{
25+
Use: "cluster-assignment",
26+
Short: "Generate cluster assignment",
27+
Long: `Generate cluster assignment for collection nodes based on partner and internal node info and weights. Serialize into file with Epoch Counter`,
28+
Run: clusterAssignment,
29+
}
30+
31+
func init() {
32+
rootCmd.AddCommand(clusterAssignmentCmd)
33+
addClusterAssignmentCmdFlags()
34+
}
35+
36+
func addClusterAssignmentCmdFlags() {
37+
// required parameters for network configuration and generation of root node identities
38+
clusterAssignmentCmd.Flags().StringVar(&flagConfig, "config", "",
39+
"path to a JSON file containing multiple node configurations (fields Role, Address, Weight)")
40+
clusterAssignmentCmd.Flags().StringVar(&flagInternalNodePrivInfoDir, "internal-priv-dir", "", "path to directory "+
41+
"containing the output from the `keygen` command for internal nodes")
42+
clusterAssignmentCmd.Flags().StringVar(&flagPartnerNodeInfoDir, "partner-dir", "", "path to directory "+
43+
"containing one JSON file starting with node-info.pub.<NODE_ID>.json for every partner node (fields "+
44+
" in the JSON file: Role, Address, NodeID, NetworkPubKey, StakingPubKey)")
45+
clusterAssignmentCmd.Flags().StringVar(&flagPartnerWeights, "partner-weights", "", "path to a JSON file containing "+
46+
"a map from partner node's NodeID to their stake")
47+
48+
cmd.MarkFlagRequired(clusterAssignmentCmd, "config")
49+
cmd.MarkFlagRequired(clusterAssignmentCmd, "internal-priv-dir")
50+
cmd.MarkFlagRequired(clusterAssignmentCmd, "partner-dir")
51+
cmd.MarkFlagRequired(clusterAssignmentCmd, "partner-weights")
52+
53+
// optional parameters for cluster assignment
54+
clusterAssignmentCmd.Flags().UintVar(&flagCollectionClusters, "collection-clusters", 2, "number of collection clusters")
55+
56+
// required parameters for generation of cluster root blocks
57+
clusterAssignmentCmd.Flags().Uint64Var(&flagEpochCounter, "epoch-counter", 0, "epoch counter for the epoch beginning with the root block")
58+
cmd.MarkFlagRequired(clusterAssignmentCmd, "epoch-counter")
59+
60+
clusterAssignmentCmd.Flags().BytesHexVar(&flagClusteringRandomSeed, "clustering-random-seed", nil, "random seed to generate the clustering assignment")
61+
cmd.MarkFlagRequired(clusterAssignmentCmd, "clustering-random-seed")
62+
63+
}
64+
65+
func clusterAssignment(cmd *cobra.Command, args []string) {
66+
// Read partner node's information and internal node's information.
67+
// With "internal nodes" we reference nodes, whose private keys we have. In comparison,
68+
// for "partner nodes" we generally do not have their keys. However, we allow some overlap,
69+
// in that we tolerate a configuration where information about an "internal node" is also
70+
// duplicated in the list of "partner nodes".
71+
log.Info().Msg("collecting partner network and staking keys")
72+
rawPartnerNodes, err := common.ReadFullPartnerNodeInfos(log, flagPartnerWeights, flagPartnerNodeInfoDir)
73+
if err != nil {
74+
log.Fatal().Err(err).Msg("failed to read full partner node infos")
75+
}
76+
log.Info().Msg("")
77+
78+
log.Info().Msg("generating internal private networking and staking keys")
79+
internalNodes, err := common.ReadFullInternalNodeInfos(log, flagInternalNodePrivInfoDir, flagConfig)
80+
if err != nil {
81+
log.Fatal().Err(err).Msg("failed to read full internal node infos")
82+
}
83+
log.Info().Msg("")
84+
85+
// we now convert to the strict meaning of: "internal nodes" vs "partner nodes"
86+
// • "internal nodes" we have they private keys for
87+
// • "partner nodes" we don't have the keys for
88+
// • both sets are disjoint (no common nodes)
89+
log.Info().Msg("remove internal partner nodes")
90+
partnerNodes := common.FilterInternalPartners(rawPartnerNodes, internalNodes)
91+
log.Info().Msgf("removed %d internal partner nodes", len(rawPartnerNodes)-len(partnerNodes))
92+
93+
log.Info().Msg("checking constraints on consensus nodes")
94+
checkConstraints(partnerNodes, internalNodes)
95+
log.Info().Msg("")
96+
97+
log.Info().Msg("assembling network and staking keys")
98+
stakingNodes, err := mergeNodeInfos(internalNodes, partnerNodes)
99+
if err != nil {
100+
log.Fatal().Err(err).Msgf("failed to merge node infos")
101+
}
102+
publicInfo, err := model.ToPublicNodeInfoList(stakingNodes)
103+
if err != nil {
104+
log.Fatal().Msg("failed to read public node info")
105+
}
106+
err = common.WriteJSON(model.PathNodeInfosPub, flagOutdir, publicInfo)
107+
if err != nil {
108+
log.Fatal().Err(err).Msg("failed to write json")
109+
}
110+
log.Info().Msgf("wrote file %s/%s", flagOutdir, model.PathNodeInfosPub)
111+
log.Info().Msg("")
112+
113+
// Convert to IdentityList
114+
partnerList := model.ToIdentityList(partnerNodes)
115+
internalList := model.ToIdentityList(internalNodes)
116+
117+
clusteringPrg, err := prg.New(flagClusteringRandomSeed, prg.BootstrapClusterAssignment, nil)
118+
if err != nil {
119+
log.Fatal().Err(err).Msg("failed to initialize pseudorandom generator")
120+
}
121+
122+
log.Info().Msg("computing collection node clusters")
123+
assignments, clusters, canConstructQCs, err := common.ConstructClusterAssignment(log, partnerList, internalList, int(flagCollectionClusters), clusteringPrg)
124+
if err != nil {
125+
log.Fatal().Err(err).Msg("unable to generate cluster assignment")
126+
}
127+
log.Info().Msg("")
128+
129+
// Output assignment with epoch counter
130+
output := IntermediaryClusteringData{
131+
EpochCounter: flagEpochCounter,
132+
Assignments: assignments,
133+
Clusters: clusters,
134+
}
135+
err = common.WriteJSON(model.PathClusteringData, flagOutdir, output)
136+
if err != nil {
137+
log.Fatal().Err(err).Msg("failed to write json")
138+
}
139+
log.Info().Msgf("wrote file %s/%s", flagOutdir, model.PathClusteringData)
140+
log.Info().Msg("")
141+
142+
log.Info().Msg("constructing and writing cluster block votes for internal nodes")
143+
constructClusterRootVotes(
144+
output,
145+
model.FilterByRole(internalNodes, flow.RoleCollection),
146+
)
147+
log.Info().Msg("")
148+
149+
if canConstructQCs {
150+
log.Info().Msg("enough votes for collection clusters are present - bootstrapping can continue with root block creation")
151+
} else {
152+
log.Info().Msg("not enough internal votes to generate cluster QCs, need partner votes before root block creation")
153+
}
154+
}
155+
156+
// constructClusterRootVotes generates and writes vote files for internal collector nodes with private keys available.
157+
func constructClusterRootVotes(data IntermediaryClusteringData, internalCollectors []model.NodeInfo) {
158+
for i := range data.Clusters {
159+
clusterRootBlock, err := cluster2.CanonicalRootBlock(data.EpochCounter, data.Assignments[i])
160+
if err != nil {
161+
log.Fatal().Err(err).Msg("could not construct cluster root block")
162+
}
163+
block := hotstuff.GenesisBlockFromFlow(clusterRootBlock.ToHeader())
164+
// collate private NodeInfos for internal nodes in this cluster
165+
signers := make([]model.NodeInfo, 0)
166+
for _, nodeID := range data.Assignments[i] {
167+
for _, node := range internalCollectors {
168+
if node.NodeID == nodeID {
169+
signers = append(signers, node)
170+
}
171+
}
172+
}
173+
votes, err := run.CreateClusterRootBlockVotes(signers, block)
174+
if err != nil {
175+
log.Fatal().Err(err).Msg("could not create cluster root block votes")
176+
}
177+
for _, vote := range votes {
178+
path := filepath.Join(model.DirnameRootBlockVotes, fmt.Sprintf(model.FilenameRootClusterBlockVote, vote.SignerID))
179+
err = common.WriteJSON(path, flagOutdir, vote)
180+
if err != nil {
181+
log.Fatal().Err(err).Msg("failed to write json")
182+
}
183+
log.Info().Msgf("wrote file %s/%s", flagOutdir, path)
184+
}
185+
}
186+
}

cmd/bootstrap/cmd/constraints.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ func ensureUniformNodeWeightsPerRole(allNodes flow.IdentityList) {
2626
}
2727
}
2828

29-
// Checks constraints about the number of partner and internal nodes.
30-
// - Internal nodes must comprise >2/3 of each collector cluster.
29+
// Checks constraints about the weights of partner and internal nodes.
3130
// - for all roles R:
3231
// all node with role R must have the same weight
3332
func checkConstraints(partnerNodes, internalNodes []model.NodeInfo) {

cmd/bootstrap/cmd/finalize.go

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,9 @@ import (
3131
)
3232

3333
var (
34-
flagConfig string
35-
flagInternalNodePrivInfoDir string
36-
flagPartnerNodeInfoDir string
37-
// Deprecated: use flagPartnerWeights instead
38-
deprecatedFlagPartnerStakes string
34+
flagConfig string
35+
flagInternalNodePrivInfoDir string
36+
flagPartnerNodeInfoDir string
3937
flagPartnerWeights string
4038
flagDKGDataPath string
4139
flagRootBlockPath string
@@ -70,8 +68,6 @@ func addFinalizeCmdFlags() {
7068
finalizeCmd.Flags().StringVar(&flagPartnerNodeInfoDir, "partner-dir", "", "path to directory "+
7169
"containing one JSON file starting with node-info.pub.<NODE_ID>.json for every partner node (fields "+
7270
" in the JSON file: Role, Address, NodeID, NetworkPubKey, StakingPubKey, StakingKeyPoP)")
73-
// Deprecated: remove this flag
74-
finalizeCmd.Flags().StringVar(&deprecatedFlagPartnerStakes, "partner-stakes", "", "deprecated: use partner-weights instead")
7571
finalizeCmd.Flags().StringVar(&flagPartnerWeights, "partner-weights", "", "path to a JSON file containing "+
7672
"a map from partner node's NodeID to their weight")
7773
finalizeCmd.Flags().StringVar(&flagDKGDataPath, "dkg-data", "", "path to a JSON file containing data as output from the random beacon key generation")
@@ -102,17 +98,6 @@ func addFinalizeCmdFlags() {
10298
}
10399

104100
func finalize(cmd *cobra.Command, args []string) {
105-
106-
// maintain backward compatibility with old flag name
107-
if deprecatedFlagPartnerStakes != "" {
108-
log.Warn().Msg("using deprecated flag --partner-stakes (use --partner-weights instead)")
109-
if flagPartnerWeights == "" {
110-
flagPartnerWeights = deprecatedFlagPartnerStakes
111-
} else {
112-
log.Fatal().Msg("cannot use both --partner-stakes and --partner-weights flags (use only --partner-weights)")
113-
}
114-
}
115-
116101
log.Info().Msg("collecting partner network and staking keys")
117102
partnerNodes, err := common.ReadFullPartnerNodeInfos(log, flagPartnerWeights, flagPartnerNodeInfoDir)
118103
if err != nil {

0 commit comments

Comments
 (0)