-
Notifications
You must be signed in to change notification settings - Fork 180
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
DKG Result Submission Backward Compatibility #6827
base: feature/efm-recovery
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,9 +2,11 @@ package dkg | |
|
||
import ( | ||
"context" | ||
_ "embed" | ||
"encoding/json" | ||
"fmt" | ||
"strconv" | ||
"strings" | ||
"time" | ||
|
||
"github.com/rs/zerolog" | ||
|
@@ -22,6 +24,17 @@ import ( | |
"github.com/onflow/flow-go/module/epochs" | ||
) | ||
|
||
// submitResultV1Script is a version of the DKG result submission script compatible with | ||
// Protocol Version 1 (omitting DKG index map field). | ||
// Deprecated: | ||
// TODO(mainnet27, #6792): remove | ||
// | ||
//go:embed dkg_submit_result_v1.cdc | ||
var submitResultV1Script string | ||
|
||
//go:embed dkg_check_v2.cdc | ||
var checkV2Script string | ||
|
||
// Client is a client to the Flow DKG contract. Allows functionality to Broadcast, | ||
// read a Broadcast and submit the final result of the DKG protocol | ||
type Client struct { | ||
|
@@ -180,6 +193,115 @@ func (c *Client) Broadcast(msg model.BroadcastDKGMessage) error { | |
return nil | ||
} | ||
|
||
// submitResult_ProtocolV1 submits this node's locally computed DKG result (public key vector) | ||
// to the FlowDKG smart contract, using the submission API associated with Protocol Version 1. | ||
// Input public keys are expected to be either all nil or all non-nil. | ||
// Deprecated: This function is a temporary measure to provide backward compatibility between Protocol Versions 1 and 2. | ||
// Protocol Version 2 introduces the DKG index map, which must be included in result submissions (see SubmitParametersAndResult). | ||
// TODO(mainnet27, #6792): remove this function | ||
func (c *Client) submitResult_ProtocolV1(groupPublicKey crypto.PublicKey, publicKeys []crypto.PublicKey) error { | ||
started := time.Now() | ||
ctx, cancel := context.WithTimeout(context.Background(), epochs.TransactionSubmissionTimeout) | ||
defer cancel() | ||
|
||
// get account for given address | ||
account, err := c.GetAccount(ctx) | ||
if err != nil { | ||
return fmt.Errorf("could not get account details: %w", err) | ||
} | ||
|
||
// get latest finalized block to execute transaction | ||
latestBlock, err := c.FlowClient.GetLatestBlock(ctx, false) | ||
if err != nil { | ||
return fmt.Errorf("could not get latest block from node: %w", err) | ||
} | ||
|
||
tx := sdk.NewTransaction(). | ||
SetScript([]byte(templates.ReplaceAddresses(submitResultV1Script, c.env))). | ||
SetComputeLimit(9999). | ||
SetReferenceBlockID(latestBlock.ID). | ||
SetProposalKey(account.Address, c.AccountKeyIndex, account.Keys[int(c.AccountKeyIndex)].SequenceNumber). | ||
SetPayer(account.Address). | ||
AddAuthorizer(account.Address) | ||
|
||
// Note: We need to make sure that we pull the keys out in the same order that | ||
// we have done here. Group Public key first followed by the individual public keys | ||
finalSubmission := make([]cadence.Value, 0, len(publicKeys)) | ||
|
||
// first append group public key | ||
if groupPublicKey != nil { | ||
trimmedGroupHexString := trim0x(groupPublicKey.String()) | ||
cdcGroupString, err := cadence.NewString(trimmedGroupHexString) | ||
if err != nil { | ||
return fmt.Errorf("could not convert group key to cadence: %w", err) | ||
} | ||
finalSubmission = append(finalSubmission, cadence.NewOptional(cdcGroupString)) | ||
} else { | ||
finalSubmission = append(finalSubmission, cadence.NewOptional(nil)) | ||
} | ||
|
||
for _, publicKey := range publicKeys { | ||
|
||
// append individual public keys | ||
if publicKey != nil { | ||
trimmedHexString := trim0x(publicKey.String()) | ||
cdcPubKey, err := cadence.NewString(trimmedHexString) | ||
if err != nil { | ||
return fmt.Errorf("could not convert pub keyshare to cadence: %w", err) | ||
} | ||
finalSubmission = append(finalSubmission, cadence.NewOptional(cdcPubKey)) | ||
} else { | ||
finalSubmission = append(finalSubmission, cadence.NewOptional(nil)) | ||
} | ||
} | ||
|
||
err = tx.AddArgument(cadence.NewArray(finalSubmission)) | ||
if err != nil { | ||
return fmt.Errorf("could not add argument to transaction: %w", err) | ||
} | ||
|
||
// sign envelope using account signer | ||
err = tx.SignEnvelope(account.Address, c.AccountKeyIndex, c.Signer) | ||
if err != nil { | ||
return fmt.Errorf("could not sign transaction: %w", err) | ||
} | ||
|
||
c.Log.Info().Str("tx_id", tx.ID().Hex()).Msg("sending SubmitResult transaction") | ||
txID, err := c.SendTransaction(ctx, tx) | ||
if err != nil { | ||
return fmt.Errorf("failed to submit transaction: %w", err) | ||
} | ||
|
||
err = c.WaitForSealed(ctx, txID, started) | ||
if err != nil { | ||
return fmt.Errorf("failed to wait for transaction seal: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// checkDKGContractV2Compatible executes a script which imports a type only defined in the | ||
// FlowDKG smart contract version associated with Protocol Version 2. | ||
// By observing the result of this script execution, we can infer the current FlowDKG version. | ||
// Deprecated: | ||
// TODO(mainnet27, #6792): remove this function | ||
// Returns: | ||
// - (true, nil) if the smart contract is certainly v2-compatible (script succeeds) | ||
// - (false, nil) if the smart contract is certainly not v2-compatible (script fails with expected error) | ||
// - (false, error) if we don't know whether the smart contract is v2-compatible (script fails with unexpected error) | ||
func (c *Client) checkDKGContractV2Compatible(ctx context.Context) (bool, error) { | ||
script := []byte(templates.ReplaceAddresses(checkV2Script, c.env)) | ||
_, err := c.FlowClient.ExecuteScriptAtLatestBlock(ctx, script, nil) | ||
if err == nil { | ||
return true, nil | ||
} | ||
v1ErrorSubstring := "cannot find type in this scope: `FlowDKG.ResultSubmission`" | ||
if strings.Contains(err.Error(), v1ErrorSubstring) { | ||
return false, nil | ||
} | ||
return false, fmt.Errorf("unable to determine FlowDKG compatibility: %w", err) | ||
} | ||
|
||
// SubmitParametersAndResult posts the DKG setup parameters (`flow.DKGIndexMap`) and the node's locally-computed DKG result to | ||
// the DKG white-board smart contract. The DKG results are the node's local computation of the group public key and the public | ||
// key shares. Serialized public keys are encoded as lower-case hex strings. | ||
|
@@ -196,6 +318,22 @@ func (c *Client) SubmitParametersAndResult(indexMap flow.DKGIndexMap, groupPubli | |
ctx, cancel := context.WithTimeout(context.Background(), epochs.TransactionSubmissionTimeout) | ||
defer cancel() | ||
|
||
// TODO(mainnet27, #6792): remove this code block | ||
{ | ||
v2Compatible, err := c.checkDKGContractV2Compatible(ctx) | ||
// CASE 1: we know FlowDKG is not v2 compatible - fallback to v1 submission | ||
if !v2Compatible && err == nil { | ||
c.Log.Debug().Msg("detected FlowDKG version incompatible with protocol v2 - falling back to v1 result submission") | ||
return c.submitResult_ProtocolV1(groupPublicKey, publicKeys) | ||
} | ||
// CASE 2: we aren't sure whether FlowDKG is v2 compatible - fallback to v1 submission | ||
if err != nil { | ||
c.Log.Warn().Err(err).Msg("unable to determine FlowDKG compatibility - falling back to v1 result submission") | ||
return c.submitResult_ProtocolV1(groupPublicKey, publicKeys) | ||
} | ||
Comment on lines
+329
to
+333
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is probably not a question that we can address as part of this PR. When reading these lines, I am wondering what would happen if the protocol was on version 2 already, but script execution failed and we would submit with version 1 ... not sure how unlikely that is and what the consequences could be. Probably something best to be discussed in person. |
||
// CASE 3: FlowDKG is compatible with v2 - proceed with happy path logic | ||
} | ||
|
||
// get account for given address | ||
account, err := c.GetAccount(ctx) | ||
if err != nil { | ||
|
@@ -265,7 +403,7 @@ func (c *Client) SubmitParametersAndResult(indexMap flow.DKGIndexMap, groupPubli | |
return fmt.Errorf("could not sign transaction: %w", err) | ||
} | ||
|
||
c.Log.Info().Str("tx_id", tx.ID().Hex()).Msg("sending SubmitResult transaction") | ||
c.Log.Info().Str("tx_id", tx.ID().Hex()).Msg("sending SubmitParametersAndResult transaction") | ||
txID, err := c.SendTransaction(ctx, tx) | ||
if err != nil { | ||
return fmt.Errorf("failed to submit transaction: %w", err) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import FlowDKG from "FlowDKG" | ||
|
||
// This script is used to detect whether the deployed FlowDKG instance is compatible with Protocol Version 2 | ||
// ResultSubmission is a type only defined in the v2 contract version, so this script will: | ||
// - return [] when executing against a v2 contract | ||
// - return a predictable error when executing against a v1 contract | ||
// | ||
// TODO(mainnet27, #6792): remove this file | ||
access(all) fun main(): [FlowDKG.ResultSubmission] { | ||
return [] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import FlowDKG from "FlowDKG" | ||
|
||
// This transaction submits the DKG result using the contract API compatible with Protocol Version 1. | ||
// It is included as a backward compatibility measure during the transition to Protocol Version 2. | ||
// TODO(mainnet27, #6792): remove this file | ||
transaction(submission: [String?]) { | ||
|
||
let dkgParticipant: &FlowDKG.Participant | ||
|
||
prepare(signer: auth(BorrowValue) &Account) { | ||
self.dkgParticipant = signer.storage.borrow<&FlowDKG.Participant>(from: FlowDKG.ParticipantStoragePath) | ||
?? panic("Cannot borrow dkg participant reference") | ||
} | ||
|
||
execute { | ||
self.dkgParticipant.sendFinalSubmission(submission) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.