protocol.go 8.53 KB
Newer Older
1
// Package sign handles contract and signature operations.
2 3 4
package sign

import (
Caro Axel's avatar
Caro Axel committed
5 6
	"bytes"
	"errors"
7
	"fmt"
Caro Axel's avatar
Caro Axel committed
8
	"io/ioutil"
Loïck Bonniot's avatar
Loïck Bonniot committed
9
	"os"
10
	"time"
11 12 13

	cAPI "dfss/dfssc/api"
	"dfss/dfssc/common"
Richer Maximilien's avatar
Richer Maximilien committed
14
	dAPI "dfss/dfssd/api"
Caro Axel's avatar
Caro Axel committed
15 16
	tAPI "dfss/dfsst/api"
	"dfss/net"
17
	"github.com/spf13/viper"
Caro Axel's avatar
Caro Axel committed
18 19
	"golang.org/x/net/context"
	"google.golang.org/grpc"
20 21
)

22
// Sign performs all the message exchanges for the contract to be signed
23 24 25 26 27 28
//
// * Initialize the SignatureManager from starter.go
// * Compute the reversed map [mail -> ID] of signers
// * Make channels for handlers
// * Promises rounds
// * Signature round
29
func (m *SignatureManager) Sign() error {
30 31 32 33 34 35

	defer func() {
		m.finished = true
		m.closeConnections()
	}()

Caro Axel's avatar
Caro Axel committed
36
	nextIndex, err := m.Initialize()
37 38 39 40
	if err != nil {
		return err
	}

41
	m.makeSignersHashToIDMap()
42 43
	m.cServerIface.incomingPromises = make(chan interface{}, chanBufferSize)
	m.cServerIface.incomingSignatures = make(chan interface{}, chanBufferSize)
44

45 46
	// Cooldown delay, let other clients wake-up their channels
	time.Sleep(time.Second)
47

Loïck Bonniot's avatar
Loïck Bonniot committed
48 49
	seqLen := len(m.sequence)

50
	// Promess rounds
51
	// Follow the sequence until there is no next occurence of me
52
	round := 0
53
	for m.currentIndex >= 0 {
54 55
		round = round + 1
		stopIfNeeded(round)
Loïck Bonniot's avatar
Loïck Bonniot committed
56
		m.OnProgressUpdate(m.currentIndex, seqLen+1)
Loïck Bonniot's avatar
Loïck Bonniot committed
57
		time.Sleep(viper.GetDuration("slowdown"))
58
		dAPI.DLog("starting round at index [" + fmt.Sprintf("%d", m.currentIndex) + "] with nextIndex=" + fmt.Sprintf("%d", nextIndex))
Richer Maximilien's avatar
Richer Maximilien committed
59

Loïck Bonniot's avatar
Loïck Bonniot committed
60
		// Set of promises we are waiting for
Caro Axel's avatar
Caro Axel committed
61 62
		var pendingSet []common.SequenceCoordinate
		pendingSet, err = common.GetPendingSet(m.sequence, m.myID, m.currentIndex)
63 64
		if err != nil {
			return err
65 66
		}

67
		// Set of the promises we must send
Caro Axel's avatar
Caro Axel committed
68 69
		var sendSet []common.SequenceCoordinate
		sendSet, err = common.GetSendSet(m.sequence, m.myID, m.currentIndex)
70 71
		if err != nil {
			return err
72 73
		}

74
		// Exchange messages
Caro Axel's avatar
Caro Axel committed
75 76 77 78
		var stop bool
		stop, err = m.promiseRound(pendingSet, sendSet)
		if err != nil || stop {
			dAPI.DLog("stopping protocol execution")
Caro Axel's avatar
Caro Axel committed
79 80
			return err
		}
81

82
		m.currentIndex = nextIndex
Caro Axel's avatar
Caro Axel committed
83
		nextIndex, err = common.FindNextIndex(m.sequence, m.myID, m.currentIndex)
84 85
		if err != nil {
			return err
86 87 88
		}
	}

Loïck Bonniot's avatar
Loïck Bonniot committed
89 90
	// Signature round
	stopIfNeeded(-1)
Loïck Bonniot's avatar
Loïck Bonniot committed
91
	m.OnProgressUpdate(seqLen, seqLen+1)
92
	dAPI.DLog("entering signature round")
93
	err = m.ExchangeAllSignatures()
94 95 96
	if err != nil {
		return err
	}
Loïck Bonniot's avatar
Loïck Bonniot committed
97

98
	dAPI.DLog("exiting signature round")
Loïck Bonniot's avatar
Loïck Bonniot committed
99
	m.OnProgressUpdate(seqLen+1, seqLen+1)
Caro Axel's avatar
Caro Axel committed
100
	return m.PersistSignaturesToFile()
101 102 103
}

