From 3cdc3708b3d9481c79ce3e269bdb73f8d58e432f Mon Sep 17 00:00:00 2001 From: David Florness Date: Sun, 20 Mar 2022 16:55:19 -0400 Subject: [PATCH] Ensure elections have more than voter because having one voter doesn't make any sense, and causes panics in the cryptography code. Some refactoring is also done in ElectionWaitTUI to improve readability. --- election/msg.go | 5 ++ ui/tui.go | 129 ++++++++++++++++++++++++++++++------------------ 2 files changed, 86 insertions(+), 48 deletions(-) diff --git a/election/msg.go b/election/msg.go index 1aae082..51693aa 100644 --- a/election/msg.go +++ b/election/msg.go @@ -379,6 +379,11 @@ func (elections *ElectionsMap) onStartElectionMessage(evt *event.Event) (success return } + if len(content.JoinIDs) == 1 { + warnf("there must be more than one join") + return + } + contents := make([]event.Content, 0, 1+len(content.JoinIDs)) contents = append(contents, createEvt.Content) diff --git a/ui/tui.go b/ui/tui.go index 79f3d48..6d5f160 100644 --- a/ui/tui.go +++ b/ui/tui.go @@ -410,25 +410,7 @@ func ElectionWaitTUI(kill <-chan error, client *mautrix.Client, el *election.Ele } defer handlePanic() - el.RLock() - if el.LocalVoter != nil && el.CreateEvt.Sender == el.LocalVoter.JoinEvt.Sender { - frame.AddText("Press enter to start the election", false, tview.AlignCenter, tcell.ColorWhite) - app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { - if event.Key() == tcell.KeyEnter { - frame.Clear() - frame.AddText("Starting election...", false, tview.AlignCenter, tcell.ColorWhite) - err = el.StartElection(client, eventStore) - } - return event - }) - } else { - frame.AddText("Waiting for election to start...", false, tview.AlignCenter, tcell.ColorWhite) - } - if err != nil { - return - } - el.RUnlock() - update := func() { + updateVoters := func() { el.RLock() defer el.RUnlock() // TODO: handle users who joined after cutoff @@ -451,53 +433,98 @@ func ElectionWaitTUI(kill <-chan error, client *mautrix.Client, el *election.Ele text = "Joined voters:\n" + text votersTextView.SetText(text) } + + doStartElection := make(chan struct{}, 1) + + startElectionInputCapture := func(event *tcell.EventKey) *tcell.EventKey { + if event.Key() != tcell.KeyEnter { + return event + } + + // so the election isn't started more than once + app.SetInputCapture(nil) + + frame.Clear() + frame.AddText("Starting election...", false, tview.AlignCenter, tcell.ColorWhite) + + // actually start election in loop goroutine so text gets + // updated first + doStartElection <- struct{}{} + + return event + } + + queueUpdateText := func(txt string) { + app.QueueUpdateDraw(func() { + frame.Clear() + frame.AddText(txt, false, tview.AlignCenter, tcell.ColorWhite) + updateVoters() + }) + } + go func() { + // exit screen on return + defer app.Stop() defer handlePanic() - var electionStarted bool - for app.alive { + var inputCaptureSet bool + userIsCreator := userIsCreator(el) + for { el.RLock() - if electionStarted { - // have we received everyone's keys? - receivedAllKeys := true + electionHasStarted := el.FinalJoinIDs != nil + userKeysSent := el.LocalVoter.KeysID != nil + enoughVoters := len(el.Joins) > 1 + + var haveReceivedAllKeys bool + if electionHasStarted && userKeysSent { + haveReceivedAllKeys = true for _, joinID := range *el.FinalJoinIDs { if el.Joins[joinID].KeysID == nil { - receivedAllKeys = false + haveReceivedAllKeys = false break } } - el.RUnlock() - if receivedAllKeys { - app.Stop() + } + el.RUnlock() + + if electionHasStarted { + if haveReceivedAllKeys { return - } - } else if el.FinalJoinIDs != nil { - electionStarted = true - el.RUnlock() - if el.LocalVoter.KeysID == nil { - app.QueueUpdateDraw(func() { - frame.Clear() - frame.AddText("Sending keys...", false, tview.AlignCenter, tcell.ColorWhite) - update() - }) + } else if userKeysSent { + queueUpdateText("Waiting for everyone's keys...") + } else { + queueUpdateText("Sending keys...") err = el.SendProvingKeys(client, eventStore) - if err != nil { - app.Stop() - return + } + } else if userIsCreator { + select { + case <-doStartElection: + err = el.StartElection(client, eventStore) + default: + if enoughVoters { + if !inputCaptureSet { + // only set once to prevent + // starting the election more + // than once + inputCaptureSet = true + app.SetInputCapture(startElectionInputCapture) + } + queueUpdateText("Press enter to start the election") + } else { + queueUpdateText("Waiting for more voters...") } } - app.QueueUpdateDraw(func() { - frame.Clear() - frame.AddText("Waiting for everyone's keys...", false, tview.AlignCenter, tcell.ColorWhite) - update() - }) } else { - el.RUnlock() - app.QueueUpdateDraw(update) + queueUpdateText("Waiting for election to start...") + } + + if err != nil { + return } time.Sleep(1 * time.Second) } }() + app.SetRoot(frame, true) err2 := app.Run() if err != nil { @@ -509,6 +536,12 @@ func ElectionWaitTUI(kill <-chan error, client *mautrix.Client, el *election.Ele return nil } +func userIsCreator(el *election.Election) bool { + el.RLock() + defer el.RUnlock() + return el.LocalVoter != nil && el.CreateEvt.Sender == el.LocalVoter.JoinEvt.Sender +} + // displays a voting UI to the user and returns the encoded ballot func VoteTUI(kill <-chan error, candidates []election.Candidate) (ballot *[][]byte, err error) { app := newTallyardApplication(kill) -- 2.38.4