feat: fixes, tests, actions

This commit is contained in:
x88 2020-08-08 15:33:34 +03:00
parent 7e3121d500
commit 897c15672a
10 changed files with 264 additions and 57 deletions

View File

@ -2,19 +2,23 @@ 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{
Participants: []*requests.SignatureProposalParticipantsEntry{
{
"John Doe",
[]byte("pubkey123123"),
@ -28,8 +32,14 @@ func main() {
[]byte("pubkey789789"),
},
},
CreatedAt: &tm,
},
)
if err != nil {
log.Println("Err", err)
return
}
log.Println("Dump", string(dump))
processResponse(resp)

View File

@ -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{

View File

@ -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:

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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[:])
}

View File

@ -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,
},
)

View File

@ -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
}

View File

@ -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
}