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 }