From 897c15672a5aed26e995369c1501fa5be08c450e Mon Sep 17 00:00:00 2001 From: x88 Date: Sat, 8 Aug 2020 15:33:34 +0300 Subject: [PATCH] feat: fixes, tests, actions --- fsm/cmd/test/test.go | 36 +++-- fsm/state_machines/dkg_proposal_fsm/init.go | 4 +- fsm/state_machines/internal/types.go | 14 +- fsm/state_machines/provider.go | 27 +++- fsm/state_machines/provider_test.go | 143 ++++++++++++++++++ .../signature_proposal_fsm/actions.go | 23 +-- .../signature_proposal_fsm/helpers.go | 4 +- .../signature_proposal_fsm/init.go | 32 ++-- fsm/types/requests/signature_proposal.go | 13 +- .../requests/signature_proposal_validation.go | 25 ++- 10 files changed, 264 insertions(+), 57 deletions(-) create mode 100644 fsm/state_machines/provider_test.go diff --git a/fsm/cmd/test/test.go b/fsm/cmd/test/test.go index cc7c48b..2647b35 100644 --- a/fsm/cmd/test/test.go +++ b/fsm/cmd/test/test.go @@ -2,34 +2,44 @@ 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( - "event_proposal_init", + signature_proposal_fsm.EventInitProposal, requests.SignatureProposalParticipantsListRequest{ - { - "John Doe", - []byte("pubkey123123"), - }, - { - "Crypto Billy", - []byte("pubkey456456"), - }, - { - "Matt", - []byte("pubkey789789"), + Participants: []*requests.SignatureProposalParticipantsEntry{ + { + "John Doe", + []byte("pubkey123123"), + }, + { + "Crypto Billy", + []byte("pubkey456456"), + }, + { + "Matt", + []byte("pubkey789789"), + }, }, + CreatedAt: &tm, }, ) - log.Println("Err", err) + if err != nil { + log.Println("Err", err) + return + } + log.Println("Dump", string(dump)) processResponse(resp) diff --git a/fsm/state_machines/dkg_proposal_fsm/init.go b/fsm/state_machines/dkg_proposal_fsm/init.go index 282c6e9..422d62c 100644 --- a/fsm/state_machines/dkg_proposal_fsm/init.go +++ b/fsm/state_machines/dkg_proposal_fsm/init.go @@ -8,7 +8,7 @@ import ( ) const ( - fsmName = "dkg_proposal_fsm" + FsmName = "dkg_proposal_fsm" StateDkgInitial = signature_proposal_fsm.StateValidationCompleted @@ -82,7 +82,7 @@ func New() internal.DumpedMachineProvider { machine := &DKGProposalFSM{} machine.FSM = fsm.MustNewFSM( - fsmName, + FsmName, StateDkgInitial, []fsm.EventDesc{ diff --git a/fsm/state_machines/internal/types.go b/fsm/state_machines/internal/types.go index ca3ac58..9b05670 100644 --- a/fsm/state_machines/internal/types.go +++ b/fsm/state_machines/internal/types.go @@ -31,7 +31,11 @@ type SignatureProposalQuorum map[string]SignatureProposalParticipant type SignatureProposalParticipantStatus uint8 const ( - PubKeyConAwaitConfirmation DKGProposalParticipantStatus = iota + SignatureConfirmationAwaitConfirmation DKGProposalParticipantStatus = iota + SignatureConfirmationConfirmed + SignatureConfirmationDeclined + SignatureConfirmationError + PubKeyConAwaitConfirmation PubKeyConfirmed PubKeyConfirmationError CommitAwaitConfirmation @@ -68,6 +72,14 @@ type DKGProposalParticipantStatus uint8 func (s DKGProposalParticipantStatus) String() string { var str = "undefined" switch s { + case SignatureConfirmationAwaitConfirmation: + str = "SignatureConfirmationAwaitConfirmation" + case SignatureConfirmationConfirmed: + str = "SignatureConfirmationConfirmed" + case SignatureConfirmationDeclined: + str = "SignatureConfirmationDeclined" + case SignatureConfirmationError: + str = "SignatureConfirmationError" case PubKeyConAwaitConfirmation: str = "PubKeyConAwaitConfirmation" case PubKeyConfirmed: diff --git a/fsm/state_machines/provider.go b/fsm/state_machines/provider.go index b88572c..d0d82ea 100644 --- a/fsm/state_machines/provider.go +++ b/fsm/state_machines/provider.go @@ -36,11 +36,11 @@ func init() { } // Create new fsm with unique id -// Transaction id required for unique identify dump -func Create(tid string) (*FSMInstance, error) { +// transactionId required for unique identify dump +func Create(transactionId string) (*FSMInstance, error) { var err error i := &FSMInstance{} - err = i.InitDump(tid) + err = i.InitDump(transactionId) if err != nil { return nil, err @@ -56,9 +56,16 @@ func Create(tid string) (*FSMInstance, error) { func FromDump(data []byte) (*FSMInstance, error) { var err error - i := &FSMInstance{} + if len(data) < 2 { + return nil, errors.New("machine dump is empty") + } + + i := &FSMInstance{ + dump: &FSMDump{}, + } err = i.dump.Unmarshal(data) + // TODO: Add logger if err != nil { return nil, errors.New("cannot read machine dump") } @@ -87,21 +94,21 @@ func (i *FSMInstance) Do(event fsm.Event, args ...interface{}) (result *fsm.Resp return result, dump, err } -func (i *FSMInstance) InitDump(tid string) error { +func (i *FSMInstance) InitDump(transactionId string) error { if i.dump != nil { return errors.New("dump already initialized") } - tid = strings.TrimSpace(tid) + transactionId = strings.TrimSpace(transactionId) - if tid == "" { + if transactionId == "" { return errors.New("empty transaction id") } i.dump = &FSMDump{ State: fsm.StateGlobalIdle, Payload: &internal.DumpedMachineStatePayload{ - TransactionId: tid, + TransactionId: transactionId, ConfirmationProposalPayload: nil, DKGProposalPayload: nil, }, @@ -116,5 +123,9 @@ 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 json.Unmarshal(data, d) } diff --git a/fsm/state_machines/provider_test.go b/fsm/state_machines/provider_test.go new file mode 100644 index 0000000..cee2500 --- /dev/null +++ b/fsm/state_machines/provider_test.go @@ -0,0 +1,143 @@ +package state_machines + +import ( + 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" + "log" + "testing" + "time" +) + +const ( + testTransactionId = "d8a928b2043db77e340b523547bf16cb4aa483f0645fe0a290ed1f20aab76257" +) + +var ( + tm = time.Now() + testParticipantsListRequest = requests.SignatureProposalParticipantsListRequest{ + Participants: []*requests.SignatureProposalParticipantsEntry{ + { + "User 1", + []byte("pubkey123123"), + }, + { + "User 2", + []byte("pubkey456456"), + }, + { + "User 3", + []byte("pubkey789789"), + }, + }, + CreatedAt: &tm, + } +) + +func TestCreate_Positive(t *testing.T) { + testFSMInstance, err := Create(testTransactionId) + if err != nil { + t.Fatalf("expected nil error") + } + + if testFSMInstance == nil { + t.Fatalf("expected {*FSMInstance}") + } +} + +func TestCreate_Negative(t *testing.T) { + _, err := Create("") + if err == nil { + t.Fatalf("expected error for empty {transactionId}") + } +} + +func Test_Workflow(t *testing.T) { + testFSMInstance, err := Create(testTransactionId) + if err != nil { + t.Fatalf("expected nil error, got {%s}", err) + } + + if testFSMInstance == nil { + t.Fatalf("expected {*FSMInstance}") + } + + 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) + } + + fsmResponse, dump, err := testFSMInstance.Do(spf.EventInitProposal, testParticipantsListRequest) + + if err != nil { + t.Fatalf("expected nil error") + } + + if len(dump) == 0 { + t.Fatalf("expected non zero dump, when executed without error") + } + + if fsmResponse == nil { + t.Fatalf("expected {*fsm.FSMResponse}") + } + + if fsmResponse.State != spf.StateAwaitParticipantsConfirmations { + t.Fatalf("expected state {%s}", spf.StateAwaitParticipantsConfirmations) + } + + 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 {EncryptedInvitation}") + } + + if participant.PubKeyFingerprint == "" { + t.Fatalf("expected not empty {PubKeyFingerprint}") + } + + participantsMap[participant.ParticipantId] = participant + } + + tm = tm.Add(10 * time.Second) + + testFSMInstance, err = FromDump(dump) + + if err != nil { + t.Fatalf("expected nil error, got {%s}", err) + } + + if testFSMInstance == nil { + t.Fatalf("expected {*FSMInstance}") + } + + for _, participant := range participantsMap { + response, _, err := testFSMInstance.Do(spf.EventConfirmProposal, requests.SignatureProposalParticipantRequest{ + PubKeyFingerprint: participant.PubKeyFingerprint, + EncryptedInvitation: "lll", + CreatedAt: &tm, + }) + log.Println(response, err) + } +} diff --git a/fsm/state_machines/signature_proposal_fsm/actions.go b/fsm/state_machines/signature_proposal_fsm/actions.go index f852c91..78f816d 100644 --- a/fsm/state_machines/signature_proposal_fsm/actions.go +++ b/fsm/state_machines/signature_proposal_fsm/actions.go @@ -34,21 +34,28 @@ func (m *SignatureProposalFSM) actionInitProposal(event fsm.Event, args ...inter m.payload.ConfirmationProposalPayload = make(internal.SignatureProposalQuorum) - for participantIntId, participant := range request { + for index, participant := range request.Participants { participantId := createFingerprint(&participant.PublicKey) secret, err := generateRandomString(32) if err != nil { return nil, errors.New("cannot generateRandomString") } m.payload.ConfirmationProposalPayload[participantId] = internal.SignatureProposalParticipant{ - ParticipantId: participantIntId, + ParticipantId: index, Title: participant.Title, PublicKey: participant.PublicKey, InvitationSecret: secret, - UpdatedAt: nil, + Status: internal.SignatureAwaitConfirmation, + UpdatedAt: request.CreatedAt, } } + // Checking fo quorum length + if len(m.payload.ConfirmationProposalPayload) != len(request.Participants) { + err = errors.New("error with creating {SignatureProposalQuorum}") + return + } + // Make response responseData := make(responses.SignatureProposalParticipantInvitationsResponse, 0) @@ -59,6 +66,7 @@ func (m *SignatureProposalFSM) actionInitProposal(event fsm.Event, args ...inter return nil, errors.New("cannot encryptWithPubKey") } responseEntry := &responses.SignatureProposalParticipantInvitationEntry{ + ParticipantId: proposal.ParticipantId, Title: proposal.Title, PubKeyFingerprint: pubKeyFingerprint, EncryptedInvitation: encryptedInvitationSecret, @@ -72,13 +80,8 @@ func (m *SignatureProposalFSM) actionInitProposal(event fsm.Event, args ...inter } // -func (m *SignatureProposalFSM) actionConfirmProposalByParticipant(event fsm.Event, args ...interface{}) (response interface{}, err error) { - log.Println("I'm actionConfirmProposalByParticipant") - return -} - -func (m *SignatureProposalFSM) actionDeclineProposalByParticipant(event fsm.Event, args ...interface{}) (response interface{}, err error) { - log.Println("I'm actionDeclineProposalByParticipant") +func (m *SignatureProposalFSM) actionProposalResponseByParticipant(event fsm.Event, args ...interface{}) (response interface{}, err error) { + // SignatureProposalParticipantRequest return } diff --git a/fsm/state_machines/signature_proposal_fsm/helpers.go b/fsm/state_machines/signature_proposal_fsm/helpers.go index e3edc4b..daada4f 100644 --- a/fsm/state_machines/signature_proposal_fsm/helpers.go +++ b/fsm/state_machines/signature_proposal_fsm/helpers.go @@ -1,7 +1,7 @@ package signature_proposal_fsm import ( - "crypto/sha256" + "crypto/sha1" "encoding/base64" "math/rand" @@ -27,7 +27,7 @@ func ProposalParticipantsQuorumToResponse(list *internal.SignatureProposalQuorum // Common functions func createFingerprint(data *[]byte) string { - hash := sha256.Sum256(*data) + hash := sha1.Sum(*data) return base64.StdEncoding.EncodeToString(hash[:]) } diff --git a/fsm/state_machines/signature_proposal_fsm/init.go b/fsm/state_machines/signature_proposal_fsm/init.go index c13eaa6..740eafa 100644 --- a/fsm/state_machines/signature_proposal_fsm/init.go +++ b/fsm/state_machines/signature_proposal_fsm/init.go @@ -7,23 +7,25 @@ import ( ) const ( - fsmName = "signature_proposal_fsm" + FsmName = "signature_proposal_fsm" signingIdLen = 32 - StateAwaitParticipantsConfirmations = fsm.State("state_validation_await_participants_confirmations") // waiting participants + StateParticipantsConfirmationsInit = fsm.StateGlobalIdle - StateValidationCanceledByParticipant = fsm.State("state_validation_canceled_by_participant") - StateValidationCanceledByTimeout = fsm.State("state_validation_canceled_by_timeout") + StateAwaitParticipantsConfirmations = fsm.State("state_sig_proposal_await_participants_confirmations") // waiting participants - StateValidationCompleted = fsm.State("state_validation_completed") + StateValidationCanceledByParticipant = fsm.State("state_sig_proposal_canceled_by_participant") + StateValidationCanceledByTimeout = fsm.State("state_sig_proposal_canceled_by_timeout") - EventInitProposal = fsm.Event("event_proposal_init") - EventConfirmProposal = fsm.Event("event_proposal_confirm_by_participant") - EventDeclineProposal = fsm.Event("event_proposal_decline_by_participant") - EventValidateProposal = fsm.Event("event_proposal_validate") - EventSetProposalValidated = fsm.Event("event_proposal_set_validated") + StateValidationCompleted = fsm.State("state_sig_proposal_completed") - eventSetValidationCanceledByTimeout = fsm.Event("proposal_canceled_timeout") + 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") + EventValidateProposal = fsm.Event("event_sig_proposal_validate") + EventSetProposalValidated = fsm.Event("event_sig_proposal_set_validated") + + eventSetValidationCanceledByTimeout = fsm.Event("event_sig_proposal_canceled_timeout") // Switch to next fsm @@ -39,13 +41,13 @@ func New() internal.DumpedMachineProvider { machine := &SignatureProposalFSM{} machine.FSM = fsm.MustNewFSM( - fsmName, + FsmName, fsm.StateGlobalIdle, []fsm.EventDesc{ // {Name: "", SrcState: []string{""}, DstState: ""}, // Init - {Name: EventInitProposal, SrcState: []fsm.State{fsm.StateGlobalIdle}, DstState: StateAwaitParticipantsConfirmations}, + {Name: EventInitProposal, SrcState: []fsm.State{StateParticipantsConfirmationsInit}, DstState: StateAwaitParticipantsConfirmations}, // Validate by participants {Name: EventConfirmProposal, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateAwaitParticipantsConfirmations}, @@ -64,8 +66,8 @@ func New() internal.DumpedMachineProvider { }, fsm.Callbacks{ EventInitProposal: machine.actionInitProposal, - EventConfirmProposal: machine.actionConfirmProposalByParticipant, - EventDeclineProposal: machine.actionDeclineProposalByParticipant, + EventConfirmProposal: machine.actionProposalResponseByParticipant, + EventDeclineProposal: machine.actionProposalResponseByParticipant, EventValidateProposal: machine.actionValidateProposal, }, ) diff --git a/fsm/types/requests/signature_proposal.go b/fsm/types/requests/signature_proposal.go index b58dcb1..dca1bf2 100644 --- a/fsm/types/requests/signature_proposal.go +++ b/fsm/types/requests/signature_proposal.go @@ -1,8 +1,15 @@ package requests +import "time" + // Requests -type SignatureProposalParticipantsListRequest []SignatureProposalParticipantsEntry +// States: "__idle" +// Events: "event_sig_proposal_init" +type SignatureProposalParticipantsListRequest struct { + Participants []*SignatureProposalParticipantsEntry + CreatedAt *time.Time +} type SignatureProposalParticipantsEntry struct { // Public title for address, such as name, nickname, organization @@ -10,8 +17,12 @@ type SignatureProposalParticipantsEntry struct { PublicKey []byte } +// States: "__idle" +// Events: "event_sig_proposal_confirm_by_participant" +// "event_sig_proposal_decline_by_participant" type SignatureProposalParticipantRequest struct { // Key for link invitations to participants PubKeyFingerprint string EncryptedInvitation string + CreatedAt *time.Time } diff --git a/fsm/types/requests/signature_proposal_validation.go b/fsm/types/requests/signature_proposal_validation.go index c6d12e4..8cbe54a 100644 --- a/fsm/types/requests/signature_proposal_validation.go +++ b/fsm/types/requests/signature_proposal_validation.go @@ -6,27 +6,42 @@ import ( ) func (r *SignatureProposalParticipantsListRequest) Validate() error { - if len(*r) < config.ParticipantsMinCount { + if len(r.Participants) < config.ParticipantsMinCount { return errors.New("too few participants") } - for _, participant := range *r { + for _, participant := range r.Participants { if len(participant.Title) < 3 { - return errors.New("title too short") + return errors.New("{Title} too short") } if len(participant.Title) > 150 { - return errors.New("title too long") + return errors.New("{Title} too long") } if len(participant.PublicKey) < 10 { - return errors.New("pub key too short") + return errors.New("{PublicKey} too short") } } + if r.CreatedAt == nil { + return errors.New("{CreatedAt} cannot be a nil") + } + return nil } func (r *SignatureProposalParticipantRequest) Validate() error { + if len(r.PubKeyFingerprint) == 0 { + return errors.New("{PubKeyFingerprint} cannot zero length") + } + + if len(r.EncryptedInvitation) == 0 { + return errors.New("{EncryptedInvitation} cannot zero length") + } + + if r.CreatedAt == nil { + return errors.New("{CreatedAt} cannot be a nil") + } return nil }