From 8f3152dcf8e11db5dc9fb99ee60347ce448c986e Mon Sep 17 00:00:00 2001 From: x88 Date: Sun, 9 Aug 2020 15:02:50 +0300 Subject: [PATCH 1/8] feat: fixes, tests, actions --- fsm/fsm/fsm.go | 129 ++++++++++++++---- fsm/fsm/fsm_machines_test.go | 12 +- fsm/fsm_pool/fsm_pool.go | 37 +++-- fsm/fsm_pool/fsm_pool_test.go | 15 +- fsm/state_machines/dkg_proposal_fsm/init.go | 7 +- fsm/state_machines/provider_test.go | 91 ++++++++---- .../signature_proposal_fsm/init.go | 4 +- 7 files changed, 213 insertions(+), 82 deletions(-) diff --git a/fsm/fsm/fsm.go b/fsm/fsm/fsm.go index 750d8cb..d6907ee 100644 --- a/fsm/fsm/fsm.go +++ b/fsm/fsm/fsm.go @@ -21,6 +21,10 @@ import ( const ( StateGlobalIdle = State("__idle") StateGlobalDone = State("__done") + + EventRunDefault EventRunMode = iota + EventRunBefore + EventRunAfter ) type State string @@ -39,6 +43,8 @@ func (e *Event) IsEmpty() bool { return e.String() == "" } +type EventRunMode uint8 + // Response returns result for processing with clientMocks events type Response struct { // Returns machine execution result state @@ -55,6 +61,8 @@ type FSM struct { // May be mapping must require pair source + event? transitions map[trKey]*trEvent + autoTransitions map[State]*trEvent + callbacks Callbacks initialEvent Event @@ -77,10 +85,11 @@ type trKey struct { // Transition lightweight event description type trEvent struct { - event Event - dstState State - isInternal bool - isStateBefore bool + event Event + dstState State + isInternal bool + isAuto bool + runMode EventRunMode } type EventDesc struct { @@ -94,8 +103,10 @@ type EventDesc struct { // Internal events, cannot be emitted from external call IsInternal bool - // Set dst state before execute action - IsDstInit bool + // Event must run without manual call + IsAuto bool + + AutoRunMode EventRunMode } type Callback func(event Event, args ...interface{}) (Event, interface{}, error) @@ -121,12 +132,13 @@ func MustNewFSM(machineName string, initialState State, events []EventDesc, call } f := &FSM{ - name: machineName, - currentState: initialState, - initialState: initialState, - transitions: make(map[trKey]*trEvent), - finStates: make(map[State]bool), - callbacks: make(map[Event]Callback), + name: machineName, + currentState: initialState, + initialState: initialState, + transitions: make(map[trKey]*trEvent), + autoTransitions: make(map[State]*trEvent), + finStates: make(map[State]bool), + callbacks: make(map[Event]Callback), } allEvents := make(map[Event]bool) @@ -177,19 +189,40 @@ func MustNewFSM(machineName string, initialState State, events []EventDesc, call panic("duplicate dst for pair `source + event`") } - f.transitions[tKey] = &trEvent{ + trEvent := &trEvent{ tKey.event, event.DstState, event.IsInternal, - event.IsDstInit, + event.IsAuto, + event.AutoRunMode, } + if trEvent.isAuto && trEvent.runMode == EventRunDefault { + trEvent.runMode = EventRunAfter + } + + f.transitions[tKey] = trEvent + // For using provider, event must use with IsGlobal = true if sourceState == initialState { - if f.initialEvent != "" { - panic("machine entry event already exist") + if f.initialEvent == "" { + f.initialEvent = event.Name } - f.initialEvent = event.Name + } + + if event.IsAuto { + if event.AutoRunMode != EventRunBefore && event.AutoRunMode != EventRunAfter { + panic("{AutoRunMode} not set for auto event") + } + + if _, ok := f.autoTransitions[sourceState]; ok { + panic(fmt.Sprintf( + "auto event \"%s\" already exists for state \"%s\"", + event.Name, + sourceState, + )) + } + f.autoTransitions[sourceState] = trEvent } allSources[sourceState] = true @@ -260,14 +293,28 @@ func (f *FSM) do(trEvent *trEvent, args ...interface{}) (resp *Response, err err // f.eventMu.Lock() // defer f.eventMu.Unlock() - if trEvent.isStateBefore { - err = f.SetState(trEvent.event) - if err != nil { - resp = &Response{ - State: f.State(), - } - return resp, err + // Process auto event + if autoEvent, ok := f.autoTransitions[f.State()]; ok { + autoEventResp := &Response{ + State: f.State(), } + if autoEvent.runMode == EventRunBefore { + if callback, ok := f.callbacks[autoEvent.event]; ok { + outEvent, autoEventResp.Data, err = callback(autoEvent.event, args...) + if err != nil { + return autoEventResp, err + } + } + if outEvent.IsEmpty() || autoEvent.event == outEvent { + err = f.SetState(autoEvent.event) + } else { + err = f.SetState(outEvent) + } + if err != nil { + return autoEventResp, err + } + } + outEvent = "" } resp = &Response{ @@ -282,13 +329,35 @@ func (f *FSM) do(trEvent *trEvent, args ...interface{}) (resp *Response, err err } } - if !trEvent.isStateBefore { - if outEvent.IsEmpty() || trEvent.event == outEvent { - err = f.SetState(trEvent.event) - } else { - err = f.SetState(outEvent) - } + // Set state when callback executed + if outEvent.IsEmpty() || trEvent.event == outEvent { + err = f.SetState(trEvent.event) + } else { + err = f.SetState(outEvent) + } + // Process auto event + if autoEvent, ok := f.autoTransitions[f.State()]; ok { + autoEventResp := &Response{ + State: f.State(), + } + if autoEvent.runMode == EventRunAfter { + if callback, ok := f.callbacks[autoEvent.event]; ok { + outEvent, autoEventResp.Data, err = callback(autoEvent.event, args...) + if err != nil { + return autoEventResp, err + } + } + if outEvent.IsEmpty() || autoEvent.event == outEvent { + err = f.SetState(autoEvent.event) + } else { + err = f.SetState(outEvent) + } + if err != nil { + return autoEventResp, err + } + } + outEvent = "" } resp.State = f.State() diff --git a/fsm/fsm/fsm_machines_test.go b/fsm/fsm/fsm_machines_test.go index 281ce23..b068dd2 100644 --- a/fsm/fsm/fsm_machines_test.go +++ b/fsm/fsm/fsm_machines_test.go @@ -48,14 +48,14 @@ var ( } testingCallbacks = Callbacks{ - eventInit: func(event Event, args ...interface{}) (interface{}, error) { - return nil, nil + eventInit: func(event Event, args ...interface{}) (Event, interface{}, error) { + return event, nil, nil }, - eventInternalOut2: func(event Event, args ...interface{}) (interface{}, error) { - return nil, nil + eventInternalOut2: func(event Event, args ...interface{}) (Event, interface{}, error) { + return event, nil, nil }, - eventProcess: func(event Event, args ...interface{}) (interface{}, error) { - return nil, nil + eventProcess: func(event Event, args ...interface{}) (Event, interface{}, error) { + return event, nil, nil }, } ) diff --git a/fsm/fsm_pool/fsm_pool.go b/fsm/fsm_pool/fsm_pool.go index 7b2edf0..a6d2dbf 100644 --- a/fsm/fsm_pool/fsm_pool.go +++ b/fsm/fsm_pool/fsm_pool.go @@ -3,7 +3,6 @@ package fsm_pool import ( "errors" "fmt" - "github.com/depools/dc4bc/fsm/fsm" ) @@ -23,6 +22,8 @@ type MachineProvider interface { GlobalInitialEvent() fsm.Event + EntryEvent() fsm.Event + EventsList() []fsm.Event StatesSourcesList() []fsm.State @@ -39,9 +40,10 @@ type FSMStatesMapper map[fsm.State]string type FSMPool struct { fsmInitialEvent fsm.Event // Pool mapper by names - mapper FSMMapper - events FSMEventsMapper - states FSMStatesMapper + mapper FSMMapper + events FSMEventsMapper + states FSMStatesMapper + entryEvents map[fsm.State]fsm.Event } func Init(machines ...MachineProvider) *FSMPool { @@ -49,9 +51,10 @@ func Init(machines ...MachineProvider) *FSMPool { panic("cannot initialize empty pool") } p := &FSMPool{ - mapper: make(FSMMapper), - events: make(FSMEventsMapper), - states: make(FSMStatesMapper), + mapper: make(FSMMapper), + events: make(FSMEventsMapper), + states: make(FSMStatesMapper), + entryEvents: make(map[fsm.State]fsm.Event), } allInitStatesMap := make(map[fsm.State]string) @@ -84,7 +87,7 @@ func Init(machines ...MachineProvider) *FSMPool { } // Setup entry event for machines pool if available - if initialEvent := machine.GlobalInitialEvent(); initialEvent != "" { + if initialEvent := machine.GlobalInitialEvent(); !initialEvent.IsEmpty() { if p.fsmInitialEvent != "" { panic("duplicate entry event initialization") } @@ -92,6 +95,10 @@ func Init(machines ...MachineProvider) *FSMPool { p.fsmInitialEvent = initialEvent } + if machineEntryEvent := machine.EntryEvent(); !machineEntryEvent.IsEmpty() { + p.entryEvents[machine.InitialState()] = machineEntryEvent + } + p.mapper[machineName] = machine } @@ -153,3 +160,17 @@ func (p *FSMPool) MachineByState(state fsm.State) (MachineProvider, error) { } return machine, nil } + +/*func (p *FSMPool) Do(machine MachineProvider, event fsm.Event, args ...interface{}) (resp *fsm.Response, err error) { + panic("llslsl") + resp, err = machine.Do(event, args...) + if err != nil { + return resp, err + } + + if machine.IsFinState(resp.State) { + log.Println("Final!!!!") + } + return +} +*/ diff --git a/fsm/fsm_pool/fsm_pool_test.go b/fsm/fsm_pool/fsm_pool_test.go index b0e0ca4..586bafc 100644 --- a/fsm/fsm_pool/fsm_pool_test.go +++ b/fsm/fsm_pool/fsm_pool_test.go @@ -44,7 +44,7 @@ const ( var ( testing1Events = []fsm.EventDesc{ // Init - {Name: eventFSM1Init, SrcState: []fsm.State{fsm1StateInit}, DstState: fsm1StateStage1, IsDstInit: true}, + {Name: eventFSM1Init, SrcState: []fsm.State{fsm1StateInit}, DstState: fsm1StateStage1, IsAuto: true, AutoRunMode: fsm.EventRunAfter}, {Name: eventFSM1Internal, SrcState: []fsm.State{fsm1StateStage1}, DstState: fsm1StateStage2, IsInternal: true}, // Cancellation events @@ -73,21 +73,24 @@ func NewFSM1() MachineProvider { return machine } -func (m *testMachineFSM1) actionFSM1SetUpData(event fsm.Event, args ...interface{}) (response interface{}, err error) { +func (m *testMachineFSM1) actionFSM1SetUpData(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { m.data = testVal1 - return m.DoInternal(eventFSM1Internal) + outEvent = eventFSM1Internal + return } -func (m *testMachineFSM1) actionFSM1ProcessData(event fsm.Event, args ...interface{}) (response interface{}, err error) { +func (m *testMachineFSM1) actionFSM1ProcessData(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { if len(args) == 1 { if val, ok := args[0].(int); ok { m.data -= val } } - return m.data, nil + + response = m.data + return } -func (m *testMachineFSM1) actionFSM1EmitOut2(event fsm.Event, args ...interface{}) (response interface{}, err error) { +func (m *testMachineFSM1) actionFSM1EmitOut2(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { return } diff --git a/fsm/state_machines/dkg_proposal_fsm/init.go b/fsm/state_machines/dkg_proposal_fsm/init.go index 422d62c..97e47a5 100644 --- a/fsm/state_machines/dkg_proposal_fsm/init.go +++ b/fsm/state_machines/dkg_proposal_fsm/init.go @@ -3,14 +3,13 @@ package dkg_proposal_fsm import ( "github.com/depools/dc4bc/fsm/fsm" "github.com/depools/dc4bc/fsm/state_machines/internal" - "github.com/depools/dc4bc/fsm/state_machines/signature_proposal_fsm" "sync" ) const ( FsmName = "dkg_proposal_fsm" - StateDkgInitial = signature_proposal_fsm.StateValidationCompleted + StateDkgInitial = StateDkgPubKeysAwaitConfirmations StateDkgPubKeysAwaitConfirmations = fsm.State("state_dkg_pub_keys_await_confirmations") // Cancelled @@ -43,7 +42,7 @@ const ( StateDkgResponsesAwaitConfirmed = fsm.State("state_dkg_responses_await_confirmed") // Events - EventDKGPubKeysSendingRequiredInternal = fsm.Event("event_dkg_pub_key_sending_required_internal") + eventDKGPubKeysSendingRequiredAuto = fsm.Event("event_dkg_pub_key_sending_required_internal") EventDKGPubKeyConfirmationReceived = fsm.Event("event_dkg_pub_key_confirm_received") EventDKGPubKeyConfirmationError = fsm.Event("event_dkg_pub_key_confirm_canceled_by_error") @@ -88,7 +87,7 @@ func New() internal.DumpedMachineProvider { // Init // Switch to pub keys required - {Name: EventDKGPubKeysSendingRequiredInternal, SrcState: []fsm.State{StateDkgInitial}, DstState: StateDkgPubKeysAwaitConfirmations, IsInternal: true}, + // {Name: eventDKGPubKeysSendingRequiredAuto, SrcState: []fsm.State{StateDkgInitial}, DstState: StateDkgPubKeysAwaitConfirmations, IsInternal: true, IsAuto: true, AutoRunMode: fsm.EventRunAfter}, // Pub keys sending {Name: EventDKGPubKeyConfirmationReceived, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitConfirmations}, diff --git a/fsm/state_machines/provider_test.go b/fsm/state_machines/provider_test.go index 8a67c3a..701a163 100644 --- a/fsm/state_machines/provider_test.go +++ b/fsm/state_machines/provider_test.go @@ -7,6 +7,8 @@ import ( "crypto/x509" "encoding/base64" "fmt" + "github.com/depools/dc4bc/fsm/fsm" + dpf "github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm" spf "github.com/depools/dc4bc/fsm/state_machines/signature_proposal_fsm" "github.com/depools/dc4bc/fsm/types/requests" "github.com/depools/dc4bc/fsm/types/responses" @@ -93,41 +95,58 @@ func TestCreate_Negative(t *testing.T) { } } -func Test_Workflow(t *testing.T) { - testFSMInstance, err := Create(testTransactionId) - if err != nil { - t.Fatalf("expected nil error, got {%s}", err) +func compareErrNil(t *testing.T, got error) { + if got != nil { + t.Fatalf("expected nil error, got {%s}", got) } +} - if testFSMInstance == nil { +func compareFSMInstanceNotNil(t *testing.T, got *FSMInstance) { + if got == nil { t.Fatalf("expected {*FSMInstance}") } +} + +func compareDumpNotZero(t *testing.T, got []byte) { + if len(got) == 0 { + t.Fatalf("expected non zero dump, when executed without error") + } +} + +func compareFSMResponseNotNil(t *testing.T, got *fsm.Response) { + if got == nil { + t.Fatalf("expected {*fsm.FSMResponse} got nil") + } +} + +func compareState(t *testing.T, expected fsm.State, got fsm.State) { + if got != expected { + t.Fatalf("expected state {%s} got {%s}", expected, got) + } +} + +func Test_Workflow(t *testing.T) { + testFSMInstance, err := Create(testTransactionId) + + compareErrNil(t, err) + + compareFSMInstanceNotNil(t, testFSMInstance) if testFSMInstance.machine.Name() != spf.FsmName { t.Fatalf("expected machine name {%s}", spf.FsmName) } - if testFSMInstance.machine.State() != spf.StateParticipantsConfirmationsInit { - t.Fatalf("expected inital state {%s}", spf.StateParticipantsConfirmationsInit) - } + compareState(t, spf.StateParticipantsConfirmationsInit, testFSMInstance.machine.State()) fsmResponse, dump, err := testFSMInstance.Do(spf.EventInitProposal, testParticipantsListRequest) - if err != nil { - t.Fatalf("expected nil error, got {%s}", err) - } + compareErrNil(t, err) - if len(dump) == 0 { - t.Fatalf("expected non zero dump, when executed without error") - } + compareDumpNotZero(t, dump) - if fsmResponse == nil { - t.Fatalf("expected {*fsm.FSMResponse}") - } + compareFSMResponseNotNil(t, fsmResponse) - if fsmResponse.State != spf.StateAwaitParticipantsConfirmations { - t.Fatalf("expected state {%s}", spf.StateAwaitParticipantsConfirmations) - } + compareState(t, spf.StateAwaitParticipantsConfirmations, fsmResponse.State) testParticipantsListResponse, ok := fsmResponse.Data.(responses.SignatureProposalParticipantInvitationsResponse) @@ -163,17 +182,17 @@ func Test_Workflow(t *testing.T) { tm = tm.Add(10 * time.Second) - for _, participant := range participantsMap { + participantsCount := len(participantsMap) + participantCounter := participantsCount + + for _, participant := range participantsMap { + participantCounter-- testFSMInstance, err = FromDump(dump) - if err != nil { - t.Fatalf("expected nil error, got {%s}", err) - } + compareErrNil(t, err) - if testFSMInstance == nil { - t.Fatalf("expected {*FSMInstance}") - } + compareFSMInstanceNotNil(t, testFSMInstance) if _, ok := testParticipants[participant.PubKeyFingerprint]; !ok { t.Fatalf("not found external user data for response fingerprint") @@ -191,6 +210,24 @@ func Test_Workflow(t *testing.T) { DecryptedInvitation: string(encrypted), CreatedAt: &tm, }) + + compareErrNil(t, err) + + compareDumpNotZero(t, dump) + + compareFSMResponseNotNil(t, fsmResponse) + + if participantCounter > 0 { + if fsmResponse.State != spf.StateAwaitParticipantsConfirmations { + t.Fatalf("expected state {%s} got {%s}", spf.StateAwaitParticipantsConfirmations, fsmResponse.State) + } + } else { + if fsmResponse.State != dpf.StateDkgInitial { + t.Fatalf("expected state {%s} got {%s}", dpf.StateDkgInitial, fsmResponse.State) + } + } + log.Println(fsmResponse.State, err) + } } diff --git a/fsm/state_machines/signature_proposal_fsm/init.go b/fsm/state_machines/signature_proposal_fsm/init.go index 75a7bfc..a338920 100644 --- a/fsm/state_machines/signature_proposal_fsm/init.go +++ b/fsm/state_machines/signature_proposal_fsm/init.go @@ -2,6 +2,7 @@ package signature_proposal_fsm import ( "github.com/depools/dc4bc/fsm/fsm" + "github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm" "github.com/depools/dc4bc/fsm/state_machines/internal" "sync" ) @@ -17,6 +18,7 @@ const ( StateValidationCanceledByParticipant = fsm.State("state_sig_proposal_canceled_by_participant") StateValidationCanceledByTimeout = fsm.State("state_sig_proposal_canceled_by_timeout") + // Out state StateValidationCompleted = fsm.State("state_sig_proposal_completed") EventInitProposal = fsm.Event("event_sig_proposal_init") @@ -60,7 +62,7 @@ func New() internal.DumpedMachineProvider { // eventProposalValidate internal or from client? // yay // Exit point - {Name: eventSetProposalValidatedInternal, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: fsm.State("state_dkg_pub_keys_sending_required"), IsInternal: true}, + {Name: eventSetProposalValidatedInternal, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: dkg_proposal_fsm.StateDkgPubKeysAwaitConfirmations, IsInternal: true}, // nan {Name: eventSetValidationCanceledByTimeout, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateValidationCanceledByTimeout, IsInternal: true}, }, From 7a910a0e8d9dee31ee96ff666b5a79cb2254b50b Mon Sep 17 00:00:00 2001 From: x88 Date: Tue, 11 Aug 2020 11:56:11 +0300 Subject: [PATCH 2/8] feat: added methods `State()` `Id()` --- fsm/state_machines/provider.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/fsm/state_machines/provider.go b/fsm/state_machines/provider.go index d0d82ea..db147d3 100644 --- a/fsm/state_machines/provider.go +++ b/fsm/state_machines/provider.go @@ -79,6 +79,10 @@ func FromDump(data []byte) (*FSMInstance, error) { func (i *FSMInstance) Do(event fsm.Event, args ...interface{}) (result *fsm.Response, dump []byte, err error) { var dumpErr error + if i.machine == nil { + return nil, []byte{}, errors.New("machine is not initialized") + } + result, err = i.machine.Do(event, args...) // On route errors result will be nil @@ -106,7 +110,8 @@ func (i *FSMInstance) InitDump(transactionId string) error { } i.dump = &FSMDump{ - State: fsm.StateGlobalIdle, + TransactionId: transactionId, + State: fsm.StateGlobalIdle, Payload: &internal.DumpedMachineStatePayload{ TransactionId: transactionId, ConfirmationProposalPayload: nil, @@ -116,6 +121,20 @@ func (i *FSMInstance) InitDump(transactionId string) error { return nil } +func (i *FSMInstance) State() (fsm.State, error) { + if i.machine == nil { + return "", errors.New("machine is not initialized") + } + return i.machine.State(), nil +} + +func (i *FSMInstance) Id() string { + if i.dump != nil { + return i.dump.TransactionId + } + return "" +} + // TODO: Add encryption func (d *FSMDump) Marshal() ([]byte, error) { return json.Marshal(d) From 1e8af819bb91d170c4fc7660f2cc2361cfb12579 Mon Sep 17 00:00:00 2001 From: x88 Date: Tue, 11 Aug 2020 12:35:43 +0300 Subject: [PATCH 3/8] feat: added generation transactionId for `Create()` --- fsm/state_machines/provider.go | 30 ++++++++++++++++++++++++++--- fsm/state_machines/provider_test.go | 21 ++++++++++---------- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/fsm/state_machines/provider.go b/fsm/state_machines/provider.go index db147d3..93f4ce9 100644 --- a/fsm/state_machines/provider.go +++ b/fsm/state_machines/provider.go @@ -1,6 +1,8 @@ package state_machines import ( + "crypto/rand" + "encoding/base64" "encoding/json" "errors" "github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm" @@ -12,6 +14,10 @@ import ( "github.com/depools/dc4bc/fsm/state_machines/signature_proposal_fsm" ) +const ( + dkgTransactionIdLength = 128 +) + // Is machine state scope dump will be locked? type FSMDump struct { TransactionId string @@ -37,9 +43,17 @@ func init() { // Create new fsm with unique id // transactionId required for unique identify dump -func Create(transactionId string) (*FSMInstance, error) { - var err error - i := &FSMInstance{} +func Create() (*FSMInstance, error) { + var ( + err error + i = &FSMInstance{} + ) + transactionId, err := generateDkgTransactionId() + + if err != nil { + return nil, err + } + err = i.InitDump(transactionId) if err != nil { @@ -148,3 +162,13 @@ func (d *FSMDump) Unmarshal(data []byte) error { return json.Unmarshal(data, d) } + +func generateDkgTransactionId() (string, error) { + b := make([]byte, dkgTransactionIdLength) + _, err := rand.Read(b) + if err != nil { + return "", err + } + + return base64.URLEncoding.EncodeToString(b), err +} diff --git a/fsm/state_machines/provider_test.go b/fsm/state_machines/provider_test.go index 701a163..5a17911 100644 --- a/fsm/state_machines/provider_test.go +++ b/fsm/state_machines/provider_test.go @@ -78,7 +78,7 @@ func init() { } func TestCreate_Positive(t *testing.T) { - testFSMInstance, err := Create(testTransactionId) + testFSMInstance, err := Create() if err != nil { t.Fatalf("expected nil error, got {%s}", err) } @@ -88,13 +88,6 @@ func TestCreate_Positive(t *testing.T) { } } -func TestCreate_Negative(t *testing.T) { - _, err := Create("") - if err == nil { - t.Fatalf("expected error for empty {transactionId}") - } -} - func compareErrNil(t *testing.T, got error) { if got != nil { t.Fatalf("expected nil error, got {%s}", got) @@ -126,7 +119,9 @@ func compareState(t *testing.T, expected fsm.State, got fsm.State) { } func Test_Workflow(t *testing.T) { - testFSMInstance, err := Create(testTransactionId) + testFSMInstance, err := Create() + + log.Println(testFSMInstance.Id()) compareErrNil(t, err) @@ -148,6 +143,10 @@ func Test_Workflow(t *testing.T) { compareState(t, spf.StateAwaitParticipantsConfirmations, fsmResponse.State) + if testFSMInstance.Id() != testTransactionId { + t.Fatalf("expected {testTransactionId}") + } + testParticipantsListResponse, ok := fsmResponse.Data.(responses.SignatureProposalParticipantInvitationsResponse) if !ok { @@ -227,7 +226,9 @@ func Test_Workflow(t *testing.T) { } } - log.Println(fsmResponse.State, err) + state, err := testFSMInstance.State() + + log.Println(err, state) } } From 10c343ac908d621699623d6bc80319bcab6b5cb7 Mon Sep 17 00:00:00 2001 From: x88 Date: Tue, 11 Aug 2020 19:46:18 +0300 Subject: [PATCH 4/8] feat: fixed flow, added tests --- fsm/cmd/test/test.go | 82 ----- fsm/config/config.go | 6 +- fsm/fsm/fsm.go | 12 +- fsm/fsm/fsm_machines_test.go | 4 +- fsm/fsm_pool/fsm_pool_test.go | 6 +- .../dkg_proposal_fsm/actions.go | 222 +++++++++++++- fsm/state_machines/dkg_proposal_fsm/init.go | 104 ++++--- fsm/state_machines/internal/types.go | 33 +- fsm/state_machines/provider.go | 9 +- fsm/state_machines/provider_test.go | 289 ++++++++++++++++-- .../signature_proposal_fsm/actions.go | 117 ++++++- .../signature_proposal_fsm/init.go | 20 +- fsm/types/responses/signature_proposal.go | 12 +- 13 files changed, 701 insertions(+), 215 deletions(-) delete mode 100644 fsm/cmd/test/test.go diff --git a/fsm/cmd/test/test.go b/fsm/cmd/test/test.go deleted file mode 100644 index 2647b35..0000000 --- a/fsm/cmd/test/test.go +++ /dev/null @@ -1,82 +0,0 @@ -package main - -import ( - "github.com/depools/dc4bc/fsm/fsm" - "github.com/depools/dc4bc/fsm/state_machines/signature_proposal_fsm" - "github.com/depools/dc4bc/fsm/types/responses" - "log" - "time" - - "github.com/depools/dc4bc/fsm/state_machines" - "github.com/depools/dc4bc/fsm/types/requests" -) - -func main() { - tm := time.Now() - fsmMachine, err := state_machines.Create("d8a928b2043db77e340b523547bf16cb4aa483f0645fe0a290ed1f20aab76257") - log.Println(fsmMachine, err) - resp, dump, err := fsmMachine.Do( - signature_proposal_fsm.EventInitProposal, - requests.SignatureProposalParticipantsListRequest{ - Participants: []*requests.SignatureProposalParticipantsEntry{ - { - "John Doe", - []byte("pubkey123123"), - }, - { - "Crypto Billy", - []byte("pubkey456456"), - }, - { - "Matt", - []byte("pubkey789789"), - }, - }, - CreatedAt: &tm, - }, - ) - if err != nil { - log.Println("Err", err) - return - } - - log.Println("Dump", string(dump)) - - processResponse(resp) - -} - -func processResponse(resp *fsm.Response) { - switch resp.State { - // Await proposals - case fsm.State("state_validation_await_participants_confirmations"): - data, ok := resp.Data.(responses.SignatureProposalParticipantInvitationsResponse) - if !ok { - log.Printf("undefined response type for state \"%s\"\n", resp.State) - return - } - sendInvitations(data) - - case fsm.State("validation_canceled_by_participant"): - updateDashboardWithCanceled("Participant") - case fsm.State("validation_canceled_by_timeout"): - updateDashboardWithCanceled("Timeout") - default: - log.Printf("undefined response type for state \"%s\"\n", resp.State) - } -} - -func sendInvitations(invitations responses.SignatureProposalParticipantInvitationsResponse) { - for _, invitation := range invitations { - log.Printf( - "Dear %s, please encrypt value \"%s\" with your key, fingerprint: %s\n", - invitation.Title, - invitation.EncryptedInvitation, - invitation.PubKeyFingerprint, - ) - } -} - -func updateDashboardWithCanceled(msg string) { - log.Printf("Breaking news! Proposal canceled with reason: %s\n", msg) -} diff --git a/fsm/config/config.go b/fsm/config/config.go index 6234e6a..bb77b16 100644 --- a/fsm/config/config.go +++ b/fsm/config/config.go @@ -1,6 +1,10 @@ package config +import "time" + const ( // TODO: Move to machine level configs? - ParticipantsMinCount = 3 + ParticipantsMinCount = 3 + SignatureProposalConfirmationDeadline = time.Hour * 24 + DkgConfirmationDeadline = time.Hour * 24 ) diff --git a/fsm/fsm/fsm.go b/fsm/fsm/fsm.go index d6907ee..0a947a4 100644 --- a/fsm/fsm/fsm.go +++ b/fsm/fsm/fsm.go @@ -21,7 +21,9 @@ import ( const ( StateGlobalIdle = State("__idle") StateGlobalDone = State("__done") +) +const ( EventRunDefault EventRunMode = iota EventRunBefore EventRunAfter @@ -189,6 +191,10 @@ func MustNewFSM(machineName string, initialState State, events []EventDesc, call panic("duplicate dst for pair `source + event`") } + if event.IsAuto && event.AutoRunMode == EventRunDefault { + event.AutoRunMode = EventRunAfter + } + trEvent := &trEvent{ tKey.event, event.DstState, @@ -197,10 +203,6 @@ func MustNewFSM(machineName string, initialState State, events []EventDesc, call event.AutoRunMode, } - if trEvent.isAuto && trEvent.runMode == EventRunDefault { - trEvent.runMode = EventRunAfter - } - f.transitions[tKey] = trEvent // For using provider, event must use with IsGlobal = true @@ -245,7 +247,7 @@ func MustNewFSM(machineName string, initialState State, events []EventDesc, call } if _, ok := allEvents[event]; !ok { - panic("callback has no event") + panic("callback has empty event") } f.callbacks[event] = callback diff --git a/fsm/fsm/fsm_machines_test.go b/fsm/fsm/fsm_machines_test.go index b068dd2..55ebc48 100644 --- a/fsm/fsm/fsm_machines_test.go +++ b/fsm/fsm/fsm_machines_test.go @@ -12,9 +12,9 @@ const ( stateStage1 = State("state_stage1") // Process data stateStage2 = State("state_stage2") - // Cancelled with internal event + // Canceled with internal event stateCanceledByInternal = State("state_canceled") - // Cancelled with external event + // Canceled with external event stateCanceled2 = State("state_canceled2") // Out endpoint to switch stateOutToFSM2 = State("state_out_to_fsm2") diff --git a/fsm/fsm_pool/fsm_pool_test.go b/fsm/fsm_pool/fsm_pool_test.go index 586bafc..a3d7f73 100644 --- a/fsm/fsm_pool/fsm_pool_test.go +++ b/fsm/fsm_pool/fsm_pool_test.go @@ -23,9 +23,9 @@ const ( fsm1StateStage1 = fsm.State("state_fsm1_stage1") // Process data fsm1StateStage2 = fsm.State("state_fsm1_stage2") - // Cancelled with internal event + // Canceled with internal event fsm1StateCanceledByInternal = fsm.State("state_fsm1_canceled") - // Cancelled with external event + // Canceled with external event fsm1StateCanceled2 = fsm.State("state_fsm1_canceled2") // Out endpoint to switch fsm1StateOutToFSM2 = fsm.State("state_fsm1_out_to_fsm2") @@ -108,7 +108,7 @@ const ( // Process data fsm2StateStage1 = fsm.State("state_fsm2_stage1") fsm2StateStage2 = fsm.State("state_fsm2_stage2") - // Cancelled with internal event + // Canceled with internal event fsm2StateCanceledByInternal = fsm.State("state_fsm2_canceled") // Out endpoint to switch fsm2StateOutToFSM3 = fsm.State("state_fsm2_out_to_fsm3") diff --git a/fsm/state_machines/dkg_proposal_fsm/actions.go b/fsm/state_machines/dkg_proposal_fsm/actions.go index 8c015ee..cce93e4 100644 --- a/fsm/state_machines/dkg_proposal_fsm/actions.go +++ b/fsm/state_machines/dkg_proposal_fsm/actions.go @@ -3,17 +3,15 @@ package dkg_proposal_fsm import ( "errors" "fmt" + "github.com/depools/dc4bc/fsm/config" "github.com/depools/dc4bc/fsm/fsm" "github.com/depools/dc4bc/fsm/state_machines/internal" "github.com/depools/dc4bc/fsm/types/requests" + "time" ) // Pub keys -func (m *DKGProposalFSM) actionPubKeyPrepareConfirmations(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { - return -} - func (m *DKGProposalFSM) actionPubKeyConfirmationReceived(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { m.payloadMu.Lock() defer m.payloadMu.Unlock() @@ -41,7 +39,12 @@ func (m *DKGProposalFSM) actionPubKeyConfirmationReceived(inEvent fsm.Event, arg return } - copy(dkgProposalParticipant.PublicKey, request.PubKey) + if dkgProposalParticipant.Status != internal.PubKeyAwaitConfirmation { + err = errors.New(fmt.Sprintf("cannot confirm pubkey with {Status} = {\"%s\"}", dkgProposalParticipant.Status)) + return + } + + copy(dkgProposalParticipant.PubKey, request.PubKey) dkgProposalParticipant.UpdatedAt = request.CreatedAt dkgProposalParticipant.Status = internal.PubKeyConfirmed @@ -50,6 +53,55 @@ func (m *DKGProposalFSM) actionPubKeyConfirmationReceived(inEvent fsm.Event, arg return } +func (m *DKGProposalFSM) actionValidateDkgProposalPubKeys(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { + var ( + isContainsError, isContainsExpired bool + ) + + m.payloadMu.Lock() + defer m.payloadMu.Unlock() + + tm := time.Now() + + unconfirmedParticipants := len(m.payload.DKGProposalPayload) + for _, participant := range m.payload.DKGProposalPayload { + if participant.Status == internal.PubKeyAwaitConfirmation { + if participant.UpdatedAt.Add(config.DkgConfirmationDeadline).Before(tm) { + isContainsExpired = true + } + } else { + if participant.Status == internal.PubKeyConfirmationError { + isContainsError = true + } else if participant.Status == internal.PubKeyConfirmed { + unconfirmedParticipants-- + } + } + } + + if isContainsError { + outEvent = eventDKGSetPubKeysConfirmationCanceledByErrorInternal + return + } + + if isContainsExpired { + outEvent = eventDKGSetPubKeysConfirmationCanceledByTimeoutInternal + return + } + + // The are no declined and timed out participants, check for all confirmations + if unconfirmedParticipants > 0 { + return + } + + outEvent = eventDKGSetPubKeysConfirmedInternal + + for _, participant := range m.payload.DKGProposalPayload { + participant.Status = internal.CommitAwaitConfirmation + } + + return +} + // Commits func (m *DKGProposalFSM) actionCommitConfirmationReceived(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { @@ -79,6 +131,11 @@ func (m *DKGProposalFSM) actionCommitConfirmationReceived(inEvent fsm.Event, arg return } + if dkgProposalParticipant.Status != internal.CommitAwaitConfirmation { + err = errors.New(fmt.Sprintf("cannot confirm commit with {Status} = {\"%s\"}", dkgProposalParticipant.Status)) + return + } + copy(dkgProposalParticipant.Commit, request.Commit) dkgProposalParticipant.UpdatedAt = request.CreatedAt dkgProposalParticipant.Status = internal.CommitConfirmed @@ -88,6 +145,55 @@ func (m *DKGProposalFSM) actionCommitConfirmationReceived(inEvent fsm.Event, arg return } +func (m *DKGProposalFSM) actionValidateDkgProposalAwaitCommits(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { + var ( + isContainsError, isContainsExpired bool + ) + + m.payloadMu.Lock() + defer m.payloadMu.Unlock() + + tm := time.Now() + + unconfirmedParticipants := len(m.payload.DKGProposalPayload) + for _, participant := range m.payload.DKGProposalPayload { + if participant.Status == internal.CommitAwaitConfirmation { + if participant.UpdatedAt.Add(config.SignatureProposalConfirmationDeadline).Before(tm) { + isContainsExpired = true + } + } else { + if participant.Status == internal.CommitConfirmationError { + isContainsError = true + } else if participant.Status == internal.CommitConfirmed { + unconfirmedParticipants-- + } + } + } + + if isContainsError { + outEvent = eventDKGCommitsConfirmationCancelByTimeoutInternal + return + } + + if isContainsExpired { + outEvent = eventDKGCommitsConfirmationCancelByErrorInternal + return + } + + // The are no declined and timed out participants, check for all confirmations + if unconfirmedParticipants > 0 { + return + } + + outEvent = eventDKGCommitsConfirmedInternal + + for _, participant := range m.payload.DKGProposalPayload { + participant.Status = internal.DealAwaitConfirmation + } + + return +} + // Deals func (m *DKGProposalFSM) actionDealConfirmationReceived(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { @@ -117,6 +223,11 @@ func (m *DKGProposalFSM) actionDealConfirmationReceived(inEvent fsm.Event, args return } + if dkgProposalParticipant.Status != internal.DealAwaitConfirmation { + err = errors.New(fmt.Sprintf("cannot confirm deal with {Status} = {\"%s\"}", dkgProposalParticipant.Status)) + return + } + copy(dkgProposalParticipant.Deal, request.Deal) dkgProposalParticipant.UpdatedAt = request.CreatedAt dkgProposalParticipant.Status = internal.DealConfirmed @@ -126,6 +237,55 @@ func (m *DKGProposalFSM) actionDealConfirmationReceived(inEvent fsm.Event, args return } +func (m *DKGProposalFSM) actionValidateDkgProposalAwaitDeals(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { + var ( + isContainsError, isContainsExpired bool + ) + + m.payloadMu.Lock() + defer m.payloadMu.Unlock() + + tm := time.Now() + + unconfirmedParticipants := len(m.payload.DKGProposalPayload) + for _, participant := range m.payload.DKGProposalPayload { + if participant.Status == internal.DealAwaitConfirmation { + if participant.UpdatedAt.Add(config.SignatureProposalConfirmationDeadline).Before(tm) { + isContainsExpired = true + } + } else { + if participant.Status == internal.DealConfirmationError { + isContainsError = true + } else if participant.Status == internal.DealConfirmed { + unconfirmedParticipants-- + } + } + } + + if isContainsError { + outEvent = eventDKGDealsConfirmationCancelByErrorInternal + return + } + + if isContainsExpired { + outEvent = eventDKGDealsConfirmationCancelByTimeoutInternal + return + } + + // The are no declined and timed out participants, check for all confirmations + if unconfirmedParticipants > 0 { + return + } + + outEvent = eventDKGDealsConfirmedInternal + + for _, participant := range m.payload.DKGProposalPayload { + participant.Status = internal.ResponseAwaitConfirmation + } + + return +} + // Responses func (m *DKGProposalFSM) actionResponseConfirmationReceived(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { @@ -155,6 +315,11 @@ func (m *DKGProposalFSM) actionResponseConfirmationReceived(inEvent fsm.Event, a return } + if dkgProposalParticipant.Status != internal.ResponseAwaitConfirmation { + err = errors.New(fmt.Sprintf("cannot confirm response with {Status} = {\"%s\"}", dkgProposalParticipant.Status)) + return + } + copy(dkgProposalParticipant.Response, request.Response) dkgProposalParticipant.UpdatedAt = request.CreatedAt dkgProposalParticipant.Status = internal.ResponseConfirmed @@ -164,6 +329,51 @@ func (m *DKGProposalFSM) actionResponseConfirmationReceived(inEvent fsm.Event, a return } +func (m *DKGProposalFSM) actionValidateDkgProposalAwaitResponses(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { + var ( + isContainsError, isContainsExpired bool + ) + + m.payloadMu.Lock() + defer m.payloadMu.Unlock() + + tm := time.Now() + + unconfirmedParticipants := len(m.payload.DKGProposalPayload) + for _, participant := range m.payload.DKGProposalPayload { + if participant.Status == internal.ResponseAwaitConfirmation { + if participant.UpdatedAt.Add(config.SignatureProposalConfirmationDeadline).Before(tm) { + isContainsExpired = true + } + } else { + if participant.Status == internal.ResponseConfirmationError { + isContainsError = true + } else if participant.Status == internal.ResponseConfirmed { + unconfirmedParticipants-- + } + } + } + + if isContainsError { + outEvent = eventDKGResponseConfirmationCancelByErrorInternal + return + } + + if isContainsExpired { + outEvent = eventDKGResponseConfirmationCancelByTimeoutInternal + return + } + + // The are no declined and timed out participants, check for all confirmations + if unconfirmedParticipants > 0 { + return + } + + outEvent = eventDKGResponsesConfirmedInternal + + return +} + // Errors func (m *DKGProposalFSM) actionConfirmationError(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { m.payloadMu.Lock() @@ -196,7 +406,7 @@ func (m *DKGProposalFSM) actionConfirmationError(inEvent fsm.Event, args ...inte switch inEvent { case EventDKGPubKeyConfirmationError: switch dkgProposalParticipant.Status { - case internal.PubKeyConAwaitConfirmation: + case internal.PubKeyAwaitConfirmation: dkgProposalParticipant.Status = internal.PubKeyConfirmationError case internal.PubKeyConfirmed: err = errors.New("{Status} already confirmed") diff --git a/fsm/state_machines/dkg_proposal_fsm/init.go b/fsm/state_machines/dkg_proposal_fsm/init.go index 97e47a5..040e759 100644 --- a/fsm/state_machines/dkg_proposal_fsm/init.go +++ b/fsm/state_machines/dkg_proposal_fsm/init.go @@ -12,61 +12,67 @@ const ( StateDkgInitial = StateDkgPubKeysAwaitConfirmations StateDkgPubKeysAwaitConfirmations = fsm.State("state_dkg_pub_keys_await_confirmations") - // Cancelled - StateDkgPubKeysAwaitCancelled = fsm.State("state_dkg_pub_keys_await_cancelled") - StateDkgPubKeysAwaitCancelledByTimeout = fsm.State("state_dkg_pub_keys_await_cancelled_by_timeout") + // Canceled + StateDkgPubKeysAwaitCanceled = fsm.State("state_dkg_pub_keys_await_canceled") + StateDkgPubKeysAwaitCanceledByTimeout = fsm.State("state_dkg_pub_keys_await_canceled_by_timeout") // Confirmed - StateDkgPubKeysAwaitConfirmed = fsm.State("state_dkg_pub_keys_await_confirmed") + // StateDkgPubKeysAwaitConfirmed = fsm.State("state_dkg_pub_keys_await_confirmed") // Sending dkg commits - StateDkgCommitsAwaitConfirmations = fsm.State("state_dkg_commits_sending_await_confirmations") - // Cancelled - StateDkgCommitsAwaitCancelled = fsm.State("state_dkg_commits_await_cancelled") - StateDkgCommitsAwaitCancelledByTimeout = fsm.State("state_dkg_commits_await_cancelled_by_timeout") + StateDkgCommitsAwaitConfirmations = fsm.State("state_dkg_commits_await_confirmations") + // Canceled + StateDkgCommitsAwaitCanceled = fsm.State("state_dkg_commits_await_canceled") + StateDkgCommitsAwaitCanceledByTimeout = fsm.State("state_dkg_commits_await_canceled_by_timeout") // Confirmed StateDkgCommitsAwaitConfirmed = fsm.State("state_dkg_commits_await_confirmed") // Sending dkg deals StateDkgDealsAwaitConfirmations = fsm.State("state_dkg_deals_await_confirmations") - // Cancelled - StateDkgDealsAwaitCancelled = fsm.State("state_dkg_deals_await_cancelled") - StateDkgDealsAwaitCancelledByTimeout = fsm.State("state_dkg_deals_sending_cancelled_by_timeout") + // Canceled + StateDkgDealsAwaitCanceled = fsm.State("state_dkg_deals_await_canceled") + StateDkgDealsAwaitCanceledByTimeout = fsm.State("state_dkg_deals_sending_canceled_by_timeout") // Confirmed - StateDkgDealsAwaitConfirmed = fsm.State("state_dkg_deals_await_confirmed") + //StateDkgDealsAwaitConfirmed = fsm.State("state_dkg_deals_await_confirmed") StateDkgResponsesAwaitConfirmations = fsm.State("state_dkg_responses_await_confirmations") - // Cancelled - StateDkgResponsesAwaitCancelled = fsm.State("state_dkg_responses_await_cancelled") - StateDkgResponsesAwaitCancelledByTimeout = fsm.State("state_dkg_responses_sending_cancelled_by_timeout") + // Canceled + StateDkgResponsesAwaitCanceled = fsm.State("state_dkg_responses_await_canceled") + StateDkgResponsesAwaitCanceledByTimeout = fsm.State("state_dkg_responses_sending_canceled_by_timeout") // Confirmed StateDkgResponsesAwaitConfirmed = fsm.State("state_dkg_responses_await_confirmed") // Events - eventDKGPubKeysSendingRequiredAuto = fsm.Event("event_dkg_pub_key_sending_required_internal") - EventDKGPubKeyConfirmationReceived = fsm.Event("event_dkg_pub_key_confirm_received") - EventDKGPubKeyConfirmationError = fsm.Event("event_dkg_pub_key_confirm_canceled_by_error") - EventDKGPubKeysConfirmationCancelByTimeoutInternal = fsm.Event("event_dkg_pub_keys_confirm_canceled_by_timeout_internal") - EventDKGPubKeysConfirmedInternal = fsm.Event("event_dkg_pub_keys_confirmed_internal") + eventAutoDKGInitialInternal = fsm.Event("event_dkg_init_internal") - EventDKGCommitsSendingRequiredInternal = fsm.Event("event_dkg_commits_sending_required_internal") + EventDKGPubKeyConfirmationReceived = fsm.Event("event_dkg_pub_key_confirm_received") + EventDKGPubKeyConfirmationError = fsm.Event("event_dkg_pub_key_confirm_canceled_by_error") + + eventAutoValidatePubKeysInternal = fsm.Event("event_dkg_pub_keys_validate_internal") + + eventDKGSetPubKeysConfirmationCanceledByTimeoutInternal = fsm.Event("event_dkg_pub_keys_confirm_canceled_by_timeout_internal") + eventDKGSetPubKeysConfirmationCanceledByErrorInternal = fsm.Event("event_dkg_pub_keys_confirm_canceled_by_error_internal") + eventDKGSetPubKeysConfirmedInternal = fsm.Event("event_dkg_pub_keys_confirmed_internal") EventDKGCommitConfirmationReceived = fsm.Event("event_dkg_commit_confirm_received") EventDKGCommitConfirmationError = fsm.Event("event_dkg_commit_confirm_canceled_by_error") - EventDKGCommitsConfirmationCancelByTimeoutInternal = fsm.Event("event_dkg_commits_confirm_canceled_by_timeout_internal") - EventDKGCommitsConfirmedInternal = fsm.Event("event_dkg_commits_confirmed_internal") + eventDKGCommitsConfirmationCancelByTimeoutInternal = fsm.Event("event_dkg_commits_confirm_canceled_by_timeout_internal") + eventDKGCommitsConfirmationCancelByErrorInternal = fsm.Event("event_dkg_commits_confirm_canceled_by_error_internal") + eventDKGCommitsConfirmedInternal = fsm.Event("event_dkg_commits_confirmed_internal") - EventDKGDealsSendingRequiredInternal = fsm.Event("event_dkg_deals_sending_required_internal") + // EventDKGDealsSendingRequiredInternal = fsm.Event("event_dkg_deals_sending_required_internal") EventDKGDealConfirmationReceived = fsm.Event("event_dkg_deal_confirm_received") EventDKGDealConfirmationError = fsm.Event("event_dkg_deal_confirm_canceled_by_error") - EventDKGDealsConfirmationCancelByTimeoutInternal = fsm.Event("event_dkg_deals_confirm_canceled_by_timeout_internal") - - EventDKGResponsesSendingRequiredInternal = fsm.Event("event_dkg_responses_sending_required_internal") + eventDKGDealsConfirmationCancelByTimeoutInternal = fsm.Event("event_dkg_deals_confirm_canceled_by_timeout_internal") + eventDKGDealsConfirmationCancelByErrorInternal = fsm.Event("event_dkg_deals_confirm_canceled_by_error_internal") + eventDKGDealsConfirmedInternal = fsm.Event("event_dkg_deals_confirmed_internal") EventDKGResponseConfirmationReceived = fsm.Event("event_dkg_response_confirm_received") EventDKGResponseConfirmationError = fsm.Event("event_dkg_response_confirm_canceled_by_error") - EventDKGResponseConfirmationCancelByTimeoutInternal = fsm.Event("event_dkg_response_confirm_canceled_by_timeout_internal") + eventDKGResponseConfirmationCancelByTimeoutInternal = fsm.Event("event_dkg_response_confirm_canceled_by_timeout_internal") + eventDKGResponseConfirmationCancelByErrorInternal = fsm.Event("event_dkg_response_confirm_canceled_by_error_internal") + eventDKGResponsesConfirmedInternal = fsm.Event("event_dkg_responses_confirmed_internal") EventDKGMasterKeyRequiredInternal = fsm.Event("event_dkg_master_key_required_internal") ) @@ -89,49 +95,57 @@ func New() internal.DumpedMachineProvider { // Switch to pub keys required // {Name: eventDKGPubKeysSendingRequiredAuto, SrcState: []fsm.State{StateDkgInitial}, DstState: StateDkgPubKeysAwaitConfirmations, IsInternal: true, IsAuto: true, AutoRunMode: fsm.EventRunAfter}, + // {Name: eventAutoDKGInitialInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitConfirmations, IsInternal: true, IsAuto: true, AutoRunMode: fsm.EventRunBefore}, + // Pub keys sending {Name: EventDKGPubKeyConfirmationReceived, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitConfirmations}, - // Cancelled - {Name: EventDKGPubKeyConfirmationError, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitCancelled}, - {Name: EventDKGPubKeysConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitCancelledByTimeout, IsInternal: true}, + // Canceled + {Name: EventDKGPubKeyConfirmationError, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitCanceled}, + + {Name: eventAutoValidatePubKeysInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitConfirmations, IsInternal: true, IsAuto: true}, + + {Name: eventDKGSetPubKeysConfirmationCanceledByTimeoutInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitCanceledByTimeout, IsInternal: true}, // Confirmed - {Name: EventDKGPubKeysConfirmedInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitConfirmed, IsInternal: true}, + {Name: eventDKGSetPubKeysConfirmedInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgCommitsAwaitConfirmations, IsInternal: true}, // Switch to commits required - {Name: EventDKGCommitsSendingRequiredInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmed}, DstState: StateDkgCommitsAwaitConfirmations, IsInternal: true}, + //{Name: EventDKGCommitsSendingRequiredInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmed}, DstState: StateDkgCommitsAwaitConfirmations, IsInternal: true}, // Commits {Name: EventDKGCommitConfirmationReceived, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitConfirmations}, - // Cancelled - {Name: EventDKGCommitConfirmationError, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitCancelled}, - {Name: EventDKGCommitsConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitCancelledByTimeout, IsInternal: true}, + // Canceled + {Name: EventDKGCommitConfirmationError, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitCanceled}, + {Name: eventDKGCommitsConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitCanceledByTimeout, IsInternal: true}, // Confirmed - {Name: EventDKGCommitsConfirmedInternal, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitConfirmed, IsInternal: true}, + {Name: eventDKGCommitsConfirmedInternal, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgDealsAwaitConfirmations, IsInternal: true}, // Switch to deals required - {Name: EventDKGDealsSendingRequiredInternal, SrcState: []fsm.State{StateDkgDealsAwaitConfirmed}, DstState: StateDkgDealsAwaitConfirmations, IsInternal: true}, + // {Name: EventDKGDealsSendingRequiredInternal, SrcState: []fsm.State{StateDkgDealsAwaitConfirmed}, DstState: StateDkgDealsAwaitConfirmations, IsInternal: true}, // Deals {Name: EventDKGDealConfirmationReceived, SrcState: []fsm.State{StateDkgDealsAwaitConfirmations}, DstState: StateDkgDealsAwaitConfirmations}, - // Cancelled - {Name: EventDKGDealConfirmationError, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgDealsAwaitCancelled}, - {Name: EventDKGDealsConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgDealsAwaitCancelledByTimeout, IsInternal: true}, + // Canceled + {Name: EventDKGDealConfirmationError, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgDealsAwaitCanceled}, + {Name: eventDKGDealsConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgDealsAwaitCanceledByTimeout, IsInternal: true}, // Switch to responses required - {Name: EventDKGResponsesSendingRequiredInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmed}, DstState: StateDkgResponsesAwaitConfirmations, IsInternal: true}, + // {Name: eventDKGResponsesSendingRequiredInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmed}, DstState: StateDkgResponsesAwaitConfirmations, IsInternal: true}, // Deals {Name: EventDKGResponseConfirmationReceived, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: StateDkgResponsesAwaitConfirmations}, - // Cancelled - {Name: EventDKGResponseConfirmationError, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: StateDkgResponsesAwaitCancelled}, - {Name: EventDKGResponseConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: StateDkgResponsesAwaitCancelledByTimeout, IsInternal: true}, + // Canceled + {Name: EventDKGResponseConfirmationError, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: StateDkgResponsesAwaitCanceled}, + {Name: eventDKGResponseConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: StateDkgResponsesAwaitCanceledByTimeout, IsInternal: true}, // Done {Name: EventDKGMasterKeyRequiredInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: fsm.StateGlobalDone, IsInternal: true}, }, fsm.Callbacks{ + EventDKGPubKeyConfirmationReceived: machine.actionPubKeyConfirmationReceived, EventDKGPubKeyConfirmationError: machine.actionConfirmationError, + // actionValidateDkgProposalPubKeys + eventAutoValidatePubKeysInternal: machine.actionValidateDkgProposalPubKeys, EventDKGCommitConfirmationReceived: machine.actionCommitConfirmationReceived, EventDKGCommitConfirmationError: machine.actionConfirmationError, diff --git a/fsm/state_machines/internal/types.go b/fsm/state_machines/internal/types.go index 8b9834a..86cfb1a 100644 --- a/fsm/state_machines/internal/types.go +++ b/fsm/state_machines/internal/types.go @@ -5,11 +5,6 @@ import ( "time" ) -const ( - SignatureAwaitConfirmation SignatureProposalParticipantStatus = iota - SignatureConfirmed -) - type ConfirmationProposal struct { Quorum SignatureProposalQuorum CreatedAt *time.Time @@ -23,22 +18,22 @@ type SignatureProposalParticipant struct { PublicKey *rsa.PublicKey // For validation user confirmation: sign(InvitationSecret, PublicKey) => user InvitationSecret string - Status SignatureProposalParticipantStatus + Status ParticipantStatus UpdatedAt *time.Time } // Unique alias for map iteration - Public Key Fingerprint // Excludes array merge and rotate operations -type SignatureProposalQuorum map[string]SignatureProposalParticipant +type SignatureProposalQuorum map[string]*SignatureProposalParticipant -type SignatureProposalParticipantStatus uint8 +type ParticipantStatus uint8 const ( - SignatureConfirmationAwaitConfirmation DKGProposalParticipantStatus = iota + SignatureConfirmationAwaitConfirmation ParticipantStatus = iota SignatureConfirmationConfirmed SignatureConfirmationDeclined SignatureConfirmationError - PubKeyConAwaitConfirmation + PubKeyAwaitConfirmation PubKeyConfirmed PubKeyConfirmationError CommitAwaitConfirmation @@ -52,27 +47,21 @@ const ( ResponseConfirmationError ) -type DKGProposal struct { - Quorum map[int]DKGProposalParticipant - CreatedAt *time.Time - ExpiresAt *time.Time -} - type DKGProposalParticipant struct { Title string - PublicKey []byte + PubKey []byte Commit []byte Deal []byte Response []byte - Status DKGProposalParticipantStatus + Status ParticipantStatus UpdatedAt *time.Time } -type DKGProposalQuorum map[int]DKGProposalParticipant +type DKGProposalQuorum map[int]*DKGProposalParticipant type DKGProposalParticipantStatus uint8 -func (s DKGProposalParticipantStatus) String() string { +func (s ParticipantStatus) String() string { var str = "undefined" switch s { case SignatureConfirmationAwaitConfirmation: @@ -83,8 +72,8 @@ func (s DKGProposalParticipantStatus) String() string { str = "SignatureConfirmationDeclined" case SignatureConfirmationError: str = "SignatureConfirmationError" - case PubKeyConAwaitConfirmation: - str = "PubKeyConAwaitConfirmation" + case PubKeyAwaitConfirmation: + str = "PubKeyAwaitConfirmation" case PubKeyConfirmed: str = "PubKeyConfirmed" case PubKeyConfirmationError: diff --git a/fsm/state_machines/provider.go b/fsm/state_machines/provider.go index 93f4ce9..24706ce 100644 --- a/fsm/state_machines/provider.go +++ b/fsm/state_machines/provider.go @@ -149,6 +149,13 @@ func (i *FSMInstance) Id() string { return "" } +func (i *FSMInstance) Dump() ([]byte, error) { + if i.dump == nil { + return []byte{}, errors.New("dump is not initialized") + } + return i.dump.Marshal() +} + // TODO: Add encryption func (d *FSMDump) Marshal() ([]byte, error) { return json.Marshal(d) @@ -157,7 +164,7 @@ func (d *FSMDump) Marshal() ([]byte, error) { // TODO: Add decryption func (d *FSMDump) Unmarshal(data []byte) error { if d == nil { - return errors.New("dump struct is not initialized") + return errors.New("dump is not initialized") } return json.Unmarshal(data, d) diff --git a/fsm/state_machines/provider_test.go b/fsm/state_machines/provider_test.go index 5a17911..71a6b55 100644 --- a/fsm/state_machines/provider_test.go +++ b/fsm/state_machines/provider_test.go @@ -36,6 +36,8 @@ var ( Participants: []*requests.SignatureProposalParticipantsEntry{}, CreatedAt: &tm, } + + testFSMDump []byte ) func init() { @@ -118,21 +120,41 @@ func compareState(t *testing.T, expected fsm.State, got fsm.State) { } } -func Test_Workflow(t *testing.T) { - testFSMInstance, err := Create() +// Test Workflow - log.Println(testFSMInstance.Id()) +func Test_SignatureProposal_Init(t *testing.T) { + testFSMInstance, err := Create() compareErrNil(t, err) compareFSMInstanceNotNil(t, testFSMInstance) + transactionId := testFSMInstance.Id() + + if transactionId == "" { + t.Fatalf("expected {transactionId for dump}") + } + if testFSMInstance.machine.Name() != spf.FsmName { t.Fatalf("expected machine name {%s}", spf.FsmName) } compareState(t, spf.StateParticipantsConfirmationsInit, testFSMInstance.machine.State()) + testFSMDump, err = testFSMInstance.Dump() + + compareErrNil(t, err) + + compareDumpNotZero(t, testFSMDump) +} + +func Test_SignatureProposal_Positive(t *testing.T) { + testFSMInstance, err := FromDump(testFSMDump) + + compareErrNil(t, err) + + compareFSMInstanceNotNil(t, testFSMInstance) + fsmResponse, dump, err := testFSMInstance.Do(spf.EventInitProposal, testParticipantsListRequest) compareErrNil(t, err) @@ -143,10 +165,251 @@ func Test_Workflow(t *testing.T) { compareState(t, spf.StateAwaitParticipantsConfirmations, fsmResponse.State) - if testFSMInstance.Id() != testTransactionId { - t.Fatalf("expected {testTransactionId}") + testParticipantsListResponse, ok := fsmResponse.Data.(responses.SignatureProposalParticipantInvitationsResponse) + + if !ok { + t.Fatalf("expected response {SignatureProposalParticipantInvitationsResponse}") } + if len(testParticipantsListResponse) != len(testParticipantsListRequest.Participants) { + t.Fatalf("expected response len {%d}, got {%d}", len(testParticipantsListRequest.Participants), len(testParticipantsListResponse)) + } + + participantsMap := map[int]*responses.SignatureProposalParticipantInvitationEntry{} + + for _, participant := range testParticipantsListResponse { + if _, ok := participantsMap[participant.ParticipantId]; ok { + t.Fatalf("expected unique {ParticipantId}") + } + + if participant.Title == "" { + t.Fatalf("expected not empty {Title}") + } + + if participant.EncryptedInvitation == "" { + t.Fatalf("expected not empty {DecryptedInvitation}") + } + + if participant.PubKeyFingerprint == "" { + t.Fatalf("expected not empty {PubKeyFingerprint}") + } + + participantsMap[participant.ParticipantId] = participant + } + + tm = tm.Add(10 * time.Hour) + + participantsCount := len(participantsMap) + + participantCounter := participantsCount + + for _, participant := range participantsMap { + participantCounter-- + testFSMInstance, err = FromDump(dump) + + compareErrNil(t, err) + + compareFSMInstanceNotNil(t, testFSMInstance) + + if _, ok := testParticipants[participant.PubKeyFingerprint]; !ok { + t.Fatalf("not found external user data for response fingerprint") + } + + r := rand.Reader + encrypted, err := rsa.DecryptPKCS1v15(r, testParticipants[participant.PubKeyFingerprint].PrivKey, []byte(participant.EncryptedInvitation)) + + if err != nil { + t.Fatalf("cannot encrypt {DecryptedInvitation} with private key") + } + + fsmResponse, dump, err = testFSMInstance.Do(spf.EventConfirmProposal, requests.SignatureProposalParticipantRequest{ + PubKeyFingerprint: participant.PubKeyFingerprint, + DecryptedInvitation: string(encrypted), + CreatedAt: &tm, + }) + + compareErrNil(t, err) + + compareDumpNotZero(t, dump) + + compareFSMResponseNotNil(t, fsmResponse) + + if participantCounter > 0 { + compareState(t, spf.StateAwaitParticipantsConfirmations, fsmResponse.State) + } else { + compareState(t, dpf.StateDkgInitial, fsmResponse.State) + } + + } + + // PubKeys + + for _, participant := range participantsMap { + participantCounter-- + testFSMInstance, err = FromDump(dump) + + compareErrNil(t, err) + + compareFSMInstanceNotNil(t, testFSMInstance) + + if _, ok := testParticipants[participant.PubKeyFingerprint]; !ok { + t.Fatalf("not found external user data for response fingerprint") + } + + pubKeyMock := make([]byte, 128) + _, err := rand.Read(pubKeyMock) + if err != nil { + compareErrNil(t, err) + } + + fsmResponse, dump, err = testFSMInstance.Do(dpf.EventDKGPubKeyConfirmationReceived, requests.DKGProposalPubKeyConfirmationRequest{ + ParticipantId: participant.ParticipantId, + PubKey: pubKeyMock, + CreatedAt: &tm, + }) + + compareErrNil(t, err) + + compareDumpNotZero(t, dump) + + compareFSMResponseNotNil(t, fsmResponse) + + /*if participantCounter > 0 { + compareState(t, dpf.StateDkgPubKeysAwaitConfirmations, fsmResponse.State) + } else { + compareState(t, dpf.StateDkgCommitsAwaitConfirmations, fsmResponse.State) + }*/ + } + + compareState(t, dpf.StateDkgCommitsAwaitConfirmations, fsmResponse.State) + + // Commits + + for _, participant := range participantsMap { + participantCounter-- + testFSMInstance, err = FromDump(dump) + + compareErrNil(t, err) + + compareFSMInstanceNotNil(t, testFSMInstance) + + if _, ok := testParticipants[participant.PubKeyFingerprint]; !ok { + t.Fatalf("not found external user data for response fingerprint") + } + + commitMock := make([]byte, 128) + _, err := rand.Read(commitMock) + if err != nil { + compareErrNil(t, err) + } + + fsmResponse, dump, err = testFSMInstance.Do(dpf.EventDKGCommitConfirmationReceived, requests.DKGProposalCommitConfirmationRequest{ + ParticipantId: participant.ParticipantId, + Commit: commitMock, + CreatedAt: &tm, + }) + + compareErrNil(t, err) + + compareDumpNotZero(t, dump) + + compareFSMResponseNotNil(t, fsmResponse) + + } + + compareState(t, dpf.StateDkgDealsAwaitConfirmations, fsmResponse.State) + + // Deals + + for _, participant := range participantsMap { + participantCounter-- + testFSMInstance, err = FromDump(dump) + + compareErrNil(t, err) + + compareFSMInstanceNotNil(t, testFSMInstance) + + if _, ok := testParticipants[participant.PubKeyFingerprint]; !ok { + t.Fatalf("not found external user data for response fingerprint") + } + + dealMock := make([]byte, 128) + _, err := rand.Read(dealMock) + if err != nil { + compareErrNil(t, err) + } + + fsmResponse, dump, err = testFSMInstance.Do(dpf.EventDKGDealConfirmationReceived, requests.DKGProposalDealConfirmationRequest{ + ParticipantId: participant.ParticipantId, + Deal: dealMock, + CreatedAt: &tm, + }) + + compareErrNil(t, err) + + compareDumpNotZero(t, dump) + + compareFSMResponseNotNil(t, fsmResponse) + + } + + compareState(t, dpf.StateDkgResponsesAwaitConfirmations, fsmResponse.State) + + // Responses + + for _, participant := range participantsMap { + participantCounter-- + testFSMInstance, err = FromDump(dump) + + compareErrNil(t, err) + + compareFSMInstanceNotNil(t, testFSMInstance) + + if _, ok := testParticipants[participant.PubKeyFingerprint]; !ok { + t.Fatalf("not found external user data for response fingerprint") + } + + responseMock := make([]byte, 128) + _, err := rand.Read(responseMock) + if err != nil { + compareErrNil(t, err) + } + + fsmResponse, dump, err = testFSMInstance.Do(dpf.EventDKGDealConfirmationReceived, requests.DKGProposalDealConfirmationRequest{ + ParticipantId: participant.ParticipantId, + Deal: responseMock, + CreatedAt: &tm, + }) + + compareErrNil(t, err) + + compareDumpNotZero(t, dump) + + compareFSMResponseNotNil(t, fsmResponse) + + } + + compareState(t, fsm.StateGlobalDone, fsmResponse.State) +} + +/* +func Test_SignatureProposal_Negative_By_Decline(t *testing.T) { + testFSMInstance, err := FromDump(testFSMDump) + + compareErrNil(t, err) + + compareFSMInstanceNotNil(t, testFSMInstance) + + fsmResponse, dump, err := testFSMInstance.Do(spf.EventInitProposal, testParticipantsListRequest) + + compareErrNil(t, err) + + compareDumpNotZero(t, dump) + + compareFSMResponseNotNil(t, fsmResponse) + + compareState(t, spf.StateAwaitParticipantsConfirmations, fsmResponse.State) + testParticipantsListResponse, ok := fsmResponse.Data.(responses.SignatureProposalParticipantInvitationsResponse) if !ok { @@ -204,7 +467,7 @@ func Test_Workflow(t *testing.T) { t.Fatalf("cannot encrypt {DecryptedInvitation} with private key") } - fsmResponse, dump, err = testFSMInstance.Do(spf.EventConfirmProposal, requests.SignatureProposalParticipantRequest{ + fsmResponse, dump, err = testFSMInstance.Do(spf.EventDeclineProposal, requests.SignatureProposalParticipantRequest{ PubKeyFingerprint: participant.PubKeyFingerprint, DecryptedInvitation: string(encrypted), CreatedAt: &tm, @@ -216,19 +479,9 @@ func Test_Workflow(t *testing.T) { compareFSMResponseNotNil(t, fsmResponse) - if participantCounter > 0 { - if fsmResponse.State != spf.StateAwaitParticipantsConfirmations { - t.Fatalf("expected state {%s} got {%s}", spf.StateAwaitParticipantsConfirmations, fsmResponse.State) - } - } else { - if fsmResponse.State != dpf.StateDkgInitial { - t.Fatalf("expected state {%s} got {%s}", dpf.StateDkgInitial, fsmResponse.State) - } - } + compareState(t, spf.StateValidationCanceledByParticipant, fsmResponse.State) - state, err := testFSMInstance.State() - - log.Println(err, state) } } +*/ diff --git a/fsm/state_machines/signature_proposal_fsm/actions.go b/fsm/state_machines/signature_proposal_fsm/actions.go index b601bbb..22e2168 100644 --- a/fsm/state_machines/signature_proposal_fsm/actions.go +++ b/fsm/state_machines/signature_proposal_fsm/actions.go @@ -3,10 +3,13 @@ package signature_proposal_fsm import ( "crypto/x509" "errors" + "fmt" + "github.com/depools/dc4bc/fsm/config" "github.com/depools/dc4bc/fsm/fsm" "github.com/depools/dc4bc/fsm/state_machines/internal" "github.com/depools/dc4bc/fsm/types/requests" "github.com/depools/dc4bc/fsm/types/responses" + "time" ) // init -> awaitingConfirmations @@ -46,12 +49,12 @@ func (m *SignatureProposalFSM) actionInitProposal(inEvent fsm.Event, args ...int return inEvent, nil, errors.New("cannot parse {PubKey}") } - m.payload.ConfirmationProposalPayload[participantId] = internal.SignatureProposalParticipant{ + m.payload.ConfirmationProposalPayload[participantId] = &internal.SignatureProposalParticipant{ ParticipantId: index, Title: participant.Title, PublicKey: parsedPubKey, InvitationSecret: secret, - Status: internal.SignatureAwaitConfirmation, + Status: internal.SignatureConfirmationAwaitConfirmation, UpdatedAt: request.CreatedAt, } } @@ -86,8 +89,8 @@ func (m *SignatureProposalFSM) actionInitProposal(inEvent fsm.Event, args ...int // TODO: Add timeout checking func (m *SignatureProposalFSM) actionProposalResponseByParticipant(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { - // m.payloadMu.Lock() - // defer m.payloadMu.Unlock() + m.payloadMu.Lock() + defer m.payloadMu.Unlock() if len(args) != 1 { err = errors.New("{arg0} required {SignatureProposalParticipantRequest}") @@ -117,30 +120,120 @@ func (m *SignatureProposalFSM) actionProposalResponseByParticipant(inEvent fsm.E return } - signatureProposalParticipant.Status = internal.SignatureConfirmed + if signatureProposalParticipant.UpdatedAt.Add(config.SignatureProposalConfirmationDeadline).Before(*request.CreatedAt) { + outEvent = eventSetValidationCanceledByTimeout + return + } + + if signatureProposalParticipant.Status != internal.SignatureConfirmationAwaitConfirmation { + err = errors.New(fmt.Sprintf("cannot apply reply participant with {Status} = {\"%s\"}", signatureProposalParticipant.Status)) + return + } + + switch inEvent { + case EventConfirmProposal: + signatureProposalParticipant.Status = internal.SignatureConfirmationConfirmed + case EventDeclineProposal: + signatureProposalParticipant.Status = internal.SignatureConfirmationDeclined + default: + err = errors.New("undefined {Event} for action") + return + } + signatureProposalParticipant.UpdatedAt = request.CreatedAt m.payload.ConfirmationProposalPayload[request.PubKeyFingerprint] = signatureProposalParticipant - outEvent, response, err = m.actionValidateProposal(eventValidateProposalInternal) - return } -func (m *SignatureProposalFSM) actionValidateProposal(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { - // m.payloadMu.Lock() - // defer m.payloadMu.Unlock() +func (m *SignatureProposalFSM) actionValidateSignatureProposal(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { + var ( + isContainsDeclined, isContainsExpired bool + ) + + m.payloadMu.Lock() + defer m.payloadMu.Unlock() + + tm := time.Now() unconfirmedParticipants := len(m.payload.ConfirmationProposalPayload) for _, participant := range m.payload.ConfirmationProposalPayload { - if participant.Status == internal.SignatureConfirmed { - unconfirmedParticipants-- + if participant.Status == internal.SignatureConfirmationAwaitConfirmation { + if participant.UpdatedAt.Add(config.SignatureProposalConfirmationDeadline).Before(tm) { + isContainsExpired = true + } + } else { + if participant.Status == internal.SignatureConfirmationConfirmed { + unconfirmedParticipants-- + } else if participant.Status == internal.SignatureConfirmationDeclined { + isContainsDeclined = true + } } } + if isContainsDeclined { + outEvent = eventSetValidationCanceledByParticipant + return + } + + if isContainsExpired { + outEvent = eventSetValidationCanceledByTimeout + return + } + + // The are no declined and timed out participants, check for all confirmations if unconfirmedParticipants > 0 { return } + outEvent = eventSetProposalValidatedInternal + + m.actionSetValidatedSignatureProposal(outEvent) + return } + +func (m *SignatureProposalFSM) actionSetValidatedSignatureProposal(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { + // m.payloadMu.Lock() + // defer m.payloadMu.Unlock() + + // TODO: Run once after validation + if m.payload.DKGProposalPayload != nil { + return + } + + m.payload.DKGProposalPayload = make(internal.DKGProposalQuorum) + + for _, participant := range m.payload.ConfirmationProposalPayload { + m.payload.DKGProposalPayload[participant.ParticipantId] = &internal.DKGProposalParticipant{ + Title: participant.Title, + Status: internal.PubKeyAwaitConfirmation, + UpdatedAt: participant.UpdatedAt, + } + } + + // Remove m.payload.ConfirmationProposalPayload? + + return +} + +func (m *SignatureProposalFSM) actionSignatureProposalCanceledByTimeout(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { + m.payloadMu.Lock() + defer m.payloadMu.Unlock() + + responseData := make(responses.SignatureProposalParticipantStatusResponse, 0) + + for pubKeyFingerprint, participant := range m.payload.ConfirmationProposalPayload { + responseEntry := &responses.SignatureProposalParticipantStatusEntry{ + ParticipantId: participant.ParticipantId, + Title: participant.Title, + PubKeyFingerprint: pubKeyFingerprint, + Status: uint8(participant.Status), + } + responseData = append(responseData, responseEntry) + } + + return inEvent, responseData, nil + +} diff --git a/fsm/state_machines/signature_proposal_fsm/init.go b/fsm/state_machines/signature_proposal_fsm/init.go index a338920..72312e2 100644 --- a/fsm/state_machines/signature_proposal_fsm/init.go +++ b/fsm/state_machines/signature_proposal_fsm/init.go @@ -19,15 +19,15 @@ const ( StateValidationCanceledByTimeout = fsm.State("state_sig_proposal_canceled_by_timeout") // Out state - StateValidationCompleted = fsm.State("state_sig_proposal_completed") EventInitProposal = fsm.Event("event_sig_proposal_init") EventConfirmProposal = fsm.Event("event_sig_proposal_confirm_by_participant") EventDeclineProposal = fsm.Event("event_sig_proposal_decline_by_participant") - eventValidateProposalInternal = fsm.Event("event_sig_proposal_validate") + eventAutoValidateProposalInternal = fsm.Event("event_sig_proposal_validate") eventSetProposalValidatedInternal = fsm.Event("event_sig_proposal_set_validated") - eventSetValidationCanceledByTimeout = fsm.Event("event_sig_proposal_canceled_timeout") + eventSetValidationCanceledByTimeout = fsm.Event("event_sig_proposal_canceled_timeout") + eventSetValidationCanceledByParticipant = fsm.Event("event_sig_proposal_declined_timeout") // Switch to next fsm @@ -55,9 +55,10 @@ func New() internal.DumpedMachineProvider { {Name: EventConfirmProposal, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateAwaitParticipantsConfirmations}, // Is decline event should auto change state to default, or it process will initiated by client (external emit)? // Now set for external emitting. - {Name: EventDeclineProposal, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateValidationCanceledByParticipant}, + {Name: EventDeclineProposal, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateAwaitParticipantsConfirmations}, + {Name: eventSetValidationCanceledByParticipant, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateValidationCanceledByParticipant, IsInternal: true}, - {Name: eventValidateProposalInternal, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateAwaitParticipantsConfirmations, IsInternal: true}, + {Name: eventAutoValidateProposalInternal, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateAwaitParticipantsConfirmations, IsInternal: true, IsAuto: true}, // eventProposalValidate internal or from client? // yay @@ -67,10 +68,11 @@ func New() internal.DumpedMachineProvider { {Name: eventSetValidationCanceledByTimeout, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateValidationCanceledByTimeout, IsInternal: true}, }, fsm.Callbacks{ - EventInitProposal: machine.actionInitProposal, - EventConfirmProposal: machine.actionProposalResponseByParticipant, - EventDeclineProposal: machine.actionProposalResponseByParticipant, - eventValidateProposalInternal: machine.actionValidateProposal, + EventInitProposal: machine.actionInitProposal, + EventConfirmProposal: machine.actionProposalResponseByParticipant, + EventDeclineProposal: machine.actionProposalResponseByParticipant, + eventAutoValidateProposalInternal: machine.actionValidateSignatureProposal, + eventSetProposalValidatedInternal: machine.actionSetValidatedSignatureProposal, }, ) return machine diff --git a/fsm/types/responses/signature_proposal.go b/fsm/types/responses/signature_proposal.go index 6a1248e..855a59a 100644 --- a/fsm/types/responses/signature_proposal.go +++ b/fsm/types/responses/signature_proposal.go @@ -2,14 +2,8 @@ package responses // Response -const ( - ProposalConfirmationStatusIdle = iota - ProposalConfirmationStatusAccepted - ProposalConfirmationStatusCanceled - ProposalConfirmationStatusTimeout -) - -// States: "validate_proposal" +// Event: "event_sig_proposal_init" +// States: "__idle" type SignatureProposalParticipantInvitationsResponse []*SignatureProposalParticipantInvitationEntry @@ -31,5 +25,5 @@ type SignatureProposalParticipantStatusEntry struct { ParticipantId int Title string PubKeyFingerprint string - Status int + Status uint8 } From 023a9f8d5c9de050c78f628e4298c1228030fc81 Mon Sep 17 00:00:00 2001 From: x88 Date: Wed, 12 Aug 2020 09:38:58 +0300 Subject: [PATCH 5/8] feat: fixed flow, added tests --- fsm/fsm/fsm.go | 2 +- fsm/state_machines/dkg_proposal_fsm/init.go | 54 +++++++----- fsm/state_machines/provider_test.go | 98 +-------------------- 3 files changed, 33 insertions(+), 121 deletions(-) diff --git a/fsm/fsm/fsm.go b/fsm/fsm/fsm.go index 0a947a4..2af2abd 100644 --- a/fsm/fsm/fsm.go +++ b/fsm/fsm/fsm.go @@ -382,7 +382,7 @@ func (f *FSM) SetState(event Event) error { trEvent, ok := f.transitions[trKey{f.currentState, event}] if !ok { - return errors.New("cannot change state") + return errors.New(fmt.Sprintf("cannot execute event \"%s\" for state \"%s\"", event, f.currentState)) } f.currentState = trEvent.dstState diff --git a/fsm/state_machines/dkg_proposal_fsm/init.go b/fsm/state_machines/dkg_proposal_fsm/init.go index 040e759..e7df621 100644 --- a/fsm/state_machines/dkg_proposal_fsm/init.go +++ b/fsm/state_machines/dkg_proposal_fsm/init.go @@ -48,7 +48,7 @@ const ( EventDKGPubKeyConfirmationReceived = fsm.Event("event_dkg_pub_key_confirm_received") EventDKGPubKeyConfirmationError = fsm.Event("event_dkg_pub_key_confirm_canceled_by_error") - eventAutoValidatePubKeysInternal = fsm.Event("event_dkg_pub_keys_validate_internal") + eventAutoDKGValidatePubKeysConfirmationInternal = fsm.Event("event_dkg_pub_keys_validate_internal") eventDKGSetPubKeysConfirmationCanceledByTimeoutInternal = fsm.Event("event_dkg_pub_keys_confirm_canceled_by_timeout_internal") eventDKGSetPubKeysConfirmationCanceledByErrorInternal = fsm.Event("event_dkg_pub_keys_confirm_canceled_by_error_internal") @@ -59,20 +59,21 @@ const ( eventDKGCommitsConfirmationCancelByTimeoutInternal = fsm.Event("event_dkg_commits_confirm_canceled_by_timeout_internal") eventDKGCommitsConfirmationCancelByErrorInternal = fsm.Event("event_dkg_commits_confirm_canceled_by_error_internal") eventDKGCommitsConfirmedInternal = fsm.Event("event_dkg_commits_confirmed_internal") - - // EventDKGDealsSendingRequiredInternal = fsm.Event("event_dkg_deals_sending_required_internal") + eventAutoDKGValidateConfirmationCommitsInternal = fsm.Event("event_dkg_commits_validate_internal") EventDKGDealConfirmationReceived = fsm.Event("event_dkg_deal_confirm_received") EventDKGDealConfirmationError = fsm.Event("event_dkg_deal_confirm_canceled_by_error") eventDKGDealsConfirmationCancelByTimeoutInternal = fsm.Event("event_dkg_deals_confirm_canceled_by_timeout_internal") eventDKGDealsConfirmationCancelByErrorInternal = fsm.Event("event_dkg_deals_confirm_canceled_by_error_internal") eventDKGDealsConfirmedInternal = fsm.Event("event_dkg_deals_confirmed_internal") + eventAutoDKGValidateConfirmationDealsInternal = fsm.Event("event_dkg_deals_validate_internal") EventDKGResponseConfirmationReceived = fsm.Event("event_dkg_response_confirm_received") EventDKGResponseConfirmationError = fsm.Event("event_dkg_response_confirm_canceled_by_error") eventDKGResponseConfirmationCancelByTimeoutInternal = fsm.Event("event_dkg_response_confirm_canceled_by_timeout_internal") eventDKGResponseConfirmationCancelByErrorInternal = fsm.Event("event_dkg_response_confirm_canceled_by_error_internal") eventDKGResponsesConfirmedInternal = fsm.Event("event_dkg_responses_confirmed_internal") + eventAutoDKGValidateResponsesConfirmationInternal = fsm.Event("event_dkg_responses_validate_internal") EventDKGMasterKeyRequiredInternal = fsm.Event("event_dkg_master_key_required_internal") ) @@ -102,59 +103,64 @@ func New() internal.DumpedMachineProvider { // Canceled {Name: EventDKGPubKeyConfirmationError, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitCanceled}, - {Name: eventAutoValidatePubKeysInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitConfirmations, IsInternal: true, IsAuto: true}, + {Name: eventAutoDKGValidatePubKeysConfirmationInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitConfirmations, IsInternal: true, IsAuto: true}, {Name: eventDKGSetPubKeysConfirmationCanceledByTimeoutInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitCanceledByTimeout, IsInternal: true}, // Confirmed {Name: eventDKGSetPubKeysConfirmedInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgCommitsAwaitConfirmations, IsInternal: true}, // Switch to commits required - //{Name: EventDKGCommitsSendingRequiredInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmed}, DstState: StateDkgCommitsAwaitConfirmations, IsInternal: true}, // Commits {Name: EventDKGCommitConfirmationReceived, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitConfirmations}, // Canceled {Name: EventDKGCommitConfirmationError, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitCanceled}, {Name: eventDKGCommitsConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitCanceledByTimeout, IsInternal: true}, + + {Name: eventAutoDKGValidateConfirmationCommitsInternal, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitConfirmations, IsInternal: true, IsAuto: true}, + // Confirmed {Name: eventDKGCommitsConfirmedInternal, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgDealsAwaitConfirmations, IsInternal: true}, - // Switch to deals required - // {Name: EventDKGDealsSendingRequiredInternal, SrcState: []fsm.State{StateDkgDealsAwaitConfirmed}, DstState: StateDkgDealsAwaitConfirmations, IsInternal: true}, - // Deals {Name: EventDKGDealConfirmationReceived, SrcState: []fsm.State{StateDkgDealsAwaitConfirmations}, DstState: StateDkgDealsAwaitConfirmations}, // Canceled - {Name: EventDKGDealConfirmationError, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgDealsAwaitCanceled}, - {Name: eventDKGDealsConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgDealsAwaitCanceledByTimeout, IsInternal: true}, + {Name: EventDKGDealConfirmationError, SrcState: []fsm.State{StateDkgDealsAwaitConfirmations}, DstState: StateDkgDealsAwaitCanceled}, + {Name: eventDKGDealsConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgDealsAwaitConfirmations}, DstState: StateDkgDealsAwaitConfirmations, IsInternal: true}, + {Name: eventAutoDKGValidateConfirmationDealsInternal, SrcState: []fsm.State{StateDkgDealsAwaitConfirmations}, DstState: StateDkgDealsAwaitConfirmations, IsInternal: true, IsAuto: true}, - // Switch to responses required - // {Name: eventDKGResponsesSendingRequiredInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmed}, DstState: StateDkgResponsesAwaitConfirmations, IsInternal: true}, + {Name: eventDKGDealsConfirmedInternal, SrcState: []fsm.State{StateDkgDealsAwaitConfirmations}, DstState: StateDkgResponsesAwaitConfirmations, IsInternal: true}, - // Deals + // Responses {Name: EventDKGResponseConfirmationReceived, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: StateDkgResponsesAwaitConfirmations}, // Canceled {Name: EventDKGResponseConfirmationError, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: StateDkgResponsesAwaitCanceled}, {Name: eventDKGResponseConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: StateDkgResponsesAwaitCanceledByTimeout, IsInternal: true}, + {Name: eventAutoDKGValidateResponsesConfirmationInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: StateDkgResponsesAwaitConfirmations, IsInternal: true, IsAuto: true}, + + {Name: eventDKGResponsesConfirmedInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: fsm.StateGlobalDone, IsInternal: true}, + // Done - {Name: EventDKGMasterKeyRequiredInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: fsm.StateGlobalDone, IsInternal: true}, + // {Name: EventDKGMasterKeyRequiredInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: fsm.StateGlobalDone, IsInternal: true}, }, fsm.Callbacks{ - EventDKGPubKeyConfirmationReceived: machine.actionPubKeyConfirmationReceived, - EventDKGPubKeyConfirmationError: machine.actionConfirmationError, - // actionValidateDkgProposalPubKeys - eventAutoValidatePubKeysInternal: machine.actionValidateDkgProposalPubKeys, + EventDKGPubKeyConfirmationReceived: machine.actionPubKeyConfirmationReceived, + EventDKGPubKeyConfirmationError: machine.actionConfirmationError, + eventAutoDKGValidatePubKeysConfirmationInternal: machine.actionValidateDkgProposalPubKeys, - EventDKGCommitConfirmationReceived: machine.actionCommitConfirmationReceived, - EventDKGCommitConfirmationError: machine.actionConfirmationError, + EventDKGCommitConfirmationReceived: machine.actionCommitConfirmationReceived, + EventDKGCommitConfirmationError: machine.actionConfirmationError, + eventAutoDKGValidateConfirmationCommitsInternal: machine.actionValidateDkgProposalAwaitCommits, - EventDKGDealConfirmationReceived: machine.actionDealConfirmationReceived, - EventDKGDealConfirmationError: machine.actionConfirmationError, + EventDKGDealConfirmationReceived: machine.actionDealConfirmationReceived, + EventDKGDealConfirmationError: machine.actionConfirmationError, + eventAutoDKGValidateConfirmationDealsInternal: machine.actionValidateDkgProposalAwaitDeals, - EventDKGResponseConfirmationReceived: machine.actionResponseConfirmationReceived, - EventDKGResponseConfirmationError: machine.actionConfirmationError, + EventDKGResponseConfirmationReceived: machine.actionResponseConfirmationReceived, + EventDKGResponseConfirmationError: machine.actionConfirmationError, + eventAutoDKGValidateResponsesConfirmationInternal: machine.actionValidateDkgProposalAwaitResponses, }, ) return machine diff --git a/fsm/state_machines/provider_test.go b/fsm/state_machines/provider_test.go index 71a6b55..1d5f60b 100644 --- a/fsm/state_machines/provider_test.go +++ b/fsm/state_machines/provider_test.go @@ -375,9 +375,9 @@ func Test_SignatureProposal_Positive(t *testing.T) { compareErrNil(t, err) } - fsmResponse, dump, err = testFSMInstance.Do(dpf.EventDKGDealConfirmationReceived, requests.DKGProposalDealConfirmationRequest{ + fsmResponse, dump, err = testFSMInstance.Do(dpf.EventDKGResponseConfirmationReceived, requests.DKGProposalResponseConfirmationRequest{ ParticipantId: participant.ParticipantId, - Deal: responseMock, + Response: responseMock, CreatedAt: &tm, }) @@ -391,97 +391,3 @@ func Test_SignatureProposal_Positive(t *testing.T) { compareState(t, fsm.StateGlobalDone, fsmResponse.State) } - -/* -func Test_SignatureProposal_Negative_By_Decline(t *testing.T) { - testFSMInstance, err := FromDump(testFSMDump) - - compareErrNil(t, err) - - compareFSMInstanceNotNil(t, testFSMInstance) - - fsmResponse, dump, err := testFSMInstance.Do(spf.EventInitProposal, testParticipantsListRequest) - - compareErrNil(t, err) - - compareDumpNotZero(t, dump) - - compareFSMResponseNotNil(t, fsmResponse) - - compareState(t, spf.StateAwaitParticipantsConfirmations, fsmResponse.State) - - testParticipantsListResponse, ok := fsmResponse.Data.(responses.SignatureProposalParticipantInvitationsResponse) - - if !ok { - t.Fatalf("expected response {SignatureProposalParticipantInvitationsResponse}") - } - - if len(testParticipantsListResponse) != len(testParticipantsListRequest.Participants) { - t.Fatalf("expected response len {%d}, got {%d}", len(testParticipantsListRequest.Participants), len(testParticipantsListResponse)) - } - - participantsMap := map[int]*responses.SignatureProposalParticipantInvitationEntry{} - - for _, participant := range testParticipantsListResponse { - if _, ok := participantsMap[participant.ParticipantId]; ok { - t.Fatalf("expected unique {ParticipantId}") - } - - if participant.Title == "" { - t.Fatalf("expected not empty {Title}") - } - - if participant.EncryptedInvitation == "" { - t.Fatalf("expected not empty {DecryptedInvitation}") - } - - if participant.PubKeyFingerprint == "" { - t.Fatalf("expected not empty {PubKeyFingerprint}") - } - - participantsMap[participant.ParticipantId] = participant - } - - tm = tm.Add(10 * time.Second) - - participantsCount := len(participantsMap) - - participantCounter := participantsCount - - for _, participant := range participantsMap { - participantCounter-- - testFSMInstance, err = FromDump(dump) - - compareErrNil(t, err) - - compareFSMInstanceNotNil(t, testFSMInstance) - - if _, ok := testParticipants[participant.PubKeyFingerprint]; !ok { - t.Fatalf("not found external user data for response fingerprint") - } - - r := rand.Reader - encrypted, err := rsa.DecryptPKCS1v15(r, testParticipants[participant.PubKeyFingerprint].PrivKey, []byte(participant.EncryptedInvitation)) - - if err != nil { - t.Fatalf("cannot encrypt {DecryptedInvitation} with private key") - } - - fsmResponse, dump, err = testFSMInstance.Do(spf.EventDeclineProposal, requests.SignatureProposalParticipantRequest{ - PubKeyFingerprint: participant.PubKeyFingerprint, - DecryptedInvitation: string(encrypted), - CreatedAt: &tm, - }) - - compareErrNil(t, err) - - compareDumpNotZero(t, dump) - - compareFSMResponseNotNil(t, fsmResponse) - - compareState(t, spf.StateValidationCanceledByParticipant, fsmResponse.State) - - - } -} -*/ From baf120cc663f1e3e78bb483f47c5e7c40a043fef Mon Sep 17 00:00:00 2001 From: x88 Date: Wed, 12 Aug 2020 16:40:03 +0300 Subject: [PATCH 6/8] feat: added additional stage --- .../dkg_proposal_fsm/actions.go | 196 ++++++++++++++---- fsm/state_machines/dkg_proposal_fsm/init.go | 29 ++- fsm/state_machines/internal/provider.go | 78 ++++++- fsm/state_machines/internal/types.go | 13 +- fsm/state_machines/provider.go | 8 +- fsm/state_machines/provider_test.go | 48 ++++- .../signature_proposal_fsm/actions.go | 34 +-- fsm/types/requests/dkg_proposal.go | 21 +- fsm/types/requests/dkg_proposal_validation.go | 40 +++- 9 files changed, 376 insertions(+), 91 deletions(-) diff --git a/fsm/state_machines/dkg_proposal_fsm/actions.go b/fsm/state_machines/dkg_proposal_fsm/actions.go index cce93e4..1f8eb07 100644 --- a/fsm/state_machines/dkg_proposal_fsm/actions.go +++ b/fsm/state_machines/dkg_proposal_fsm/actions.go @@ -32,23 +32,23 @@ func (m *DKGProposalFSM) actionPubKeyConfirmationReceived(inEvent fsm.Event, arg return } - dkgProposalParticipant, ok := m.payload.DKGProposalPayload[request.ParticipantId] - - if !ok { + if !m.payload.DKGQuorumExists(request.ParticipantId) { err = errors.New("{ParticipantId} not exist in quorum") return } + dkgProposalParticipant := m.payload.DKGQuorumGet(request.ParticipantId) + if dkgProposalParticipant.Status != internal.PubKeyAwaitConfirmation { err = errors.New(fmt.Sprintf("cannot confirm pubkey with {Status} = {\"%s\"}", dkgProposalParticipant.Status)) return } copy(dkgProposalParticipant.PubKey, request.PubKey) - dkgProposalParticipant.UpdatedAt = request.CreatedAt + dkgProposalParticipant.UpdatedAt = &request.CreatedAt dkgProposalParticipant.Status = internal.PubKeyConfirmed - m.payload.DKGProposalPayload[request.ParticipantId] = dkgProposalParticipant + m.payload.DKGQuorumUpdate(request.ParticipantId, dkgProposalParticipant) return } @@ -63,8 +63,8 @@ func (m *DKGProposalFSM) actionValidateDkgProposalPubKeys(inEvent fsm.Event, arg tm := time.Now() - unconfirmedParticipants := len(m.payload.DKGProposalPayload) - for _, participant := range m.payload.DKGProposalPayload { + unconfirmedParticipants := m.payload.DKGQuorumCount() + for _, participant := range m.payload.DKGProposalPayload.Quorum { if participant.Status == internal.PubKeyAwaitConfirmation { if participant.UpdatedAt.Add(config.DkgConfirmationDeadline).Before(tm) { isContainsExpired = true @@ -95,7 +95,7 @@ func (m *DKGProposalFSM) actionValidateDkgProposalPubKeys(inEvent fsm.Event, arg outEvent = eventDKGSetPubKeysConfirmedInternal - for _, participant := range m.payload.DKGProposalPayload { + for _, participant := range m.payload.DKGProposalPayload.Quorum { participant.Status = internal.CommitAwaitConfirmation } @@ -124,23 +124,23 @@ func (m *DKGProposalFSM) actionCommitConfirmationReceived(inEvent fsm.Event, arg return } - dkgProposalParticipant, ok := m.payload.DKGProposalPayload[request.ParticipantId] - - if !ok { + if !m.payload.DKGQuorumExists(request.ParticipantId) { err = errors.New("{ParticipantId} not exist in quorum") return } + dkgProposalParticipant := m.payload.DKGQuorumGet(request.ParticipantId) + if dkgProposalParticipant.Status != internal.CommitAwaitConfirmation { err = errors.New(fmt.Sprintf("cannot confirm commit with {Status} = {\"%s\"}", dkgProposalParticipant.Status)) return } copy(dkgProposalParticipant.Commit, request.Commit) - dkgProposalParticipant.UpdatedAt = request.CreatedAt + dkgProposalParticipant.UpdatedAt = &request.CreatedAt dkgProposalParticipant.Status = internal.CommitConfirmed - m.payload.DKGProposalPayload[request.ParticipantId] = dkgProposalParticipant + m.payload.DKGQuorumUpdate(request.ParticipantId, dkgProposalParticipant) return } @@ -155,10 +155,10 @@ func (m *DKGProposalFSM) actionValidateDkgProposalAwaitCommits(inEvent fsm.Event tm := time.Now() - unconfirmedParticipants := len(m.payload.DKGProposalPayload) - for _, participant := range m.payload.DKGProposalPayload { + unconfirmedParticipants := m.payload.DKGQuorumCount() + for _, participant := range m.payload.DKGProposalPayload.Quorum { if participant.Status == internal.CommitAwaitConfirmation { - if participant.UpdatedAt.Add(config.SignatureProposalConfirmationDeadline).Before(tm) { + if participant.UpdatedAt.Add(config.DkgConfirmationDeadline).Before(tm) { isContainsExpired = true } } else { @@ -187,7 +187,7 @@ func (m *DKGProposalFSM) actionValidateDkgProposalAwaitCommits(inEvent fsm.Event outEvent = eventDKGCommitsConfirmedInternal - for _, participant := range m.payload.DKGProposalPayload { + for _, participant := range m.payload.DKGProposalPayload.Quorum { participant.Status = internal.DealAwaitConfirmation } @@ -216,23 +216,23 @@ func (m *DKGProposalFSM) actionDealConfirmationReceived(inEvent fsm.Event, args return } - dkgProposalParticipant, ok := m.payload.DKGProposalPayload[request.ParticipantId] - - if !ok { + if !m.payload.DKGQuorumExists(request.ParticipantId) { err = errors.New("{ParticipantId} not exist in quorum") return } + dkgProposalParticipant := m.payload.DKGQuorumGet(request.ParticipantId) + if dkgProposalParticipant.Status != internal.DealAwaitConfirmation { err = errors.New(fmt.Sprintf("cannot confirm deal with {Status} = {\"%s\"}", dkgProposalParticipant.Status)) return } copy(dkgProposalParticipant.Deal, request.Deal) - dkgProposalParticipant.UpdatedAt = request.CreatedAt + dkgProposalParticipant.UpdatedAt = &request.CreatedAt dkgProposalParticipant.Status = internal.DealConfirmed - m.payload.DKGProposalPayload[request.ParticipantId] = dkgProposalParticipant + m.payload.DKGQuorumUpdate(request.ParticipantId, dkgProposalParticipant) return } @@ -247,10 +247,10 @@ func (m *DKGProposalFSM) actionValidateDkgProposalAwaitDeals(inEvent fsm.Event, tm := time.Now() - unconfirmedParticipants := len(m.payload.DKGProposalPayload) - for _, participant := range m.payload.DKGProposalPayload { + unconfirmedParticipants := m.payload.DKGQuorumCount() + for _, participant := range m.payload.DKGProposalPayload.Quorum { if participant.Status == internal.DealAwaitConfirmation { - if participant.UpdatedAt.Add(config.SignatureProposalConfirmationDeadline).Before(tm) { + if participant.UpdatedAt.Add(config.DkgConfirmationDeadline).Before(tm) { isContainsExpired = true } } else { @@ -279,7 +279,7 @@ func (m *DKGProposalFSM) actionValidateDkgProposalAwaitDeals(inEvent fsm.Event, outEvent = eventDKGDealsConfirmedInternal - for _, participant := range m.payload.DKGProposalPayload { + for _, participant := range m.payload.DKGProposalPayload.Quorum { participant.Status = internal.ResponseAwaitConfirmation } @@ -308,23 +308,23 @@ func (m *DKGProposalFSM) actionResponseConfirmationReceived(inEvent fsm.Event, a return } - dkgProposalParticipant, ok := m.payload.DKGProposalPayload[request.ParticipantId] - - if !ok { + if !m.payload.DKGQuorumExists(request.ParticipantId) { err = errors.New("{ParticipantId} not exist in quorum") return } + dkgProposalParticipant := m.payload.DKGQuorumGet(request.ParticipantId) + if dkgProposalParticipant.Status != internal.ResponseAwaitConfirmation { err = errors.New(fmt.Sprintf("cannot confirm response with {Status} = {\"%s\"}", dkgProposalParticipant.Status)) return } copy(dkgProposalParticipant.Response, request.Response) - dkgProposalParticipant.UpdatedAt = request.CreatedAt + dkgProposalParticipant.UpdatedAt = &request.CreatedAt dkgProposalParticipant.Status = internal.ResponseConfirmed - m.payload.DKGProposalPayload[request.ParticipantId] = dkgProposalParticipant + m.payload.DKGQuorumUpdate(request.ParticipantId, dkgProposalParticipant) return } @@ -339,10 +339,10 @@ func (m *DKGProposalFSM) actionValidateDkgProposalAwaitResponses(inEvent fsm.Eve tm := time.Now() - unconfirmedParticipants := len(m.payload.DKGProposalPayload) - for _, participant := range m.payload.DKGProposalPayload { + unconfirmedParticipants := m.payload.DKGQuorumCount() + for _, participant := range m.payload.DKGProposalPayload.Quorum { if participant.Status == internal.ResponseAwaitConfirmation { - if participant.UpdatedAt.Add(config.SignatureProposalConfirmationDeadline).Before(tm) { + if participant.UpdatedAt.Add(config.DkgConfirmationDeadline).Before(tm) { isContainsExpired = true } } else { @@ -371,6 +371,98 @@ func (m *DKGProposalFSM) actionValidateDkgProposalAwaitResponses(inEvent fsm.Eve outEvent = eventDKGResponsesConfirmedInternal + for _, participant := range m.payload.DKGProposalPayload.Quorum { + participant.Status = internal.MasterKeyAwaitConfirmation + } + + return +} + +// Master key + +func (m *DKGProposalFSM) actionMasterKeyConfirmationReceived(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { + m.payloadMu.Lock() + defer m.payloadMu.Unlock() + + if len(args) != 1 { + err = errors.New("{arg0} required {DKGProposalMasterKeyConfirmationRequest}") + return + } + + request, ok := args[0].(requests.DKGProposalMasterKeyConfirmationRequest) + + if !ok { + err = errors.New("cannot cast {arg0} to type {DKGProposalMasterKeyConfirmationRequest}") + return + } + + if err = request.Validate(); err != nil { + return + } + + if !m.payload.DKGQuorumExists(request.ParticipantId) { + err = errors.New("{ParticipantId} not exist in quorum") + return + } + + dkgProposalParticipant := m.payload.DKGQuorumGet(request.ParticipantId) + + if dkgProposalParticipant.Status != internal.MasterKeyAwaitConfirmation { + err = errors.New(fmt.Sprintf("cannot confirm response with {Status} = {\"%s\"}", dkgProposalParticipant.Status)) + return + } + + copy(dkgProposalParticipant.MasterKey, request.MasterKey) + dkgProposalParticipant.UpdatedAt = &request.CreatedAt + dkgProposalParticipant.Status = internal.MasterKeyConfirmed + + m.payload.DKGQuorumUpdate(request.ParticipantId, dkgProposalParticipant) + + return +} + +func (m *DKGProposalFSM) actionValidateDkgProposalAwaitMasterKey(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { + var ( + isContainsError, isContainsExpired bool + ) + + m.payloadMu.Lock() + defer m.payloadMu.Unlock() + + tm := time.Now() + + unconfirmedParticipants := m.payload.DKGQuorumCount() + for _, participant := range m.payload.DKGProposalPayload.Quorum { + if participant.Status == internal.MasterKeyAwaitConfirmation { + if participant.UpdatedAt.Add(config.DkgConfirmationDeadline).Before(tm) { + isContainsExpired = true + } + } else { + if participant.Status == internal.MasterKeyConfirmationError { + isContainsError = true + } else if participant.Status == internal.MasterKeyConfirmed { + unconfirmedParticipants-- + } + } + } + + if isContainsError { + outEvent = eventDKGMasterKeyConfirmationCancelByErrorInternal + return + } + + if isContainsExpired { + outEvent = eventDKGMasterKeyConfirmationCancelByTimeoutInternal + return + } + + // The are no declined and timed out participants, check for all confirmations + if unconfirmedParticipants > 0 { + return + } + + outEvent = eventDKGMasterKeyConfirmedInternal + return } @@ -395,13 +487,13 @@ func (m *DKGProposalFSM) actionConfirmationError(inEvent fsm.Event, args ...inte return } - dkgProposalParticipant, ok := m.payload.DKGProposalPayload[request.ParticipantId] - - if !ok { + if !m.payload.DKGQuorumExists(request.ParticipantId) { err = errors.New("{ParticipantId} not exist in quorum") return } + dkgProposalParticipant := m.payload.DKGQuorumGet(request.ParticipantId) + // TODO: Move to methods switch inEvent { case EventDKGPubKeyConfirmationError: @@ -431,13 +523,13 @@ func (m *DKGProposalFSM) actionConfirmationError(inEvent fsm.Event, args ...inte err = errors.New(fmt.Sprintf( "{Status} now is \"%s\" and cannot set to {\"%s\"}", dkgProposalParticipant.Status, - internal.PubKeyConfirmationError, + internal.CommitConfirmationError, )) } case EventDKGDealConfirmationError: switch dkgProposalParticipant.Status { case internal.DealAwaitConfirmation: - dkgProposalParticipant.Status = internal.PubKeyConfirmationError + dkgProposalParticipant.Status = internal.DealConfirmationError case internal.DealConfirmed: err = errors.New("{Status} already confirmed") case internal.DealConfirmationError: @@ -446,13 +538,13 @@ func (m *DKGProposalFSM) actionConfirmationError(inEvent fsm.Event, args ...inte err = errors.New(fmt.Sprintf( "{Status} now is \"%s\" and cannot set to {\"%s\"}", dkgProposalParticipant.Status, - internal.PubKeyConfirmationError, + internal.DealConfirmationError, )) } case EventDKGResponseConfirmationError: switch dkgProposalParticipant.Status { case internal.ResponseAwaitConfirmation: - dkgProposalParticipant.Status = internal.PubKeyConfirmationError + dkgProposalParticipant.Status = internal.ResponseConfirmationError case internal.ResponseConfirmed: err = errors.New("{Status} already confirmed") case internal.ResponseConfirmationError: @@ -461,7 +553,22 @@ func (m *DKGProposalFSM) actionConfirmationError(inEvent fsm.Event, args ...inte err = errors.New(fmt.Sprintf( "{Status} now is \"%s\" and cannot set to {\"%s\"}", dkgProposalParticipant.Status, - internal.PubKeyConfirmationError, + internal.ResponseConfirmationError, + )) + } + case EventDKGMasterKeyConfirmationError: + switch dkgProposalParticipant.Status { + case internal.MasterKeyAwaitConfirmation: + dkgProposalParticipant.Status = internal.MasterKeyConfirmationError + case internal.MasterKeyConfirmed: + err = errors.New("{Status} already confirmed") + case internal.MasterKeyConfirmationError: + err = errors.New(fmt.Sprintf("{Status} already has {\"%s\"}", internal.MasterKeyConfirmationError)) + default: + err = errors.New(fmt.Sprintf( + "{Status} now is \"%s\" and cannot set to {\"%s\"}", + dkgProposalParticipant.Status, + internal.MasterKeyConfirmationError, )) } default: @@ -472,9 +579,10 @@ func (m *DKGProposalFSM) actionConfirmationError(inEvent fsm.Event, args ...inte return } - dkgProposalParticipant.UpdatedAt = request.CreatedAt + dkgProposalParticipant.Error = request.Error + dkgProposalParticipant.UpdatedAt = &request.CreatedAt - m.payload.DKGProposalPayload[request.ParticipantId] = dkgProposalParticipant + m.payload.DKGQuorumUpdate(request.ParticipantId, dkgProposalParticipant) // TODO: Add outEvent diff --git a/fsm/state_machines/dkg_proposal_fsm/init.go b/fsm/state_machines/dkg_proposal_fsm/init.go index e7df621..7fa89ed 100644 --- a/fsm/state_machines/dkg_proposal_fsm/init.go +++ b/fsm/state_machines/dkg_proposal_fsm/init.go @@ -30,7 +30,7 @@ const ( StateDkgDealsAwaitConfirmations = fsm.State("state_dkg_deals_await_confirmations") // Canceled StateDkgDealsAwaitCanceled = fsm.State("state_dkg_deals_await_canceled") - StateDkgDealsAwaitCanceledByTimeout = fsm.State("state_dkg_deals_sending_canceled_by_timeout") + StateDkgDealsAwaitCanceledByTimeout = fsm.State("state_dkg_deals_await_canceled_by_timeout") // Confirmed //StateDkgDealsAwaitConfirmed = fsm.State("state_dkg_deals_await_confirmed") @@ -41,6 +41,10 @@ const ( // Confirmed StateDkgResponsesAwaitConfirmed = fsm.State("state_dkg_responses_await_confirmed") + StateDkgMasterKeyAwaitConfirmations = fsm.State("state_dkg_master_key_await_confirmations") + StateDkgMasterKeyAwaitCanceled = fsm.State("state_dkg_master_key_await_canceled") + StateDkgMasterKeyAwaitCanceledByTimeout = fsm.State("state_dkg_master_key_await_canceled_by_timeout") + // Events eventAutoDKGInitialInternal = fsm.Event("event_dkg_init_internal") @@ -75,6 +79,13 @@ const ( eventDKGResponsesConfirmedInternal = fsm.Event("event_dkg_responses_confirmed_internal") eventAutoDKGValidateResponsesConfirmationInternal = fsm.Event("event_dkg_responses_validate_internal") + EventDKGMasterKeyConfirmationReceived = fsm.Event("event_dkg_master_key_confirm_received") + EventDKGMasterKeyConfirmationError = fsm.Event("event_dkg_master_key_confirm_canceled_by_error") + eventDKGMasterKeyConfirmationCancelByTimeoutInternal = fsm.Event("event_dkg_master_key_confirm_canceled_by_timeout_internal") + eventDKGMasterKeyConfirmationCancelByErrorInternal = fsm.Event("event_dkg_master_key_confirm_canceled_by_error_internal") + eventDKGMasterKeyConfirmedInternal = fsm.Event("event_dkg_master_key_confirmed_internal") + eventAutoDKGValidateMasterKeyConfirmationInternal = fsm.Event("event_dkg_master_key_validate_internal") + EventDKGMasterKeyRequiredInternal = fsm.Event("event_dkg_master_key_required_internal") ) @@ -139,7 +150,17 @@ func New() internal.DumpedMachineProvider { {Name: eventAutoDKGValidateResponsesConfirmationInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: StateDkgResponsesAwaitConfirmations, IsInternal: true, IsAuto: true}, - {Name: eventDKGResponsesConfirmedInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: fsm.StateGlobalDone, IsInternal: true}, + {Name: eventDKGResponsesConfirmedInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: StateDkgMasterKeyAwaitConfirmations, IsInternal: true}, + + // Master key + + {Name: EventDKGMasterKeyConfirmationReceived, SrcState: []fsm.State{StateDkgMasterKeyAwaitConfirmations}, DstState: StateDkgMasterKeyAwaitConfirmations}, + {Name: EventDKGMasterKeyConfirmationError, SrcState: []fsm.State{StateDkgMasterKeyAwaitConfirmations}, DstState: StateDkgMasterKeyAwaitCanceled}, + {Name: eventDKGMasterKeyConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgMasterKeyAwaitConfirmations}, DstState: StateDkgMasterKeyAwaitCanceledByTimeout, IsInternal: true}, + + {Name: eventAutoDKGValidateMasterKeyConfirmationInternal, SrcState: []fsm.State{StateDkgMasterKeyAwaitConfirmations}, DstState: StateDkgMasterKeyAwaitConfirmations, IsInternal: true, IsAuto: true}, + + {Name: eventDKGMasterKeyConfirmedInternal, SrcState: []fsm.State{StateDkgMasterKeyAwaitConfirmations}, DstState: fsm.StateGlobalDone, IsInternal: true}, // Done // {Name: EventDKGMasterKeyRequiredInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: fsm.StateGlobalDone, IsInternal: true}, @@ -161,6 +182,10 @@ func New() internal.DumpedMachineProvider { EventDKGResponseConfirmationReceived: machine.actionResponseConfirmationReceived, EventDKGResponseConfirmationError: machine.actionConfirmationError, eventAutoDKGValidateResponsesConfirmationInternal: machine.actionValidateDkgProposalAwaitResponses, + + EventDKGMasterKeyConfirmationReceived: machine.actionMasterKeyConfirmationReceived, + EventDKGMasterKeyConfirmationError: machine.actionConfirmationError, + eventAutoDKGValidateMasterKeyConfirmationInternal: machine.actionValidateDkgProposalAwaitMasterKey, }, ) return machine diff --git a/fsm/state_machines/internal/provider.go b/fsm/state_machines/internal/provider.go index f771aba..f78c25f 100644 --- a/fsm/state_machines/internal/provider.go +++ b/fsm/state_machines/internal/provider.go @@ -2,13 +2,79 @@ package internal import "github.com/depools/dc4bc/fsm/fsm_pool" -type DumpedMachineStatePayload struct { - TransactionId string - ConfirmationProposalPayload SignatureProposalQuorum - DKGProposalPayload DKGProposalQuorum -} - type DumpedMachineProvider interface { fsm_pool.MachineProvider SetUpPayload(payload *DumpedMachineStatePayload) } + +// DKG and other stages quorums are separated, +// because unnecessary data may be unset +type DumpedMachineStatePayload struct { + TransactionId string + SignatureProposalPayload *SignatureConfirmation + DKGProposalPayload *DKGConfirmation +} + +// Signature quorum + +func (p *DumpedMachineStatePayload) SigQuorumCount() int { + var count int + if p.SignatureProposalPayload.Quorum != nil { + count = len(p.SignatureProposalPayload.Quorum) + } + return count +} + +func (p *DumpedMachineStatePayload) SigQuorumExists(id string) bool { + var exists bool + if p.SignatureProposalPayload.Quorum != nil { + _, exists = p.SignatureProposalPayload.Quorum[id] + } + return exists +} + +func (p *DumpedMachineStatePayload) SigQuorumGet(id string) (participant *SignatureProposalParticipant) { + if p.SignatureProposalPayload.Quorum != nil { + participant, _ = p.SignatureProposalPayload.Quorum[id] + } + return +} + +func (p *DumpedMachineStatePayload) SigQuorumUpdate(id string, participant *SignatureProposalParticipant) { + if p.SignatureProposalPayload.Quorum != nil { + p.SignatureProposalPayload.Quorum[id] = participant + } + return +} + +// DKG quorum + +func (p *DumpedMachineStatePayload) DKGQuorumCount() int { + var count int + if p.DKGProposalPayload.Quorum != nil { + count = len(p.DKGProposalPayload.Quorum) + } + return count +} + +func (p *DumpedMachineStatePayload) DKGQuorumExists(id int) bool { + var exists bool + if p.DKGProposalPayload.Quorum != nil { + _, exists = p.DKGProposalPayload.Quorum[id] + } + return exists +} + +func (p *DumpedMachineStatePayload) DKGQuorumGet(id int) (participant *DKGProposalParticipant) { + if p.DKGProposalPayload.Quorum != nil { + participant, _ = p.DKGProposalPayload.Quorum[id] + } + return +} + +func (p *DumpedMachineStatePayload) DKGQuorumUpdate(id int, participant *DKGProposalParticipant) { + if p.DKGProposalPayload.Quorum != nil { + p.DKGProposalPayload.Quorum[id] = participant + } + return +} diff --git a/fsm/state_machines/internal/types.go b/fsm/state_machines/internal/types.go index 86cfb1a..ed24ecb 100644 --- a/fsm/state_machines/internal/types.go +++ b/fsm/state_machines/internal/types.go @@ -5,7 +5,7 @@ import ( "time" ) -type ConfirmationProposal struct { +type SignatureConfirmation struct { Quorum SignatureProposalQuorum CreatedAt *time.Time ExpiresAt *time.Time @@ -45,6 +45,9 @@ const ( ResponseAwaitConfirmation ResponseConfirmed ResponseConfirmationError + MasterKeyAwaitConfirmation + MasterKeyConfirmed + MasterKeyConfirmationError ) type DKGProposalParticipant struct { @@ -53,12 +56,20 @@ type DKGProposalParticipant struct { Commit []byte Deal []byte Response []byte + MasterKey []byte Status ParticipantStatus + Error error UpdatedAt *time.Time } type DKGProposalQuorum map[int]*DKGProposalParticipant +type DKGConfirmation struct { + Quorum DKGProposalQuorum + CreatedAt *time.Time + ExpiresAt *time.Time +} + type DKGProposalParticipantStatus uint8 func (s ParticipantStatus) String() string { diff --git a/fsm/state_machines/provider.go b/fsm/state_machines/provider.go index 24706ce..56f7bb4 100644 --- a/fsm/state_machines/provider.go +++ b/fsm/state_machines/provider.go @@ -66,7 +66,7 @@ func Create() (*FSMInstance, error) { return i, err } -// Get fsm from dump +// DKGQuorumGet fsm from dump func FromDump(data []byte) (*FSMInstance, error) { var err error @@ -127,9 +127,9 @@ func (i *FSMInstance) InitDump(transactionId string) error { TransactionId: transactionId, State: fsm.StateGlobalIdle, Payload: &internal.DumpedMachineStatePayload{ - TransactionId: transactionId, - ConfirmationProposalPayload: nil, - DKGProposalPayload: nil, + TransactionId: transactionId, + SignatureProposalPayload: nil, + DKGProposalPayload: nil, }, } return nil diff --git a/fsm/state_machines/provider_test.go b/fsm/state_machines/provider_test.go index 1d5f60b..c49bb5d 100644 --- a/fsm/state_machines/provider_test.go +++ b/fsm/state_machines/provider_test.go @@ -265,7 +265,7 @@ func Test_SignatureProposal_Positive(t *testing.T) { fsmResponse, dump, err = testFSMInstance.Do(dpf.EventDKGPubKeyConfirmationReceived, requests.DKGProposalPubKeyConfirmationRequest{ ParticipantId: participant.ParticipantId, PubKey: pubKeyMock, - CreatedAt: &tm, + CreatedAt: tm, }) compareErrNil(t, err) @@ -306,7 +306,7 @@ func Test_SignatureProposal_Positive(t *testing.T) { fsmResponse, dump, err = testFSMInstance.Do(dpf.EventDKGCommitConfirmationReceived, requests.DKGProposalCommitConfirmationRequest{ ParticipantId: participant.ParticipantId, Commit: commitMock, - CreatedAt: &tm, + CreatedAt: tm, }) compareErrNil(t, err) @@ -342,7 +342,7 @@ func Test_SignatureProposal_Positive(t *testing.T) { fsmResponse, dump, err = testFSMInstance.Do(dpf.EventDKGDealConfirmationReceived, requests.DKGProposalDealConfirmationRequest{ ParticipantId: participant.ParticipantId, Deal: dealMock, - CreatedAt: &tm, + CreatedAt: tm, }) compareErrNil(t, err) @@ -378,7 +378,43 @@ func Test_SignatureProposal_Positive(t *testing.T) { fsmResponse, dump, err = testFSMInstance.Do(dpf.EventDKGResponseConfirmationReceived, requests.DKGProposalResponseConfirmationRequest{ ParticipantId: participant.ParticipantId, Response: responseMock, - CreatedAt: &tm, + CreatedAt: tm, + }) + + compareErrNil(t, err) + + compareDumpNotZero(t, dump) + + compareFSMResponseNotNil(t, fsmResponse) + + } + + compareState(t, dpf.StateDkgMasterKeyAwaitConfirmations, fsmResponse.State) + + // Master keys + + masterKeyMock := make([]byte, 128) + _, err = rand.Read(masterKeyMock) + if err != nil { + compareErrNil(t, err) + } + + for _, participant := range participantsMap { + participantCounter-- + testFSMInstance, err = FromDump(dump) + + compareErrNil(t, err) + + compareFSMInstanceNotNil(t, testFSMInstance) + + if _, ok := testParticipants[participant.PubKeyFingerprint]; !ok { + t.Fatalf("not found external user data for response fingerprint") + } + + fsmResponse, dump, err = testFSMInstance.Do(dpf.EventDKGMasterKeyConfirmationReceived, requests.DKGProposalMasterKeyConfirmationRequest{ + ParticipantId: participant.ParticipantId, + MasterKey: masterKeyMock, + CreatedAt: tm, }) compareErrNil(t, err) @@ -391,3 +427,7 @@ func Test_SignatureProposal_Positive(t *testing.T) { compareState(t, fsm.StateGlobalDone, fsmResponse.State) } + +func Test_DKGProposal_Positive(t *testing.T) { + +} diff --git a/fsm/state_machines/signature_proposal_fsm/actions.go b/fsm/state_machines/signature_proposal_fsm/actions.go index 22e2168..46a8c8d 100644 --- a/fsm/state_machines/signature_proposal_fsm/actions.go +++ b/fsm/state_machines/signature_proposal_fsm/actions.go @@ -34,7 +34,9 @@ func (m *SignatureProposalFSM) actionInitProposal(inEvent fsm.Event, args ...int return } - m.payload.ConfirmationProposalPayload = make(internal.SignatureProposalQuorum) + m.payload.SignatureProposalPayload = &internal.SignatureConfirmation{ + Quorum: make(internal.SignatureProposalQuorum), + } for index, participant := range request.Participants { participantId := createFingerprint(&participant.PubKey) @@ -49,7 +51,7 @@ func (m *SignatureProposalFSM) actionInitProposal(inEvent fsm.Event, args ...int return inEvent, nil, errors.New("cannot parse {PubKey}") } - m.payload.ConfirmationProposalPayload[participantId] = &internal.SignatureProposalParticipant{ + m.payload.SignatureProposalPayload.Quorum[participantId] = &internal.SignatureProposalParticipant{ ParticipantId: index, Title: participant.Title, PublicKey: parsedPubKey, @@ -60,7 +62,7 @@ func (m *SignatureProposalFSM) actionInitProposal(inEvent fsm.Event, args ...int } // Checking fo quorum length - if len(m.payload.ConfirmationProposalPayload) != len(request.Participants) { + if m.payload.SigQuorumCount() != len(request.Participants) { err = errors.New("error with creating {SignatureProposalQuorum}") return } @@ -69,7 +71,7 @@ func (m *SignatureProposalFSM) actionInitProposal(inEvent fsm.Event, args ...int responseData := make(responses.SignatureProposalParticipantInvitationsResponse, 0) - for pubKeyFingerprint, proposal := range m.payload.ConfirmationProposalPayload { + for pubKeyFingerprint, proposal := range m.payload.SignatureProposalPayload.Quorum { encryptedInvitationSecret, err := encryptWithPubKey(proposal.PublicKey, proposal.InvitationSecret) if err != nil { return inEvent, nil, errors.New("cannot encryptWithPubKey") @@ -108,13 +110,13 @@ func (m *SignatureProposalFSM) actionProposalResponseByParticipant(inEvent fsm.E return } - signatureProposalParticipant, ok := m.payload.ConfirmationProposalPayload[request.PubKeyFingerprint] - - if !ok { + if !m.payload.SigQuorumExists(request.PubKeyFingerprint) { err = errors.New("{PubKeyFingerprint} not exist in quorum") return } + signatureProposalParticipant := m.payload.SigQuorumGet(request.PubKeyFingerprint) + if signatureProposalParticipant.InvitationSecret != request.DecryptedInvitation { err = errors.New("{InvitationSecret} not match {DecryptedInvitation}") return @@ -142,7 +144,7 @@ func (m *SignatureProposalFSM) actionProposalResponseByParticipant(inEvent fsm.E signatureProposalParticipant.UpdatedAt = request.CreatedAt - m.payload.ConfirmationProposalPayload[request.PubKeyFingerprint] = signatureProposalParticipant + m.payload.SigQuorumUpdate(request.PubKeyFingerprint, signatureProposalParticipant) return } @@ -157,8 +159,8 @@ func (m *SignatureProposalFSM) actionValidateSignatureProposal(inEvent fsm.Event tm := time.Now() - unconfirmedParticipants := len(m.payload.ConfirmationProposalPayload) - for _, participant := range m.payload.ConfirmationProposalPayload { + unconfirmedParticipants := m.payload.SigQuorumCount() + for _, participant := range m.payload.SignatureProposalPayload.Quorum { if participant.Status == internal.SignatureConfirmationAwaitConfirmation { if participant.UpdatedAt.Add(config.SignatureProposalConfirmationDeadline).Before(tm) { isContainsExpired = true @@ -203,17 +205,19 @@ func (m *SignatureProposalFSM) actionSetValidatedSignatureProposal(inEvent fsm.E return } - m.payload.DKGProposalPayload = make(internal.DKGProposalQuorum) + m.payload.DKGProposalPayload = &internal.DKGConfirmation{ + Quorum: make(internal.DKGProposalQuorum), + } - for _, participant := range m.payload.ConfirmationProposalPayload { - m.payload.DKGProposalPayload[participant.ParticipantId] = &internal.DKGProposalParticipant{ + for _, participant := range m.payload.SignatureProposalPayload.Quorum { + m.payload.DKGProposalPayload.Quorum[participant.ParticipantId] = &internal.DKGProposalParticipant{ Title: participant.Title, Status: internal.PubKeyAwaitConfirmation, UpdatedAt: participant.UpdatedAt, } } - // Remove m.payload.ConfirmationProposalPayload? + // Remove m.payload.SignatureProposalPayload? return } @@ -224,7 +228,7 @@ func (m *SignatureProposalFSM) actionSignatureProposalCanceledByTimeout(inEvent responseData := make(responses.SignatureProposalParticipantStatusResponse, 0) - for pubKeyFingerprint, participant := range m.payload.ConfirmationProposalPayload { + for pubKeyFingerprint, participant := range m.payload.SignatureProposalPayload.Quorum { responseEntry := &responses.SignatureProposalParticipantStatusEntry{ ParticipantId: participant.ParticipantId, Title: participant.Title, diff --git a/fsm/types/requests/dkg_proposal.go b/fsm/types/requests/dkg_proposal.go index 23fc19d..65bc61a 100644 --- a/fsm/types/requests/dkg_proposal.go +++ b/fsm/types/requests/dkg_proposal.go @@ -7,7 +7,7 @@ import "time" type DKGProposalPubKeyConfirmationRequest struct { ParticipantId int PubKey []byte - CreatedAt *time.Time + CreatedAt time.Time } // States: "state_dkg_commits_sending_await_confirmations" @@ -15,7 +15,7 @@ type DKGProposalPubKeyConfirmationRequest struct { type DKGProposalCommitConfirmationRequest struct { ParticipantId int Commit []byte - CreatedAt *time.Time + CreatedAt time.Time } // States: "state_dkg_deals_await_confirmations" @@ -23,7 +23,7 @@ type DKGProposalCommitConfirmationRequest struct { type DKGProposalDealConfirmationRequest struct { ParticipantId int Deal []byte - CreatedAt *time.Time + CreatedAt time.Time } // States: "state_dkg_responses_await_confirmations" @@ -31,19 +31,30 @@ type DKGProposalDealConfirmationRequest struct { type DKGProposalResponseConfirmationRequest struct { ParticipantId int Response []byte - CreatedAt *time.Time + CreatedAt time.Time +} + +// States: "state_dkg_master_key_await_confirmations" +// Events: "event_dkg_master_key_confirm_received" +type DKGProposalMasterKeyConfirmationRequest struct { + ParticipantId int + MasterKey []byte + CreatedAt time.Time } // States: "state_dkg_pub_keys_await_confirmations" // "state_dkg_commits_sending_await_confirmations" // "state_dkg_deals_await_confirmations" // "state_dkg_responses_await_confirmations" +// "state_dkg_master_key_await_confirmations" // // Events: "event_dkg_pub_key_confirm_canceled_by_error", // "event_dkg_commit_confirm_canceled_by_error" // "event_dkg_deal_confirm_canceled_by_error" // "event_dkg_response_confirm_canceled_by_error" +// "event_dkg_master_key_confirm_canceled_by_error" type DKGProposalConfirmationErrorRequest struct { ParticipantId int - CreatedAt *time.Time + Error error + CreatedAt time.Time } diff --git a/fsm/types/requests/dkg_proposal_validation.go b/fsm/types/requests/dkg_proposal_validation.go index 931255f..a9ce43d 100644 --- a/fsm/types/requests/dkg_proposal_validation.go +++ b/fsm/types/requests/dkg_proposal_validation.go @@ -11,8 +11,8 @@ func (r *DKGProposalPubKeyConfirmationRequest) Validate() error { return errors.New("{PubKey} cannot zero length") } - if r.CreatedAt == nil { - return errors.New("{CreatedAt} cannot be a nil") + if r.CreatedAt.IsZero() { + return errors.New("{CreatedAt} is not set") } return nil @@ -27,8 +27,8 @@ func (r *DKGProposalCommitConfirmationRequest) Validate() error { return errors.New("{Commit} cannot zero length") } - if r.CreatedAt == nil { - return errors.New("{CreatedAt} cannot be a nil") + if r.CreatedAt.IsZero() { + return errors.New("{CreatedAt} is not set") } return nil @@ -43,8 +43,8 @@ func (r *DKGProposalDealConfirmationRequest) Validate() error { return errors.New("{Deal} cannot zero length") } - if r.CreatedAt == nil { - return errors.New("{CreatedAt} cannot be a nil") + if r.CreatedAt.IsZero() { + return errors.New("{CreatedAt} is not set") } return nil @@ -59,8 +59,24 @@ func (r *DKGProposalResponseConfirmationRequest) Validate() error { return errors.New("{Response} cannot zero length") } - if r.CreatedAt == nil { - return errors.New("{CreatedAt} cannot be a nil") + if r.CreatedAt.IsZero() { + return errors.New("{CreatedAt} is not set") + } + + return nil +} + +func (r *DKGProposalMasterKeyConfirmationRequest) Validate() error { + if r.ParticipantId < 0 { + return errors.New("{ParticipantId} cannot be a negative number") + } + + if len(r.MasterKey) == 0 { + return errors.New("{MasterKey} cannot zero length") + } + + if r.CreatedAt.IsZero() { + return errors.New("{CreatedAt} is not set") } return nil @@ -71,8 +87,12 @@ func (r *DKGProposalConfirmationErrorRequest) Validate() error { return errors.New("{ParticipantId} cannot be a negative number") } - if r.CreatedAt == nil { - return errors.New("{CreatedAt} cannot be a nil") + if r.Error == nil { + return errors.New("{Error} cannot be a nil") + } + + if r.CreatedAt.IsZero() { + return errors.New("{CreatedAt} is not set") } return nil From 9e3380330714d3665e0dc8259bc767a459c62b8e Mon Sep 17 00:00:00 2001 From: x88 Date: Wed, 12 Aug 2020 16:41:38 +0300 Subject: [PATCH 7/8] removed comment --- fsm/state_machines/provider_test.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/fsm/state_machines/provider_test.go b/fsm/state_machines/provider_test.go index c49bb5d..e080caf 100644 --- a/fsm/state_machines/provider_test.go +++ b/fsm/state_machines/provider_test.go @@ -274,11 +274,6 @@ func Test_SignatureProposal_Positive(t *testing.T) { compareFSMResponseNotNil(t, fsmResponse) - /*if participantCounter > 0 { - compareState(t, dpf.StateDkgPubKeysAwaitConfirmations, fsmResponse.State) - } else { - compareState(t, dpf.StateDkgCommitsAwaitConfirmations, fsmResponse.State) - }*/ } compareState(t, dpf.StateDkgCommitsAwaitConfirmations, fsmResponse.State) From 4ed9afc8dd471929343ea62148ddff4d7a8d247e Mon Sep 17 00:00:00 2001 From: x88 Date: Wed, 12 Aug 2020 17:16:07 +0300 Subject: [PATCH 8/8] feat: master keys matcher --- .../dkg_proposal_fsm/actions.go | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/fsm/state_machines/dkg_proposal_fsm/actions.go b/fsm/state_machines/dkg_proposal_fsm/actions.go index 1f8eb07..3fc1913 100644 --- a/fsm/state_machines/dkg_proposal_fsm/actions.go +++ b/fsm/state_machines/dkg_proposal_fsm/actions.go @@ -7,6 +7,7 @@ import ( "github.com/depools/dc4bc/fsm/fsm" "github.com/depools/dc4bc/fsm/state_machines/internal" "github.com/depools/dc4bc/fsm/types/requests" + "reflect" "time" ) @@ -424,6 +425,7 @@ func (m *DKGProposalFSM) actionMasterKeyConfirmationReceived(inEvent fsm.Event, func (m *DKGProposalFSM) actionValidateDkgProposalAwaitMasterKey(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { var ( isContainsError, isContainsExpired bool + masterKeys [][]byte ) m.payloadMu.Lock() @@ -432,6 +434,7 @@ func (m *DKGProposalFSM) actionValidateDkgProposalAwaitMasterKey(inEvent fsm.Eve tm := time.Now() unconfirmedParticipants := m.payload.DKGQuorumCount() + for _, participant := range m.payload.DKGProposalPayload.Quorum { if participant.Status == internal.MasterKeyAwaitConfirmation { if participant.UpdatedAt.Add(config.DkgConfirmationDeadline).Before(tm) { @@ -441,6 +444,7 @@ func (m *DKGProposalFSM) actionValidateDkgProposalAwaitMasterKey(inEvent fsm.Eve if participant.Status == internal.MasterKeyConfirmationError { isContainsError = true } else if participant.Status == internal.MasterKeyConfirmed { + masterKeys = append(masterKeys, participant.MasterKey) unconfirmedParticipants-- } } @@ -456,6 +460,27 @@ func (m *DKGProposalFSM) actionValidateDkgProposalAwaitMasterKey(inEvent fsm.Eve return } + // Temporary simplest match master keys + if len(masterKeys) > 1 { + for i, masterKey := range masterKeys { + for j := range masterKeys { + if i == j { + continue + } + + if !reflect.DeepEqual(masterKey, masterKeys[i]) { + for _, participant := range m.payload.DKGProposalPayload.Quorum { + participant.Status = internal.MasterKeyConfirmationError + participant.Error = errors.New("master key is mismatched") + } + + outEvent = eventDKGMasterKeyConfirmationCancelByErrorInternal + return + } + } + } + } + // The are no declined and timed out participants, check for all confirmations if unconfirmedParticipants > 0 { return @@ -463,6 +488,10 @@ func (m *DKGProposalFSM) actionValidateDkgProposalAwaitMasterKey(inEvent fsm.Eve outEvent = eventDKGMasterKeyConfirmedInternal + for _, participant := range m.payload.DKGProposalPayload.Quorum { + participant.Status = internal.MasterKeyConfirmed + } + return }