Merge pull request #18 from depools/fsm-draft

Fsm draft
This commit is contained in:
Dmitriy 2020-08-10 18:50:15 +03:00 committed by GitHub
commit bd4c22eb4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1062 additions and 213 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

@ -21,6 +21,10 @@ import (
const (
StateGlobalIdle = State("__idle")
StateGlobalDone = State("__done")
EventRunDefault EventRunMode = iota
EventRunBefore
EventRunAfter
)
type State string
@ -35,6 +39,12 @@ func (e *Event) String() string {
return string(*e)
}
func (e *Event) IsEmpty() bool {
return e.String() == ""
}
type EventRunMode uint8
// Response returns result for processing with clientMocks events
type Response struct {
// Returns machine execution result state
@ -51,6 +61,8 @@ type FSM struct {
// May be mapping must require pair source + event?
transitions map[trKey]*trEvent
autoTransitions map[State]*trEvent
callbacks Callbacks
initialEvent Event
@ -76,7 +88,8 @@ type trEvent struct {
event Event
dstState State
isInternal bool
isDstInit bool
isAuto bool
runMode EventRunMode
}
type EventDesc struct {
@ -90,11 +103,13 @@ type EventDesc struct {
// Internal events, cannot be emitted from external call
IsInternal bool
// Set dst state before execute action
IsDstInit bool
// Event must run without manual call
IsAuto bool
AutoRunMode EventRunMode
}
type Callback func(event Event, args ...interface{}) (interface{}, error)
type Callback func(event Event, args ...interface{}) (Event, interface{}, error)
type Callbacks map[Event]Callback
@ -121,6 +136,7 @@ func MustNewFSM(machineName string, initialState State, events []EventDesc, call
currentState: initialState,
initialState: initialState,
transitions: make(map[trKey]*trEvent),
autoTransitions: make(map[State]*trEvent),
finStates: make(map[State]bool),
callbacks: make(map[Event]Callback),
}
@ -173,20 +189,41 @@ func MustNewFSM(machineName string, initialState State, events []EventDesc, call
panic("duplicate dst for pair `source + event`")
}
f.transitions[tKey] = &trEvent{
trEvent := &trEvent{
tKey.event,
event.DstState,
event.IsInternal,
event.IsDstInit,
event.IsAuto,
event.AutoRunMode,
}
if trEvent.isAuto && trEvent.runMode == EventRunDefault {
trEvent.runMode = EventRunAfter
}
f.transitions[tKey] = trEvent
// For using provider, event must use with IsGlobal = true
if sourceState == initialState {
if f.initialEvent != "" {
panic("machine entry event already exist")
}
if f.initialEvent == "" {
f.initialEvent = event.Name
}
}
if event.IsAuto {
if event.AutoRunMode != EventRunBefore && event.AutoRunMode != EventRunAfter {
panic("{AutoRunMode} not set for auto event")
}
if _, ok := f.autoTransitions[sourceState]; ok {
panic(fmt.Sprintf(
"auto event \"%s\" already exists for state \"%s\"",
event.Name,
sourceState,
))
}
f.autoTransitions[sourceState] = trEvent
}
allSources[sourceState] = true
trimmedSourcesCounter++
@ -252,33 +289,75 @@ func (f *FSM) Do(event Event, args ...interface{}) (resp *Response, err error) {
return f.do(trEvent, args...)
}
func (f *FSM) do(trEvent *trEvent, args ...interface{}) (resp *Response, err error) {
var outEvent Event
// f.eventMu.Lock()
// defer f.eventMu.Unlock()
if trEvent.isDstInit {
err = f.SetState(trEvent.event)
if err != nil {
resp = &Response{
// Process auto event
if autoEvent, ok := f.autoTransitions[f.State()]; ok {
autoEventResp := &Response{
State: f.State(),
}
return resp, err
if autoEvent.runMode == EventRunBefore {
if callback, ok := f.callbacks[autoEvent.event]; ok {
outEvent, autoEventResp.Data, err = callback(autoEvent.event, args...)
if err != nil {
return autoEventResp, err
}
}
if outEvent.IsEmpty() || autoEvent.event == outEvent {
err = f.SetState(autoEvent.event)
} else {
err = f.SetState(outEvent)
}
if err != nil {
return autoEventResp, err
}
}
outEvent = ""
}
resp = &Response{
State: f.State(),
}
if callback, ok := f.callbacks[trEvent.event]; ok {
resp.Data, err = callback(trEvent.event, args...)
outEvent, resp.Data, err = callback(trEvent.event, args...)
// Do not try change state on error
if err != nil {
return resp, err
}
}
if !trEvent.isDstInit {
// Set state when callback executed
if outEvent.IsEmpty() || trEvent.event == outEvent {
err = f.SetState(trEvent.event)
} else {
err = f.SetState(outEvent)
}
// Process auto event
if autoEvent, ok := f.autoTransitions[f.State()]; ok {
autoEventResp := &Response{
State: f.State(),
}
if autoEvent.runMode == EventRunAfter {
if callback, ok := f.callbacks[autoEvent.event]; ok {
outEvent, autoEventResp.Data, err = callback(autoEvent.event, args...)
if err != nil {
return autoEventResp, err
}
}
if outEvent.IsEmpty() || autoEvent.event == outEvent {
err = f.SetState(autoEvent.event)
} else {
err = f.SetState(outEvent)
}
if err != nil {
return autoEventResp, err
}
}
outEvent = ""
}
resp.State = f.State()

View File

@ -48,14 +48,14 @@ var (
}
testingCallbacks = Callbacks{
eventInit: func(event Event, args ...interface{}) (interface{}, error) {
return nil, nil
eventInit: func(event Event, args ...interface{}) (Event, interface{}, error) {
return event, nil, nil
},
eventInternalOut2: func(event Event, args ...interface{}) (interface{}, error) {
return nil, nil
eventInternalOut2: func(event Event, args ...interface{}) (Event, interface{}, error) {
return event, nil, nil
},
eventProcess: func(event Event, args ...interface{}) (interface{}, error) {
return nil, nil
eventProcess: func(event Event, args ...interface{}) (Event, interface{}, error) {
return event, nil, nil
},
}
)

View File

@ -3,7 +3,6 @@ package fsm_pool
import (
"errors"
"fmt"
"github.com/depools/dc4bc/fsm/fsm"
)
@ -23,6 +22,8 @@ type MachineProvider interface {
GlobalInitialEvent() fsm.Event
EntryEvent() fsm.Event
EventsList() []fsm.Event
StatesSourcesList() []fsm.State
@ -42,6 +43,7 @@ type FSMPool struct {
mapper FSMMapper
events FSMEventsMapper
states FSMStatesMapper
entryEvents map[fsm.State]fsm.Event
}
func Init(machines ...MachineProvider) *FSMPool {
@ -52,6 +54,7 @@ func Init(machines ...MachineProvider) *FSMPool {
mapper: make(FSMMapper),
events: make(FSMEventsMapper),
states: make(FSMStatesMapper),
entryEvents: make(map[fsm.State]fsm.Event),
}
allInitStatesMap := make(map[fsm.State]string)
@ -84,7 +87,7 @@ func Init(machines ...MachineProvider) *FSMPool {
}
// Setup entry event for machines pool if available
if initialEvent := machine.GlobalInitialEvent(); initialEvent != "" {
if initialEvent := machine.GlobalInitialEvent(); !initialEvent.IsEmpty() {
if p.fsmInitialEvent != "" {
panic("duplicate entry event initialization")
}
@ -92,6 +95,10 @@ func Init(machines ...MachineProvider) *FSMPool {
p.fsmInitialEvent = initialEvent
}
if machineEntryEvent := machine.EntryEvent(); !machineEntryEvent.IsEmpty() {
p.entryEvents[machine.InitialState()] = machineEntryEvent
}
p.mapper[machineName] = machine
}
@ -153,3 +160,17 @@ func (p *FSMPool) MachineByState(state fsm.State) (MachineProvider, error) {
}
return machine, nil
}
/*func (p *FSMPool) Do(machine MachineProvider, event fsm.Event, args ...interface{}) (resp *fsm.Response, err error) {
panic("llslsl")
resp, err = machine.Do(event, args...)
if err != nil {
return resp, err
}
if machine.IsFinState(resp.State) {
log.Println("Final!!!!")
}
return
}
*/

View File

@ -44,7 +44,7 @@ const (
var (
testing1Events = []fsm.EventDesc{
// Init
{Name: eventFSM1Init, SrcState: []fsm.State{fsm1StateInit}, DstState: fsm1StateStage1, IsDstInit: true},
{Name: eventFSM1Init, SrcState: []fsm.State{fsm1StateInit}, DstState: fsm1StateStage1, IsAuto: true, AutoRunMode: fsm.EventRunAfter},
{Name: eventFSM1Internal, SrcState: []fsm.State{fsm1StateStage1}, DstState: fsm1StateStage2, IsInternal: true},
// Cancellation events
@ -73,21 +73,24 @@ func NewFSM1() MachineProvider {
return machine
}
func (m *testMachineFSM1) actionFSM1SetUpData(event fsm.Event, args ...interface{}) (response interface{}, err error) {
func (m *testMachineFSM1) actionFSM1SetUpData(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
m.data = testVal1
return m.DoInternal(eventFSM1Internal)
outEvent = eventFSM1Internal
return
}
func (m *testMachineFSM1) actionFSM1ProcessData(event fsm.Event, args ...interface{}) (response interface{}, err error) {
func (m *testMachineFSM1) actionFSM1ProcessData(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
if len(args) == 1 {
if val, ok := args[0].(int); ok {
m.data -= val
}
}
return m.data, nil
response = m.data
return
}
func (m *testMachineFSM1) actionFSM1EmitOut2(event fsm.Event, args ...interface{}) (response interface{}, err error) {
func (m *testMachineFSM1) actionFSM1EmitOut2(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
return
}

View File

@ -1,54 +1,272 @@
package dkg_proposal_fsm
import (
"errors"
"fmt"
"github.com/depools/dc4bc/fsm/fsm"
"log"
"github.com/depools/dc4bc/fsm/state_machines/internal"
"github.com/depools/dc4bc/fsm/types/requests"
)
// Pub keys
func (m *DKGProposalFSM) actionDKGPubKeysSent(event fsm.Event, args ...interface{}) (response interface{}, err error) {
log.Println("I'm actionDKGPubKeysSent")
func (m *DKGProposalFSM) actionPubKeyPrepareConfirmations(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
return
}
func (m *DKGProposalFSM) actionDKGPubKeyConfirmationReceived(event fsm.Event, args ...interface{}) (response interface{}, err error) {
log.Println("I'm actionDKGPubKeyConfirmationReceived")
return
}
func (m *DKGProposalFSM) actionPubKeyConfirmationReceived(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 {DKGProposalPubKeyConfirmationRequest}")
return
}
request, ok := args[0].(requests.DKGProposalPubKeyConfirmationRequest)
if !ok {
err = errors.New("cannot cast {arg0} to type {DKGProposalPubKeyConfirmationRequest}")
return
}
if err = request.Validate(); err != nil {
return
}
dkgProposalParticipant, ok := m.payload.DKGProposalPayload[request.ParticipantId]
if !ok {
err = errors.New("{ParticipantId} not exist in quorum")
return
}
copy(dkgProposalParticipant.PublicKey, request.PubKey)
dkgProposalParticipant.UpdatedAt = request.CreatedAt
dkgProposalParticipant.Status = internal.PubKeyConfirmed
m.payload.DKGProposalPayload[request.ParticipantId] = dkgProposalParticipant
func (m *DKGProposalFSM) actionDKGPubKeyConfirmationError(event fsm.Event, args ...interface{}) (response interface{}, err error) {
log.Println("I'm actionDKGPubKeyConfirmationError")
return
}
// Commits
func (m *DKGProposalFSM) actionDKGCommitsSent(event fsm.Event, args ...interface{}) (response interface{}, err error) {
log.Println("I'm actionDKGCommitsSent")
return
}
func (m *DKGProposalFSM) actionDKGCommitConfirmationReceived(event fsm.Event, args ...interface{}) (response interface{}, err error) {
log.Println("I'm actionDKGCommitConfirmationReceived")
return
}
func (m *DKGProposalFSM) actionCommitConfirmationReceived(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 {DKGProposalCommitConfirmationRequest}")
return
}
request, ok := args[0].(requests.DKGProposalCommitConfirmationRequest)
if !ok {
err = errors.New("cannot cast {arg0} to type {DKGProposalCommitConfirmationRequest}")
return
}
if err = request.Validate(); err != nil {
return
}
dkgProposalParticipant, ok := m.payload.DKGProposalPayload[request.ParticipantId]
if !ok {
err = errors.New("{ParticipantId} not exist in quorum")
return
}
copy(dkgProposalParticipant.Commit, request.Commit)
dkgProposalParticipant.UpdatedAt = request.CreatedAt
dkgProposalParticipant.Status = internal.CommitConfirmed
m.payload.DKGProposalPayload[request.ParticipantId] = dkgProposalParticipant
func (m *DKGProposalFSM) actionDKGCommitConfirmationError(event fsm.Event, args ...interface{}) (response interface{}, err error) {
log.Println("I'm actionDKGCommitConfirmationError")
return
}
// Deals
func (m *DKGProposalFSM) actionDKGDealsSent(event fsm.Event, args ...interface{}) (response interface{}, err error) {
log.Println("I'm actionDKGDealsSent")
func (m *DKGProposalFSM) actionDealConfirmationReceived(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 {DKGProposalDealConfirmationRequest}")
return
}
request, ok := args[0].(requests.DKGProposalDealConfirmationRequest)
if !ok {
err = errors.New("cannot cast {arg0} to type {DKGProposalDealConfirmationRequest}")
return
}
if err = request.Validate(); err != nil {
return
}
dkgProposalParticipant, ok := m.payload.DKGProposalPayload[request.ParticipantId]
if !ok {
err = errors.New("{ParticipantId} not exist in quorum")
return
}
copy(dkgProposalParticipant.Deal, request.Deal)
dkgProposalParticipant.UpdatedAt = request.CreatedAt
dkgProposalParticipant.Status = internal.DealConfirmed
m.payload.DKGProposalPayload[request.ParticipantId] = dkgProposalParticipant
return
}
func (m *DKGProposalFSM) actionDKGDealConfirmationReceived(event fsm.Event, args ...interface{}) (response interface{}, err error) {
log.Println("I'm actionDKGDealConfirmationReceived")
// Responses
func (m *DKGProposalFSM) actionResponseConfirmationReceived(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 {DKGProposalResponseConfirmationRequest}")
return
}
request, ok := args[0].(requests.DKGProposalResponseConfirmationRequest)
if !ok {
err = errors.New("cannot cast {arg0} to type {DKGProposalResponseConfirmationRequest}")
return
}
if err = request.Validate(); err != nil {
return
}
dkgProposalParticipant, ok := m.payload.DKGProposalPayload[request.ParticipantId]
if !ok {
err = errors.New("{ParticipantId} not exist in quorum")
return
}
copy(dkgProposalParticipant.Response, request.Response)
dkgProposalParticipant.UpdatedAt = request.CreatedAt
dkgProposalParticipant.Status = internal.ResponseConfirmed
m.payload.DKGProposalPayload[request.ParticipantId] = dkgProposalParticipant
return
}
func (m *DKGProposalFSM) actionDKGDealConfirmationError(event fsm.Event, args ...interface{}) (response interface{}, err error) {
log.Println("I'm actionDKGDealConfirmationError")
// Errors
func (m *DKGProposalFSM) actionConfirmationError(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 {DKGProposalConfirmationErrorRequest}")
return
}
request, ok := args[0].(requests.DKGProposalConfirmationErrorRequest)
if !ok {
err = errors.New("cannot cast {arg0} to type {DKGProposalConfirmationErrorRequest}")
return
}
if err = request.Validate(); err != nil {
return
}
dkgProposalParticipant, ok := m.payload.DKGProposalPayload[request.ParticipantId]
if !ok {
err = errors.New("{ParticipantId} not exist in quorum")
return
}
// TODO: Move to methods
switch inEvent {
case EventDKGPubKeyConfirmationError:
switch dkgProposalParticipant.Status {
case internal.PubKeyConAwaitConfirmation:
dkgProposalParticipant.Status = internal.PubKeyConfirmationError
case internal.PubKeyConfirmed:
err = errors.New("{Status} already confirmed")
case internal.PubKeyConfirmationError:
err = errors.New(fmt.Sprintf("{Status} already has {\"%s\"}", internal.PubKeyConfirmationError))
default:
err = errors.New(fmt.Sprintf(
"{Status} now is \"%s\" and cannot set to {\"%s\"}",
dkgProposalParticipant.Status,
internal.PubKeyConfirmationError,
))
}
case EventDKGCommitConfirmationError:
switch dkgProposalParticipant.Status {
case internal.CommitAwaitConfirmation:
dkgProposalParticipant.Status = internal.CommitConfirmationError
case internal.CommitConfirmed:
err = errors.New("{Status} already confirmed")
case internal.CommitConfirmationError:
err = errors.New(fmt.Sprintf("{Status} already has {\"%s\"}", internal.CommitConfirmationError))
default:
err = errors.New(fmt.Sprintf(
"{Status} now is \"%s\" and cannot set to {\"%s\"}",
dkgProposalParticipant.Status,
internal.PubKeyConfirmationError,
))
}
case EventDKGDealConfirmationError:
switch dkgProposalParticipant.Status {
case internal.DealAwaitConfirmation:
dkgProposalParticipant.Status = internal.PubKeyConfirmationError
case internal.DealConfirmed:
err = errors.New("{Status} already confirmed")
case internal.DealConfirmationError:
err = errors.New(fmt.Sprintf("{Status} already has {\"%s\"}", internal.DealConfirmationError))
default:
err = errors.New(fmt.Sprintf(
"{Status} now is \"%s\" and cannot set to {\"%s\"}",
dkgProposalParticipant.Status,
internal.PubKeyConfirmationError,
))
}
case EventDKGResponseConfirmationError:
switch dkgProposalParticipant.Status {
case internal.ResponseAwaitConfirmation:
dkgProposalParticipant.Status = internal.PubKeyConfirmationError
case internal.ResponseConfirmed:
err = errors.New("{Status} already confirmed")
case internal.ResponseConfirmationError:
err = errors.New(fmt.Sprintf("{Status} already has {\"%s\"}", internal.ResponseConfirmationError))
default:
err = errors.New(fmt.Sprintf(
"{Status} now is \"%s\" and cannot set to {\"%s\"}",
dkgProposalParticipant.Status,
internal.PubKeyConfirmationError,
))
}
default:
err = errors.New(fmt.Sprintf("{%s} event cannot be used for action {actionConfirmationError}", inEvent))
}
if err != nil {
return
}
dkgProposalParticipant.UpdatedAt = request.CreatedAt
m.payload.DKGProposalPayload[request.ParticipantId] = dkgProposalParticipant
// TODO: Add outEvent
return
}

View File

@ -0,0 +1 @@
package dkg_proposal_fsm

View File

@ -3,46 +3,47 @@ package dkg_proposal_fsm
import (
"github.com/depools/dc4bc/fsm/fsm"
"github.com/depools/dc4bc/fsm/state_machines/internal"
"github.com/depools/dc4bc/fsm/state_machines/signature_proposal_fsm"
"sync"
)
const (
fsmName = "dkg_proposal_fsm"
FsmName = "dkg_proposal_fsm"
StateDkgInitial = signature_proposal_fsm.StateValidationCompleted
// Sending dkg pub keys
StateDkgPubKeysSendingRequired = fsm.State("state_dkg_pub_keys_sending_required")
StateDkgInitial = StateDkgPubKeysAwaitConfirmations
StateDkgPubKeysSendingAwaitConfirmations = fsm.State("state_dkg_pub_keys_sending_await_confirmations")
StateDkgPubKeysAwaitConfirmations = fsm.State("state_dkg_pub_keys_await_confirmations")
// Cancelled
StateDkgPubKeysSendingCancelled = fsm.State("state_dkg_pub_keys_sending_cancelled")
StateDkgPubKeysSendingCancelledByTimeout = fsm.State("state_dkg_pub_keys_sending_cancelled_by_timeout")
StateDkgPubKeysAwaitCancelled = fsm.State("state_dkg_pub_keys_await_cancelled")
StateDkgPubKeysAwaitCancelledByTimeout = fsm.State("state_dkg_pub_keys_await_cancelled_by_timeout")
// Confirmed
StateDkgPubKeysSendingConfirmed = fsm.State("state_dkg_pub_keys_sending_confirmed")
StateDkgPubKeysAwaitConfirmed = fsm.State("state_dkg_pub_keys_await_confirmed")
// Sending dkg commits
StateDkgCommitsSendingRequired = fsm.State("state_dkg_commits_sending_required")
StateDkgCommitsSendingAwaitConfirmations = fsm.State("state_dkg_commits_sending_await_confirmations")
StateDkgCommitsAwaitConfirmations = fsm.State("state_dkg_commits_sending_await_confirmations")
// Cancelled
StateDkgCommitsSendingCancelled = fsm.State("state_dkg_commits_sending_cancelled")
StateDkgCommitsSendingCancelledByTimeout = fsm.State("state_dkg_commits_sending_cancelled_by_timeout")
StateDkgCommitsAwaitCancelled = fsm.State("state_dkg_commits_await_cancelled")
StateDkgCommitsAwaitCancelledByTimeout = fsm.State("state_dkg_commits_await_cancelled_by_timeout")
// Confirmed
StateDkgCommitsSendingConfirmed = fsm.State("state_dkg_commits_sending_confirmed")
StateDkgCommitsAwaitConfirmed = fsm.State("state_dkg_commits_await_confirmed")
// Sending dkg deals
StateDkgDealsSendingRequired = fsm.State("state_dkg_deals_sending_required")
StateDkgDealsSendingAwaitConfirmations = fsm.State("state_dkg_deals_sending_await_confirmations")
StateDkgDealsAwaitConfirmations = fsm.State("state_dkg_deals_await_confirmations")
// Cancelled
StateDkgDealsSendingCancelled = fsm.State("state_dkg_deals_sending_cancelled")
StateDkgDealsSendingCancelledByTimeout = fsm.State("state_dkg_deals_sending_cancelled_by_timeout")
StateDkgDealsAwaitCancelled = fsm.State("state_dkg_deals_await_cancelled")
StateDkgDealsAwaitCancelledByTimeout = fsm.State("state_dkg_deals_sending_cancelled_by_timeout")
// Confirmed
StateDkgDealsSendingConfirmed = fsm.State("state_dkg_deals_sending_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")
// Confirmed
StateDkgResponsesAwaitConfirmed = fsm.State("state_dkg_responses_await_confirmed")
// Events
EventDKGPubKeysSendingRequiredInternal = fsm.Event("event_dkg_pub_key_sending_required_internal")
eventDKGPubKeysSendingRequiredAuto = fsm.Event("event_dkg_pub_key_sending_required_internal")
EventDKGPubKeysSent = fsm.Event("event_dkg_pub_keys_sent")
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")
@ -50,7 +51,6 @@ const (
EventDKGCommitsSendingRequiredInternal = fsm.Event("event_dkg_commits_sending_required_internal")
EventDKGCommitsSent = fsm.Event("event_dkg_commits_sent")
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")
@ -58,11 +58,16 @@ const (
EventDKGDealsSendingRequiredInternal = fsm.Event("event_dkg_deals_sending_required_internal")
EventDKGDealsSent = fsm.Event("event_dkg_deals_sent")
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")
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")
EventDKGMasterKeyRequiredInternal = fsm.Event("event_dkg_master_key_required_internal")
)
@ -76,60 +81,66 @@ func New() internal.DumpedMachineProvider {
machine := &DKGProposalFSM{}
machine.FSM = fsm.MustNewFSM(
fsmName,
FsmName,
StateDkgInitial,
[]fsm.EventDesc{
// Init
// Switch to pub keys required
{Name: EventDKGPubKeysSendingRequiredInternal, SrcState: []fsm.State{StateDkgInitial}, DstState: StateDkgPubKeysSendingRequired, IsInternal: true},
// {Name: eventDKGPubKeysSendingRequiredAuto, SrcState: []fsm.State{StateDkgInitial}, DstState: StateDkgPubKeysAwaitConfirmations, IsInternal: true, IsAuto: true, AutoRunMode: fsm.EventRunAfter},
// Pub keys sending
{Name: EventDKGPubKeysSent, SrcState: []fsm.State{StateDkgPubKeysSendingRequired}, DstState: StateDkgPubKeysSendingAwaitConfirmations},
{Name: EventDKGPubKeyConfirmationReceived, SrcState: []fsm.State{StateDkgPubKeysSendingAwaitConfirmations}, DstState: StateDkgPubKeysSendingAwaitConfirmations},
{Name: EventDKGPubKeyConfirmationReceived, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitConfirmations},
// Cancelled
{Name: EventDKGPubKeyConfirmationError, SrcState: []fsm.State{StateDkgPubKeysSendingAwaitConfirmations}, DstState: StateDkgPubKeysSendingCancelled},
{Name: EventDKGPubKeysConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgPubKeysSendingAwaitConfirmations}, DstState: StateDkgPubKeysSendingCancelledByTimeout, IsInternal: true},
{Name: EventDKGPubKeyConfirmationError, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitCancelled},
{Name: EventDKGPubKeysConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitCancelledByTimeout, IsInternal: true},
// Confirmed
{Name: EventDKGPubKeysConfirmedInternal, SrcState: []fsm.State{StateDkgPubKeysSendingAwaitConfirmations}, DstState: StateDkgPubKeysSendingConfirmed, IsInternal: true},
{Name: EventDKGPubKeysConfirmedInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitConfirmed, IsInternal: true},
// Switch to commits required
{Name: EventDKGCommitsSendingRequiredInternal, SrcState: []fsm.State{StateDkgPubKeysSendingConfirmed}, DstState: StateDkgCommitsSendingRequired, IsInternal: true},
{Name: EventDKGCommitsSendingRequiredInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmed}, DstState: StateDkgCommitsAwaitConfirmations, IsInternal: true},
// Commits
{Name: EventDKGCommitsSent, SrcState: []fsm.State{StateDkgCommitsSendingRequired}, DstState: StateDkgCommitsSendingAwaitConfirmations},
{Name: EventDKGCommitConfirmationReceived, SrcState: []fsm.State{StateDkgCommitsSendingAwaitConfirmations}, DstState: StateDkgCommitsSendingAwaitConfirmations},
{Name: EventDKGCommitConfirmationReceived, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitConfirmations},
// Cancelled
{Name: EventDKGCommitConfirmationError, SrcState: []fsm.State{StateDkgCommitsSendingAwaitConfirmations}, DstState: StateDkgCommitsSendingCancelled},
{Name: EventDKGCommitsConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgCommitsSendingAwaitConfirmations}, DstState: StateDkgCommitsSendingCancelledByTimeout, IsInternal: true},
{Name: EventDKGCommitConfirmationError, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitCancelled},
{Name: EventDKGCommitsConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitCancelledByTimeout, IsInternal: true},
// Confirmed
{Name: EventDKGCommitsConfirmedInternal, SrcState: []fsm.State{StateDkgCommitsSendingAwaitConfirmations}, DstState: StateDkgCommitsSendingConfirmed, IsInternal: true},
{Name: EventDKGCommitsConfirmedInternal, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitConfirmed, IsInternal: true},
// Switch to deals required
{Name: EventDKGDealsSendingRequiredInternal, SrcState: []fsm.State{StateDkgDealsSendingConfirmed}, DstState: StateDkgDealsSendingRequired, IsInternal: true},
{Name: EventDKGDealsSendingRequiredInternal, SrcState: []fsm.State{StateDkgDealsAwaitConfirmed}, DstState: StateDkgDealsAwaitConfirmations, IsInternal: true},
// Deals
{Name: EventDKGDealsSent, SrcState: []fsm.State{StateDkgDealsSendingRequired}, DstState: StateDkgDealsSendingAwaitConfirmations},
{Name: EventDKGDealConfirmationReceived, SrcState: []fsm.State{StateDkgDealsSendingAwaitConfirmations}, DstState: StateDkgDealsSendingAwaitConfirmations},
{Name: EventDKGDealConfirmationReceived, SrcState: []fsm.State{StateDkgDealsAwaitConfirmations}, DstState: StateDkgDealsAwaitConfirmations},
// Cancelled
{Name: EventDKGDealConfirmationError, SrcState: []fsm.State{StateDkgCommitsSendingAwaitConfirmations}, DstState: StateDkgDealsSendingCancelled},
{Name: EventDKGDealsConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgCommitsSendingAwaitConfirmations}, DstState: StateDkgDealsSendingCancelledByTimeout, IsInternal: true},
{Name: EventDKGDealConfirmationError, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgDealsAwaitCancelled},
{Name: EventDKGDealsConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgDealsAwaitCancelledByTimeout, IsInternal: true},
// Switch to responses required
{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},
// Done
{Name: EventDKGMasterKeyRequiredInternal, SrcState: []fsm.State{StateDkgCommitsSendingAwaitConfirmations}, DstState: fsm.StateGlobalDone, IsInternal: true},
{Name: EventDKGMasterKeyRequiredInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: fsm.StateGlobalDone, IsInternal: true},
},
fsm.Callbacks{
EventDKGPubKeysSent: machine.actionDKGPubKeysSent,
EventDKGPubKeyConfirmationReceived: machine.actionDKGPubKeyConfirmationReceived,
EventDKGPubKeyConfirmationError: machine.actionDKGPubKeyConfirmationError,
EventDKGPubKeyConfirmationReceived: machine.actionPubKeyConfirmationReceived,
EventDKGPubKeyConfirmationError: machine.actionConfirmationError,
EventDKGCommitsSent: machine.actionDKGCommitsSent,
EventDKGCommitConfirmationReceived: machine.actionDKGCommitConfirmationReceived,
EventDKGCommitConfirmationError: machine.actionDKGCommitConfirmationError,
EventDKGCommitConfirmationReceived: machine.actionCommitConfirmationReceived,
EventDKGCommitConfirmationError: machine.actionConfirmationError,
EventDKGDealsSent: machine.actionDKGDealsSent,
EventDKGDealConfirmationReceived: machine.actionDKGDealConfirmationReceived,
EventDKGDealConfirmationError: machine.actionDKGDealConfirmationError,
EventDKGDealConfirmationReceived: machine.actionDealConfirmationReceived,
EventDKGDealConfirmationError: machine.actionConfirmationError,
EventDKGResponseConfirmationReceived: machine.actionResponseConfirmationReceived,
EventDKGResponseConfirmationError: machine.actionConfirmationError,
},
)
return machine

View File

@ -1,6 +1,9 @@
package internal
import "time"
import (
"crypto/rsa"
"time"
)
const (
SignatureAwaitConfirmation SignatureProposalParticipantStatus = iota
@ -17,7 +20,7 @@ type SignatureProposalParticipant struct {
// Public title for address, such as name, nickname, organization
ParticipantId int
Title string
PublicKey []byte
PublicKey *rsa.PublicKey
// For validation user confirmation: sign(InvitationSecret, PublicKey) => user
InvitationSecret string
Status SignatureProposalParticipantStatus
@ -31,12 +34,22 @@ type SignatureProposalQuorum map[string]SignatureProposalParticipant
type SignatureProposalParticipantStatus uint8
const (
PubKeyConAwaitConfirmation DKGProposalParticipantStatus = iota
SignatureConfirmationAwaitConfirmation DKGProposalParticipantStatus = iota
SignatureConfirmationConfirmed
SignatureConfirmationDeclined
SignatureConfirmationError
PubKeyConAwaitConfirmation
PubKeyConfirmed
PubKeyConfirmationError
CommitAwaitConfirmation
CommitConfirmed
CommitConfirmationError
DealAwaitConfirmation
DealConfirmed
DealConfirmationError
ResponseAwaitConfirmation
ResponseConfirmed
ResponseConfirmationError
)
type DKGProposal struct {
@ -50,6 +63,7 @@ type DKGProposalParticipant struct {
PublicKey []byte
Commit []byte
Deal []byte
Response []byte
Status DKGProposalParticipantStatus
UpdatedAt *time.Time
}
@ -57,3 +71,42 @@ type DKGProposalParticipant struct {
type DKGProposalQuorum map[int]DKGProposalParticipant
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:
str = "PubKeyConfirmed"
case PubKeyConfirmationError:
str = "PubKeyConfirmationError"
case CommitAwaitConfirmation:
str = "CommitAwaitConfirmation"
case CommitConfirmed:
str = "CommitConfirmed"
case CommitConfirmationError:
str = "CommitConfirmationError"
case DealAwaitConfirmation:
str = "DealAwaitConfirmation"
case DealConfirmed:
str = "DealConfirmed"
case DealConfirmationError:
str = "DealConfirmationError"
case ResponseAwaitConfirmation:
str = "ResponseAwaitConfirmation"
case ResponseConfirmed:
str = "ResponseConfirmed"
case ResponseConfirmationError:
str = "ResponseConfirmationError"
}
return str
}

View File

@ -35,11 +35,12 @@ func init() {
)
}
// Transaction id required for unique identify dump
func Create(tid string) (*FSMInstance, error) {
// Create new fsm with unique id
// 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
@ -51,12 +52,20 @@ func Create(tid string) (*FSMInstance, error) {
return i, err
}
// Get fsm from dump
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")
}
@ -85,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,
},
@ -114,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,233 @@
package state_machines
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"fmt"
"github.com/depools/dc4bc/fsm/fsm"
dpf "github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm"
spf "github.com/depools/dc4bc/fsm/state_machines/signature_proposal_fsm"
"github.com/depools/dc4bc/fsm/types/requests"
"github.com/depools/dc4bc/fsm/types/responses"
"log"
"testing"
"time"
)
const (
testTransactionId = "d8a928b2043db77e340b523547bf16cb4aa483f0645fe0a290ed1f20aab76257"
)
type testExternalParticipants struct {
Title string
PrivKey *rsa.PrivateKey
PubKey *rsa.PublicKey
}
var (
tm = time.Now()
testParticipants = map[string]*testExternalParticipants{}
testParticipantsListRequest = requests.SignatureProposalParticipantsListRequest{
Participants: []*requests.SignatureProposalParticipantsEntry{},
CreatedAt: &tm,
}
)
func init() {
r := rand.Reader
for i := 0; i < 3; i++ {
key, err := rsa.GenerateKey(r, 2048)
if err != nil {
log.Fatal("Cannot generate key for user:", err)
return
}
key.Precompute()
marshaledPubKey := x509.MarshalPKCS1PublicKey(&key.PublicKey)
hash := sha1.Sum(marshaledPubKey)
fingerprint := base64.StdEncoding.EncodeToString(hash[:])
participant := &testExternalParticipants{
Title: fmt.Sprintf("User %d", i),
PrivKey: key,
PubKey: &key.PublicKey,
}
testParticipants[fingerprint] = participant
}
participantsForRequest := make([]*requests.SignatureProposalParticipantsEntry, 0)
for _, participant := range testParticipants {
participantsForRequest = append(participantsForRequest, &requests.SignatureProposalParticipantsEntry{
Title: participant.Title,
PubKey: x509.MarshalPKCS1PublicKey(participant.PubKey),
})
}
testParticipantsListRequest.Participants = participantsForRequest
}
func TestCreate_Positive(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}")
}
}
func TestCreate_Negative(t *testing.T) {
_, err := Create("")
if err == nil {
t.Fatalf("expected error for empty {transactionId}")
}
}
func compareErrNil(t *testing.T, got error) {
if got != nil {
t.Fatalf("expected nil error, got {%s}", got)
}
}
func compareFSMInstanceNotNil(t *testing.T, got *FSMInstance) {
if got == nil {
t.Fatalf("expected {*FSMInstance}")
}
}
func compareDumpNotZero(t *testing.T, got []byte) {
if len(got) == 0 {
t.Fatalf("expected non zero dump, when executed without error")
}
}
func compareFSMResponseNotNil(t *testing.T, got *fsm.Response) {
if got == nil {
t.Fatalf("expected {*fsm.FSMResponse} got nil")
}
}
func compareState(t *testing.T, expected fsm.State, got fsm.State) {
if got != expected {
t.Fatalf("expected state {%s} got {%s}", expected, got)
}
}
func Test_Workflow(t *testing.T) {
testFSMInstance, err := Create(testTransactionId)
compareErrNil(t, err)
compareFSMInstanceNotNil(t, testFSMInstance)
if testFSMInstance.machine.Name() != spf.FsmName {
t.Fatalf("expected machine name {%s}", spf.FsmName)
}
compareState(t, spf.StateParticipantsConfirmationsInit, testFSMInstance.machine.State())
fsmResponse, dump, err := testFSMInstance.Do(spf.EventInitProposal, testParticipantsListRequest)
compareErrNil(t, err)
compareDumpNotZero(t, dump)
compareFSMResponseNotNil(t, fsmResponse)
compareState(t, spf.StateAwaitParticipantsConfirmations, fsmResponse.State)
testParticipantsListResponse, ok := fsmResponse.Data.(responses.SignatureProposalParticipantInvitationsResponse)
if !ok {
t.Fatalf("expected response {SignatureProposalParticipantInvitationsResponse}")
}
if len(testParticipantsListResponse) != len(testParticipantsListRequest.Participants) {
t.Fatalf("expected response len {%d}, got {%d}", len(testParticipantsListRequest.Participants), len(testParticipantsListResponse))
}
participantsMap := map[int]*responses.SignatureProposalParticipantInvitationEntry{}
for _, participant := range testParticipantsListResponse {
if _, ok := participantsMap[participant.ParticipantId]; ok {
t.Fatalf("expected unique {ParticipantId}")
}
if participant.Title == "" {
t.Fatalf("expected not empty {Title}")
}
if participant.EncryptedInvitation == "" {
t.Fatalf("expected not empty {DecryptedInvitation}")
}
if participant.PubKeyFingerprint == "" {
t.Fatalf("expected not empty {PubKeyFingerprint}")
}
participantsMap[participant.ParticipantId] = participant
}
tm = tm.Add(10 * time.Second)
participantsCount := len(participantsMap)
participantCounter := participantsCount
for _, participant := range participantsMap {
participantCounter--
testFSMInstance, err = FromDump(dump)
compareErrNil(t, err)
compareFSMInstanceNotNil(t, testFSMInstance)
if _, ok := testParticipants[participant.PubKeyFingerprint]; !ok {
t.Fatalf("not found external user data for response fingerprint")
}
r := rand.Reader
encrypted, err := rsa.DecryptPKCS1v15(r, testParticipants[participant.PubKeyFingerprint].PrivKey, []byte(participant.EncryptedInvitation))
if err != nil {
t.Fatalf("cannot encrypt {DecryptedInvitation} with private key")
}
fsmResponse, dump, err = testFSMInstance.Do(spf.EventConfirmProposal, requests.SignatureProposalParticipantRequest{
PubKeyFingerprint: participant.PubKeyFingerprint,
DecryptedInvitation: string(encrypted),
CreatedAt: &tm,
})
compareErrNil(t, err)
compareDumpNotZero(t, dump)
compareFSMResponseNotNil(t, fsmResponse)
if participantCounter > 0 {
if fsmResponse.State != spf.StateAwaitParticipantsConfirmations {
t.Fatalf("expected state {%s} got {%s}", spf.StateAwaitParticipantsConfirmations, fsmResponse.State)
}
} else {
if fsmResponse.State != dpf.StateDkgInitial {
t.Fatalf("expected state {%s} got {%s}", dpf.StateDkgInitial, fsmResponse.State)
}
}
log.Println(fsmResponse.State, err)
}
}

View File

@ -1,9 +1,8 @@
package signature_proposal_fsm
import (
"crypto/x509"
"errors"
"log"
"github.com/depools/dc4bc/fsm/fsm"
"github.com/depools/dc4bc/fsm/state_machines/internal"
"github.com/depools/dc4bc/fsm/types/requests"
@ -12,19 +11,19 @@ import (
// init -> awaitingConfirmations
// args: payload, signing id, participants list
func (m *SignatureProposalFSM) actionInitProposal(event fsm.Event, args ...interface{}) (response interface{}, err error) {
func (m *SignatureProposalFSM) actionInitProposal(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("participants list required")
err = errors.New("{arg0} required {SignatureProposalParticipantsListRequest}")
return
}
request, ok := args[0].(requests.SignatureProposalParticipantsListRequest)
if !ok {
err = errors.New("cannot cast participants list")
err = errors.New("cannot cast {arg0} to type {SignatureProposalParticipantsListRequest}")
return
}
@ -34,21 +33,35 @@ func (m *SignatureProposalFSM) actionInitProposal(event fsm.Event, args ...inter
m.payload.ConfirmationProposalPayload = make(internal.SignatureProposalQuorum)
for participantIntId, participant := range request {
participantId := createFingerprint(&participant.PublicKey)
for index, participant := range request.Participants {
participantId := createFingerprint(&participant.PubKey)
secret, err := generateRandomString(32)
if err != nil {
return nil, errors.New("cannot generateRandomString")
return inEvent, nil, errors.New("cannot generate source for {InvitationSecret}")
}
parsedPubKey, err := x509.ParsePKCS1PublicKey(participant.PubKey)
if err != nil {
return inEvent, nil, errors.New("cannot parse {PubKey}")
}
m.payload.ConfirmationProposalPayload[participantId] = internal.SignatureProposalParticipant{
ParticipantId: participantIntId,
ParticipantId: index,
Title: participant.Title,
PublicKey: participant.PublicKey,
PublicKey: parsedPubKey,
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)
@ -56,9 +69,10 @@ func (m *SignatureProposalFSM) actionInitProposal(event fsm.Event, args ...inter
for pubKeyFingerprint, proposal := range m.payload.ConfirmationProposalPayload {
encryptedInvitationSecret, err := encryptWithPubKey(proposal.PublicKey, proposal.InvitationSecret)
if err != nil {
return nil, errors.New("cannot encryptWithPubKey")
return inEvent, nil, errors.New("cannot encryptWithPubKey")
}
responseEntry := &responses.SignatureProposalParticipantInvitationEntry{
ParticipantId: proposal.ParticipantId,
Title: proposal.Title,
PubKeyFingerprint: pubKeyFingerprint,
EncryptedInvitation: encryptedInvitationSecret,
@ -67,22 +81,66 @@ func (m *SignatureProposalFSM) actionInitProposal(event fsm.Event, args ...inter
}
// Change state
return responseData, nil
return inEvent, responseData, nil
}
//
func (m *SignatureProposalFSM) actionConfirmProposalByParticipant(event fsm.Event, args ...interface{}) (response interface{}, err error) {
log.Println("I'm actionConfirmProposalByParticipant")
// 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()
if len(args) != 1 {
err = errors.New("{arg0} required {SignatureProposalParticipantRequest}")
return
}
request, ok := args[0].(requests.SignatureProposalParticipantRequest)
if !ok {
err = errors.New("cannot cast {arg0} to type {SignatureProposalParticipantRequest}")
return
}
if err = request.Validate(); err != nil {
return
}
signatureProposalParticipant, ok := m.payload.ConfirmationProposalPayload[request.PubKeyFingerprint]
if !ok {
err = errors.New("{PubKeyFingerprint} not exist in quorum")
return
}
if signatureProposalParticipant.InvitationSecret != request.DecryptedInvitation {
err = errors.New("{InvitationSecret} not match {DecryptedInvitation}")
return
}
signatureProposalParticipant.Status = internal.SignatureConfirmed
signatureProposalParticipant.UpdatedAt = request.CreatedAt
m.payload.ConfirmationProposalPayload[request.PubKeyFingerprint] = signatureProposalParticipant
outEvent, response, err = m.actionValidateProposal(eventValidateProposalInternal)
return
}
func (m *SignatureProposalFSM) actionDeclineProposalByParticipant(event fsm.Event, args ...interface{}) (response interface{}, err error) {
log.Println("I'm actionDeclineProposalByParticipant")
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) actionValidateProposal(event fsm.Event, args ...interface{}) (response interface{}, err error) {
log.Println("I'm actionValidateProposal")
unconfirmedParticipants := len(m.payload.ConfirmationProposalPayload)
for _, participant := range m.payload.ConfirmationProposalPayload {
if participant.Status == internal.SignatureConfirmed {
unconfirmedParticipants--
}
}
if unconfirmedParticipants > 0 {
return
}
outEvent = eventSetProposalValidatedInternal
return
}

View File

@ -1,10 +1,10 @@
package signature_proposal_fsm
import (
"crypto/sha256"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"encoding/base64"
"math/rand"
"github.com/depools/dc4bc/fsm/state_machines/internal"
"github.com/depools/dc4bc/fsm/types/responses"
)
@ -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[:])
}
@ -58,6 +58,13 @@ func generateRandomString(s int) (string, error) {
return base64.URLEncoding.EncodeToString(b), err
}
func encryptWithPubKey(key []byte, value string) (string, error) {
return value, nil
func encryptWithPubKey(pubKey *rsa.PublicKey, value string) (string, error) {
r := rand.Reader
encryptedData, err := rsa.EncryptPKCS1v15(r, pubKey, []byte(value))
if err != nil {
return "", err
}
return string(encryptedData), nil
}

View File

@ -2,28 +2,32 @@ package signature_proposal_fsm
import (
"github.com/depools/dc4bc/fsm/fsm"
"github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm"
"github.com/depools/dc4bc/fsm/state_machines/internal"
"sync"
)
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")
// Out state
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")
eventValidateProposalInternal = fsm.Event("event_sig_proposal_validate")
eventSetProposalValidatedInternal = fsm.Event("event_sig_proposal_set_validated")
eventSetValidationCanceledByTimeout = fsm.Event("event_sig_proposal_canceled_timeout")
// Switch to next fsm
@ -39,13 +43,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},
@ -53,20 +57,20 @@ func New() internal.DumpedMachineProvider {
// Now set for external emitting.
{Name: EventDeclineProposal, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateValidationCanceledByParticipant},
{Name: EventValidateProposal, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateAwaitParticipantsConfirmations},
{Name: eventValidateProposalInternal, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateAwaitParticipantsConfirmations, IsInternal: true},
// eventProposalValidate internal or from client?
// yay
// Exit point
{Name: EventSetProposalValidated, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: fsm.State("state_dkg_pub_keys_sending_required"), IsInternal: true},
{Name: eventSetProposalValidatedInternal, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: dkg_proposal_fsm.StateDkgPubKeysAwaitConfirmations, IsInternal: true},
// nan
{Name: eventSetValidationCanceledByTimeout, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateValidationCanceledByTimeout, IsInternal: true},
},
fsm.Callbacks{
EventInitProposal: machine.actionInitProposal,
EventConfirmProposal: machine.actionConfirmProposalByParticipant,
EventDeclineProposal: machine.actionDeclineProposalByParticipant,
EventValidateProposal: machine.actionValidateProposal,
EventConfirmProposal: machine.actionProposalResponseByParticipant,
EventDeclineProposal: machine.actionProposalResponseByParticipant,
eventValidateProposalInternal: machine.actionValidateProposal,
},
)
return machine

View File

@ -1,16 +1,49 @@
package requests
// Event: "event_dkg_pub_keys_sent"
type DKGProposalPubKeysSendingRequest struct {
PubKeys map[int][]byte
import "time"
// States: "state_dkg_pub_keys_await_confirmations"
// Events: "event_dkg_pub_key_confirm_received"
type DKGProposalPubKeyConfirmationRequest struct {
ParticipantId int
PubKey []byte
CreatedAt *time.Time
}
// Event: "event_dkg_commits_sent"
type DKGProposalCommitsSendingRequest struct {
Commits map[int][]byte
// States: "state_dkg_commits_sending_await_confirmations"
// Events: "event_dkg_commit_confirm_received"
type DKGProposalCommitConfirmationRequest struct {
ParticipantId int
Commit []byte
CreatedAt *time.Time
}
// Event: "event_dkg_deals_sent"
type DKGProposalDealsSendingRequest struct {
Deals map[int][]byte
// States: "state_dkg_deals_await_confirmations"
// Events: "event_dkg_deal_confirm_received"
type DKGProposalDealConfirmationRequest struct {
ParticipantId int
Deal []byte
CreatedAt *time.Time
}
// States: "state_dkg_responses_await_confirmations"
// Events: "event_dkg_response_confirm_received"
type DKGProposalResponseConfirmationRequest struct {
ParticipantId int
Response []byte
CreatedAt *time.Time
}
// States: "state_dkg_pub_keys_await_confirmations"
// "state_dkg_commits_sending_await_confirmations"
// "state_dkg_deals_await_confirmations"
// "state_dkg_responses_await_confirmations"
//
// Events: "event_dkg_pub_key_confirm_canceled_by_error",
// "event_dkg_commit_confirm_canceled_by_error"
// "event_dkg_deal_confirm_canceled_by_error"
// "event_dkg_response_confirm_canceled_by_error"
type DKGProposalConfirmationErrorRequest struct {
ParticipantId int
CreatedAt *time.Time
}

View File

@ -1 +1,79 @@
package requests
import "errors"
func (r *DKGProposalPubKeyConfirmationRequest) Validate() error {
if r.ParticipantId < 0 {
return errors.New("{ParticipantId} cannot be a negative number")
}
if len(r.PubKey) == 0 {
return errors.New("{PubKey} cannot zero length")
}
if r.CreatedAt == nil {
return errors.New("{CreatedAt} cannot be a nil")
}
return nil
}
func (r *DKGProposalCommitConfirmationRequest) Validate() error {
if r.ParticipantId < 0 {
return errors.New("{ParticipantId} cannot be a negative number")
}
if len(r.Commit) == 0 {
return errors.New("{Commit} cannot zero length")
}
if r.CreatedAt == nil {
return errors.New("{CreatedAt} cannot be a nil")
}
return nil
}
func (r *DKGProposalDealConfirmationRequest) Validate() error {
if r.ParticipantId < 0 {
return errors.New("{ParticipantId} cannot be a negative number")
}
if len(r.Deal) == 0 {
return errors.New("{Deal} cannot zero length")
}
if r.CreatedAt == nil {
return errors.New("{CreatedAt} cannot be a nil")
}
return nil
}
func (r *DKGProposalResponseConfirmationRequest) Validate() error {
if r.ParticipantId < 0 {
return errors.New("{ParticipantId} cannot be a negative number")
}
if len(r.Response) == 0 {
return errors.New("{Response} cannot zero length")
}
if r.CreatedAt == nil {
return errors.New("{CreatedAt} cannot be a nil")
}
return nil
}
func (r *DKGProposalConfirmationErrorRequest) Validate() error {
if r.ParticipantId < 0 {
return errors.New("{ParticipantId} cannot be a negative number")
}
if r.CreatedAt == nil {
return errors.New("{CreatedAt} cannot be a nil")
}
return nil
}

View File

@ -1,17 +1,28 @@
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
Title string
PublicKey []byte
PubKey []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
DecryptedInvitation string
CreatedAt *time.Time
}

View File

@ -2,31 +2,47 @@ package requests
import (
"errors"
"fmt"
"github.com/depools/dc4bc/fsm/config"
)
func (r *SignatureProposalParticipantsListRequest) Validate() error {
if len(*r) < config.ParticipantsMinCount {
return errors.New("too few participants")
if len(r.Participants) < config.ParticipantsMinCount {
return errors.New(fmt.Sprintf("too few participants, minimum is {%d}", config.ParticipantsMinCount))
}
for _, participant := range *r {
for _, participant := range r.Participants {
if len(participant.Title) < 3 {
return errors.New("title too short")
return errors.New("{Title} minimum length is {3}")
}
if len(participant.Title) > 150 {
return errors.New("title too long")
return errors.New("{Title} maximum length is {150}")
}
if len(participant.PublicKey) < 10 {
return errors.New("pub key too short")
if len(participant.PubKey) < 10 {
return errors.New("{PubKey} 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.DecryptedInvitation) == 0 {
return errors.New("{DecryptedInvitation} cannot zero length")
}
if r.CreatedAt == nil {
return errors.New("{CreatedAt} cannot be a nil")
}
return nil
}

View File

@ -1,6 +1,6 @@
package responses
// Responses
// Response
const (
ProposalConfirmationStatusIdle = iota