Commit 6999665d authored by Anders Jensen Løvig's avatar Anders Jensen Løvig
Browse files

Election: voters can only vote once

parent 35a090b5
Pipeline #21380 passed with stages
in 1 minute and 1 second
......@@ -126,7 +126,7 @@ func (box *BallotBox) Tally() *Tally {
}
// Count of ballots in the box
func (box *BallotBox) Count() int {
func (box *BallotBox) Size() int {
return len(box.ballots)
}
......
......@@ -24,10 +24,9 @@ const (
type Reason string
const (
ReasonDeadline = "Deadline reached"
ReasonVotes = "All votes received"
ReasonAgreement = "Majority decided to end election"
ReasonTally = "Recevied enough tallies"
ReasonDeadline = "deadline reached"
ReasonVotes = "all votes received"
ReasonAgreement = "majority decided to end election"
)
// Phase of an election
......@@ -69,8 +68,8 @@ type Participants struct {
// Status contains the current status of the election.
type Status struct {
Phase Phase
Closed bool // True if the election closed.
ClosedServers map[UniqueID]bool // Map of closed servers
Turnout map[UniqueID]bool // Map of voters who voted
}
// Election is an abstraction of an election.
......@@ -141,6 +140,7 @@ func NewElection(config *Config) *Election {
Status: Status{
Phase: PhaseNotStarted,
ClosedServers: make(map[UniqueID]bool),
Turnout: make(map[UniqueID]bool),
},
closeCallback: config.CloseCallback,
tallyCallback: config.TallyCallback,
......@@ -159,7 +159,6 @@ func (election *Election) Start() error {
return fmt.Errorf("deadline is in the past")
}
election.Status.Closed = false
election.Status.Phase = PhaseCollecting
go func(election *Election) {
......@@ -183,10 +182,9 @@ func (election *Election) Start() error {
}
func (election *Election) nextPhase(reason Reason) {
log.Println("Election: next phase because", reason)
switch election.Status.Phase {
case PhaseCollecting:
log.Println("Election closing, reason:", reason)
election.Status.Closed = true
election.Status.ClosedServers[election.ServerID] = true
election.closeCallback(reason)
......@@ -203,7 +201,7 @@ func (election *Election) nextPhase(reason Reason) {
fallthrough
case PhaseClosed:
// If the ballot box is empty we can go directly to result
if election.ballotBox.Count() != 0 {
if election.ballotBox.Size() != 0 {
election.Status.Phase = PhaseTallying
tally := election.ballotBox.Tally()
election.tallyCallback(tally)
......@@ -219,23 +217,26 @@ func (election *Election) nextPhase(reason Reason) {
// HandleBallot puts ballot in the ballot box if ballot is valid.
func (election *Election) HandleBallot(voterID UniqueID, ballot *Ballot) {
if election.Status.Closed {
log.Printf("Recevied ballot '%s' after election closed\n", ballot.ID)
return
}
// TODO verify voterID have not voted!!
err := election.ballotBox.Put(ballot)
if err != nil {
log.Println("ERROR: " + err.Error())
}
election.Lock()
defer election.Unlock()
if election.ballotBox.Count() >= election.Participants.Voters {
election.nextPhase(ReasonVotes)
if election.Status.Phase == PhaseCollecting {
if _, ok := election.Status.Turnout[voterID]; ok {
log.Printf("ERROR: Voter %s tried to vote agian\n", voterID)
return
}
err := election.ballotBox.Put(ballot)
if err != nil {
log.Print("ERROR: ballot box put:", err.Error())
return
}
election.Status.Turnout[voterID] = true
if election.ballotBox.Size() >= election.Participants.Voters {
election.nextPhase(ReasonVotes)
}
} else {
log.Printf("Recevied ballot '%s' while election is not collecting ballots\n", ballot.ID)
}
}
......@@ -243,14 +244,20 @@ func (election *Election) HandleBallot(voterID UniqueID, ballot *Ballot) {
func (election *Election) HandleClosing(id UniqueID) {
election.Lock()
defer election.Unlock()
if election.Status.Phase == PhaseNotStarted {
log.Printf("ERROR: Server %s closed before election started\n", id)
return
}
if _, ok := election.Status.ClosedServers[id]; ok {
log.Printf("ERROR: Server '%s' already closed\n", id)
log.Printf("ERROR: Server %s already closed\n", id)
return
}
election.Status.ClosedServers[id] = true
if election.shouldTally() && election.Status.Phase < PhaseTallying {
if len(election.Status.ClosedServers) >= election.Participants.RequiredServers &&
election.Status.Phase < PhaseTallying {
election.nextPhase(ReasonAgreement)
}
}
......@@ -274,22 +281,15 @@ func (election *Election) HandleTally(serverID UniqueID, tally *Tally) {
if err != nil {
log.Println("ERROR: " + err.Error())
}
election.Lock()
defer election.Unlock()
if election.tallyBox.Count() >= election.Participants.RequiredServers {
election.nextPhase(ReasonTally)
}
}
func (election *Election) createResults() *Result {
if election.tallyBox.Count() == 0 {
if election.tallyBox.Size() == 0 {
return nil
}
return &Result{
Votes: election.ballotBox.Count(),
Votes: election.ballotBox.Size(),
Tally: int(election.tallyBox.Combine().Int64()),
}
}
......@@ -233,3 +233,17 @@ func TestTallyAfterClose(t *testing.T) {
e.HandleClosing("2")
})
}
func TestOnlyVoteOnce(t *testing.T) {
e := setupElection()
_ = e.Start()
ballots := CreateBallots(3, createXS(1), big.NewInt(1))
e.HandleBallot("4", ballots["1"])
e.HandleBallot("4", ballots["1"])
if e.ballotBox.Size() != 1 {
t.Errorf("Expected ballot box size 1, got %d", e.ballotBox.Size())
}
}
......@@ -75,7 +75,7 @@ func (box *TallyBox) Combine() *big.Int {
return pedersen.NewParams(box.params).Combine(shares)
}
// Count of the number of tallies
func (box *TallyBox) Count() int {
// Size of the number of tallies
func (box *TallyBox) Size() int {
return len(box.tallies)
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment