github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/dkg/client.go (about)

     1  package dkg
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"strconv"
     8  	"time"
     9  
    10  	"github.com/rs/zerolog"
    11  
    12  	"github.com/onflow/cadence"
    13  	"github.com/onflow/crypto"
    14  	"github.com/onflow/flow-core-contracts/lib/go/templates"
    15  
    16  	sdk "github.com/onflow/flow-go-sdk"
    17  	sdkcrypto "github.com/onflow/flow-go-sdk/crypto"
    18  
    19  	"github.com/onflow/flow-go/model/flow"
    20  	model "github.com/onflow/flow-go/model/messages"
    21  	"github.com/onflow/flow-go/module"
    22  	"github.com/onflow/flow-go/module/epochs"
    23  )
    24  
    25  // Client is a client to the Flow DKG contract. Allows functionality to Broadcast,
    26  // read a Broadcast and submit the final result of the DKG protocol
    27  type Client struct {
    28  	epochs.BaseClient
    29  
    30  	env templates.Environment
    31  }
    32  
    33  // NewClient initializes a new client to the Flow DKG contract
    34  func NewClient(
    35  	log zerolog.Logger,
    36  	flowClient module.SDKClientWrapper,
    37  	flowClientANID flow.Identifier,
    38  	signer sdkcrypto.Signer,
    39  	dkgContractAddress,
    40  	accountAddress string,
    41  	accountKeyIndex uint,
    42  ) *Client {
    43  
    44  	log = log.With().
    45  		Str("component", "dkg_contract_client").
    46  		Str("flow_client_an_id", flowClientANID.String()).
    47  		Logger()
    48  	base := epochs.NewBaseClient(log, flowClient, accountAddress, accountKeyIndex, signer)
    49  
    50  	env := templates.Environment{DkgAddress: dkgContractAddress}
    51  
    52  	return &Client{
    53  		BaseClient: *base,
    54  		env:        env,
    55  	}
    56  }
    57  
    58  // ReadBroadcast reads the broadcast messages from the smart contract.
    59  // Messages are returned in the order in which they were received
    60  // and stored in the smart contract
    61  func (c *Client) ReadBroadcast(fromIndex uint, referenceBlock flow.Identifier) ([]model.BroadcastDKGMessage, error) {
    62  
    63  	ctx := context.Background()
    64  
    65  	// construct read latest broadcast messages transaction
    66  	template := templates.GenerateGetDKGLatestWhiteBoardMessagesScript(c.env)
    67  	value, err := c.FlowClient.ExecuteScriptAtBlockID(
    68  		ctx,
    69  		sdk.Identifier(referenceBlock),
    70  		template,
    71  		[]cadence.Value{
    72  			cadence.NewInt(int(fromIndex)),
    73  		},
    74  	)
    75  	if err != nil {
    76  		return nil, fmt.Errorf("could not execute read broadcast script: %w", err)
    77  	}
    78  	values := value.(cadence.Array).Values
    79  
    80  	// unpack return from contract to `model.DKGMessage`
    81  	messages := make([]model.BroadcastDKGMessage, 0, len(values))
    82  	for _, val := range values {
    83  		fields := cadence.FieldsMappedByName(val.(cadence.Struct))
    84  
    85  		id, err := strconv.Unquote(fields["nodeID"].String())
    86  		if err != nil {
    87  			return nil, fmt.Errorf("could not unquote nodeID cadence string (%s): %w", id, err)
    88  		}
    89  
    90  		nodeID, err := flow.HexStringToIdentifier(id)
    91  		if err != nil {
    92  			return nil, fmt.Errorf("could not parse nodeID (%v): %w", val, err)
    93  		}
    94  
    95  		content := fields["content"]
    96  		jsonString, err := strconv.Unquote(content.String())
    97  		if err != nil {
    98  			return nil, fmt.Errorf("could not unquote json string: %w", err)
    99  		}
   100  
   101  		var flowMsg model.BroadcastDKGMessage
   102  		err = json.Unmarshal([]byte(jsonString), &flowMsg)
   103  		if err != nil {
   104  			return nil, fmt.Errorf("could not unmarshal dkg message: %w", err)
   105  		}
   106  		flowMsg.NodeID = nodeID
   107  		messages = append(messages, flowMsg)
   108  	}
   109  
   110  	return messages, nil
   111  }
   112  
   113  // Broadcast broadcasts a message to all other nodes participating in the
   114  // DKG. The message is broadcast by submitting a transaction to the DKG
   115  // smart contract. An error is returned if the transaction has failed.
   116  func (c *Client) Broadcast(msg model.BroadcastDKGMessage) error {
   117  
   118  	started := time.Now()
   119  
   120  	ctx, cancel := context.WithTimeout(context.Background(), epochs.TransactionSubmissionTimeout)
   121  	defer cancel()
   122  
   123  	// get account for given address
   124  	account, err := c.GetAccount(ctx)
   125  	if err != nil {
   126  		return fmt.Errorf("could not get account details: %w", err)
   127  	}
   128  
   129  	// get latest finalized block to execute transaction
   130  	latestBlock, err := c.FlowClient.GetLatestBlock(ctx, false)
   131  	if err != nil {
   132  		return fmt.Errorf("could not get latest block from node: %w", err)
   133  	}
   134  
   135  	// construct transaction to send dkg whiteboard message to contract
   136  	tx := sdk.NewTransaction().
   137  		SetScript(templates.GenerateSendDKGWhiteboardMessageScript(c.env)).
   138  		SetComputeLimit(9999).
   139  		SetReferenceBlockID(latestBlock.ID).
   140  		SetProposalKey(account.Address, int(c.AccountKeyIndex), account.Keys[int(c.AccountKeyIndex)].SequenceNumber).
   141  		SetPayer(account.Address).
   142  		AddAuthorizer(account.Address)
   143  
   144  	// json encode the DKG message
   145  	jsonMessage, err := json.Marshal(msg)
   146  	if err != nil {
   147  		return fmt.Errorf("could not marshal DKG messages struct: %v", err)
   148  	}
   149  
   150  	// add dkg message json encoded string to tx args
   151  	cdcMessage, err := cadence.NewString(string(jsonMessage))
   152  	if err != nil {
   153  		return fmt.Errorf("could not convert DKG message to cadence: %w", err)
   154  	}
   155  	err = tx.AddArgument(cdcMessage)
   156  	if err != nil {
   157  		return fmt.Errorf("could not add whiteboard dkg message to transaction: %w", err)
   158  	}
   159  
   160  	// sign envelope using account signer
   161  	err = tx.SignEnvelope(account.Address, int(c.AccountKeyIndex), c.Signer)
   162  	if err != nil {
   163  		return fmt.Errorf("could not sign transaction: %w", err)
   164  	}
   165  
   166  	// submit signed transaction to node
   167  	c.Log.Info().Str("tx_id", tx.ID().Hex()).Msg("sending Broadcast transaction")
   168  	txID, err := c.SendTransaction(ctx, tx)
   169  	if err != nil {
   170  		return fmt.Errorf("failed to submit transaction: %w", err)
   171  	}
   172  
   173  	err = c.WaitForSealed(ctx, txID, started)
   174  	if err != nil {
   175  		return fmt.Errorf("failed to wait for transaction seal: %w", err)
   176  	}
   177  
   178  	return nil
   179  }
   180  
   181  // SubmitResult submits the final public result of the DKG protocol. This
   182  // represents the group public key and the node's local computation of the
   183  // public keys for each DKG participant. Serialized pub keys are encoded as hex.
   184  func (c *Client) SubmitResult(groupPublicKey crypto.PublicKey, publicKeys []crypto.PublicKey) error {
   185  
   186  	started := time.Now()
   187  	ctx, cancel := context.WithTimeout(context.Background(), epochs.TransactionSubmissionTimeout)
   188  	defer cancel()
   189  
   190  	// get account for given address
   191  	account, err := c.GetAccount(ctx)
   192  	if err != nil {
   193  		return fmt.Errorf("could not get account details: %w", err)
   194  	}
   195  
   196  	// get latest finalized block to execute transaction
   197  	latestBlock, err := c.FlowClient.GetLatestBlock(ctx, false)
   198  	if err != nil {
   199  		return fmt.Errorf("could not get latest block from node: %w", err)
   200  	}
   201  
   202  	tx := sdk.NewTransaction().
   203  		SetScript(templates.GenerateSendDKGFinalSubmissionScript(c.env)).
   204  		SetComputeLimit(9999).
   205  		SetReferenceBlockID(latestBlock.ID).
   206  		SetProposalKey(account.Address, int(c.AccountKeyIndex), account.Keys[int(c.AccountKeyIndex)].SequenceNumber).
   207  		SetPayer(account.Address).
   208  		AddAuthorizer(account.Address)
   209  
   210  	// Note: We need to make sure that we pull the keys out in the same order that
   211  	// we have done here. Group Public key first followed by the individual public keys
   212  	finalSubmission := make([]cadence.Value, 0, len(publicKeys))
   213  
   214  	// first append group public key
   215  	if groupPublicKey != nil {
   216  		trimmedGroupHexString := trim0x(groupPublicKey.String())
   217  		cdcGroupString, err := cadence.NewString(trimmedGroupHexString)
   218  		if err != nil {
   219  			return fmt.Errorf("could not convert group key to cadence: %w", err)
   220  		}
   221  		finalSubmission = append(finalSubmission, cadence.NewOptional(cdcGroupString))
   222  	} else {
   223  		finalSubmission = append(finalSubmission, cadence.NewOptional(nil))
   224  	}
   225  
   226  	for _, publicKey := range publicKeys {
   227  
   228  		// append individual public keys
   229  		if publicKey != nil {
   230  			trimmedHexString := trim0x(publicKey.String())
   231  			cdcPubKey, err := cadence.NewString(trimmedHexString)
   232  			if err != nil {
   233  				return fmt.Errorf("could not convert pub keyshare to cadence: %w", err)
   234  			}
   235  			finalSubmission = append(finalSubmission, cadence.NewOptional(cdcPubKey))
   236  		} else {
   237  			finalSubmission = append(finalSubmission, cadence.NewOptional(nil))
   238  		}
   239  	}
   240  
   241  	err = tx.AddArgument(cadence.NewArray(finalSubmission))
   242  	if err != nil {
   243  		return fmt.Errorf("could not add argument to transaction: %w", err)
   244  	}
   245  
   246  	// sign envelope using account signer
   247  	err = tx.SignEnvelope(account.Address, int(c.AccountKeyIndex), c.Signer)
   248  	if err != nil {
   249  		return fmt.Errorf("could not sign transaction: %w", err)
   250  	}
   251  
   252  	c.Log.Info().Str("tx_id", tx.ID().Hex()).Msg("sending SubmitResult transaction")
   253  	txID, err := c.SendTransaction(ctx, tx)
   254  	if err != nil {
   255  		return fmt.Errorf("failed to submit transaction: %w", err)
   256  	}
   257  
   258  	err = c.WaitForSealed(ctx, txID, started)
   259  	if err != nil {
   260  		return fmt.Errorf("failed to wait for transaction seal: %w", err)
   261  	}
   262  
   263  	return nil
   264  }
   265  
   266  // trim0x trims the `0x` if it exists from a hexadecimal string
   267  // This method is required as the DKG contract expects key lengths of 192 bytes
   268  // the `PublicKey.String()` method returns the hexadecimal string representation of the
   269  // public key prefixed with `0x` resulting in length of 194 bytes.
   270  func trim0x(hexString string) string {
   271  
   272  	prefix := hexString[:2]
   273  	if prefix == "0x" {
   274  		return hexString[2:]
   275  	}
   276  
   277  	return hexString
   278  }