From 3b4e09f6cbb39694a363adc99adede8fb436d398 Mon Sep 17 00:00:00 2001 From: x88 Date: Tue, 18 Aug 2020 14:27:30 +0300 Subject: [PATCH] wip --- fsm/config/config.go | 1 + fsm/state_machines/README.md | 4 + fsm/state_machines/dkg_proposal_fsm/init.go | 20 ++- fsm/state_machines/internal/provider.go | 33 ++++ fsm/state_machines/internal/types.go | 109 ++++++++++--- fsm/state_machines/provider_test.go | 3 +- .../signature_proposal_fsm/actions.go | 14 +- .../signature_proposal_fsm/init.go | 4 +- .../signing_proposal_fsm/actions.go | 143 ++++++++++++++++++ .../signing_proposal_fsm/init.go | 106 +++++++++++++ fsm/types/requests/signature_proposal.go | 5 +- .../requests/signature_proposal_validation.go | 8 + fsm/types/requests/signing_proposal.go | 27 ++++ .../requests/signing_proposal_validation.go | 47 ++++++ fsm/types/responses/signing_proposal.go | 1 + 15 files changed, 485 insertions(+), 40 deletions(-) create mode 100644 fsm/state_machines/README.md create mode 100644 fsm/state_machines/signing_proposal_fsm/actions.go create mode 100644 fsm/state_machines/signing_proposal_fsm/init.go create mode 100644 fsm/types/requests/signing_proposal.go create mode 100644 fsm/types/requests/signing_proposal_validation.go create mode 100644 fsm/types/responses/signing_proposal.go diff --git a/fsm/config/config.go b/fsm/config/config.go index bb77b16..d9981fd 100644 --- a/fsm/config/config.go +++ b/fsm/config/config.go @@ -7,4 +7,5 @@ const ( ParticipantsMinCount = 3 SignatureProposalConfirmationDeadline = time.Hour * 24 DkgConfirmationDeadline = time.Hour * 24 + SigningConfirmationDeadline = time.Hour * 24 ) diff --git a/fsm/state_machines/README.md b/fsm/state_machines/README.md new file mode 100644 index 0000000..599185e --- /dev/null +++ b/fsm/state_machines/README.md @@ -0,0 +1,4 @@ + + + +Events diff --git a/fsm/state_machines/dkg_proposal_fsm/init.go b/fsm/state_machines/dkg_proposal_fsm/init.go index c55014f..04e869c 100644 --- a/fsm/state_machines/dkg_proposal_fsm/init.go +++ b/fsm/state_machines/dkg_proposal_fsm/init.go @@ -15,7 +15,7 @@ const ( // Sending dkg commits StateDkgCommitsAwaitConfirmations = fsm.State("state_dkg_commits_await_confirmations") // Canceled - StateDkgCommitsAwaitCanceled = fsm.State("state_dkg_commits_await_canceled") + StateDkgCommitsAwaitCanceledByError = fsm.State("state_dkg_commits_await_canceled_by_error") StateDkgCommitsAwaitCanceledByTimeout = fsm.State("state_dkg_commits_await_canceled_by_timeout") // Confirmed StateDkgCommitsCollected = fsm.State("state_dkg_commits_collected") @@ -23,20 +23,20 @@ const ( // Sending dkg deals StateDkgDealsAwaitConfirmations = fsm.State("state_dkg_deals_await_confirmations") // Canceled - StateDkgDealsAwaitCanceled = fsm.State("state_dkg_deals_await_canceled") + StateDkgDealsAwaitCanceledByError = fsm.State("state_dkg_deals_await_canceled_by_error") StateDkgDealsAwaitCanceledByTimeout = fsm.State("state_dkg_deals_await_canceled_by_timeout") // Confirmed //StateDkgDealsCollected = fsm.State("state_dkg_deals_collected") StateDkgResponsesAwaitConfirmations = fsm.State("state_dkg_responses_await_confirmations") // Canceled - StateDkgResponsesAwaitCanceled = fsm.State("state_dkg_responses_await_canceled") + StateDkgResponsesAwaitCanceledByError = fsm.State("state_dkg_responses_await_canceled_by_error") StateDkgResponsesAwaitCanceledByTimeout = fsm.State("state_dkg_responses_sending_canceled_by_timeout") // Confirmed StateDkgResponsesCollected = fsm.State("state_dkg_responses_collected") StateDkgMasterKeyAwaitConfirmations = fsm.State("state_dkg_master_key_await_confirmations") - StateDkgMasterKeyAwaitCanceled = fsm.State("state_dkg_master_key_await_canceled") + StateDkgMasterKeyAwaitCanceledByError = fsm.State("state_dkg_master_key_await_canceled_by_error") StateDkgMasterKeyAwaitCanceledByTimeout = fsm.State("state_dkg_master_key_await_canceled_by_timeout") StateDkgMasterKeyCollected = fsm.State("state_dkg_master_key_collected") @@ -104,7 +104,7 @@ func New() internal.DumpedMachineProvider { // Commits {Name: EventDKGCommitConfirmationReceived, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitConfirmations}, // Canceled - {Name: EventDKGCommitConfirmationError, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitCanceled}, + {Name: EventDKGCommitConfirmationError, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitCanceledByError}, {Name: eventDKGCommitsConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitCanceledByTimeout, IsInternal: true}, {Name: eventAutoDKGValidateConfirmationCommitsInternal, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitConfirmations, IsInternal: true, IsAuto: true}, @@ -115,7 +115,7 @@ func New() internal.DumpedMachineProvider { // Deals {Name: EventDKGDealConfirmationReceived, SrcState: []fsm.State{StateDkgDealsAwaitConfirmations}, DstState: StateDkgDealsAwaitConfirmations}, // Canceled - {Name: EventDKGDealConfirmationError, SrcState: []fsm.State{StateDkgDealsAwaitConfirmations}, DstState: StateDkgDealsAwaitCanceled}, + {Name: EventDKGDealConfirmationError, SrcState: []fsm.State{StateDkgDealsAwaitConfirmations}, DstState: StateDkgDealsAwaitCanceledByError}, {Name: eventDKGDealsConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgDealsAwaitConfirmations}, DstState: StateDkgDealsAwaitConfirmations, IsInternal: true}, {Name: eventAutoDKGValidateConfirmationDealsInternal, SrcState: []fsm.State{StateDkgDealsAwaitConfirmations}, DstState: StateDkgDealsAwaitConfirmations, IsInternal: true, IsAuto: true}, @@ -124,7 +124,7 @@ func New() internal.DumpedMachineProvider { // Responses {Name: EventDKGResponseConfirmationReceived, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: StateDkgResponsesAwaitConfirmations}, // Canceled - {Name: EventDKGResponseConfirmationError, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: StateDkgResponsesAwaitCanceled}, + {Name: EventDKGResponseConfirmationError, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: StateDkgResponsesAwaitCanceledByError}, {Name: eventDKGResponseConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: StateDkgResponsesAwaitCanceledByTimeout, IsInternal: true}, {Name: eventAutoDKGValidateResponsesConfirmationInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: StateDkgResponsesAwaitConfirmations, IsInternal: true, IsAuto: true}, @@ -134,15 +134,13 @@ func New() internal.DumpedMachineProvider { // Master key {Name: EventDKGMasterKeyConfirmationReceived, SrcState: []fsm.State{StateDkgMasterKeyAwaitConfirmations}, DstState: StateDkgMasterKeyAwaitConfirmations}, - {Name: EventDKGMasterKeyConfirmationError, SrcState: []fsm.State{StateDkgMasterKeyAwaitConfirmations}, DstState: StateDkgMasterKeyAwaitCanceled}, + {Name: EventDKGMasterKeyConfirmationError, SrcState: []fsm.State{StateDkgMasterKeyAwaitConfirmations}, DstState: StateDkgMasterKeyAwaitCanceledByError}, {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}, + {Name: eventDKGMasterKeyConfirmedInternal, SrcState: []fsm.State{StateDkgMasterKeyAwaitConfirmations}, DstState: StateDkgMasterKeyCollected, IsInternal: true}, }, fsm.Callbacks{ EventDKGInitProcess: machine.actionInitDKGProposal, diff --git a/fsm/state_machines/internal/provider.go b/fsm/state_machines/internal/provider.go index f78c25f..2b3b177 100644 --- a/fsm/state_machines/internal/provider.go +++ b/fsm/state_machines/internal/provider.go @@ -13,6 +13,7 @@ type DumpedMachineStatePayload struct { TransactionId string SignatureProposalPayload *SignatureConfirmation DKGProposalPayload *DKGConfirmation + SigningProposalPayload *SigningConfirmation } // Signature quorum @@ -78,3 +79,35 @@ func (p *DumpedMachineStatePayload) DKGQuorumUpdate(id int, participant *DKGProp } return } + +// Signing quorum + +func (p *DumpedMachineStatePayload) SigningQuorumCount() int { + var count int + if p.SigningProposalPayload.Quorum != nil { + count = len(p.SigningProposalPayload.Quorum) + } + return count +} + +func (p *DumpedMachineStatePayload) SigningQuorumExists(id int) bool { + var exists bool + if p.SigningProposalPayload.Quorum != nil { + _, exists = p.SigningProposalPayload.Quorum[id] + } + return exists +} + +func (p *DumpedMachineStatePayload) SigningQuorumGet(id int) (participant *SigningProposalParticipant) { + if p.SigningProposalPayload.Quorum != nil { + participant, _ = p.SigningProposalPayload.Quorum[id] + } + return +} + +func (p *DumpedMachineStatePayload) SigningQuorumUpdate(id int, participant *SigningProposalParticipant) { + if p.SigningProposalPayload.Quorum != nil { + p.SigningProposalPayload.Quorum[id] = participant + } + return +} diff --git a/fsm/state_machines/internal/types.go b/fsm/state_machines/internal/types.go index ea45971..78d6e8b 100644 --- a/fsm/state_machines/internal/types.go +++ b/fsm/state_machines/internal/types.go @@ -5,6 +5,30 @@ import ( "time" ) +type ConfirmationParticipantStatus uint8 + +const ( + SigConfirmationAwaitConfirmation ConfirmationParticipantStatus = iota + SigConfirmationConfirmed + SigConfirmationDeclined + SigConfirmationError +) + +func (s ConfirmationParticipantStatus) String() string { + var str = "undefined" + switch s { + case SigConfirmationAwaitConfirmation: + str = "SigConfirmationAwaitConfirmation" + case SigConfirmationConfirmed: + str = "SigConfirmationConfirmed" + case SigConfirmationDeclined: + str = "SigConfirmationDeclined" + case SigConfirmationError: + str = "SigConfirmationError" + } + return str +} + type SignatureConfirmation struct { Quorum SignatureProposalQuorum CreatedAt time.Time @@ -19,7 +43,7 @@ type SignatureProposalParticipant struct { DkgPubKey []byte // For validation user confirmation: sign(InvitationSecret, PubKey) => user InvitationSecret string - Status ParticipantStatus + Status ConfirmationParticipantStatus UpdatedAt time.Time } @@ -27,14 +51,12 @@ type SignatureProposalParticipant struct { // Excludes array merge and rotate operations type SignatureProposalQuorum map[string]*SignatureProposalParticipant -type ParticipantStatus uint8 +// DKG proposal + +type DKGParticipantStatus uint8 const ( - SignatureConfirmationAwaitConfirmation ParticipantStatus = iota - SignatureConfirmationConfirmed - SignatureConfirmationDeclined - SignatureConfirmationError - CommitAwaitConfirmation + CommitAwaitConfirmation DKGParticipantStatus = iota CommitConfirmed CommitConfirmationError DealAwaitConfirmation @@ -55,7 +77,7 @@ type DKGProposalParticipant struct { Deal []byte Response []byte MasterKey []byte - Status ParticipantStatus + Status DKGParticipantStatus Error error UpdatedAt time.Time } @@ -70,17 +92,9 @@ type DKGConfirmation struct { type DKGProposalParticipantStatus uint8 -func (s ParticipantStatus) String() string { +func (s DKGParticipantStatus) String() string { var str = "undefined" switch s { - case SignatureConfirmationAwaitConfirmation: - str = "SignatureConfirmationAwaitConfirmation" - case SignatureConfirmationConfirmed: - str = "SignatureConfirmationConfirmed" - case SignatureConfirmationDeclined: - str = "SignatureConfirmationDeclined" - case SignatureConfirmationError: - str = "SignatureConfirmationError" case CommitAwaitConfirmation: str = "CommitAwaitConfirmation" case CommitConfirmed: @@ -99,6 +113,67 @@ func (s ParticipantStatus) String() string { str = "ResponseConfirmed" case ResponseConfirmationError: str = "ResponseConfirmationError" + case MasterKeyAwaitConfirmation: + str = "MasterKeyAwaitConfirmation" + case MasterKeyConfirmed: + str = "MasterKeyConfirmed" + case MasterKeyConfirmationError: + str = "MasterKeyConfirmationError" } return str } + +// Signing proposal + +type SigningConfirmation struct { + Quorum SigningProposalQuorum + RecoveredKey []byte + SrcPayload []byte + EncryptedPayload []byte + CreatedAt time.Time + ExpiresAt time.Time +} + +type SigningProposalQuorum map[int]*SigningProposalParticipant + +type SigningParticipantStatus uint8 + +const ( + SigningIdle SigningParticipantStatus = iota + SigningAwaitConfirmation + SigningConfirmed + SigningDeclined + SigningAwaitPartialKeys + SigningPartialKeysConfirmed + SigningError + SigningProcess +) + +func (s SigningParticipantStatus) String() string { + var str = "undefined" + switch s { + case SigningIdle: + str = "SigningIdle" + case SigningAwaitConfirmation: + str = "SigningAwaitConfirmation" + case SigningConfirmed: + str = "SigningConfirmed" + case SigningAwaitPartialKeys: + str = "SigningAwaitPartialKeys" + case SigningPartialKeysConfirmed: + str = "SigningPartialKeysConfirmed" + case SigningError: + str = "SigningError" + case SigningProcess: + str = "SigningProcess" + } + return str +} + +type SigningProposalParticipant struct { + Title string + Status SigningParticipantStatus + PartialKey []byte + Error error + UpdatedAt time.Time +} diff --git a/fsm/state_machines/provider_test.go b/fsm/state_machines/provider_test.go index 06d121b..8849c19 100644 --- a/fsm/state_machines/provider_test.go +++ b/fsm/state_machines/provider_test.go @@ -80,6 +80,7 @@ func init() { }) } testParticipantsListRequest.Participants = participantsForRequest + testParticipantsListRequest.SigningThreshold = len(participantsForRequest) } func TestCreate_Positive(t *testing.T) { @@ -399,7 +400,7 @@ func Test_SignatureProposal_Positive(t *testing.T) { } - compareState(t, fsm.StateGlobalDone, fsmResponse.State) + compareState(t, dpf.StateDkgMasterKeyCollected, 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 45a27ed..5adeb98 100644 --- a/fsm/state_machines/signature_proposal_fsm/actions.go +++ b/fsm/state_machines/signature_proposal_fsm/actions.go @@ -57,7 +57,7 @@ func (m *SignatureProposalFSM) actionInitSignatureProposal(inEvent fsm.Event, ar PubKey: parsedPubKey, DkgPubKey: participant.DkgPubKey, InvitationSecret: secret, - Status: internal.SignatureConfirmationAwaitConfirmation, + Status: internal.SigConfirmationAwaitConfirmation, UpdatedAt: request.CreatedAt, } } @@ -128,16 +128,16 @@ func (m *SignatureProposalFSM) actionProposalResponseByParticipant(inEvent fsm.E return } - if signatureProposalParticipant.Status != internal.SignatureConfirmationAwaitConfirmation { + if signatureProposalParticipant.Status != internal.SigConfirmationAwaitConfirmation { err = errors.New(fmt.Sprintf("cannot apply reply participant with {Status} = {\"%s\"}", signatureProposalParticipant.Status)) return } switch inEvent { case EventConfirmSignatureProposal: - signatureProposalParticipant.Status = internal.SignatureConfirmationConfirmed + signatureProposalParticipant.Status = internal.SigConfirmationConfirmed case EventDeclineProposal: - signatureProposalParticipant.Status = internal.SignatureConfirmationDeclined + signatureProposalParticipant.Status = internal.SigConfirmationDeclined default: err = errors.New("undefined {Event} for action") return @@ -162,14 +162,14 @@ func (m *SignatureProposalFSM) actionValidateSignatureProposal(inEvent fsm.Event unconfirmedParticipants := m.payload.SigQuorumCount() for _, participant := range m.payload.SignatureProposalPayload.Quorum { - if participant.Status == internal.SignatureConfirmationAwaitConfirmation { + if participant.Status == internal.SigConfirmationAwaitConfirmation { if participant.UpdatedAt.Add(config.SignatureProposalConfirmationDeadline).Before(tm) { isContainsExpired = true } } else { - if participant.Status == internal.SignatureConfirmationConfirmed { + if participant.Status == internal.SigConfirmationConfirmed { unconfirmedParticipants-- - } else if participant.Status == internal.SignatureConfirmationDeclined { + } else if participant.Status == internal.SigConfirmationDeclined { isContainsDeclined = true } } diff --git a/fsm/state_machines/signature_proposal_fsm/init.go b/fsm/state_machines/signature_proposal_fsm/init.go index 9dfbbef..ed8b7d5 100644 --- a/fsm/state_machines/signature_proposal_fsm/init.go +++ b/fsm/state_machines/signature_proposal_fsm/init.go @@ -28,7 +28,7 @@ const ( eventDoneInternal = fsm.Event("event_sig_proposal_done") eventSetValidationCanceledByTimeout = fsm.Event("event_sig_proposal_canceled_timeout") - eventSetValidationCanceledByParticipant = fsm.Event("event_sig_proposal_declined_timeout") + eventSetValidationCanceledByParticipant = fsm.Event("event_sig_proposal_canceled_participant") StateSignatureProposalCollected = fsm.State("state_sig_proposal_collected") @@ -49,7 +49,7 @@ func New() internal.DumpedMachineProvider { FsmName, fsm.StateGlobalIdle, []fsm.EventDesc{ - // {Name: "", SrcState: []string{""}, DstState: ""}, + // {Name: "", SrcState: []fsm.State{""}, DstState: ""}, // Init {Name: EventInitProposal, SrcState: []fsm.State{StateParticipantsConfirmationsInit}, DstState: StateAwaitParticipantsConfirmations}, diff --git a/fsm/state_machines/signing_proposal_fsm/actions.go b/fsm/state_machines/signing_proposal_fsm/actions.go new file mode 100644 index 0000000..e32fe13 --- /dev/null +++ b/fsm/state_machines/signing_proposal_fsm/actions.go @@ -0,0 +1,143 @@ +package signing_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" +) + +func (m *SigningProposalFSM) actionInitSigningProposal(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { + m.payloadMu.Lock() + defer m.payloadMu.Unlock() + + m.payload.SigningProposalPayload = &internal.SigningConfirmation{ + Quorum: make(internal.SigningProposalQuorum), + } + + for _, participant := range m.payload.SignatureProposalPayload.Quorum { + m.payload.SigningProposalPayload.Quorum[participant.ParticipantId] = &internal.SigningProposalParticipant{ + Title: participant.Title, + Status: internal.SigningIdle, + UpdatedAt: participant.UpdatedAt, + } + } + + return +} + +func (m *SigningProposalFSM) actionStartSigningProposal(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 {SigningProposalStartRequest}") + return + } + + request, ok := args[0].(requests.SigningProposalStartRequest) + + if !ok { + err = errors.New("cannot cast {arg0} to type {SigningProposalStartRequest}") + return + } + + if err = request.Validate(); err != nil { + return + } + + return +} + +func (m *SigningProposalFSM) actionProposalResponseByParticipant(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 {SigningProposalParticipantRequest}") + return + } + + request, ok := args[0].(requests.SigningProposalParticipantRequest) + + if !ok { + err = errors.New("cannot cast {arg0} to type {SigningProposalParticipantRequest}") + return + } + + if err = request.Validate(); err != nil { + return + } + + if !m.payload.SigningQuorumExists(request.ParticipantId) { + err = errors.New("{ParticipantId} not exist in quorum") + return + } + + signingProposalParticipant := m.payload.SigningQuorumGet(request.ParticipantId) + + if signingProposalParticipant.Status != internal.SigningAwaitConfirmation { + err = errors.New(fmt.Sprintf("cannot confirm commit with {Status} = {\"%s\"}", signingProposalParticipant.Status)) + return + } + + // copy(signingProposalParticipant.Commit, request.Commit) + signingProposalParticipant.UpdatedAt = request.CreatedAt + signingProposalParticipant.Status = internal.SigningConfirmed + + m.payload.SigningQuorumUpdate(request.ParticipantId, signingProposalParticipant) + + return +} + +func (m *SigningProposalFSM) actionValidateSigningProposalConfirmations(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.SigningQuorumCount() + for _, participant := range m.payload.SigningProposalPayload.Quorum { + if participant.Status == internal.SigningAwaitConfirmation { + if participant.UpdatedAt.Add(config.SigningConfirmationDeadline).Before(tm) { + isContainsExpired = true + } + } else { + if participant.Status == internal.SigningDeclined { + isContainsError = true + } else if participant.Status == internal.SigningConfirmed { + unconfirmedParticipants-- + } + } + } + + if isContainsError { + outEvent = eventSetSigningConfirmCanceledByTimeoutInternal + return + } + + if isContainsExpired { + outEvent = eventSetSigningConfirmCanceledByParticipantInternal + return + } + + // The are no declined and timed out participants, check for all confirmations + if unconfirmedParticipants > 0 { + return + } + + outEvent = eventSetProposalValidatedInternal + + for _, participant := range m.payload.SigningProposalPayload.Quorum { + participant.Status = internal.SigningAwaitPartialKeys + } + + return +} diff --git a/fsm/state_machines/signing_proposal_fsm/init.go b/fsm/state_machines/signing_proposal_fsm/init.go new file mode 100644 index 0000000..92c80a4 --- /dev/null +++ b/fsm/state_machines/signing_proposal_fsm/init.go @@ -0,0 +1,106 @@ +package signing_proposal_fsm + +import ( + "github.com/depools/dc4bc/fsm/fsm" + dkp "github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm" + "github.com/depools/dc4bc/fsm/state_machines/internal" + "sync" +) + +const ( + FsmName = "signing_proposal_fsm" + + StateSigningInitial = dkp.StateDkgMasterKeyCollected + + StateSigningIdle = fsm.State("stage_signing_idle") + + // Starting + + StateSigningAwaitConfirmations = fsm.State("state_signing_await_confirmations") + // Cancelled + StateSigningConfirmationsAwaitCancelledByTimeout = fsm.State("state_signing_confirmations_await_cancelled_by_timeout") + StateSigningConfirmationsAwaitCancelledByParticipant = fsm.State("state_signing_confirmations_await_cancelled_by_participant") + + StateSigningAwaitPartialKeys = fsm.State("state_signing_await_partial_keys") + // Cancelled + StateSigningPartialKeysAwaitCancelledByTimeout = fsm.State("state_signing_partial_keys_await_cancelled_by_timeout") + StateSigningPartialKeysAwaitCancelledByParticipant = fsm.State("state_signing_partial_keys_await_cancelled_by_participant") + + StateSigningPartialKeysCollected = fsm.State("state_signing_partial_keys_collected") + + // Events + + EventSigningInit = fsm.Event("event_signing_init") + EventSigningStart = fsm.Event("event_signing_start") + EventConfirmSigningConfirmation = fsm.Event("event_signing_proposal_confirm_by_participant") + EventDeclineSigningConfirmation = fsm.Event("event_signing_proposal_decline_by_participant") + eventSetSigningConfirmCanceledByParticipantInternal = fsm.Event("event_signing_proposal_canceled_by_participant") + eventSetSigningConfirmCanceledByTimeoutInternal = fsm.Event("event_signing_proposal_canceled_by_timeout") + + eventAutoValidateProposalInternal = fsm.Event("event_signing_proposal_validate") + eventSetProposalValidatedInternal = fsm.Event("event_signing_proposal_set_validated") + + EventSigningPartialKeyReceived = fsm.Event("event_signing_partial_key_received") + EventSigningPartialKeyError = fsm.Event("event_signing_partial_key_error_received") + eventSigningPartialKeyCancelByTimeoutInternal = fsm.Event("event_signing_partial_key_canceled_by_timeout_internal") + eventSigningPartialKeyCancelByErrorInternal = fsm.Event("event_signing_partial_key_canceled_by_error_internal") + eventSigningPartialKeysConfirmedInternal = fsm.Event("event_signing_partial_keys_confirmed_internal") + EventSigningFinish = fsm.Event("event_signing_finish") +) + +type SigningProposalFSM struct { + *fsm.FSM + payload *internal.DumpedMachineStatePayload + payloadMu sync.RWMutex +} + +func New() internal.DumpedMachineProvider { + machine := &SigningProposalFSM{} + + machine.FSM = fsm.MustNewFSM( + FsmName, + StateSigningInitial, + []fsm.EventDesc{ + // Init + {Name: EventSigningInit, SrcState: []fsm.State{StateSigningInitial}, DstState: StateSigningIdle}, + + // Start + {Name: EventSigningStart, SrcState: []fsm.State{StateSigningIdle}, DstState: StateSigningAwaitConfirmations}, + + // Validate by participants + {Name: EventConfirmSigningConfirmation, SrcState: []fsm.State{StateSigningAwaitConfirmations}, DstState: StateSigningAwaitConfirmations}, + {Name: EventDeclineSigningConfirmation, SrcState: []fsm.State{StateSigningAwaitConfirmations}, DstState: StateSigningAwaitConfirmations}, + + // Canceled + {Name: eventSetSigningConfirmCanceledByParticipantInternal, SrcState: []fsm.State{StateSigningAwaitConfirmations}, DstState: StateSigningConfirmationsAwaitCancelledByParticipant, IsInternal: true}, + {Name: eventSetSigningConfirmCanceledByTimeoutInternal, SrcState: []fsm.State{StateSigningAwaitConfirmations}, DstState: StateSigningConfirmationsAwaitCancelledByTimeout, IsInternal: true}, + + // Validate + {Name: eventAutoValidateProposalInternal, SrcState: []fsm.State{StateSigningAwaitConfirmations}, DstState: StateSigningAwaitConfirmations, IsInternal: true, IsAuto: true}, + + {Name: eventSetProposalValidatedInternal, SrcState: []fsm.State{StateSigningAwaitConfirmations}, DstState: StateSigningAwaitConfirmations, IsInternal: true, IsAuto: true}, + + // Canceled + {Name: EventSigningPartialKeyReceived, SrcState: []fsm.State{StateSigningAwaitPartialKeys}, DstState: StateSigningAwaitPartialKeys}, + {Name: EventSigningPartialKeyError, SrcState: []fsm.State{StateSigningAwaitPartialKeys}, DstState: StateSigningPartialKeysAwaitCancelledByParticipant}, + {Name: eventSigningPartialKeyCancelByTimeoutInternal, SrcState: []fsm.State{StateSigningAwaitPartialKeys}, DstState: StateSigningPartialKeysAwaitCancelledByTimeout, IsInternal: true}, + {Name: eventSigningPartialKeyCancelByErrorInternal, SrcState: []fsm.State{StateSigningAwaitPartialKeys}, DstState: StateSigningPartialKeysAwaitCancelledByParticipant, IsInternal: true, IsAuto: true}, + + {Name: eventSigningPartialKeysConfirmedInternal, SrcState: []fsm.State{StateSigningAwaitPartialKeys}, DstState: StateSigningPartialKeysCollected, IsInternal: true}, + + {Name: EventSigningFinish, SrcState: []fsm.State{StateSigningPartialKeysCollected}, DstState: StateSigningIdle, IsInternal: true}, + }, + fsm.Callbacks{ + EventSigningInit: machine.actionInitSigningProposal, + }, + ) + + return machine +} + +func (m *SigningProposalFSM) SetUpPayload(payload *internal.DumpedMachineStatePayload) { + m.payloadMu.Lock() + defer m.payloadMu.Unlock() + + m.payload = payload +} diff --git a/fsm/types/requests/signature_proposal.go b/fsm/types/requests/signature_proposal.go index 8de5b8d..2fdf2cc 100644 --- a/fsm/types/requests/signature_proposal.go +++ b/fsm/types/requests/signature_proposal.go @@ -7,8 +7,9 @@ import "time" // States: "__idle" // Events: "event_sig_proposal_init" type SignatureProposalParticipantsListRequest struct { - Participants []*SignatureProposalParticipantsEntry - CreatedAt time.Time + Participants []*SignatureProposalParticipantsEntry + SigningThreshold int + CreatedAt time.Time } type SignatureProposalParticipantsEntry struct { diff --git a/fsm/types/requests/signature_proposal_validation.go b/fsm/types/requests/signature_proposal_validation.go index accbb62..4281418 100644 --- a/fsm/types/requests/signature_proposal_validation.go +++ b/fsm/types/requests/signature_proposal_validation.go @@ -11,6 +11,14 @@ func (r *SignatureProposalParticipantsListRequest) Validate() error { return errors.New(fmt.Sprintf("too few participants, minimum is {%d}", config.ParticipantsMinCount)) } + if r.SigningThreshold < 2 { + return errors.New("{SigningThreshold} minimum count is {2}") + } + + if r.SigningThreshold > len(r.Participants) { + return errors.New("{SigningThreshold} cannot be higher than {ParticipantsCount}") + } + for _, participant := range r.Participants { if len(participant.Title) < 3 { return errors.New("{Title} minimum length is {3}") diff --git a/fsm/types/requests/signing_proposal.go b/fsm/types/requests/signing_proposal.go new file mode 100644 index 0000000..03bc67a --- /dev/null +++ b/fsm/types/requests/signing_proposal.go @@ -0,0 +1,27 @@ +package requests + +import "time" + +// States: "stage_signing_idle" +// Events: "event_signing_start" +type SigningProposalStartRequest struct { + ParticipantId int + SrcPayload []byte + CreatedAt time.Time +} + +// States: "state_signing_await_confirmations" +// Events: "event_signing_proposal_confirm_by_participant" +// "event_signing_proposal_decline_by_participant" +type SigningProposalParticipantRequest struct { + ParticipantId int + CreatedAt time.Time +} + +// States: "state_signing_await_partial_keys" +// Events: "event_signing_partial_key_received" +type SigningProposalPartialKeyRequest struct { + ParticipantId int + PartialKey []byte + CreatedAt time.Time +} diff --git a/fsm/types/requests/signing_proposal_validation.go b/fsm/types/requests/signing_proposal_validation.go new file mode 100644 index 0000000..cf973ec --- /dev/null +++ b/fsm/types/requests/signing_proposal_validation.go @@ -0,0 +1,47 @@ +package requests + +import "errors" + +func (r *SigningProposalStartRequest) Validate() error { + if r.ParticipantId < 0 { + return errors.New("{ParticipantId} cannot be a negative number") + } + + if len(r.SrcPayload) == 0 { + return errors.New("{SrcPayload} cannot zero length") + } + + if r.CreatedAt.IsZero() { + return errors.New("{CreatedAt} is not set") + } + + return nil +} + +func (r *SigningProposalParticipantRequest) Validate() error { + if r.ParticipantId < 0 { + return errors.New("{ParticipantId} cannot be a negative number") + } + + if r.CreatedAt.IsZero() { + return errors.New("{CreatedAt} is not set") + } + + return nil +} + +func (r *SigningProposalPartialKeyRequest) Validate() error { + if r.ParticipantId < 0 { + return errors.New("{ParticipantId} cannot be a negative number") + } + + if len(r.PartialKey) == 0 { + return errors.New("{PartialKey} cannot zero length") + } + + if r.CreatedAt.IsZero() { + return errors.New("{CreatedAt} is not set") + } + + return nil +} diff --git a/fsm/types/responses/signing_proposal.go b/fsm/types/responses/signing_proposal.go new file mode 100644 index 0000000..6652278 --- /dev/null +++ b/fsm/types/responses/signing_proposal.go @@ -0,0 +1 @@ +package responses