@@ -6,12 +6,16 @@ package ballot
6
6
7
7
import (
8
8
"context"
9
+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
9
10
"github.com/ethereum/go-ethereum/common"
10
11
"github.com/ethereum/go-ethereum/common/hexutil"
11
12
"github.com/ethereum/go-ethereum/ethclient"
12
13
"github.com/ethereum/go-ethereum/log"
13
14
"github.com/machinebox/graphql"
15
+ "math"
16
+ "math/big"
14
17
"oracle-watchdog/internal/logger"
18
+ "oracle-watchdog/internal/modules/utils"
15
19
"sync"
16
20
"time"
17
21
)
@@ -23,9 +27,14 @@ query ($addr: Address!) {
23
27
}
24
28
}`
25
29
30
+ // participantsWeightPushPackSize represents the number of participants
31
+ // pushed into the voting contract at once.
32
+ const participantsWeightPushPackSize = 20
33
+
26
34
// FinalizingWorker represents a worker structure and processor for single
27
35
// closed ballot processing.
28
36
type FinalizingWorker struct {
37
+ cfg * FinalizingOracleConfig
29
38
eth * ethclient.Client
30
39
api * graphql.Client
31
40
sigTerminate chan bool
@@ -38,14 +47,16 @@ type FinalizingWorker struct {
38
47
// ClosedBallot represents a ballot record received from the remote API server
39
48
// and prepared to be finished by the oracle.
40
49
type ClosedBallot struct {
41
- Name string `json:"name"`
42
- Address common.Address `json:"address"`
50
+ Name string `json:"name"`
51
+ Address common.Address `json:"address"`
52
+ Proposals []string `json:"proposals"`
43
53
}
44
54
45
55
// Participant represents a single address involved in the ballot voting.
46
56
type Participant struct {
47
57
Address common.Address
48
- Total hexutil.Big
58
+ Total * big.Int
59
+ Vote int
49
60
TimeStamp int64
50
61
}
51
62
@@ -57,9 +68,11 @@ func NewWorker(
57
68
wg * sync.WaitGroup ,
58
69
queue chan ClosedBallot ,
59
70
log logger.Logger ,
71
+ cfg * FinalizingOracleConfig ,
60
72
) * FinalizingWorker {
61
73
// make the worker
62
74
w := FinalizingWorker {
75
+ cfg : cfg ,
63
76
eth : rpc ,
64
77
api : api ,
65
78
waitGroup : wg ,
@@ -141,14 +154,32 @@ func (fw *FinalizingWorker) processBallot() error {
141
154
}
142
155
143
156
// do we have any participants at all?
144
- if party == nil || len (party ) == 0 {
157
+ if party == nil {
158
+ fw .log .Noticef ("nil voters received for %s" , fw .ballot .Address .String ())
145
159
return nil
146
160
}
147
161
162
+ // get the signing transactor
163
+ sig , err := utils .Transactor (fw .log , & fw .cfg .KeyStore , & fw .cfg .KeySecret )
164
+ if err != nil {
165
+ fw .log .Errorf ("can not get push transactor; %s" , err .Error ())
166
+ return err
167
+ }
168
+
148
169
// push participants data to the ballot contract
170
+ if err := fw .pushVoters (contract , sig , party ); err != nil {
171
+ fw .log .Debugf ("can not push voters; %s" , err .Error ())
172
+ return err
173
+ }
149
174
150
175
// finalize the ballot
176
+ if err := fw .finalize (contract , sig ); err != nil {
177
+ fw .log .Debugf ("can not finalize ballot; %s" , err .Error ())
178
+ return err
179
+ }
151
180
181
+ // calculate and log the winner of the ballot
182
+ fw .winner (party )
152
183
return nil
153
184
}
154
185
@@ -183,12 +214,15 @@ func (fw *FinalizingWorker) participants(contract *BallotContract) ([]Participan
183
214
// make the participant
184
215
party := Participant {
185
216
Address : it .Event .Voter ,
217
+ Vote : int (it .Event .Vote .Uint64 ()),
186
218
Total : fw .accountTotal (it .Event .Voter ),
187
219
TimeStamp : time .Now ().UTC ().Unix (),
188
220
}
189
221
190
222
// push to the list
191
- list = append (list , party )
223
+ if party .Total != nil {
224
+ list = append (list , party )
225
+ }
192
226
193
227
// check for possible termination request
194
228
select {
@@ -207,7 +241,7 @@ func (fw *FinalizingWorker) participants(contract *BallotContract) ([]Participan
207
241
208
242
// accountTotal pulls the current total value of the given account
209
243
// from remote API server using GraphQL call.
210
- func (fw * FinalizingWorker ) accountTotal (addr common.Address ) hexutil. Big {
244
+ func (fw * FinalizingWorker ) accountTotal (addr common.Address ) * big. Int {
211
245
// log action
212
246
fw .log .Debugf ("loading account total for [%s]" , addr .String ())
213
247
@@ -225,10 +259,122 @@ func (fw *FinalizingWorker) accountTotal(addr common.Address) hexutil.Big {
225
259
// execute the request and parse the response
226
260
if err := fw .api .Run (context .Background (), req , & res ); err != nil {
227
261
fw .log .Errorf ("can not pull account total for [%s]; %s" , addr .String (), err .Error ())
228
- return hexutil. Big {}
262
+ return nil
229
263
}
230
264
231
265
// log the value
232
266
fw .log .Debugf ("account total for [%s] is %s" , addr .String (), res .Account .TotalValue .String ())
233
- return res .Account .TotalValue
267
+ return res .Account .TotalValue .ToInt ()
268
+ }
269
+
270
+ // pushVoters updates the ballot voters information inside the contract
271
+ // so the winner can be decided.
272
+ func (fw * FinalizingWorker ) pushVoters (contract * BallotContract , sig * bind.TransactOpts , party []Participant ) error {
273
+ // inform
274
+ fw .log .Debugf ("pushing %d voters stats into %s" , len (party ), fw .ballot .Address .String ())
275
+
276
+ // prep the pack containers
277
+ voters := make ([]common.Address , 0 )
278
+ totals := make ([]* big.Int , 0 )
279
+ stamps := make ([]* big.Int , 0 )
280
+
281
+ // loop the whole party and each time build a pack to be pushed
282
+ var index int
283
+ for index < len (party ) {
284
+ // add the participant to the pack
285
+ voters = append (voters , party [index ].Address )
286
+ totals = append (totals , party [index ].Total )
287
+ stamps = append (stamps , big .NewInt (party [index ].TimeStamp ))
288
+ index ++
289
+
290
+ // if we reached the pack boundary, push the pack to contract
291
+ if len (voters ) >= participantsWeightPushPackSize {
292
+ // make the push
293
+ if err := fw .pushPack (contract , sig , voters , totals , stamps ); err != nil {
294
+ fw .log .Errorf ("can not push a voters pack; %s" , err .Error ())
295
+ return err
296
+ }
297
+
298
+ // clear the pack so we can start again
299
+ voters = voters [:0 ]
300
+ totals = totals [:0 ]
301
+ stamps = stamps [:0 ]
302
+ }
303
+ }
304
+
305
+ // any remaining pack members left to process?
306
+ if 0 < len (voters ) {
307
+ // make the last pack push
308
+ if err := fw .pushPack (contract , sig , voters , totals , stamps ); err != nil {
309
+ fw .log .Errorf ("can not push a voters pack; %s" , err .Error ())
310
+ return err
311
+ }
312
+ }
313
+
314
+ return nil
315
+ }
316
+
317
+ // pushPack uses the contract transaction call to make a push to the contract
318
+ func (fw * FinalizingWorker ) pushPack (contract * BallotContract , sig * bind.TransactOpts , voters []common.Address , totals []* big.Int , stamps []* big.Int ) error {
319
+ // make a transaction
320
+ tx , err := contract .FeedWeights (sig , voters , totals , stamps )
321
+ if err != nil {
322
+ fw .log .Errorf ("voters transaction failed; %s" , err .Error ())
323
+ return err
324
+ }
325
+
326
+ // inform
327
+ fw .log .Infof ("voters pack fed into the ballot %s by %s" , fw .ballot .Address .String (), tx .Hash ().String ())
328
+ return nil
329
+ }
330
+
331
+ // finalize locks the ballot in finished state and allows winner calculation.
332
+ func (fw * FinalizingWorker ) finalize (contract * BallotContract , sig * bind.TransactOpts ) error {
333
+ // inform
334
+ fw .log .Debugf ("finalizing ballot %s" , fw .ballot .Address .String ())
335
+
336
+ // invoke the finalize
337
+ tx , err := contract .Finalize (sig )
338
+ if err != nil {
339
+ fw .log .Errorf ("can not finalize ballot %s; %s" , fw .ballot .Address .String (), err .Error ())
340
+ return err
341
+ }
342
+
343
+ // inform about success
344
+ fw .log .Infof ("ballot %s has been finalized by %s" , fw .ballot .Address .String (), tx .Hash ().String ())
345
+ return nil
346
+ }
347
+
348
+ // winner calculates the winning proposal of the ballot.
349
+ func (fw * FinalizingWorker ) winner (party []Participant ) {
350
+ // inform
351
+ fw .log .Debugf ("calculating ballot %s results" , fw .ballot .Address .String ())
352
+
353
+ // container for votes
354
+ votes := make ([]uint64 , len (party ))
355
+
356
+ // container for weights
357
+ weights := make ([]* big.Int , len (party ))
358
+
359
+ // loop all voters
360
+ for _ , voter := range party {
361
+ // advance votes counter
362
+ votes [voter .Vote ]++
363
+
364
+ // make sure to list the weight
365
+ if weights [voter .Vote ] == nil {
366
+ weights [voter .Vote ] = new (big.Int )
367
+ }
368
+
369
+ // advance weight
370
+ weights [voter .Vote ] = new (big.Int ).Add (weights [voter .Vote ], voter .Total )
371
+ }
372
+
373
+ // log results
374
+ for index , name := range fw .ballot .Proposals {
375
+ if weights [index ] != nil {
376
+ w := new (big.Int ).Div (weights [index ], big .NewInt (int64 (math .Pow10 (18 )))).Uint64 ()
377
+ fw .log .Noticef ("%s: Proposal #%d %s, votes %d, weight %d FTM" , fw .ballot .Name , index , name , votes [index ], w )
378
+ }
379
+ }
234
380
}
0 commit comments