// GetClient retrieves the Client to the specified sequence id provided it exists
104 105 106 107
func (m *SignatureManager) GetClient(to uint32) (client *cAPI.ClientClient, mail string) {
	mail = m.contract.Signers[to].Email
	client = m.peers[mail]
	return
108 109
}

110
// makeSignersHashToIDMap builds an association to reverse a hash to the sequence ID
111 112
func (m *SignatureManager) makeSignersHashToIDMap() {
	m.hashToID = make(map[string]uint32)
113 114
	signers := m.contract.Signers
	for id, signer := range signers {
115
		m.hashToID[signer.Hash] = uint32(id)
116 117 118
	}
}

119
// promiseRound describes a promise round: reception and sending
Caro Axel's avatar
Caro Axel committed
120 121 122
// returns true if the client has to stop the protocol, false otherwise.
// returns an error if any occured.
func (m *SignatureManager) promiseRound(pendingSet, sendSet []common.SequenceCoordinate) (bool, error) {
123
	// Reception of the due promises
Caro Axel's avatar
Caro Axel committed
124
	var promises []*cAPI.Promise
125
	for len(pendingSet) > 0 {
126 127 128
		select {
		case promiseIface := <-m.cServerIface.incomingPromises:
			promise := (promiseIface).(*cAPI.Promise)
Caro Axel's avatar
Caro Axel committed
129 130
			valid, senderID := m.checkPromise(pendingSet, promise)
			if valid {
131
				var err error
Caro Axel's avatar
Caro Axel committed
132
				pendingSet, err = common.RemoveCoordinate(pendingSet, senderID)
133 134 135
				if err != nil {
					continue
				}
Caro Axel's avatar
Caro Axel committed
136 137
				promises = append(promises, promise)
			} else {
Caro Axel's avatar
Caro Axel committed
138
				return true, m.resolve()
139
			}
140

141
		case <-time.After(net.DefaultTimeout):
Caro Axel's avatar
Caro Axel committed
142
			return true, m.resolve()
143
		}
144 145
	}

Caro Axel's avatar
Caro Axel committed
146 147 148 149
	// Now that we received everything, we update the evidence we will give to the ttp
	m.updateReceivedPromises(promises)
	m.lastValidIndex = m.currentIndex

Caro Axel's avatar
Caro Axel committed
150
	c := make(chan error, chanBufferSize)
151
	// Sending of due promises
Caro Axel's avatar
Caro Axel committed
152 153 154
	for _, coord := range sendSet {
		go func(coord common.SequenceCoordinate, m *SignatureManager) {
			promise, err := m.CreatePromise(m.myID, coord.Signer, uint32(m.currentIndex))
155
			if err == nil {
Caro Axel's avatar
Caro Axel committed
156
				err = m.SendEvidence(promise, nil, coord.Signer)
157
			}
Caro Axel's avatar
Caro Axel committed
158
			c <- err
Caro Axel's avatar
Caro Axel committed
159
		}(coord, m)
160 161
	}

162
	// Verifying we sent all the due promises
163
	for range sendSet {
Caro Axel's avatar
Caro Axel committed
164 165 166 167 168 169
		v := <-c
		if v != nil {
			// We couldn't send a due promise
			dAPI.DLog("Couldn't send promise: " + v.Error())
			return true, m.resolve()
		}
170
	}
Caro Axel's avatar
Caro Axel committed
171

Caro Axel's avatar
Caro Axel committed
172
	return false, nil
173
}
174

175 176 177 178 179 180
// 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()
181 182
		delete(m.peers, k)
	}
183
	m.cServer.Stop()
184
}
Caro Axel's avatar
Caro Axel committed
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233

// updateReceivedPromises : updates the RecievedPromises field of the SignatureManager with the provided promises:
// if we don't yet have a promise from this signer, we add it to the array.
// otherwise we replace the one we have by the provided promise.
func (m *SignatureManager) updateReceivedPromises(promises []*cAPI.Promise) {
	for _, p := range promises {
		present, index := m.containsPromiseFrom(p.Context.SenderKeyHash, p.Index)

		if present {
			// it's present, so there is no index error
			_ = m.removeReceivedPromise(index)
		}
		m.archives.receivedPromises = append(m.archives.receivedPromises, p)
	}
}

