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 }