diff --git a/dfssc/sign.go b/dfssc/sign.go index 0befe10eb05c8211c0fcc077e66f3222e391fd10..e1610b4598316f1d075524782c3fe95147348900 100644 --- a/dfssc/sign.go +++ b/dfssc/sign.go @@ -37,6 +37,14 @@ func signContract(filename string) { } // Ignition + fmt.Println("Waiting for other signers to be ready...") + signatureUUID, err := manager.SendReadySign() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(5) + } + + fmt.Println("Everybody is ready, starting the signature", signatureUUID) // Signature } diff --git a/dfssc/sign/starter.go b/dfssc/sign/starter.go index ccac6513c804055d44d97af3c10b9ce3f225250d..89fa2f66a01b4564746559268158cd4d59da11a0 100644 --- a/dfssc/sign/starter.go +++ b/dfssc/sign/starter.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "strconv" + "time" "dfss" cAPI "dfss/dfssc/api" @@ -125,3 +126,24 @@ func (m *SignatureManager) addPeer(user *pAPI.User) (ready bool, err error) { return true, nil } + +// SendReadySign sends the READY signal to the platform, and wait (potentially a long time) for START signal. +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, &api.ReadySignRequest{ + ContractUuid: m.contract.UUID, + }) + if err != nil { + return + } + + errorCode := launch.GetErrorCode() + if errorCode.Code != api.ErrorCode_SUCCESS { + err = errors.New(errorCode.Code.String() + " " + errorCode.Message) + return + } + + signatureUUID = launch.SignatureUuid + return +} diff --git a/dfssp/api/platform.pb.go b/dfssp/api/platform.pb.go index 1d32837868d3b26581ca89573a734bfebb5952f1..b9e4552f946425e81c44ea33a5ea5cf424ee2acc 100644 --- a/dfssp/api/platform.pb.go +++ b/dfssp/api/platform.pb.go @@ -210,7 +210,7 @@ func (*ReadySignRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, type LaunchSignature struct { ErrorCode *ErrorCode `protobuf:"bytes,1,opt,name=errorCode" json:"errorCode,omitempty"` SignatureUuid string `protobuf:"bytes,2,opt,name=signatureUuid" json:"signatureUuid,omitempty"` - KeyHash []string `protobuf:"bytes,3,rep,name=keyHash" json:"keyHash,omitempty"` + KeyHash [][]byte `protobuf:"bytes,3,rep,name=keyHash,proto3" json:"keyHash,omitempty"` } func (m *LaunchSignature) Reset() { *m = LaunchSignature{} } @@ -476,39 +476,39 @@ var fileDescriptor0 = []byte{ 0x14, 0x6d, 0xda, 0xec, 0xa3, 0x77, 0x6b, 0x17, 0xb9, 0x03, 0x95, 0x4a, 0x43, 0x93, 0x85, 0x04, 0x42, 0xa8, 0x9d, 0x8a, 0x84, 0x04, 0x6f, 0x59, 0xa9, 0xb6, 0x21, 0x54, 0x4d, 0xee, 0x0a, 0x12, 0x6f, 0x21, 0xf1, 0xd6, 0x68, 0xcd, 0x07, 0xb6, 0x23, 0xb4, 0x27, 0xf8, 0x25, 0xfc, 0x2d, 0x7e, - 0x0e, 0xd8, 0x4e, 0x9c, 0x25, 0xa5, 0x42, 0xda, 0x1e, 0x32, 0x9f, 0xeb, 0xeb, 0x73, 0x7c, 0xcf, + 0x0e, 0xd8, 0x4e, 0x9c, 0x26, 0xa3, 0x42, 0xda, 0x1e, 0x32, 0xdf, 0xeb, 0xeb, 0x73, 0x7c, 0xce, 0xbd, 0x2e, 0x1c, 0x05, 0xd7, 0x9c, 0x8f, 0xd4, 0x27, 0x1d, 0x79, 0x69, 0x38, 0x4a, 0x57, 0x9e, - 0xb8, 0x4e, 0x58, 0x34, 0x4c, 0x59, 0x22, 0x12, 0xd4, 0x92, 0x31, 0xec, 0xc2, 0x01, 0xa1, 0x37, + 0xb8, 0x4e, 0x58, 0x34, 0x4c, 0x59, 0x22, 0x12, 0xd4, 0x92, 0x39, 0xec, 0xc2, 0x01, 0xa1, 0x37, 0x21, 0x17, 0x94, 0x11, 0xfa, 0x2d, 0xa3, 0x5c, 0xa0, 0x43, 0xd8, 0xa2, 0x91, 0x17, 0xae, 0xfa, - 0xd6, 0xb1, 0xf5, 0xa2, 0x4d, 0x72, 0x80, 0xfa, 0xb0, 0xc3, 0xf2, 0x84, 0x7e, 0x53, 0xc7, 0x0d, - 0xc4, 0xbf, 0x2c, 0x68, 0x4f, 0x19, 0x4b, 0xd8, 0x24, 0x09, 0x28, 0x7a, 0x0e, 0xb6, 0x2f, 0xff, - 0xeb, 0xc3, 0xdd, 0x71, 0x6f, 0x28, 0x45, 0x86, 0xe5, 0xee, 0x50, 0x7d, 0x88, 0x4e, 0x50, 0x84, - 0x11, 0xe5, 0xdc, 0xbb, 0xa1, 0x86, 0xb0, 0x80, 0x78, 0x06, 0xb6, 0xa6, 0xda, 0x83, 0x9d, 0xf9, - 0x62, 0x32, 0x99, 0xce, 0xe7, 0x4e, 0x03, 0x01, 0x6c, 0x5f, 0xcc, 0x3e, 0xb9, 0xe4, 0xcc, 0xb1, - 0xd4, 0xc6, 0xa9, 0xfb, 0xde, 0x5d, 0x5c, 0x9d, 0x3b, 0x4d, 0x05, 0x3e, 0xbb, 0x64, 0x76, 0x31, - 0x3b, 0x73, 0x5a, 0xa8, 0xa7, 0xb2, 0xae, 0xa6, 0x84, 0x38, 0x7f, 0xcc, 0x9f, 0x85, 0xdf, 0xc2, - 0x9e, 0x9b, 0x89, 0xe5, 0xff, 0xeb, 0x93, 0x51, 0x91, 0xdc, 0xd2, 0xb8, 0xb8, 0x4c, 0x0e, 0xf0, - 0x09, 0x74, 0x8d, 0x3d, 0x34, 0x58, 0x70, 0xca, 0xd0, 0x53, 0x00, 0x7f, 0x15, 0xd2, 0x58, 0x4c, - 0x28, 0x13, 0x05, 0x45, 0x25, 0x82, 0x77, 0x60, 0x6b, 0x1a, 0xa5, 0xe2, 0x0e, 0x7f, 0x87, 0xde, - 0x65, 0xc2, 0xc5, 0x24, 0x89, 0x05, 0xf3, 0x7c, 0x61, 0xd4, 0x11, 0xd8, 0x4b, 0x8f, 0x2f, 0xf5, - 0xc9, 0x7d, 0xa2, 0xd7, 0x68, 0x00, 0xbb, 0xd7, 0xe1, 0x8a, 0xc6, 0x5e, 0x64, 0xbc, 0x28, 0x31, - 0x7a, 0x0c, 0xdb, 0x3c, 0xbc, 0x89, 0x29, 0xeb, 0xb7, 0x8e, 0x5b, 0x72, 0xa7, 0x40, 0xca, 0x3e, - 0x3f, 0x89, 0x22, 0x29, 0xdb, 0xb7, 0x73, 0xfb, 0x0a, 0x28, 0xed, 0x3b, 0xfc, 0x90, 0x84, 0xf1, - 0x5c, 0xe6, 0x79, 0x22, 0x63, 0xd4, 0x28, 0x63, 0xd8, 0xf7, 0x8b, 0xcb, 0x2c, 0xb2, 0x30, 0x28, - 0xee, 0x5e, 0x8b, 0xa9, 0xdb, 0xa5, 0x09, 0xcb, 0x5b, 0xdc, 0x21, 0x7a, 0x8d, 0x7f, 0x5a, 0xd0, - 0x51, 0xa5, 0xcb, 0x4a, 0x62, 0xea, 0x0b, 0x1a, 0xa0, 0x57, 0xd0, 0xa6, 0xa6, 0xa5, 0x9a, 0x66, - 0x6f, 0xdc, 0xad, 0x37, 0x9a, 0xdc, 0x27, 0xfc, 0xa3, 0xdb, 0xdc, 0xa0, 0x7b, 0x04, 0x76, 0xc6, - 0x75, 0x8d, 0x8a, 0xac, 0xad, 0xc9, 0x94, 0x26, 0xd1, 0x61, 0xfc, 0x05, 0x6c, 0x6d, 0xbe, 0x2c, - 0xfa, 0x96, 0xde, 0x9d, 0xdf, 0xfb, 0x67, 0xe0, 0x7d, 0x53, 0x9b, 0xd5, 0xa6, 0x76, 0xa1, 0x19, - 0xa6, 0x9a, 0xb4, 0x4d, 0xe4, 0xaa, 0x2c, 0xcf, 0xae, 0x94, 0xf7, 0x06, 0x1c, 0x42, 0xbd, 0xe0, - 0x4e, 0xf9, 0xf5, 0x00, 0xab, 0xf0, 0x0f, 0x38, 0xf8, 0xe8, 0x65, 0xb1, 0xbf, 0x2c, 0x8d, 0x7e, - 0xa0, 0x2f, 0xcf, 0xa0, 0xc3, 0xcd, 0xd1, 0x8a, 0x31, 0xf5, 0x60, 0xb5, 0xe4, 0x7c, 0x00, 0x0c, - 0x1c, 0xff, 0x6e, 0xc2, 0xee, 0x65, 0xf1, 0xa4, 0xd1, 0x18, 0x76, 0xcd, 0xa0, 0xa2, 0x43, 0xad, - 0xb9, 0xf6, 0xac, 0x07, 0x6b, 0x37, 0xc1, 0x0d, 0x34, 0x02, 0x5b, 0xbd, 0x0b, 0xe4, 0xe8, 0x9d, - 0xca, 0x13, 0x19, 0xf4, 0x6a, 0x0c, 0xf9, 0xe4, 0xcb, 0x03, 0x2f, 0x01, 0x16, 0x31, 0x33, 0x32, - 0x90, 0x13, 0xaa, 0x61, 0xdf, 0x40, 0xfe, 0x0e, 0xf6, 0xab, 0xe3, 0x8f, 0xfa, 0x3a, 0x63, 0xc3, - 0x8b, 0xd8, 0x70, 0xf6, 0x14, 0x3a, 0xb5, 0x09, 0x46, 0x4f, 0x74, 0xca, 0xa6, 0xa9, 0x1e, 0xa0, - 0x72, 0x56, 0xca, 0xf9, 0xc4, 0x8d, 0x13, 0x4b, 0xea, 0xb7, 0xcb, 0xb6, 0xa2, 0x47, 0x45, 0x3d, - 0xf5, 0x36, 0x0f, 0x72, 0xa3, 0xd6, 0xba, 0x88, 0x1b, 0x5f, 0xb7, 0xf5, 0x0f, 0xe4, 0xeb, 0xbf, - 0x01, 0x00, 0x00, 0xff, 0xff, 0xa1, 0x73, 0x51, 0x2e, 0x41, 0x05, 0x00, 0x00, + 0xd6, 0xb1, 0xf5, 0xa2, 0x4d, 0xf2, 0x00, 0xf5, 0x61, 0x87, 0xe5, 0x05, 0xfd, 0xa6, 0xce, 0x9b, + 0x10, 0xff, 0xb2, 0xa0, 0x3d, 0x65, 0x2c, 0x61, 0x93, 0x24, 0xa0, 0xe8, 0x39, 0xd8, 0xbe, 0xfc, + 0xaf, 0x0f, 0x77, 0xc7, 0xbd, 0xa1, 0x24, 0x19, 0x96, 0xbb, 0x43, 0xf5, 0x21, 0xba, 0x40, 0x01, + 0x46, 0x94, 0x73, 0xef, 0x86, 0x1a, 0xc0, 0x22, 0xc4, 0x33, 0xb0, 0x35, 0xd4, 0x1e, 0xec, 0xcc, + 0x17, 0x93, 0xc9, 0x74, 0x3e, 0x77, 0x1a, 0x08, 0x60, 0xfb, 0x62, 0xf6, 0xc9, 0x25, 0x67, 0x8e, + 0xa5, 0x36, 0x4e, 0xdd, 0xf7, 0xee, 0xe2, 0xea, 0xdc, 0x69, 0xaa, 0xe0, 0xb3, 0x4b, 0x66, 0x17, + 0xb3, 0x33, 0xa7, 0x85, 0x7a, 0xaa, 0xea, 0x6a, 0x4a, 0x88, 0xf3, 0xc7, 0xfc, 0x59, 0xf8, 0x2d, + 0xec, 0xb9, 0x99, 0x58, 0xfe, 0x5f, 0x9f, 0xcc, 0x8a, 0xe4, 0x96, 0xc6, 0xc5, 0x65, 0xf2, 0x00, + 0x9f, 0x40, 0xd7, 0xd8, 0x43, 0x83, 0x05, 0xa7, 0x0c, 0x3d, 0x05, 0xf0, 0x57, 0x21, 0x8d, 0xc5, + 0x84, 0x32, 0x51, 0x40, 0x54, 0x32, 0x78, 0x07, 0xb6, 0xa6, 0x51, 0x2a, 0xee, 0xf0, 0x77, 0xe8, + 0x5d, 0x26, 0x5c, 0x4c, 0x92, 0x58, 0x30, 0xcf, 0x17, 0x86, 0x1d, 0x81, 0xbd, 0xf4, 0xf8, 0x52, + 0x9f, 0xdc, 0x27, 0x7a, 0x8d, 0x06, 0xb0, 0x7b, 0x1d, 0xae, 0x68, 0xec, 0x45, 0xc6, 0x8b, 0x32, + 0x46, 0x8f, 0x61, 0x9b, 0x87, 0x37, 0x31, 0x65, 0xfd, 0xd6, 0x71, 0x4b, 0xee, 0x14, 0x91, 0xb2, + 0xcf, 0x4f, 0xa2, 0x48, 0xd2, 0xf6, 0xed, 0xdc, 0xbe, 0x22, 0x94, 0xf6, 0x1d, 0x7e, 0x48, 0xc2, + 0x78, 0x2e, 0xeb, 0x3c, 0x91, 0x31, 0x6a, 0x98, 0x31, 0xec, 0xfb, 0xc5, 0x65, 0x16, 0x59, 0x18, + 0x14, 0x77, 0xaf, 0xe5, 0xd4, 0xed, 0xd2, 0x84, 0xe5, 0x2d, 0xee, 0x10, 0xbd, 0xc6, 0x3f, 0x2d, + 0xe8, 0x28, 0xe9, 0x52, 0x49, 0x4c, 0x7d, 0x41, 0x03, 0xf4, 0x0a, 0xda, 0xd4, 0xb4, 0x54, 0xc3, + 0xec, 0x8d, 0xbb, 0xf5, 0x46, 0x93, 0x75, 0xc1, 0x3f, 0xbc, 0xcd, 0x0d, 0xbc, 0x47, 0x60, 0x67, + 0x5c, 0x6b, 0x54, 0x60, 0x6d, 0x0d, 0xa6, 0x38, 0x89, 0x4e, 0xe3, 0x2f, 0x60, 0x6b, 0xf3, 0xa5, + 0xe8, 0x5b, 0x7a, 0x77, 0xbe, 0xf6, 0xcf, 0x84, 0xeb, 0xa6, 0x36, 0xab, 0x4d, 0xed, 0x42, 0x33, + 0x4c, 0x35, 0x68, 0x9b, 0xc8, 0x55, 0x29, 0xcf, 0xae, 0xc8, 0x7b, 0x03, 0x0e, 0xa1, 0x5e, 0x70, + 0xa7, 0xfc, 0x7a, 0x80, 0x55, 0xf8, 0x07, 0x1c, 0x7c, 0xf4, 0xb2, 0xd8, 0x5f, 0x96, 0x46, 0x3f, + 0xd0, 0x97, 0x67, 0xd0, 0xe1, 0xe6, 0x68, 0xc5, 0x98, 0x7a, 0xb2, 0x2a, 0x59, 0x0d, 0xc0, 0x5a, + 0xf2, 0xf8, 0x77, 0x13, 0x76, 0x2f, 0x8b, 0x27, 0x8d, 0xc6, 0xb0, 0x6b, 0x06, 0x15, 0x1d, 0x6a, + 0xce, 0x7b, 0xcf, 0x7a, 0x70, 0xef, 0x26, 0xb8, 0x81, 0x46, 0x60, 0xab, 0x77, 0x81, 0x1c, 0xbd, + 0x53, 0x79, 0x22, 0x83, 0x5e, 0x0d, 0x21, 0x9f, 0x7c, 0x79, 0xe0, 0x25, 0xc0, 0x22, 0x66, 0x86, + 0x06, 0x72, 0x40, 0x35, 0xec, 0x1b, 0xc0, 0xdf, 0xc1, 0x7e, 0x75, 0xfc, 0x51, 0x5f, 0x57, 0x6c, + 0x78, 0x11, 0x1b, 0xce, 0x9e, 0x42, 0xa7, 0x36, 0xc1, 0xe8, 0x89, 0x2e, 0xd9, 0x34, 0xd5, 0x03, + 0x54, 0xce, 0x4a, 0x39, 0x9f, 0xb8, 0x71, 0x62, 0x49, 0xfe, 0x76, 0xd9, 0x56, 0xf4, 0xa8, 0xd0, + 0x53, 0x6f, 0xf3, 0x20, 0x37, 0xea, 0x5e, 0x17, 0x71, 0xe3, 0xeb, 0xb6, 0xfe, 0x81, 0x7c, 0xfd, + 0x37, 0x00, 0x00, 0xff, 0xff, 0xe1, 0xe6, 0x41, 0xec, 0x41, 0x05, 0x00, 0x00, } diff --git a/dfssp/api/platform.proto b/dfssp/api/platform.proto index 6802559d13f9c09127897f59b3e454737fffd3e7..5dd180b581901cd0d0c508704799402c57d3b0f9 100644 --- a/dfssp/api/platform.proto +++ b/dfssp/api/platform.proto @@ -93,5 +93,5 @@ message ReadySignRequest { message LaunchSignature { ErrorCode errorCode = 1; string signatureUuid = 2; - repeated string keyHash = 3; + repeated bytes keyHash = 3; } diff --git a/dfssp/common/group.go b/dfssp/common/group.go index afc97587c2d31972abd2ae8822c172a57031a4d2..d780a11e0ca8c609a8e81d7c229cd7e849ef730d 100644 --- a/dfssp/common/group.go +++ b/dfssp/common/group.go @@ -33,7 +33,7 @@ func NewWaitingGroupMap() *WaitingGroupMap { // Join permits the current goroutine to join a room. // It returns the listenning channel and a slice containing messages already sent by other members of the room. // The room is automatically created if unknown. -func (g *WaitingGroupMap) Join(room string) (listen chan interface{}, oldMessages []interface{}) { +func (g *WaitingGroupMap) Join(room string) (listen chan interface{}, oldMessages []interface{}, newRoom bool) { // Check if the current waiting group knows this room g.mutex.Lock() _, present := g.data[room] @@ -42,6 +42,7 @@ func (g *WaitingGroupMap) Join(room string) (listen chan interface{}, oldMessage channels: list.New(), oldMessages: make([]interface{}, 0), } + newRoom = true } g.mutex.Unlock() diff --git a/dfssp/common/group_test.go b/dfssp/common/group_test.go index 5b387b5f1fbafb79b62af1bb554691ab3a7b4ad0..172dddca0ef3b712f12e1197b062a650abe89f6a 100644 --- a/dfssp/common/group_test.go +++ b/dfssp/common/group_test.go @@ -19,7 +19,7 @@ func TestWaitingGroup(t *testing.T) { // Add some virtual latency time.Sleep(time.Duration(i) * time.Millisecond) // Join the waitingGroupMap - myChan, nbs := w.Join("A") + myChan, nbs, _ := w.Join("A") w.Broadcast("A", i) // Wait for other msg for m := range myChan { @@ -43,7 +43,7 @@ func TestCloseWaitingGroup(t *testing.T) { waitGroup.Add(1) go func() { - myChan, _ := w.Join("A") + myChan, _, _ := w.Join("A") for _ = range myChan { t.Fatal("Should not be here") } diff --git a/dfssp/contract/contract_test.go b/dfssp/contract/contract_test.go index 3b62080db8dc0631724ff3e96f6aff5f75a94822..332d5f6e6cd22b4bb2f25120b7d3953deee13e97 100644 --- a/dfssp/contract/contract_test.go +++ b/dfssp/contract/contract_test.go @@ -73,6 +73,19 @@ func TestAddSigner(t *testing.T) { assert.Equal(t, signers[1].UserID.Hex(), id.Hex()) } +func TestGetHashChain(t *testing.T) { + c := entities.NewContract() + c.AddSigner(nil, "mail1", []byte{0xaa}) + c.AddSigner(nil, "mail2", []byte{0xbb, 0xcc}) + c.AddSigner(nil, "mail3", []byte{}) + + chain := c.GetHashChain() + assert.Equal(t, 3, len(chain)) + assert.Equal(t, []byte{0xaa}, chain[0]) + assert.Equal(t, []byte{0xbb, 0xcc}, chain[1]) + assert.Equal(t, []byte{}, chain[2]) +} + func assertContractEqual(t *testing.T, contract, fetched entities.Contract) { assert.Equal(t, contract.File, fetched.File) assert.Equal(t, contract.Date.Unix(), fetched.Date.Unix()) diff --git a/dfssp/contract/starter.go b/dfssp/contract/join.go similarity index 96% rename from dfssp/contract/starter.go rename to dfssp/contract/join.go index 5973986dca6b75a5c6d5dc84b919a1f8d91c3dc7..58262fe875a238bcf24f2ad403d9d3a0724cca87 100644 --- a/dfssp/contract/starter.go +++ b/dfssp/contract/join.go @@ -29,8 +29,8 @@ func JoinSignature(db *mgdb.MongoManager, rooms *common.WaitingGroupMap, in *api } // Join room - roomID := "contract_" + in.ContractUuid - channel, pendingSigners := rooms.Join(roomID) + roomID := "connect_" + in.ContractUuid + channel, pendingSigners, _ := rooms.Join(roomID) // Send pendingSigners for _, p := range pendingSigners { @@ -103,3 +103,4 @@ func sendUserToStream(stream *api.Platform_JoinSignatureServer, contractUUID str User: user, }) } + diff --git a/dfssp/contract/starter_test.go b/dfssp/contract/join_test.go similarity index 77% rename from dfssp/contract/starter_test.go rename to dfssp/contract/join_test.go index 16d2fad9f938969267aee5e009db5410a0cc4e44..71733d05ec9abcfca47b63e5619b277a5ebb6eee 100644 --- a/dfssp/contract/starter_test.go +++ b/dfssp/contract/join_test.go @@ -56,3 +56,20 @@ func TestJoinSignatureBadContract(t *testing.T) { assert.Equal(t, "unauthorized signature", user.ErrorCode.Message) assert.Equal(t, api.ErrorCode_INVARG, user.ErrorCode.Code) } + +func TestJoinSignatureBadUUID(t *testing.T) { + dropDataset() + createDataset() + + client := clientTest(t) + stream, err := client.JoinSignature(context.Background(), &api.JoinSignatureRequest{ + ContractUuid: "VERY_BAD", + Port: 5050, + }) + assert.Equal(t, nil, err) + + user, err := stream.Recv() + assert.Equal(t, nil, err) + assert.Equal(t, "invalid contract uuid", user.ErrorCode.Message) + assert.Equal(t, api.ErrorCode_INVARG, user.ErrorCode.Code) +} diff --git a/dfssp/contract/ready.go b/dfssp/contract/ready.go new file mode 100644 index 0000000000000000000000000000000000000000..5749531bf7063bb7d74217163a04e50813ef3696 --- /dev/null +++ b/dfssp/contract/ready.go @@ -0,0 +1,145 @@ +package contract + +import ( + "time" + + "dfss/dfssp/api" + "dfss/dfssp/common" + "dfss/dfssp/entities" + "dfss/mgdb" + "dfss/net" + "golang.org/x/net/context" + "gopkg.in/mgo.v2/bson" +) + +// readySignal is the structure that is transmitted accross goroutines +type readySignal struct { + ready bool // If true, this is the ready signal. If not, this is a new connection signal + data string // Various data (CN or SignatureUUID) + chain [][]byte // Only used to broadcast hash chain (signers hashes in order) +} + +// ReadySign is the last job of the platform before the signature can occur. +// When a new client is ready, it joins a waitingGroup a waits for a master broadcast announcing that everybody is ready. +// +// Doing it this way is efficient in time, as only one goroutine deals with the database and do global checks. +func ReadySign(db *mgdb.MongoManager, rooms *common.WaitingGroupMap, ctx *context.Context, in *api.ReadySignRequest) *api.LaunchSignature { + roomID := "ready_" + in.ContractUuid + channel, _, first := rooms.Join(roomID) + cn := net.GetCN(ctx) + + // Check UUID + if !bson.IsObjectIdHex(in.ContractUuid) { + return &api.LaunchSignature{ErrorCode: &api.ErrorCode{Code: api.ErrorCode_INVARG}} + } + + // If first in the room, create a goroutine for ready check. + // It is absolutely thread safe thanks to a mutex applied on the `first` variable. + if first { + go masterReadyRoutine(db, rooms, in.ContractUuid) + } + + // Broadcast identity + rooms.Broadcast(roomID, &readySignal{data: cn}) + + // Wait for ready signal + for { + select { + case signal, ok := <-channel: + if !ok { + return &api.LaunchSignature{ErrorCode: &api.ErrorCode{Code: api.ErrorCode_INTERR}} + } + s := signal.(*readySignal) + if s.ready { + if len(s.data) > 0 { + return &api.LaunchSignature{ + ErrorCode: &api.ErrorCode{Code: api.ErrorCode_SUCCESS}, + SignatureUuid: s.data, + KeyHash: s.chain, + } + } // data == "" means the contractUUID is bad + return &api.LaunchSignature{ErrorCode: &api.ErrorCode{Code: api.ErrorCode_INVARG}} + } + case <-(*ctx).Done(): + rooms.Unjoin(roomID, channel) + return nil + case <-time.After(10 * time.Minute): + rooms.Unjoin(roomID, channel) + return &api.LaunchSignature{ErrorCode: &api.ErrorCode{Code: api.ErrorCode_INTERR, Message: "timeout"}} + } + } + +} + +// masterReadyRoutine is a function to be started by the first signer ready as a goroutine. +// It will join the associated ready room and check ready status of each signer when a new signer signals its readiness. +func masterReadyRoutine(db *mgdb.MongoManager, rooms *common.WaitingGroupMap, contractUUID string) { + roomID := "ready_" + contractUUID + channel, oldMessages, _ := rooms.Join(roomID) + + // Push oldMessages into the channel. + // It is safe as this sould be a very small slice (the room is just created). + for _, v := range oldMessages { + channel <- v + } + + // Get contract signers from database + fetch := entities.Contract{ID: bson.ObjectIdHex(contractUUID)} + contract := entities.Contract{} + err := db.Get("contracts").FindByID(fetch, &contract) + if err != nil { + rooms.Broadcast(roomID, &readySignal{ + ready: true, + data: "", + }) // This represents a "error" response + rooms.Unjoin(roomID, channel) + return + } + + signersReady := make([]bool, len(contract.Signers)) + work := true + for work { + select { + case signal, ok := <-channel: + if !ok { // Channel closed, aborting everything + return + } + cn := signal.(*readySignal).data + ready := FindAndUpdatePendingSigner(cn, &signersReady, &contract.Signers) + if ready { + rooms.Broadcast(roomID, &readySignal{ + ready: true, + data: bson.NewObjectId().Hex(), + chain: contract.GetHashChain(), + }) + work = false + } + case <-time.After(10 * time.Minute): + work = false + } + } + + rooms.Unjoin(roomID, channel) +} + +// FindAndUpdatePendingSigner is a utility function to return the state of current signers readiness. +// It has absolutely no interaction with the database. +func FindAndUpdatePendingSigner(mail string, signersReady *[]bool, signers *[]entities.Signer) (ready bool) { + // Find an update ready status + for i, s := range *signers { + if s.Email == mail { + (*signersReady)[i] = true + break + } + } + + // Check if everyone is ready + for _, s := range *signersReady { + if s == false { + return + } + } + + ready = true + return +} diff --git a/dfssp/contract/ready_test.go b/dfssp/contract/ready_test.go new file mode 100644 index 0000000000000000000000000000000000000000..4ff6f155cff41402f3bd38c7aade692b74921103 --- /dev/null +++ b/dfssp/contract/ready_test.go @@ -0,0 +1,53 @@ +package contract_test + +import ( + "testing" + + "dfss/dfssp/api" + "dfss/dfssp/contract" + "dfss/dfssp/entities" + "github.com/bmizerany/assert" + "golang.org/x/net/context" + "gopkg.in/mgo.v2/bson" +) + +func TestReadySignBadContract(t *testing.T) { + dropDataset() + createDataset() + + client := clientTest(t) + result, err := client.ReadySign(context.Background(), &api.ReadySignRequest{ + ContractUuid: bson.NewObjectId().Hex(), + }) + assert.Equal(t, nil, err) + assert.Equal(t, api.ErrorCode_INVARG, result.ErrorCode.Code) +} + +func TestReadySignBadUUID(t *testing.T) { + dropDataset() + createDataset() + + client := clientTest(t) + result, err := client.ReadySign(context.Background(), &api.ReadySignRequest{ + ContractUuid: "VERY_VERY_BAD", + }) + assert.Equal(t, nil, err) + assert.Equal(t, api.ErrorCode_INVARG, result.ErrorCode.Code) +} + +func TestFindAndUpdatePendingSigner(t *testing.T) { + signers := []entities.Signer{ + entities.Signer{Email: "a"}, + entities.Signer{Email: "b"}, + entities.Signer{Email: "c"}, + } + + signersReady := make([]bool, 3) + assert.Equal(t, false, contract.FindAndUpdatePendingSigner("a", &signersReady, &signers)) + assert.Equal(t, false, contract.FindAndUpdatePendingSigner("c", &signersReady, &signers)) + assert.Equal(t, false, contract.FindAndUpdatePendingSigner("c", &signersReady, &signers)) + assert.Equal(t, false, contract.FindAndUpdatePendingSigner("a", &signersReady, &signers)) + assert.Equal(t, true, contract.FindAndUpdatePendingSigner("b", &signersReady, &signers)) + assert.Equal(t, true, contract.FindAndUpdatePendingSigner("b", &signersReady, &signers)) + assert.Equal(t, true, contract.FindAndUpdatePendingSigner("a", &signersReady, &signers)) +} diff --git a/dfssp/entities/contract.go b/dfssp/entities/contract.go index fa262edd9770487d2553a0096418831e02b3d887..885894d1b5d87bc6c9bea8435a9ea4da3ba807d8 100644 --- a/dfssp/entities/contract.go +++ b/dfssp/entities/contract.go @@ -58,6 +58,16 @@ func (c *Contract) AddSigner(id *bson.ObjectId, email string, hash []byte) { c.Signers = append(c.Signers, *signer) } +// GetHashChain returns the ordered slice of signers hashes. +// It's used to check the dfss file if needed. +func (c *Contract) GetHashChain() [][]byte { + chain := make([][]byte, len(c.Signers)) + for i, s := range c.Signers { + chain[i] = s.Hash + } + return chain +} + // ContractRepository to contains every complex methods related to contract type ContractRepository struct { Collection *mgdb.MongoCollection diff --git a/dfssp/server/server.go b/dfssp/server/server.go index e195926ea8f92f1678289f2f063e74601b2773e9..0d927863baf014c7915ae30fd1fee4f815b5d170 100644 --- a/dfssp/server/server.go +++ b/dfssp/server/server.go @@ -81,8 +81,11 @@ func (s *platformServer) JoinSignature(in *api.JoinSignatureRequest, stream api. // // Handle incoming ReadySignRequest messages func (s *platformServer) ReadySign(ctx context.Context, in *api.ReadySignRequest) (*api.LaunchSignature, error) { - // TODO - return nil, nil + cn := net.GetCN(&ctx) + if len(cn) == 0 { + return &api.LaunchSignature{ErrorCode: &api.ErrorCode{Code: api.ErrorCode_BADAUTH}}, nil + } + return contract.ReadySign(s.DB, s.Rooms, &ctx, in), nil } // GetServer returns the GRPC server associated with the platform diff --git a/tests/impexp_test.go b/tests/impexp_test.go index 7071406e9fa44bd733799942e8a241cfd1499ec9..3df50d48bed1c4c528d6e6c05e0198692e24a2bd 100644 --- a/tests/impexp_test.go +++ b/tests/impexp_test.go @@ -47,7 +47,6 @@ func TestExport(t *testing.T) { "pass\n" + "password\n", ) - cmd.Stdout = os.Stdout err = cmd.Run() assert.Equal(t, nil, err) assert.T(t, common.FileExists(confPath)) diff --git a/tests/sign_test.go b/tests/sign_test.go index ddd9040dfc27f726db9d58de08bc97837f8360c6..b86a17f9675bd83cd1e05ced9b40c5d2a2dfb4f2 100644 --- a/tests/sign_test.go +++ b/tests/sign_test.go @@ -5,6 +5,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "strings" "testing" "time" @@ -71,18 +72,31 @@ func TestSignContract(t *testing.T) { clients[0] = newClient(client1) clients[1] = newClient(client2) clients[2] = newClient(client3) - closeChannel := make(chan error, 3) + + closeChannel := make(chan []byte, 3) for i := 0; i < 3; i++ { setLastArg(clients[i], "sign", true) setLastArg(clients[i], contractPath, false) go func(c *exec.Cmd, i int) { time.Sleep(time.Duration(i*2) * time.Second) c.Stdin = strings.NewReader("password\nyes\n") - closeChannel <- c.Run() + output, err := c.Output() + if err != nil { + output = nil + } + closeChannel <- output }(clients[i], i) } + regexes := []*regexp.Regexp{ + regexp.MustCompile(`Everybody is ready, starting the signature [a-f0-9]+`), + regexp.MustCompile(`Do you REALLY want to sign contract\.txt\? Type 'yes' to confirm:`), + } for i := 0; i < 3; i++ { - assert.Equal(t, nil, <-closeChannel) + output := <-closeChannel + assert.NotEqual(t, nil, output, "The return error should be null") + for _, r := range regexes { + assert.T(t, r.Match(output), "Regex is not satisfied: ", r.String()) + } } }