Skip to content

Commit

Permalink
e2etest: e2etest: starting to port anonelection to use helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
mariajdab authored and altergui committed Apr 17, 2023
1 parent 24324f9 commit 5e2ccd6
Showing 1 changed file with 76 additions and 204 deletions.
280 changes: 76 additions & 204 deletions cmd/end2endtest/zkweighted.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package main

import (
"context"
"fmt"
"errors"
"math/big"
"os"
"strings"
Expand All @@ -12,11 +12,7 @@ import (
vapi "go.vocdoni.io/dvote/api"
"go.vocdoni.io/dvote/apiclient"
"go.vocdoni.io/dvote/crypto/ethereum"
"go.vocdoni.io/dvote/crypto/zk"
"go.vocdoni.io/dvote/log"
"go.vocdoni.io/dvote/types"
"go.vocdoni.io/dvote/util"
"go.vocdoni.io/proto/build/go/models"
)

func init() {
Expand All @@ -31,8 +27,7 @@ func init() {
var _ VochainTest = (*E2EAnonElection)(nil)

type E2EAnonElection struct {
api *apiclient.HTTPclient
config *config
electionBase
}

func (t *E2EAnonElection) Setup(api *apiclient.HTTPclient, config *config) error {
Expand All @@ -58,47 +53,13 @@ func (t *E2EAnonElection) Run() (duration time.Duration, err error) {
}

// If the account does not exist, create a new one
// TODO: check if the account balance is low and use the faucet
// TODO: check if the account balance is low and use the faucet )
acc, err := api.Account("")
if err != nil {
var faucetPkg *models.FaucetPackage
if c.faucet != "" {
// Get the faucet package of bootstrap tokens
log.Infof("getting faucet package")
if c.faucet == "dev" {
faucetPkg, err = apiclient.GetFaucetPackageFromDevService(api.MyAddress().Hex())
} else {
faucetPkg, err = apiclient.GetFaucetPackageFromRemoteService(c.faucet+api.MyAddress().Hex(), c.faucetAuthToken)
}

if err != nil {
log.Fatal(err)
}
}
// Create the organization account and bootstraping with the faucet package
log.Infof("creating Vocdoni account %s", api.MyAddress().Hex())
log.Debugf("faucetPackage is %x", faucetPkg)
hash, err := api.AccountBootstrap(faucetPkg, &vapi.AccountMetadata{
Name: map[string]string{"default": "test account " + api.MyAddress().Hex()},
Description: map[string]string{"default": "test description"},
Version: "1.0",
})
acc, err = t.createAccount(api.MyAddress().Hex())
if err != nil {
log.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*40)
defer cancel()
if _, err := api.WaitUntilTxIsMined(ctx, hash); err != nil {
log.Fatalf("gave up waiting for tx %x to be mined: %s", hash, err)
}

acc, err = api.Account("")
if err != nil {
log.Fatal(err)
}
if c.faucet != "" && acc.Balance == 0 {
log.Fatal("account balance is 0")
}
}

log.Infof("account %s balance is %d", api.MyAddress().Hex(), acc.Balance)
Expand All @@ -111,40 +72,17 @@ func (t *E2EAnonElection) Run() (duration time.Duration, err error) {
log.Infof("new census created with id %s", censusID.String())

// Generate n participant accounts
voterAccounts := ethereum.NewSignKeysBatch(c.nvotes)
t.voterAccounts = ethereum.NewSignKeysBatch(c.nvotes)

// Add the accounts to the census by batches
participants := &vapi.CensusParticipants{}
for i, voterAccount := range voterAccounts {
zkAddr, err := zk.AddressFromSignKeys(voterAccount)
if err != nil {
log.Fatal(err)
}

participants.Participants = append(participants.Participants,
vapi.CensusParticipant{
Key: zkAddr.Bytes(),
Weight: (*types.BigInt)(new(big.Int).SetUint64(10)),
})
if i == len(voterAccounts)-1 || ((i+1)%vapi.MaxCensusAddBatchSize == 0) {
if err := api.CensusAddParticipants(censusID, participants); err != nil {
log.Fatal(err)
}
log.Infof("added %d participants to census %s",
len(participants.Participants), censusID.String())
participants = &vapi.CensusParticipants{}
}
if err := t.addParticipantsCensus(vapi.CensusTypeZKWeighted, censusID); err != nil {
log.Fatal(err)
}

// Check census size
size, err := api.CensusSize(censusID)
if err != nil {
if !t.isCensusSizeValid(censusID) {
log.Fatal(err)
}
if size != uint64(c.nvotes) {
log.Fatalf("census size is %d, expected %d", size, c.nvotes)
}
log.Infof("census %s size is %d", censusID.String(), size)

// Publish the census
root, censusURI, err := api.CensusPublish(censusID)
Expand All @@ -154,167 +92,101 @@ func (t *E2EAnonElection) Run() (duration time.Duration, err error) {
log.Infof("census published with root %s", root.String())

// Check census size (of the published census)
size, err = api.CensusSize(root)
if err != nil {
if !t.isCensusSizeValid(root) {
log.Fatal(err)
}
if size != uint64(c.nvotes) {
log.Fatalf("published census size is %d, expected %d", size, c.nvotes)
}

// Create a new Election
electionID, err := api.NewElection(&vapi.ElectionDescription{
Title: map[string]string{"default": fmt.Sprintf("Test election %s", util.RandomHex(8))},
Description: map[string]string{"default": "Test election description"},
EndDate: time.Now().Add(time.Minute * 20),

VoteType: vapi.VoteType{
UniqueChoices: false,
MaxVoteOverwrites: 1,
},

ElectionType: vapi.ElectionType{
Autostart: true,
Interruptible: true,
Anonymous: true,
SecretUntilTheEnd: false,
DynamicCensus: false,
},

Census: vapi.CensusTypeDescription{
RootHash: root,
URL: censusURI,
Type: vapi.CensusTypeZKWeighted,
Size: size,
},

Questions: []vapi.Question{
{
Title: map[string]string{"default": "Test question 1"},
Description: map[string]string{"default": "Test question 1 description"},
Choices: []vapi.ChoiceMetadata{
{
Title: map[string]string{"default": "Yes"},
Value: 0,
},
{
Title: map[string]string{"default": "No"},
Value: 1,
},
},
},
},
})
if err != nil {
log.Fatal(err)
}
description := newDefaultElectionDescription(root, censusURI, uint64(t.config.nvotes))
// update some default values
description.Census.Type = vapi.CensusTypeZKWeighted
description.ElectionType.Anonymous = true

// Wait for the election creation
ctx, cancel := context.WithTimeout(context.Background(), time.Second*40)
defer cancel()
election, err := api.WaitUntilElectionCreated(ctx, electionID)
if err != nil {
log.Errorw(err, "error creating the election")
return
}
log.Debugf("election details: %+v", *election)
log.Infof("created new election with id %s - now wait until it starts", electionID.String())

// Wait for the election to start
ctx, cancel = context.WithTimeout(context.Background(), time.Second*40)
defer cancel()
election, err = api.WaitUntilElectionStarts(ctx, electionID)
election, err := t.createElection(description)
if err != nil {
log.Fatal(err)
}

t.election = election
log.Debugf("election details: %+v", *election)

t.proofs = t.generateProofs(root, true)

// Send the votes (parallelized)
startTime := time.Now()
successProofs, successVotes := 0, 0
proofCh, voteCh, stopCh := make(chan bool), make(chan bool), make(chan bool)
go func() {
for {
select {
case <-proofCh:
successProofs++
case <-voteCh:
successVotes++
case <-stopCh:
return
}
}
}()

wg := sync.WaitGroup{}
apiClientMtx := &sync.Mutex{}
addNaccounts := func(accounts []*ethereum.SignKeys, wg *sync.WaitGroup) {
voteAccounts := func(accounts []*ethereum.SignKeys, wg *sync.WaitGroup) {
defer wg.Done()
log.Infof("generating %d voting proofs", len(accounts))
log.Infof("sending %d votes", len(accounts))
// We use maps instead of slices to have the capacity of resending votes
// without repeating them.
accountsMap := make(map[int]*ethereum.SignKeys, len(accounts))
for i, acc := range accounts {
apiClientMtx.Lock()

privKey := acc.PrivateKey()
if err = api.SetAccount(privKey.String()); err != nil {
accountsMap[i] = acc
}
// Send the votes
votesSent := 0
for {
contextDeadlines := 0
for i, voterAccount := range accountsMap {
apiClientMtx.Lock()
privKey := voterAccount.PrivateKey()
if err = api.SetAccount(privKey.String()); err != nil {
apiClientMtx.Unlock()
log.Fatal(err)
return
}

_, err = api.Vote(&apiclient.VoteData{
ElectionID: t.election.ElectionID,
ProofMkTree: t.proofs[voterAccount.Address().Hex()],
Choices: []int{i % 2},
VotingWeight: new(big.Int).SetUint64(8),
})
apiClientMtx.Unlock()
log.Fatal(err)
return
}
// if the context deadline is reached, we don't need to print it (let's jus retry)
if err != nil && errors.Is(err, context.DeadlineExceeded) || os.IsTimeout(err) {
contextDeadlines++
continue
} else if err != nil && !strings.Contains(err.Error(), "already exists") {
// if the error is not "vote already exists", we need to print it
log.Warn(err)
continue
}
// if the vote was sent successfully or already exists, we remove it from the accounts map
votesSent++
delete(accountsMap, i)

pr, err := api.CensusGenProof(root, api.MyZkAddress().Bytes())
if err != nil {
apiClientMtx.Unlock()
log.Warnw(err.Error(),
"current", i,
"total", c.nvotes)
continue
}

log.Debugw("census proof generated",
"current", i,
"total", len(accounts))
proofCh <- true

_, err = api.Vote(&apiclient.VoteData{
ElectionID: electionID,
ProofMkTree: pr,
Choices: []int{i % 2},
VotingWeight: new(big.Int).SetUint64(8),
})
apiClientMtx.Unlock()
if err != nil && !strings.Contains(err.Error(), "already exists") {
// if the error is not "vote already exists", we need to print it
log.Warn(err.Error())
continue
if len(accountsMap) == 0 {
break
}
log.Debugw("vote sent",
"current", i,
"total", len(accounts))
voteCh <- true
pr = nil
log.Infof("sent %d/%d votes... got %d HTTP errors", votesSent, len(accounts), contextDeadlines)
time.Sleep(time.Second * 5)
}
log.Infof("successfully sent %d votes", votesSent)
time.Sleep(time.Second * 2)
}

pcount := c.nvotes / c.parallelCount
var wg sync.WaitGroup
for i := 0; i < len(voterAccounts); i += pcount {
for i := 0; i < len(t.voterAccounts); i += pcount {
end := i + pcount
if end > len(voterAccounts) {
end = len(voterAccounts)
if end > len(t.voterAccounts) {
end = len(t.voterAccounts)
}
wg.Add(1)
go addNaccounts(voterAccounts[i:end], &wg)
go voteAccounts(t.voterAccounts[i:end], &wg)
}

wg.Wait()
time.Sleep(time.Second) // wait a grace time for the last proof to be added
log.Debugf("%d/%d voting proofs generated successfully", successProofs, len(voterAccounts))
log.Debugf("%d/%d votes sent successfully", successVotes, len(voterAccounts))
stopCh <- true
log.Infof("%d votes submitted successfully, took %s (%d votes/second)",
c.nvotes, time.Since(startTime), int(float64(c.nvotes)/time.Since(startTime).Seconds()))

// Wait for all the votes to be verified
log.Infof("waiting for all the votes to be registered...")
for {
count, err := api.ElectionVoteCount(electionID)
count, err := api.ElectionVoteCount(t.election.ElectionID)
if err != nil {
log.Warn(err)
}
Expand All @@ -338,35 +210,35 @@ func (t *E2EAnonElection) Run() (duration time.Duration, err error) {

// End the election by setting the status to ENDED
log.Infof("ending election...")
hash, err := api.SetElectionStatus(electionID, "ENDED")
hash, err := api.SetElectionStatus(t.election.ElectionID, "ENDED")
if err != nil {
log.Fatal(err)
}

// Check the election status is actually ENDED
ctx, cancel = context.WithTimeout(context.Background(), time.Second*40)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*40)
defer cancel()
if _, err := api.WaitUntilTxIsMined(ctx, hash); err != nil {
log.Fatalf("gave up waiting for tx %s to be mined: %s", hash, err)
}

election, err = api.Election(electionID)
election, err = api.Election(t.election.ElectionID)
if err != nil {
log.Fatal(err)
}
if election.Status != "ENDED" {
log.Fatal("election status is not ENDED")
}
log.Infof("election %s status is ENDED", electionID.String())
log.Infof("election %s status is ENDED", t.election.ElectionID.String())

// Wait for the election to be in RESULTS state
ctx, cancel = context.WithTimeout(context.Background(), time.Second*300)
defer cancel()
election, err = api.WaitUntilElectionStatus(ctx, electionID, "RESULTS")
election, err = api.WaitUntilElectionStatus(ctx, t.election.ElectionID, "RESULTS")
if err != nil {
log.Fatal(err)
}
log.Infof("election %s status is RESULTS", electionID.String())
log.Infof("election %s status is RESULTS", t.election.ElectionID.String())
log.Infof("election results: %v", election.Results)

return time.Since(start), nil
Expand Down

0 comments on commit 5e2ccd6

Please sign in to comment.