Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bootstrap] Extracted rootblock and updated finalize #1371

Merged
merged 33 commits into from
Oct 4, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
858d909
Extracted rootBlock command from finalize command for bootstrap util.…
durkmurder Sep 28, 2021
95d6a16
Implemented QC creation as part of finalizing cmd. Updated docs. Impl…
durkmurder Sep 28, 2021
ce49da7
Fixed typo, updated tests
durkmurder Sep 28, 2021
e0e0061
Updated QC building logic. Fixed signing of QC
durkmurder Sep 28, 2021
a125806
Updated godoc
durkmurder Sep 28, 2021
221a262
Updated comments. Reverted last commit
durkmurder Sep 28, 2021
cd65783
Updated how root block is created. Separated root block and votes
durkmurder Sep 28, 2021
0c7b8b9
Added encoding of full DKG to construct QC in finilize step
durkmurder Sep 28, 2021
84a4c79
Merge branch 'master' into yurii/5891-update-finalize
Sep 28, 2021
fb25b62
Updated reading of votes, dkg data in finalizer. Simplified logic for…
durkmurder Sep 28, 2021
fdbd24c
Merge branch 'yurii/5891-update-finalize' of https://github.com/onflo…
durkmurder Sep 28, 2021
567811b
Updated tests
durkmurder Sep 28, 2021
a58bb18
Fixed tests
durkmurder Sep 28, 2021
8757ea6
Linted. Fixed tests
durkmurder Sep 28, 2021
f73b5af
Update cmd/bootstrap/run/qc.go
Sep 28, 2021
175116b
renamed methods for reading node infos to reflect that fact that we a…
Sep 28, 2021
503e8ea
consolidated consistency checks
Sep 28, 2021
e011135
cleanup
Sep 28, 2021
e0de23c
format
jordanschalm Sep 28, 2021
5def7f1
update localnet/integration tests to generate votes separately
jordanschalm Sep 28, 2021
5bee68e
put votes in same directory
jordanschalm Sep 28, 2021
aded4bc
fix path join
jordanschalm Sep 28, 2021
5b1e0bd
Merge branch 'master' into yurii/5891-update-finalize
Sep 29, 2021
77138ac
pass in identities separate from participant data
jordanschalm Sep 29, 2021
80e41b4
Merge branch 'yurii/5891-update-finalize' of github.com:onflow/flow-g…
jordanschalm Sep 29, 2021
44d1fc1
add log
jordanschalm Sep 29, 2021
461a0ef
unwrap encodable type of key for partners
jordanschalm Sep 29, 2021
a404fe0
fix test (lint)
jordanschalm Sep 29, 2021
389a43a
LInted and fixed unit tests
durkmurder Sep 29, 2021
f7cf0b8
Fixed network test
durkmurder Sep 29, 2021
2476f96
Merge branch 'master' of https://github.com/onflow/flow-go into yurii…
durkmurder Sep 29, 2021
c805a2f
Updated godoc
durkmurder Sep 29, 2021
a1d2b2b
Merge branch 'master' into yurii/5891-update-finalize
zhangchiqing Oct 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 9 additions & 2 deletions cmd/bootstrap/cmd/constraints.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import (
// Checks constraints about the number of partner and internal nodes.
// Internal nodes must comprise >2/3 of consensus committee.
// Internal nodes must comprise >2/3 of each collector cluster.
func checkConstraints(partnerNodes, internalNodes []model.NodeInfo) {

func checkConsensusConstraints(partnerNodes, internalNodes []model.NodeInfo) {
AlexHentschel marked this conversation as resolved.
Show resolved Hide resolved
partners := model.ToIdentityList(partnerNodes)
internals := model.ToIdentityList(internalNodes)
all := append(partners, internals...)
Expand Down Expand Up @@ -39,6 +38,14 @@ func checkConstraints(partnerNodes, internalNodes []model.NodeInfo) {
partnerCONCount, internalCONCount, partnerCONCount*2+1)
}

}

// Checks constraints about the number of partner and internal nodes.
// Internal nodes must comprise >2/3 of consensus committee.
// Internal nodes must comprise >2/3 of each collector cluster.
func checkCollectionConstraints(partnerNodes, internalNodes []model.NodeInfo) {
AlexHentschel marked this conversation as resolved.
Show resolved Hide resolved
partners := model.ToIdentityList(partnerNodes)
internals := model.ToIdentityList(internalNodes)
// check collection committee Byzantine threshold for each cluster
// for checking Byzantine constraints, the seed doesn't matter
_, clusters := constructClusterAssignment(partnerNodes, internalNodes, 0)
Expand Down
19 changes: 19 additions & 0 deletions cmd/bootstrap/cmd/dkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
model "github.com/onflow/flow-go/model/bootstrap"
"github.com/onflow/flow-go/model/dkg"
"github.com/onflow/flow-go/model/encodable"
"github.com/onflow/flow-go/model/flow"
"github.com/onflow/flow-go/state/protocol/inmem"
)

func runDKG(nodes []model.NodeInfo) dkg.DKGData {
Expand All @@ -27,6 +29,16 @@ func runDKG(nodes []model.NodeInfo) dkg.DKGData {
}
log.Info().Msgf("finished running DKG")

dkgParticipans := make(map[flow.Identifier]flow.DKGParticipant)
AlexHentschel marked this conversation as resolved.
Show resolved Hide resolved

for i, pubKey := range dkgData.PubKeyShares {
nodeID := nodes[i].NodeID
dkgParticipans[nodeID] = flow.DKGParticipant{
Index: uint(i),
KeyShare: pubKey,
}
}

for i, privKey := range dkgData.PrivKeyShares {
nodeID := nodes[i].NodeID

Expand All @@ -40,5 +52,12 @@ func runDKG(nodes []model.NodeInfo) dkg.DKGData {
writeJSON(fmt.Sprintf(model.PathRandomBeaconPriv, nodeID), privParticpant)
}

writeJSON(model.PathRandomBeaconPub, inmem.EncodableDKG{
GroupKey: encodable.RandomBeaconPubKey{
PublicKey: dkgData.PubGroupKey,
},
Participants: dkgParticipans,
})

return dkgData
}
3 changes: 2 additions & 1 deletion cmd/bootstrap/cmd/final_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ func finalList(cmd *cobra.Command, args []string) {
flowNodes := assembleInternalNodesWithoutStake()

log.Info().Msg("checking constraints on consensus/cluster nodes")
checkConstraints(partnerNodes, flowNodes)
checkConsensusConstraints(partnerNodes, flowNodes)
checkCollectionConstraints(partnerNodes, flowNodes)

log.Info().Msgf("reading staking contract node information: %s", flagStakingNodesPath)
stakingNodes := readStakingContractDetails()
Expand Down
89 changes: 65 additions & 24 deletions cmd/bootstrap/cmd/finalize.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import (
"fmt"
"path/filepath"
"strings"
"time"

"github.com/onflow/cadence"
"github.com/spf13/cobra"

"github.com/onflow/flow-go/cmd"
"github.com/onflow/flow-go/cmd/bootstrap/run"
"github.com/onflow/flow-go/cmd/bootstrap/utils"
"github.com/onflow/flow-go/crypto"
"github.com/onflow/flow-go/fvm"
model "github.com/onflow/flow-go/model/bootstrap"
"github.com/onflow/flow-go/model/dkg"
Expand All @@ -31,11 +32,9 @@ var (
flagCollectionClusters uint
flagPartnerNodeInfoDir string
flagPartnerStakes string
flagFastKG bool
flagRootChain string
flagRootParent string
flagRootHeight uint64
flagRootTimestamp string
flagDKGPubDataPath string
flagSignerDKGDataPath string
flagRootBlock string
flagRootCommit string
flagServiceAccountPublicKeyJSON string
flagGenesisTokenSupply string
Expand Down Expand Up @@ -76,26 +75,27 @@ func addFinalizeCmdFlags() {
" in the JSON file: Role, Address, NodeID, NetworkPubKey, StakingPubKey)")
finalizeCmd.Flags().StringVar(&flagPartnerStakes, "partner-stakes", "", "path to a JSON file containing "+
"a map from partner node's NodeID to their stake")
finalizeCmd.Flags().StringVar(&flagDKGPubDataPath, "dkg-data", "", "path to a JSON file containing public data as output from DKG process")
finalizeCmd.Flags().StringVar(&flagSignerDKGDataPath, "signer-dkg-data", "",
durkmurder marked this conversation as resolved.
Show resolved Hide resolved
"path to a JSON file containing private DKG data of node that will sign the QC")

cmd.MarkFlagRequired(finalizeCmd, "config")
cmd.MarkFlagRequired(finalizeCmd, "internal-priv-dir")
cmd.MarkFlagRequired(finalizeCmd, "partner-dir")
cmd.MarkFlagRequired(finalizeCmd, "partner-stakes")
cmd.MarkFlagRequired(finalizeCmd, "dkg-data")
cmd.MarkFlagRequired(finalizeCmd, "signer-dkg-data")

// required parameters for generation of root block, root execution result and root block seal
finalizeCmd.Flags().StringVar(&flagRootChain, "root-chain", "local", "chain ID for the root block (can be 'main', 'test', 'canary', 'bench', or 'local'")
finalizeCmd.Flags().StringVar(&flagRootParent, "root-parent", "0000000000000000000000000000000000000000000000000000000000000000", "ID for the parent of the root block")
finalizeCmd.Flags().Uint64Var(&flagRootHeight, "root-height", 0, "height of the root block")
finalizeCmd.Flags().StringVar(&flagRootTimestamp, "root-timestamp", time.Now().UTC().Format(time.RFC3339), "timestamp of the root block (RFC3339)")
finalizeCmd.Flags().StringVar(&flagRootBlock, "root-block", "",
jordanschalm marked this conversation as resolved.
Show resolved Hide resolved
"path to a JSON file containing root block and votes")
durkmurder marked this conversation as resolved.
Show resolved Hide resolved
finalizeCmd.Flags().StringVar(&flagRootCommit, "root-commit", "0000000000000000000000000000000000000000000000000000000000000000", "state commitment of root execution state")
finalizeCmd.Flags().Uint64Var(&flagEpochCounter, "epoch-counter", 0, "epoch counter for the epoch beginning with the root block")
finalizeCmd.Flags().Uint64Var(&flagNumViewsInEpoch, "epoch-length", 4000, "length of each epoch measured in views")
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")

cmd.MarkFlagRequired(finalizeCmd, "root-chain")
cmd.MarkFlagRequired(finalizeCmd, "root-parent")
cmd.MarkFlagRequired(finalizeCmd, "root-height")
cmd.MarkFlagRequired(finalizeCmd, "root-block")
cmd.MarkFlagRequired(finalizeCmd, "root-commit")
cmd.MarkFlagRequired(finalizeCmd, "epoch-counter")
cmd.MarkFlagRequired(finalizeCmd, "epoch-length")
Expand All @@ -106,7 +106,6 @@ func addFinalizeCmdFlags() {

// optional parameters to influence various aspects of identity generation
finalizeCmd.Flags().UintVar(&flagCollectionClusters, "collection-clusters", 2, "number of collection clusters")
finalizeCmd.Flags().BoolVar(&flagFastKG, "fast-kg", false, "use fast (centralized) random beacon key generation instead of DKG")

// these two flags are only used when setup a network from genesis
finalizeCmd.Flags().StringVar(&flagServiceAccountPublicKeyJSON, "service-account-public-key-json",
Expand All @@ -127,6 +126,9 @@ func finalize(cmd *cobra.Command, args []string) {
log.Info().Str("seed", hex.EncodeToString(flagBootstrapRandomSeed)).Msg("deterministic bootstrapping random seed")
log.Info().Msg("")

// TODO: we already have all nodes generated from rootblock step, instead of assembling them again
// we just need to read them. Fix this.
AlexHentschel marked this conversation as resolved.
Show resolved Hide resolved

log.Info().Msg("collecting partner network and staking keys")
partnerNodes := assemblePartnerNodes()
log.Info().Msg("")
Expand All @@ -136,30 +138,36 @@ func finalize(cmd *cobra.Command, args []string) {
log.Info().Msg("")

log.Info().Msg("checking constraints on consensus/cluster nodes")
checkConstraints(partnerNodes, internalNodes)
checkConsensusConstraints(partnerNodes, internalNodes)
checkCollectionConstraints(partnerNodes, internalNodes)
log.Info().Msg("")

log.Info().Msg("assembling network and staking keys")
stakingNodes := mergeNodeInfos(internalNodes, partnerNodes)
writeJSON(model.PathNodeInfosPub, model.ToPublicNodeInfoList(stakingNodes))
log.Info().Msg("")

// create flow.IdentityList representation of participant set
participants := model.ToIdentityList(stakingNodes).Sort(order.Canonical)

log.Info().Msg("running DKG for consensus nodes")
dkgData := runDKG(model.FilterByRole(stakingNodes, flow.RoleConsensus))
log.Info().Msg("reading root block data")
rootBlockData := readRootBlockData()
block := rootBlockData.Block
log.Info().Msg("")

log.Info().Msg("reading dkg pub data")
dkgData := readDKGPubData()
log.Info().Msg("")

log.Info().Msg("constructing root block")
block := constructRootBlock(flagRootChain, flagRootParent, flagRootHeight, flagRootTimestamp)
log.Info().Msg("reading QC signer")
signer := readQCSigner()
log.Info().Msg("")

log.Info().Msg("constructing root QC")
rootQC := constructRootQC(
block,
model.FilterByRole(stakingNodes, flow.RoleConsensus),
rootBlockData.Votes,
model.FilterByRole(internalNodes, flow.RoleConsensus),
signer,
dkgData,
)
log.Info().Msg("")
Expand All @@ -180,6 +188,7 @@ func finalize(cmd *cobra.Command, args []string) {
// if no root commit is specified, bootstrap an empty execution state
if flagRootCommit == "0000000000000000000000000000000000000000000000000000000000000000" {
generateEmptyExecutionState(
block.Header.ChainID,
getRandomSource(flagBootstrapRandomSeed),
assignments,
clusterQCs,
Expand Down Expand Up @@ -422,6 +431,32 @@ func mergeNodeInfos(internalNodes, partnerNodes []model.NodeInfo) []model.NodeIn
return nodes
}

// readRootBlockData reads root block data from disc, this file needs to be prepared with
// rootblock command
func readRootBlockData() *inmem.EncodableRootBlockData {
rootBlockData, err := utils.ReadRootBlockData(flagRootBlock)
if err != nil {
log.Fatal().Err(err).Msg("could not read root block data")
}
return rootBlockData
}

func readDKGPubData() inmem.EncodableDKG {
dkgData, err := utils.ReadDKGPubData(flagDKGPubDataPath)
if err != nil {
log.Fatal().Err(err).Msg("could not read DKG data")
}
return *dkgData
}

func readQCSigner() dkg.DKGParticipantPriv {
participant, err := utils.ReadDKGParticipant(flagSignerDKGDataPath)
if err != nil {
log.Fatal().Err(err).Msg("could not read signer DKG data")
}
return *participant
}

// Validation utility methods ------------------------------------------------

func validateNodeID(nodeID flow.Identifier) flow.Identifier {
Expand Down Expand Up @@ -468,10 +503,11 @@ func loadRootProtocolSnapshot(path string) (*inmem.Snapshot, error) {
// generateEmptyExecutionState generates a new empty execution state with the
// given configuration. Sets the flagRootCommit variable for future reads.
func generateEmptyExecutionState(
chainID flow.ChainID,
randomSource []byte,
assignments flow.AssignmentList,
clusterQCs []*flow.QuorumCertificate,
dkg dkg.DKGData,
dkg inmem.EncodableDKG,
identities flow.IdentityList,
) (commit flow.StateCommitment) {

Expand All @@ -492,6 +528,11 @@ func generateEmptyExecutionState(
log.Fatal().Err(err).Msg("invalid random source")
}

dkgPubKeys := make([]crypto.PublicKey, 0)
for _, participant := range dkg.Participants {
durkmurder marked this conversation as resolved.
Show resolved Hide resolved
dkgPubKeys = append(dkgPubKeys, participant.KeyShare)
}

epochConfig := epochs.EpochConfig{
EpochTokenPayout: cadence.UFix64(0),
RewardCut: cadence.UFix64(0),
Expand All @@ -504,13 +545,13 @@ func generateEmptyExecutionState(
RandomSource: cdcRandomSource,
CollectorClusters: assignments,
ClusterQCs: clusterQCs,
DKGPubKeys: dkg.PubKeyShares,
DKGPubKeys: dkgPubKeys,
}

commit, err = run.GenerateExecutionState(
filepath.Join(flagOutdir, model.DirnameExecutionState),
serviceAccountPublicKey,
parseChainID(flagRootChain).Chain(),
chainID.Chain(),
fvm.WithInitialTokenSupply(cdcInitialTokenSupply),
fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation),
fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee),
Expand Down
67 changes: 56 additions & 11 deletions cmd/bootstrap/cmd/finalize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,9 @@ const finalizeHappyPathLogs = "^deterministic bootstrapping random seed" +
`read \d+ stakes for internal nodes` +
`checking constraints on consensus/cluster nodes` +
`assembling network and staking keys` +
`wrote file \S+/node-infos.pub.json` +
`running DKG for consensus nodes` +
`read \d+ node infos for DKG` +
`will run DKG` +
`finished running DKG` +
`.+/random-beacon.priv.json` +
`constructing root block` +
`reading root block data` +
`reading dkg pub data` +
`reading QC signer` +
`constructing root QC` +
`computing collection node clusters` +
`constructing root blocks for collection node clusters` +
Expand All @@ -53,6 +49,29 @@ const finalizeHappyPathLogs = "^deterministic bootstrapping random seed" +

var finalizeHappyPathRegex = regexp.MustCompile(finalizeHappyPathLogs)

// getFirstQCSignerPath picks first internal node as participant who will do the QC signing
// node needs to have random beacon key and node info to be treated as internal.
func getFirstQCSignerPath(t *testing.T, bootDir string) string {
durkmurder marked this conversation as resolved.
Show resolved Hide resolved
privateFiles, err := filesInDir(filepath.Join(bootDir, model.DirPrivateRoot))
assert.NoError(t, err)

for _, privateDir := range privateFiles {
files, err := filesInDir(privateDir)
assert.NoError(t, err)

for _, f := range files {
// skip files that do not include random beacon key and node-info
if !strings.Contains(f, model.FilenameRandomBeaconPriv) && strings.Contains(f, model.PathPrivNodeInfoPrefix) {
continue
}
return f
}
}

assert.Fail(t, "no random beacon signer found")
return ""
}

func TestFinalize_HappyPath(t *testing.T) {
deterministicSeed := GenerateRandomSeed()
rootCommit := unittest.StateCommitmentFixture()
Expand All @@ -71,16 +90,24 @@ func TestFinalize_HappyPath(t *testing.T) {
flagInternalNodePrivInfoDir = internalPrivDir

flagFastKG = true

flagRootCommit = hex.EncodeToString(rootCommit[:])
flagRootParent = hex.EncodeToString(rootParent[:])
flagRootChain = chainName
flagRootParent = hex.EncodeToString(rootParent[:])
flagRootHeight = rootHeight
flagEpochCounter = epochCounter

// set deterministic bootstrapping seed
flagBootstrapRandomSeed = deterministicSeed

// rootBlock will generate DKG and place it into bootDir/public-root-information
rootBlock(nil, nil)

flagRootCommit = hex.EncodeToString(rootCommit[:])
flagEpochCounter = epochCounter
flagRootBlock = filepath.Join(bootDir, model.PathRootBlockData)
flagDKGPubDataPath = filepath.Join(bootDir, model.PathRandomBeaconPub)

// pick participant as signer
flagSignerDKGDataPath = getFirstQCSignerPath(t, internalPrivDir)

hook := zeroLoggerHook{logs: &strings.Builder{}}
log = log.Hook(hook)

Expand Down Expand Up @@ -122,6 +149,15 @@ func TestFinalize_Deterministic(t *testing.T) {
// 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)
flagDKGPubDataPath = filepath.Join(bootDir, model.PathRandomBeaconPub)

// pick participant as signer
flagSignerDKGDataPath = getFirstQCSignerPath(t, internalPrivDir)

hook := zeroLoggerHook{logs: &strings.Builder{}}
log = log.Hook(hook)

Expand Down Expand Up @@ -184,6 +220,15 @@ func TestFinalize_SameSeedDifferentStateCommits(t *testing.T) {
// 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)
flagDKGPubDataPath = filepath.Join(bootDir, model.PathRandomBeaconPub)

// pick participant as signer
flagSignerDKGDataPath = getFirstQCSignerPath(t, internalPrivDir)

hook := zeroLoggerHook{logs: &strings.Builder{}}
log = log.Hook(hook)

Expand Down