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