diff --git a/dfssc/sign/protocol.go b/dfssc/sign/protocol.go index 617a2593d0edfec9a9808a8aad4e3dccd82a8f98..21a870c01e6d43c8fc59096e05ec5d9104b39f6b 100644 --- a/dfssc/sign/protocol.go +++ b/dfssc/sign/protocol.go @@ -18,6 +18,12 @@ import ( // * Promises rounds // * Signature round func (m *SignatureManager) Sign() error { + + defer func() { + m.finished = true + m.closeConnections() + }() + myID, nextIndex, err := m.Initialize() if err != nil { return err @@ -72,9 +78,7 @@ func (m *SignatureManager) Sign() error { dAPI.DLog("exiting signature round") m.OnProgressUpdate(seqLen+1, seqLen+1) - // Network's job is done, cleaning time - // Shutdown and platform client and TODO peer server & connections - return m.platformConn.Close() + return nil } // GetClient retrieves the Client to the specified sequence id provided it exists @@ -135,11 +139,13 @@ func (m *SignatureManager) promiseRound(pendingSet, sendSet []uint32, myID uint3 } } -// closeAllPeerClient tries to close all established connection with other peers -func (m *SignatureManager) closeAllPeerClient() { - for k, client := range m.peersConn { - _ = client.Close() - // Remove associated grpc client +// closeConnections tries to close all established connection with other peers and platform. +// It also stops the local server. +func (m *SignatureManager) closeConnections() { + _ = m.platformConn.Close() + for k, peer := range m.peersConn { + _ = peer.Close() delete(m.peers, k) } + m.cServer.Stop() } diff --git a/dfssc/sign/starter.go b/dfssc/sign/starter.go index d69eb0f716630af5efdf2361f50acdc1d9c91c25..dded16ca2394df08b77fbef829b2ac21525e901a 100644 --- a/dfssc/sign/starter.go +++ b/dfssc/sign/starter.go @@ -3,7 +3,6 @@ package sign import ( "errors" "fmt" - "log" "strconv" "sync" "time" @@ -43,10 +42,13 @@ type SignatureManager struct { mail string archives *Archives seal []byte + cancelled bool + finished bool // Callbacks OnSignerStatusUpdate func(mail string, status SignerStatus, data string) OnProgressUpdate func(current int, end int) + Cancel chan interface{} } // Archives stores the received and sent messages, as evidence if needed @@ -69,6 +71,7 @@ func NewSignatureManager(passphrase string, c *contract.JSON) (*SignatureManager sentSignatures: make([]*cAPI.Signature, 0), receivedSignatures: make([]*cAPI.Signature, 0), }, + Cancel: make(chan interface{}), } var err error _, _, _, err = m.auth.LoadFiles() @@ -80,7 +83,7 @@ func NewSignatureManager(passphrase string, c *contract.JSON) (*SignatureManager dAPI.SetIdentifier(m.mail) m.cServer = m.GetServer() - go func() { log.Fatalln(net.Listen("0.0.0.0:"+strconv.Itoa(viper.GetInt("local_port")), m.cServer)) }() + go func() { _ = net.Listen("0.0.0.0:"+strconv.Itoa(viper.GetInt("local_port")), m.cServer) }() conn, err := net.Connect(viper.GetString("platform_addrport"), m.auth.Cert, m.auth.Key, m.auth.CA, nil) if err != nil { @@ -114,28 +117,49 @@ func (m *SignatureManager) ConnectToPeers() error { Ip: localIps, }) if err != nil { + m.finished = true + m.closeConnections() return err } - for { + c := make(chan error) + go connectToPeersLoop(m, stream, c) + + select { + case err = <-c: + if err != nil { + m.finished = true + m.closeConnections() + } + return err + case <-m.Cancel: + m.cancelled = true + m.closeConnections() + return errors.New("Signature cancelled") + } +} + +func connectToPeersLoop(m *SignatureManager, stream pAPI.Platform_JoinSignatureClient, c chan error) { + for !m.cancelled { userConnected, err := stream.Recv() if err != nil { - return err + c <- err + return } errorCode := userConnected.GetErrorCode() if errorCode.Code != pAPI.ErrorCode_SUCCESS { - return errors.New(errorCode.Message) + c <- errors.New(errorCode.Message) + return } ready, err := m.addPeer(userConnected.User) if err != nil { continue // Unable to connect to this user, ignore it for the moment } if ready { - break + c <- nil + return } } - - return nil } // addPeer stores a peer from the platform and tries to establish a connection to this peer. @@ -158,6 +182,11 @@ func (m *SignatureManager) addPeer(user *pAPI.User) (ready bool, err error) { if err == nil { break } + + if m.cancelled { + err = errors.New("Signature cancelled") + break + } } if err != nil { @@ -200,28 +229,46 @@ func (m *SignatureManager) addPeer(user *pAPI.User) (ready bool, err error) { func (m *SignatureManager) SendReadySign() (signatureUUID string, err error) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) defer cancel() - launch, err := m.platform.ReadySign(ctx, &pAPI.ReadySignRequest{ - ContractUuid: m.contract.UUID, - }) - if err != nil { + + c := make(chan *pAPI.LaunchSignature) + go func() { + launch, _ := m.platform.ReadySign(ctx, &pAPI.ReadySignRequest{ + ContractUuid: m.contract.UUID, + }) + c <- launch + }() + + var launch *pAPI.LaunchSignature + select { + case launch = <-c: // OK + case <-m.Cancel: + m.cancelled = true + m.closeConnections() + err = errors.New("Signature cancelled") return } errorCode := launch.GetErrorCode() if errorCode.Code != pAPI.ErrorCode_SUCCESS { err = errors.New(errorCode.Code.String() + " " + errorCode.Message) + m.finished = true + m.closeConnections() return } // Check signers from platform data if len(m.contract.Signers) != len(launch.KeyHash) { err = errors.New("Corrupted DFSS file: bad number of signers, unable to sign safely") + m.finished = true + m.closeConnections() return } for i, s := range m.contract.Signers { if s.Hash != fmt.Sprintf("%x", launch.KeyHash[i]) { err = errors.New("Corrupted DFSS file: signer " + s.Email + " has an invalid hash, unable to sign safely") + m.finished = true + m.closeConnections() return } } @@ -264,3 +311,8 @@ func (m *SignatureManager) FindID() (uint32, error) { } return 0, errors.New("Mail couldn't be found amongst signers") } + +// IsTerminated returns true if the signature is cancelled or finished +func (m *SignatureManager) IsTerminated() bool { + return m.cancelled || m.finished +} diff --git a/gui/signform/signform.go b/gui/signform/signform.go index 74b855db678f2d0465922be717d41cd1c5a2def1..5f8ead1c50f7d7357f7244397f4c648e901fee21 100644 --- a/gui/signform/signform.go +++ b/gui/signform/signform.go @@ -22,9 +22,11 @@ type Widget struct { table *ui.QTableWidget progressBar *ui.QProgressBar feedbackLabel *ui.QLabel + cancelButton *ui.QPushButton lines []line statusMax, statusCurrent int32 feedback string + running bool } func NewWidget(contract *contract.JSON, pwd string) *Widget { @@ -40,6 +42,7 @@ func NewWidget(contract *contract.JSON, pwd string) *Widget { w.feedbackLabel = ui.NewLabelFromDriver(w.FindChild("mainLabel")) w.table = ui.NewTableWidgetFromDriver(w.FindChild("signersTable")) w.progressBar = ui.NewProgressBarFromDriver(w.FindChild("progressBar")) + w.cancelButton = ui.NewPushButtonFromDriver(w.FindChild("cancelButton")) m, err := sign.NewSignatureManager( pwd, @@ -56,6 +59,18 @@ func NewWidget(contract *contract.JSON, pwd string) *Widget { w.statusMax = int32(max) } + w.cancelButton.OnClicked(func() { + // Render an immediate feedback to user + f := "Cancelling signature process..." + w.feedback = f + w.feedbackLabel.SetText(f) + w.cancelButton.SetDisabled(true) + w.statusMax = 1 + w.statusCurrent = 0 + // Ask for cancellation in a separate goroutine to avoid blocking Qt + go func() { w.manager.Cancel <- true }() + }) + w.initLines() w.signerUpdated(viper.GetString("email"), sign.StatusConnected, "It's you!") go func() { @@ -70,6 +85,8 @@ func NewWidget(contract *contract.JSON, pwd string) *Widget { return w } +// execute() is called in a goroutine OUTSIDE of Qt loop. +// WE SHOULD NOT CALL ANY QT FUNCTION FROM IT. func (w *Widget) execute() error { w.feedback = "Connecting to peers..." err := w.manager.ConnectToPeers() @@ -84,6 +101,7 @@ func (w *Widget) execute() error { } w.feedback = "Signature in progress..." + w.running = true err = w.manager.Sign() if err != nil { return err @@ -109,6 +127,7 @@ func (w *Widget) Tick() { w.feedbackLabel.SetText(w.feedback) w.progressBar.SetMaximum(w.statusMax) w.progressBar.SetValue(w.statusCurrent) + w.cancelButton.SetDisabled(w.running || w.manager.IsTerminated()) for _, l := range w.lines { l.cellA.SetIcon(icons[l.status]) l.cellA.SetText(icons_labels[l.status])