Skip to content

Commit ff46eaf

Browse files
author
Jiri Malek
committed
Add ballot finalization process.
1 parent 755be0e commit ff46eaf

File tree

2 files changed

+158
-12
lines changed

2 files changed

+158
-12
lines changed

internal/modules/ballot/ballot.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ query {
3030
ballotsClosed(finalized: false) {
3131
name
3232
address
33+
proposals
3334
}
3435
}
3536
`
@@ -38,9 +39,8 @@ query {
3839
// waiting for the closed ballots to process them.
3940
const workersPoolSize = 2
4041

41-
// jobChannelBuffer represents the size of workers job channel
42-
// buffer.
43-
const jobChannelBuffer = 10
42+
// jobChannelBuffer represents the size of workers job channel buffer.
43+
const jobChannelBuffer = 5
4444

4545
// FinalizingOracle defines an oracle module for feeding
4646
// ballot participants' account totals and finalizing
@@ -103,7 +103,7 @@ func (fo *FinalizingOracle) Run() {
103103
// make workers
104104
fo.workers = make([]*FinalizingWorker, workersPoolSize)
105105
for i := 0; i < workersPoolSize; i++ {
106-
fo.workers[i] = NewWorker(eth, fo.apiClient, fo.workersGroup, fo.jobQueue, fo.sup.Log())
106+
fo.workers[i] = NewWorker(eth, fo.apiClient, fo.workersGroup, fo.jobQueue, fo.sup.Log(), fo.cfg)
107107
fo.workers[i].Run()
108108
}
109109

internal/modules/ballot/worker.go

Lines changed: 154 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ package ballot
66

77
import (
88
"context"
9+
"github.com/ethereum/go-ethereum/accounts/abi/bind"
910
"github.com/ethereum/go-ethereum/common"
1011
"github.com/ethereum/go-ethereum/common/hexutil"
1112
"github.com/ethereum/go-ethereum/ethclient"
1213
"github.com/ethereum/go-ethereum/log"
1314
"github.com/machinebox/graphql"
15+
"math"
16+
"math/big"
1417
"oracle-watchdog/internal/logger"
18+
"oracle-watchdog/internal/modules/utils"
1519
"sync"
1620
"time"
1721
)
@@ -23,9 +27,14 @@ query ($addr: Address!) {
2327
}
2428
}`
2529

30+
// participantsWeightPushPackSize represents the number of participants
31+
// pushed into the voting contract at once.
32+
const participantsWeightPushPackSize = 20
33+
2634
// FinalizingWorker represents a worker structure and processor for single
2735
// closed ballot processing.
2836
type FinalizingWorker struct {
37+
cfg *FinalizingOracleConfig
2938
eth *ethclient.Client
3039
api *graphql.Client
3140
sigTerminate chan bool
@@ -38,14 +47,16 @@ type FinalizingWorker struct {
3847
// ClosedBallot represents a ballot record received from the remote API server
3948
// and prepared to be finished by the oracle.
4049
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"`
4353
}
4454

4555
// Participant represents a single address involved in the ballot voting.
4656
type Participant struct {
4757
Address common.Address
48-
Total hexutil.Big
58+
Total *big.Int
59+
Vote int
4960
TimeStamp int64
5061
}
5162

@@ -57,9 +68,11 @@ func NewWorker(
5768
wg *sync.WaitGroup,
5869
queue chan ClosedBallot,
5970
log logger.Logger,
71+
cfg *FinalizingOracleConfig,
6072
) *FinalizingWorker {
6173
// make the worker
6274
w := FinalizingWorker{
75+
cfg: cfg,
6376
eth: rpc,
6477
api: api,
6578
waitGroup: wg,
@@ -141,14 +154,32 @@ func (fw *FinalizingWorker) processBallot() error {
141154
}
142155

143156
// 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())
145159
return nil
146160
}
147161

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+
148169
// 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+
}
149174

150175
// 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+
}
151180

181+
// calculate and log the winner of the ballot
182+
fw.winner(party)
152183
return nil
153184
}
154185

@@ -183,12 +214,15 @@ func (fw *FinalizingWorker) participants(contract *BallotContract) ([]Participan
183214
// make the participant
184215
party := Participant{
185216
Address: it.Event.Voter,
217+
Vote: int(it.Event.Vote.Uint64()),
186218
Total: fw.accountTotal(it.Event.Voter),
187219
TimeStamp: time.Now().UTC().Unix(),
188220
}
189221

190222
// push to the list
191-
list = append(list, party)
223+
if party.Total != nil {
224+
list = append(list, party)
225+
}
192226

193227
// check for possible termination request
194228
select {
@@ -207,7 +241,7 @@ func (fw *FinalizingWorker) participants(contract *BallotContract) ([]Participan
207241

208242
// accountTotal pulls the current total value of the given account
209243
// 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 {
211245
// log action
212246
fw.log.Debugf("loading account total for [%s]", addr.String())
213247

@@ -225,10 +259,122 @@ func (fw *FinalizingWorker) accountTotal(addr common.Address) hexutil.Big {
225259
// execute the request and parse the response
226260
if err := fw.api.Run(context.Background(), req, &res); err != nil {
227261
fw.log.Errorf("can not pull account total for [%s]; %s", addr.String(), err.Error())
228-
return hexutil.Big{}
262+
return nil
229263
}
230264

231265
// log the value
232266
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+
}
234380
}

0 commit comments

Comments
 (0)