From 10c343ac908d621699623d6bc80319bcab6b5cb7 Mon Sep 17 00:00:00 2001 From: x88 Date: Tue, 11 Aug 2020 19:46:18 +0300 Subject: [PATCH] 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 }