// containsPromiseFrom : determines if the SignatureManager has already archived a promise from the specified signer, previous to the specified index.
func (m *SignatureManager) containsPromiseFrom(signer []byte, index uint32) (bool, int) {
	for i, p := range m.archives.receivedPromises {
		if bytes.Equal(p.Context.SenderKeyHash, signer) {
			return p.Index < index, i
		}
	}
	return false, 0
}

// removeReceivedPromise : removes the promise at the specified index from the archived received promises.
// If the index is invalid, return an error.
// If the promise is not there, does nothing.
func (m *SignatureManager) removeReceivedPromise(index int) error {
	promises := m.archives.receivedPromises
	if index < 0 || index >= len(promises) {
		return errors.New("Index out of range")
	}

	m.archives.receivedPromises = append(promises[:index], promises[index+1:]...)

	return nil
}

// callForResolve : calls the ttp for resolution.
func (m *SignatureManager) callForResolve() (*tAPI.TTPResponse, error) {
	selfPromise, err := m.CreatePromise(m.myID, m.myID, uint32(m.lastValidIndex))
	if err != nil {
		return nil, err
	}

	toSend := append(m.archives.receivedPromises, selfPromise)

Caro Axel's avatar
Caro Axel committed
234
	request := &tAPI.AlertRequest{Promises: toSend, Index: uint32(m.lastValidIndex)}
Caro Axel's avatar
Caro Axel committed
235 236 237 238 239 240 241 242 243 244 245 246 247 248

	ctx, cancel := context.WithTimeout(context.Background(), net.DefaultTimeout)
	defer cancel()
	response, err := m.ttp.Alert(ctx, request)
	if err != nil {
		return nil, errors.New(grpc.ErrorDesc(err))
	}

	return response, nil
}

// resolve : calls for the resolution, and persists the contract if obtained.
func (m *SignatureManager) resolve() error {
	if m.ttp == nil {
249
		dAPI.DLog("unable to contact TTP")
Caro Axel's avatar
Caro Axel committed
250 251 252
		return errors.New("No connection to TTP, aborting!")
	}

Caro Axel's avatar
Caro Axel committed
253
	dAPI.DLog("contacting TTP with resolve index " + fmt.Sprint(m.lastValidIndex))
Caro Axel's avatar
Caro Axel committed
254 255
	response, err := m.callForResolve()
	if err != nil {
Caro Axel's avatar
Caro Axel committed
256
		dAPI.DLog("Resolve call generated an error: " + err.Error())
Caro Axel's avatar
Caro Axel committed
257 258 259
		return err
	}
	if response.Abort {
260
		dAPI.DLog("contacted TTP, received abort token")
Caro Axel's avatar
Caro Axel committed
261 262
		return nil
	}
263
	dAPI.DLog("contacted TTP, received signed contract")
Caro Axel's avatar
Caro Axel committed
264
	return ioutil.WriteFile(m.mail+"-"+m.uuid+".proof", response.Contract, 0600)
Caro Axel's avatar
Caro Axel committed
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
}

// checkPromise : verifies that the promise is valid wrt the expected promises.
// We assume that the promise data is consistent wrt the platform seal.
func (m *SignatureManager) checkPromise(expected []common.SequenceCoordinate, promise *cAPI.Promise) (bool, uint32) {
	// the promise is consistent, but not for the expected signature
	// this should not happen
	if promise.Context.SignatureUUID != m.uuid {
		return false, 0
	}

	// the promise is not for us
	recipientID, exist := m.hashToID[fmt.Sprintf("%x", promise.Context.RecipientKeyHash)]
	if !exist || recipientID != m.myID {
		return false, 0
	}

	// we didn't expect a promise from this client
	senderID, exist := m.hashToID[fmt.Sprintf("%x", promise.Context.SenderKeyHash)]
	if !exist {
		return false, 0
	}
	for _, c := range expected {
		if c.Signer == senderID && c.Index == promise.Index {
			return true, senderID
		}
	}

	return false, 0
}
295

Loïck Bonniot's avatar
Loïck Bonniot committed
296 297 298 299 300 301
func stopIfNeeded(index int) {
	s := viper.GetInt("stopbefore")
	if s == 0 {
		return
	}

302
	if index == -1 && s == -1 || index == s {
Loïck Bonniot's avatar
Loïck Bonniot committed
303 304
		os.Exit(0)
	}
305
}