Skip to content

Commit

Permalink
Merge branch 'feature/efm-recovery' into jord/6622-chunk-service-events
Browse files Browse the repository at this point in the history
  • Loading branch information
jordanschalm authored Dec 3, 2024
2 parents 49bcbf6 + 7c71c41 commit 176100f
Show file tree
Hide file tree
Showing 26 changed files with 191 additions and 142 deletions.
2 changes: 1 addition & 1 deletion cmd/bootstrap/cmd/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func constructRootEpochEvents(
participants flow.IdentityList,
assignments flow.AssignmentList,
clusterQCs []*flow.QuorumCertificate,
dkgData dkg.DKGData,
dkgData dkg.ThresholdKeySet,
dkgIndexMap flow.DKGIndexMap,
) (*flow.EpochSetup, *flow.EpochCommit) {

Expand Down
32 changes: 14 additions & 18 deletions cmd/bootstrap/cmd/dkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,27 @@ import (
"github.com/onflow/flow-go/state/protocol/inmem"
)

func runBeaconKG(nodes []model.NodeInfo) (dkg.DKGData, flow.DKGIndexMap) {
func runBeaconKG(nodes []model.NodeInfo) (dkg.ThresholdKeySet, flow.DKGIndexMap) {
n := len(nodes)
log.Info().Msgf("read %v node infos for DKG", n)

log.Debug().Msgf("will run DKG")
var dkgData dkg.DKGData
var err error
dkgData, err = bootstrapDKG.RandomBeaconKG(n, GenerateRandomSeed(crypto.KeyGenSeedMinLen))
randomBeaconData, err := bootstrapDKG.RandomBeaconKG(n, GenerateRandomSeed(crypto.KeyGenSeedMinLen))
if err != nil {
log.Fatal().Err(err).Msg("error running DKG")
}
log.Info().Msgf("finished running DKG")

pubKeyShares := make([]encodable.RandomBeaconPubKey, 0, len(dkgData.PubKeyShares))
for _, pubKey := range dkgData.PubKeyShares {
pubKeyShares = append(pubKeyShares, encodable.RandomBeaconPubKey{PublicKey: pubKey})
}

privKeyShares := make([]encodable.RandomBeaconPrivKey, 0, len(dkgData.PrivKeyShares))
for i, privKey := range dkgData.PrivKeyShares {
encodableParticipants := make([]inmem.ThresholdParticipant, 0, len(nodes))
for i, privKey := range randomBeaconData.PrivKeyShares {
nodeID := nodes[i].NodeID

encKey := encodable.RandomBeaconPrivKey{PrivateKey: privKey}
privKeyShares = append(privKeyShares, encKey)
encodableParticipants = append(encodableParticipants, inmem.ThresholdParticipant{
PrivKeyShare: encKey,
PubKeyShare: encodable.RandomBeaconPubKey{PublicKey: randomBeaconData.PubKeyShares[i]},
NodeID: nodeID,
})

err = common.WriteJSON(fmt.Sprintf(model.PathRandomBeaconPriv, nodeID), flagOutdir, encKey)
if err != nil {
Expand All @@ -46,23 +43,22 @@ func runBeaconKG(nodes []model.NodeInfo) (dkg.DKGData, flow.DKGIndexMap) {
log.Info().Msgf("wrote file %s/%s", flagOutdir, fmt.Sprintf(model.PathRandomBeaconPriv, nodeID))
}

indexMap := make(flow.DKGIndexMap, len(pubKeyShares))
indexMap := make(flow.DKGIndexMap, len(nodes))
for i, node := range nodes {
indexMap[node.NodeID] = i
}

// write full DKG info that will be used to construct QC
err = common.WriteJSON(model.PathRootDKGData, flagOutdir, inmem.EncodableFullDKG{
err = common.WriteJSON(model.PathRootDKGData, flagOutdir, inmem.ThresholdKeySet{
GroupKey: encodable.RandomBeaconPubKey{
PublicKey: dkgData.PubGroupKey,
PublicKey: randomBeaconData.PubGroupKey,
},
PubKeyShares: pubKeyShares,
PrivKeyShares: privKeyShares,
Participants: encodableParticipants,
})
if err != nil {
log.Fatal().Err(err).Msg("failed to write json")
}
log.Info().Msgf("wrote file %s/%s", flagOutdir, model.PathRootDKGData)

return dkgData, indexMap
return randomBeaconData, indexMap
}
27 changes: 13 additions & 14 deletions cmd/bootstrap/cmd/finalize.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ func finalize(cmd *cobra.Command, args []string) {
log.Info().Msgf("received votes total: %v", len(votes))

log.Info().Msg("reading dkg data")
dkgData := readDKGData()
dkgData, _ := readRandomBeaconKeys()
log.Info().Msg("")

log.Info().Msg("reading intermediary bootstrapping data")
Expand Down Expand Up @@ -347,29 +347,28 @@ func readRootBlock() *flow.Block {
return rootBlock
}

// readDKGData reads DKG data from disc, this file needs to be prepared with
// rootblock command
func readDKGData() dkg.DKGData {
encodableDKG, err := utils.ReadData[inmem.EncodableFullDKG](flagDKGDataPath)
// readRandomBeaconKeys reads the threshold key data from disc.
// This file needs to be prepared with rootblock command
func readRandomBeaconKeys() (dkg.ThresholdKeySet, flow.DKGIndexMap) {
encodableDKG, err := utils.ReadData[inmem.ThresholdKeySet](flagDKGDataPath)
if err != nil {
log.Fatal().Err(err).Msg("could not read DKG data")
log.Fatal().Err(err).Msg("loading threshold key data for Random Beacon failed")
}

dkgData := dkg.DKGData{
dkgData := dkg.ThresholdKeySet{
PrivKeyShares: nil,
PubGroupKey: encodableDKG.GroupKey.PublicKey,
PubKeyShares: nil,
}

for _, pubKey := range encodableDKG.PubKeyShares {
dkgData.PubKeyShares = append(dkgData.PubKeyShares, pubKey.PublicKey)
}

for _, privKey := range encodableDKG.PrivKeyShares {
dkgData.PrivKeyShares = append(dkgData.PrivKeyShares, privKey.PrivateKey)
indexMap := make(flow.DKGIndexMap, len(encodableDKG.Participants))
for i, participant := range encodableDKG.Participants {
dkgData.PubKeyShares = append(dkgData.PubKeyShares, participant.PubKeyShare.PublicKey)
dkgData.PrivKeyShares = append(dkgData.PrivKeyShares, participant.PrivKeyShare.PrivateKey)
indexMap[participant.NodeID] = i
}

return dkgData
return dkgData, indexMap
}

// Validation utility methods ------------------------------------------------
Expand Down
8 changes: 4 additions & 4 deletions cmd/bootstrap/cmd/qc.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import (
)

// constructRootQC constructs root QC based on root block, votes and dkg info
func constructRootQC(block *flow.Block, votes []*model.Vote, allNodes, internalNodes []bootstrap.NodeInfo, dkgData dkg.DKGData) *flow.QuorumCertificate {
func constructRootQC(block *flow.Block, votes []*model.Vote, allNodes, internalNodes []bootstrap.NodeInfo, randomBeaconData dkg.ThresholdKeySet) *flow.QuorumCertificate {

identities := bootstrap.ToIdentityList(allNodes)
participantData, err := run.GenerateQCParticipantData(allNodes, internalNodes, dkgData)
participantData, err := run.GenerateQCParticipantData(allNodes, internalNodes, randomBeaconData)
if err != nil {
log.Fatal().Err(err).Msg("failed to generate QC participant data")
}
Expand All @@ -36,8 +36,8 @@ func constructRootQC(block *flow.Block, votes []*model.Vote, allNodes, internalN
}

// NOTE: allNodes must be in the same order as when generating the DKG
func constructRootVotes(block *flow.Block, allNodes, internalNodes []bootstrap.NodeInfo, dkgData dkg.DKGData) {
participantData, err := run.GenerateQCParticipantData(allNodes, internalNodes, dkgData)
func constructRootVotes(block *flow.Block, allNodes, internalNodes []bootstrap.NodeInfo, randomBeaconData dkg.ThresholdKeySet) {
participantData, err := run.GenerateQCParticipantData(allNodes, internalNodes, randomBeaconData)
if err != nil {
log.Fatal().Err(err).Msg("failed to generate QC participant data")
}
Expand Down
10 changes: 5 additions & 5 deletions cmd/bootstrap/cmd/rootblock.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ func rootBlock(cmd *cobra.Command, args []string) {
log.Info().Msg("")

log.Info().Msg("running DKG for consensus nodes")
dkgData, dkgIndexMap := runBeaconKG(model.FilterByRole(stakingNodes, flow.RoleConsensus))
randomBeaconData, dkgIndexMap := runBeaconKG(model.FilterByRole(stakingNodes, flow.RoleConsensus))
log.Info().Msg("")

// create flow.IdentityList representation of the participant set
Expand All @@ -200,8 +200,8 @@ func rootBlock(cmd *cobra.Command, args []string) {
log.Info().Msg("")

log.Info().Msg("constructing intermediary bootstrapping data")
epochSetup, epochCommit := constructRootEpochEvents(header.View, participants, assignments, clusterQCs, dkgData, dkgIndexMap)
epochConfig := generateExecutionStateEpochConfig(epochSetup, clusterQCs, dkgData)
epochSetup, epochCommit := constructRootEpochEvents(header.View, participants, assignments, clusterQCs, randomBeaconData, dkgIndexMap)
epochConfig := generateExecutionStateEpochConfig(epochSetup, clusterQCs, randomBeaconData)
intermediaryEpochData := IntermediaryEpochData{
RootEpochSetup: epochSetup,
RootEpochCommit: epochCommit,
Expand Down Expand Up @@ -245,7 +245,7 @@ func rootBlock(cmd *cobra.Command, args []string) {
block,
model.FilterByRole(stakingNodes, flow.RoleConsensus),
model.FilterByRole(internalNodes, flow.RoleConsensus),
dkgData,
randomBeaconData,
)
log.Info().Msg("")
}
Expand Down Expand Up @@ -286,7 +286,7 @@ func validateEpochConfig() error {
func generateExecutionStateEpochConfig(
epochSetup *flow.EpochSetup,
clusterQCs []*flow.QuorumCertificate,
dkgData dkg.DKGData,
dkgData dkg.ThresholdKeySet,
) epochs.EpochConfig {

randomSource := make([]byte, flow.EpochSetupRandomSourceLength)
Expand Down
12 changes: 6 additions & 6 deletions cmd/bootstrap/dkg/dkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import (
)

// RandomBeaconKG is centralized BLS threshold signature key generation.
func RandomBeaconKG(n int, seed []byte) (model.DKGData, error) {
func RandomBeaconKG(n int, seed []byte) (model.ThresholdKeySet, error) {

if n == 1 {
sk, pk, pkGroup, err := thresholdSignKeyGenOneNode(seed)
if err != nil {
return model.DKGData{}, fmt.Errorf("Beacon KeyGen failed: %w", err)
return model.ThresholdKeySet{}, fmt.Errorf("Beacon KeyGen failed: %w", err)
}

dkgData := model.DKGData{
dkgData := model.ThresholdKeySet{
PrivKeyShares: sk,
PubGroupKey: pkGroup,
PubKeyShares: pk,
Expand All @@ -29,16 +29,16 @@ func RandomBeaconKG(n int, seed []byte) (model.DKGData, error) {
skShares, pkShares, pkGroup, err := crypto.BLSThresholdKeyGen(int(n),
signature.RandomBeaconThreshold(int(n)), seed)
if err != nil {
return model.DKGData{}, fmt.Errorf("Beacon KeyGen failed: %w", err)
return model.ThresholdKeySet{}, fmt.Errorf("Beacon KeyGen failed: %w", err)
}

dkgData := model.DKGData{
randomBeaconData := model.ThresholdKeySet{
PrivKeyShares: skShares,
PubGroupKey: pkGroup,
PubKeyShares: pkShares,
}

return dkgData, nil
return randomBeaconData, nil
}

// Beacon KG with one node
Expand Down
6 changes: 3 additions & 3 deletions cmd/bootstrap/run/qc.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type Participant struct {
RandomBeaconPrivKey crypto.PrivateKey
}

// ParticipantData represents a subset of all consensus participants that contributing to some signing process (at the moment, we only use
// ParticipantData represents a subset of all consensus participants that contribute to some signing process (at the moment, we only use
// it for the contributors for the root QC). For mainnet, this a *strict subset* of all consensus participants:
// - In an early step during the bootstrapping process, every node operator locally generates votes for the root block from the nodes they
// operate. During the vote-generation step, (see function `constructRootVotes`), `Participants` represents only the operator's own
Expand Down Expand Up @@ -190,7 +190,7 @@ func createValidator(committee hotstuff.DynamicCommittee) (hotstuff.Validator, e
// LIMITATION: this function only supports the 'trusted dealer' model, where for the consensus committee (`allNodes`)
// a trusted dealer generated the threshold-signature key (`dkgData` containing key shares and group key). Therefore,
// `allNodes` must be in the same order that was used when running the DKG.
func GenerateQCParticipantData(allNodes, internalNodes []bootstrap.NodeInfo, dkgData dkg.DKGData) (*ParticipantData, error) {
func GenerateQCParticipantData(allNodes, internalNodes []bootstrap.NodeInfo, dkgData dkg.ThresholdKeySet) (*ParticipantData, error) {
// stakingNodes can include external validators, so it can be longer than internalNodes
if len(allNodes) < len(internalNodes) {
return nil, fmt.Errorf("need at least as many staking public keys as private keys (pub=%d, priv=%d)", len(allNodes), len(internalNodes))
Expand Down Expand Up @@ -236,7 +236,7 @@ func GenerateQCParticipantData(allNodes, internalNodes []bootstrap.NodeInfo, dkg

dkgParticipant, ok := participantLookup[node.NodeID]
if !ok {
return nil, fmt.Errorf("nonexistannt node id (%x) in participant lookup", node.NodeID)
return nil, fmt.Errorf("nonexistent node id (%x) in participant lookup", node.NodeID)
}
dkgIndex := dkgParticipant.Index

Expand Down
2 changes: 1 addition & 1 deletion cmd/util/cmd/epochs/cmd/recover.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
// EFM can be exited only by a special service event, EpochRecover, which initially originates from a manual service account transaction.
// The full epoch data must be generated manually and submitted with this transaction in order for an
// EpochRecover event to be emitted. This command retrieves the current protocol state identities, computes the cluster assignment using those
// identities, generates the cluster QCs and retrieves the DKG key vector of the last successful epoch.
// identities, generates the cluster QCs and retrieves the Random Beacon key vector of the last successful epoch.
// This recovery process has some constraints:
// - The RecoveryEpoch must have exactly the same consensus committee as participated in the most recent successful DKG.
// - The RecoveryEpoch must contain enough "internal" collection nodes so that all clusters contain a supermajority of "internal" collection nodes (same constraint as sporks)
Expand Down
6 changes: 3 additions & 3 deletions consensus/hotstuff/signature/randombeacon_signer_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ func (s *EpochAwareRandomBeaconKeyStore) ByView(view uint64) (crypto.PrivateKey,
}

// When DKG has completed,
// - if a node successfully generated the DKG key, the valid private key will be stored in database.
// - if a node failed to generate the DKG key, we will save a record in database to indicate this
// node has no private key for this epoch.
// - if a node successfully generated the Random Beacon key, the valid private key will be stored in database.
// - if a node failed to generate the Random Beacon key, we will save a record in database to indicate this
// node has no private key for this epoch.
// Within the epoch, we can look up my random beacon private key for the epoch. There are 3 cases:
// 1. DKG has completed, and the private key is stored in database, and we can retrieve it (happy path)
// 2. DKG has completed, but we failed to generate a private key (unhappy path)
Expand Down
2 changes: 1 addition & 1 deletion consensus/hotstuff/verification/combined_verifier_v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ func (c *CombinedVerifierV3) VerifyQC(signers flow.IdentitySkeletonList, sigData
if protocol.IsIdentityNotFound(err) {
return model.NewInvalidSignerErrorf("%v is not a random beacon participant: %w", signerID, err)
}
return fmt.Errorf("unexpected error retrieving dkg key share for signer %v: %w", signerID, err)
return fmt.Errorf("unexpected error retrieving Random Beacon key share for signer %v: %w", signerID, err)
}
beaconPubKeys = append(beaconPubKeys, keyShare)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -812,7 +812,7 @@ func TestCombinedVoteProcessorV2_BuildVerifyQC(t *testing.T) {
identity.StakingPubKey = stakingPriv.PublicKey()

keys := &storagemock.SafeBeaconKeys{}
// there is no DKG key for this epoch
// there is no Random Beacon key for this epoch
keys.On("RetrieveMyBeaconPrivateKey", epochCounter).Return(nil, false, nil)

beaconSignerStore := hsig.NewEpochAwareRandomBeaconKeyStore(epochLookup, keys)
Expand All @@ -833,7 +833,7 @@ func TestCombinedVoteProcessorV2_BuildVerifyQC(t *testing.T) {
}

keys := &storagemock.SafeBeaconKeys{}
// there is DKG key for this epoch
// there is Random Beacon key for this epoch
keys.On("RetrieveMyBeaconPrivateKey", epochCounter).Return(dkgKey, true, nil)

beaconSignerStore := hsig.NewEpochAwareRandomBeaconKeyStore(epochLookup, keys)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -948,7 +948,7 @@ func TestCombinedVoteProcessorV3_BuildVerifyQC(t *testing.T) {
identity.StakingPubKey = stakingPriv.PublicKey()

keys := &storagemock.SafeBeaconKeys{}
// there is no DKG key for this epoch
// there is no Random Beacon key for this epoch
keys.On("RetrieveMyBeaconPrivateKey", epochCounter).Return(nil, false, nil)

beaconSignerStore := hsig.NewEpochAwareRandomBeaconKeyStore(epochLookup, keys)
Expand All @@ -970,7 +970,7 @@ func TestCombinedVoteProcessorV3_BuildVerifyQC(t *testing.T) {
}

keys := &storagemock.SafeBeaconKeys{}
// there is DKG key for this epoch
// there is Random Beacon key for this epoch
keys.On("RetrieveMyBeaconPrivateKey", epochCounter).Return(dkgKey, true, nil)

beaconSignerStore := hsig.NewEpochAwareRandomBeaconKeyStore(epochLookup, keys)
Expand Down
2 changes: 1 addition & 1 deletion consensus/integration/epoch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func TestEpochTransition_IdentitiesOverlap(t *testing.T) {
newIdentity,
)

// generate new identities for next epoch, it will generate new DKG keys for random beacon participants
// generate new identities for next epoch, it will generate new Random Beacon keys for random beacon participants
nextEpochParticipantData := completeConsensusIdentities(t, privateNodeInfos[1:])
rootSnapshot = withNextEpoch(t, rootSnapshot, nextEpochIdentities, nextEpochParticipantData, consensusParticipants, 4, func(block *flow.Block) *flow.QuorumCertificate {
return createRootQC(t, block, firstEpochConsensusParticipants)
Expand Down
2 changes: 1 addition & 1 deletion consensus/integration/nodes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ func createNode(
require.NoError(t, err)

keys := &storagemock.SafeBeaconKeys{}
// there is DKG key for this epoch
// there is Random Beacon key for this epoch
keys.On("RetrieveMyBeaconPrivateKey", mock.Anything).Return(
func(epochCounter uint64) crypto.PrivateKey {
dkgInfo, ok := participant.beaconInfoByEpoch[epochCounter]
Expand Down
14 changes: 7 additions & 7 deletions integration/testnet/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -1072,8 +1072,8 @@ func BootstrapNetwork(networkConf NetworkConfig, bootstrapDir string, chainID fl

allNodeInfos := append(toNodeInfos(stakedConfs), followerInfos...)

// IMPORTANT: we must use this ordering when writing the DKG keys as
// this ordering defines the DKG participant's indices
// IMPORTANT: we must use this ordering when writing the Random Beacon keys as
// this ordering defines the DKG participants' indices
stakedNodeInfos := bootstrap.Sort(toNodeInfos(stakedConfs), flow.Canonical[flow.Identity])

dkg, dkgIndexMap, err := runBeaconKG(stakedConfs)
Expand Down Expand Up @@ -1344,28 +1344,28 @@ func setupKeys(networkConf NetworkConfig) ([]ContainerConfig, error) {
// and returns all DKG data. This includes the group private key, node indices,
// and per-node public and private key-shares.
// Only consensus nodes participate in the DKG.
func runBeaconKG(confs []ContainerConfig) (dkgmod.DKGData, flow.DKGIndexMap, error) {
func runBeaconKG(confs []ContainerConfig) (dkgmod.ThresholdKeySet, flow.DKGIndexMap, error) {

// filter by consensus nodes
consensusNodes := bootstrap.Sort(bootstrap.FilterByRole(toNodeInfos(confs), flow.RoleConsensus), flow.Canonical[flow.Identity])
nConsensusNodes := len(consensusNodes)

dkgSeed, err := getSeed()
if err != nil {
return dkgmod.DKGData{}, nil, err
return dkgmod.ThresholdKeySet{}, nil, err
}

dkg, err := dkg.RandomBeaconKG(nConsensusNodes, dkgSeed)
randomBeaconData, err := dkg.RandomBeaconKG(nConsensusNodes, dkgSeed)
if err != nil {
return dkgmod.DKGData{}, nil, err
return dkgmod.ThresholdKeySet{}, nil, err
}

indexMap := make(flow.DKGIndexMap, nConsensusNodes)
for i, node := range consensusNodes {
indexMap[node.NodeID] = i
}

return dkg, indexMap, nil
return randomBeaconData, indexMap, nil
}

// setupClusterGenesisBlockQCs generates bootstrapping resources necessary for each collector cluster:
Expand Down
Loading

0 comments on commit 176100f

Please sign in to comment.