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