feat: fixed flow, added tests

This commit is contained in:
x88 2020-08-11 19:46:18 +03:00
parent 1e8af819bb
commit 10c343ac90
13 changed files with 701 additions and 215 deletions

View File

@ -1,82 +0,0 @@
package main
import (
"github.com/depools/dc4bc/fsm/fsm"
"github.com/depools/dc4bc/fsm/state_machines/signature_proposal_fsm"
"github.com/depools/dc4bc/fsm/types/responses"
"log"
"time"
"github.com/depools/dc4bc/fsm/state_machines"
"github.com/depools/dc4bc/fsm/types/requests"
)
func main() {
tm := time.Now()
fsmMachine, err := state_machines.Create("d8a928b2043db77e340b523547bf16cb4aa483f0645fe0a290ed1f20aab76257")
log.Println(fsmMachine, err)
resp, dump, err := fsmMachine.Do(
signature_proposal_fsm.EventInitProposal,
requests.SignatureProposalParticipantsListRequest{
Participants: []*requests.SignatureProposalParticipantsEntry{
{
"John Doe",
[]byte("pubkey123123"),
},
{
"Crypto Billy",
[]byte("pubkey456456"),
},
{
"Matt",
[]byte("pubkey789789"),
},
},
CreatedAt: &tm,
},
)
if err != nil {
log.Println("Err", err)
return
}
log.Println("Dump", string(dump))
processResponse(resp)
}
func processResponse(resp *fsm.Response) {
switch resp.State {
// Await proposals
case fsm.State("state_validation_await_participants_confirmations"):
data, ok := resp.Data.(responses.SignatureProposalParticipantInvitationsResponse)
if !ok {
log.Printf("undefined response type for state \"%s\"\n", resp.State)
return
}
sendInvitations(data)
case fsm.State("validation_canceled_by_participant"):
updateDashboardWithCanceled("Participant")
case fsm.State("validation_canceled_by_timeout"):
updateDashboardWithCanceled("Timeout")
default:
log.Printf("undefined response type for state \"%s\"\n", resp.State)
}
}
func sendInvitations(invitations responses.SignatureProposalParticipantInvitationsResponse) {
for _, invitation := range invitations {
log.Printf(
"Dear %s, please encrypt value \"%s\" with your key, fingerprint: %s\n",
invitation.Title,
invitation.EncryptedInvitation,
invitation.PubKeyFingerprint,
)
}
}
func updateDashboardWithCanceled(msg string) {
log.Printf("Breaking news! Proposal canceled with reason: %s\n", msg)
}

View File

@ -1,6 +1,10 @@
package config package config
import "time"
const ( const (
// TODO: Move to machine level configs? // TODO: Move to machine level configs?
ParticipantsMinCount = 3 ParticipantsMinCount = 3
SignatureProposalConfirmationDeadline = time.Hour * 24
DkgConfirmationDeadline = time.Hour * 24
) )

View File

@ -21,7 +21,9 @@ import (
const ( const (
StateGlobalIdle = State("__idle") StateGlobalIdle = State("__idle")
StateGlobalDone = State("__done") StateGlobalDone = State("__done")
)
const (
EventRunDefault EventRunMode = iota EventRunDefault EventRunMode = iota
EventRunBefore EventRunBefore
EventRunAfter EventRunAfter
@ -189,6 +191,10 @@ func MustNewFSM(machineName string, initialState State, events []EventDesc, call
panic("duplicate dst for pair `source + event`") panic("duplicate dst for pair `source + event`")
} }
if event.IsAuto && event.AutoRunMode == EventRunDefault {
event.AutoRunMode = EventRunAfter
}
trEvent := &trEvent{ trEvent := &trEvent{
tKey.event, tKey.event,
event.DstState, event.DstState,
@ -197,10 +203,6 @@ func MustNewFSM(machineName string, initialState State, events []EventDesc, call
event.AutoRunMode, event.AutoRunMode,
} }
if trEvent.isAuto && trEvent.runMode == EventRunDefault {
trEvent.runMode = EventRunAfter
}
f.transitions[tKey] = trEvent f.transitions[tKey] = trEvent
// For using provider, event must use with IsGlobal = true // For using provider, event must use with IsGlobal = true
@ -245,7 +247,7 @@ func MustNewFSM(machineName string, initialState State, events []EventDesc, call
} }
if _, ok := allEvents[event]; !ok { if _, ok := allEvents[event]; !ok {
panic("callback has no event") panic("callback has empty event")
} }
f.callbacks[event] = callback f.callbacks[event] = callback

View File

@ -12,9 +12,9 @@ const (
stateStage1 = State("state_stage1") stateStage1 = State("state_stage1")
// Process data // Process data
stateStage2 = State("state_stage2") stateStage2 = State("state_stage2")
// Cancelled with internal event // Canceled with internal event
stateCanceledByInternal = State("state_canceled") stateCanceledByInternal = State("state_canceled")
// Cancelled with external event // Canceled with external event
stateCanceled2 = State("state_canceled2") stateCanceled2 = State("state_canceled2")
// Out endpoint to switch // Out endpoint to switch
stateOutToFSM2 = State("state_out_to_fsm2") stateOutToFSM2 = State("state_out_to_fsm2")

View File

@ -23,9 +23,9 @@ const (
fsm1StateStage1 = fsm.State("state_fsm1_stage1") fsm1StateStage1 = fsm.State("state_fsm1_stage1")
// Process data // Process data
fsm1StateStage2 = fsm.State("state_fsm1_stage2") fsm1StateStage2 = fsm.State("state_fsm1_stage2")
// Cancelled with internal event // Canceled with internal event
fsm1StateCanceledByInternal = fsm.State("state_fsm1_canceled") fsm1StateCanceledByInternal = fsm.State("state_fsm1_canceled")
// Cancelled with external event // Canceled with external event
fsm1StateCanceled2 = fsm.State("state_fsm1_canceled2") fsm1StateCanceled2 = fsm.State("state_fsm1_canceled2")
// Out endpoint to switch // Out endpoint to switch
fsm1StateOutToFSM2 = fsm.State("state_fsm1_out_to_fsm2") fsm1StateOutToFSM2 = fsm.State("state_fsm1_out_to_fsm2")
@ -108,7 +108,7 @@ const (
// Process data // Process data
fsm2StateStage1 = fsm.State("state_fsm2_stage1") fsm2StateStage1 = fsm.State("state_fsm2_stage1")
fsm2StateStage2 = fsm.State("state_fsm2_stage2") fsm2StateStage2 = fsm.State("state_fsm2_stage2")
// Cancelled with internal event // Canceled with internal event
fsm2StateCanceledByInternal = fsm.State("state_fsm2_canceled") fsm2StateCanceledByInternal = fsm.State("state_fsm2_canceled")
// Out endpoint to switch // Out endpoint to switch
fsm2StateOutToFSM3 = fsm.State("state_fsm2_out_to_fsm3") fsm2StateOutToFSM3 = fsm.State("state_fsm2_out_to_fsm3")

View File

@ -3,17 +3,15 @@ package dkg_proposal_fsm
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/depools/dc4bc/fsm/config"
"github.com/depools/dc4bc/fsm/fsm" "github.com/depools/dc4bc/fsm/fsm"
"github.com/depools/dc4bc/fsm/state_machines/internal" "github.com/depools/dc4bc/fsm/state_machines/internal"
"github.com/depools/dc4bc/fsm/types/requests" "github.com/depools/dc4bc/fsm/types/requests"
"time"
) )
// Pub keys // Pub keys
func (m *DKGProposalFSM) actionPubKeyPrepareConfirmations(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
return
}
func (m *DKGProposalFSM) actionPubKeyConfirmationReceived(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { func (m *DKGProposalFSM) actionPubKeyConfirmationReceived(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
m.payloadMu.Lock() m.payloadMu.Lock()
defer m.payloadMu.Unlock() defer m.payloadMu.Unlock()
@ -41,7 +39,12 @@ func (m *DKGProposalFSM) actionPubKeyConfirmationReceived(inEvent fsm.Event, arg
return return
} }
copy(dkgProposalParticipant.PublicKey, request.PubKey) if dkgProposalParticipant.Status != internal.PubKeyAwaitConfirmation {
err = errors.New(fmt.Sprintf("cannot confirm pubkey with {Status} = {\"%s\"}", dkgProposalParticipant.Status))
return
}
copy(dkgProposalParticipant.PubKey, request.PubKey)
dkgProposalParticipant.UpdatedAt = request.CreatedAt dkgProposalParticipant.UpdatedAt = request.CreatedAt
dkgProposalParticipant.Status = internal.PubKeyConfirmed dkgProposalParticipant.Status = internal.PubKeyConfirmed
@ -50,6 +53,55 @@ func (m *DKGProposalFSM) actionPubKeyConfirmationReceived(inEvent fsm.Event, arg
return return
} }
func (m *DKGProposalFSM) actionValidateDkgProposalPubKeys(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
var (
isContainsError, isContainsExpired bool
)
m.payloadMu.Lock()
defer m.payloadMu.Unlock()
tm := time.Now()
unconfirmedParticipants := len(m.payload.DKGProposalPayload)
for _, participant := range m.payload.DKGProposalPayload {
if participant.Status == internal.PubKeyAwaitConfirmation {
if participant.UpdatedAt.Add(config.DkgConfirmationDeadline).Before(tm) {
isContainsExpired = true
}
} else {
if participant.Status == internal.PubKeyConfirmationError {
isContainsError = true
} else if participant.Status == internal.PubKeyConfirmed {
unconfirmedParticipants--
}
}
}
if isContainsError {
outEvent = eventDKGSetPubKeysConfirmationCanceledByErrorInternal
return
}
if isContainsExpired {
outEvent = eventDKGSetPubKeysConfirmationCanceledByTimeoutInternal
return
}
// The are no declined and timed out participants, check for all confirmations
if unconfirmedParticipants > 0 {
return
}
outEvent = eventDKGSetPubKeysConfirmedInternal
for _, participant := range m.payload.DKGProposalPayload {
participant.Status = internal.CommitAwaitConfirmation
}
return
}
// Commits // Commits
func (m *DKGProposalFSM) actionCommitConfirmationReceived(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { func (m *DKGProposalFSM) actionCommitConfirmationReceived(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
@ -79,6 +131,11 @@ func (m *DKGProposalFSM) actionCommitConfirmationReceived(inEvent fsm.Event, arg
return return
} }
if dkgProposalParticipant.Status != internal.CommitAwaitConfirmation {
err = errors.New(fmt.Sprintf("cannot confirm commit with {Status} = {\"%s\"}", dkgProposalParticipant.Status))
return
}
copy(dkgProposalParticipant.Commit, request.Commit) copy(dkgProposalParticipant.Commit, request.Commit)
dkgProposalParticipant.UpdatedAt = request.CreatedAt dkgProposalParticipant.UpdatedAt = request.CreatedAt
dkgProposalParticipant.Status = internal.CommitConfirmed dkgProposalParticipant.Status = internal.CommitConfirmed
@ -88,6 +145,55 @@ func (m *DKGProposalFSM) actionCommitConfirmationReceived(inEvent fsm.Event, arg
return return
} }
func (m *DKGProposalFSM) actionValidateDkgProposalAwaitCommits(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
var (
isContainsError, isContainsExpired bool
)
m.payloadMu.Lock()
defer m.payloadMu.Unlock()
tm := time.Now()
unconfirmedParticipants := len(m.payload.DKGProposalPayload)
for _, participant := range m.payload.DKGProposalPayload {
if participant.Status == internal.CommitAwaitConfirmation {
if participant.UpdatedAt.Add(config.SignatureProposalConfirmationDeadline).Before(tm) {
isContainsExpired = true
}
} else {
if participant.Status == internal.CommitConfirmationError {
isContainsError = true
} else if participant.Status == internal.CommitConfirmed {
unconfirmedParticipants--
}
}
}
if isContainsError {
outEvent = eventDKGCommitsConfirmationCancelByTimeoutInternal
return
}
if isContainsExpired {
outEvent = eventDKGCommitsConfirmationCancelByErrorInternal
return
}
// The are no declined and timed out participants, check for all confirmations
if unconfirmedParticipants > 0 {
return
}
outEvent = eventDKGCommitsConfirmedInternal
for _, participant := range m.payload.DKGProposalPayload {
participant.Status = internal.DealAwaitConfirmation
}
return
}
// Deals // Deals
func (m *DKGProposalFSM) actionDealConfirmationReceived(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { func (m *DKGProposalFSM) actionDealConfirmationReceived(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
@ -117,6 +223,11 @@ func (m *DKGProposalFSM) actionDealConfirmationReceived(inEvent fsm.Event, args
return return
} }
if dkgProposalParticipant.Status != internal.DealAwaitConfirmation {
err = errors.New(fmt.Sprintf("cannot confirm deal with {Status} = {\"%s\"}", dkgProposalParticipant.Status))
return
}
copy(dkgProposalParticipant.Deal, request.Deal) copy(dkgProposalParticipant.Deal, request.Deal)
dkgProposalParticipant.UpdatedAt = request.CreatedAt dkgProposalParticipant.UpdatedAt = request.CreatedAt
dkgProposalParticipant.Status = internal.DealConfirmed dkgProposalParticipant.Status = internal.DealConfirmed
@ -126,6 +237,55 @@ func (m *DKGProposalFSM) actionDealConfirmationReceived(inEvent fsm.Event, args
return return
} }
func (m *DKGProposalFSM) actionValidateDkgProposalAwaitDeals(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
var (
isContainsError, isContainsExpired bool
)
m.payloadMu.Lock()
defer m.payloadMu.Unlock()
tm := time.Now()
unconfirmedParticipants := len(m.payload.DKGProposalPayload)
for _, participant := range m.payload.DKGProposalPayload {
if participant.Status == internal.DealAwaitConfirmation {
if participant.UpdatedAt.Add(config.SignatureProposalConfirmationDeadline).Before(tm) {
isContainsExpired = true
}
} else {
if participant.Status == internal.DealConfirmationError {
isContainsError = true
} else if participant.Status == internal.DealConfirmed {
unconfirmedParticipants--
}
}
}
if isContainsError {
outEvent = eventDKGDealsConfirmationCancelByErrorInternal
return
}
if isContainsExpired {
outEvent = eventDKGDealsConfirmationCancelByTimeoutInternal
return
}
// The are no declined and timed out participants, check for all confirmations
if unconfirmedParticipants > 0 {
return
}
outEvent = eventDKGDealsConfirmedInternal
for _, participant := range m.payload.DKGProposalPayload {
participant.Status = internal.ResponseAwaitConfirmation
}
return
}
// Responses // Responses
func (m *DKGProposalFSM) actionResponseConfirmationReceived(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { func (m *DKGProposalFSM) actionResponseConfirmationReceived(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
@ -155,6 +315,11 @@ func (m *DKGProposalFSM) actionResponseConfirmationReceived(inEvent fsm.Event, a
return return
} }
if dkgProposalParticipant.Status != internal.ResponseAwaitConfirmation {
err = errors.New(fmt.Sprintf("cannot confirm response with {Status} = {\"%s\"}", dkgProposalParticipant.Status))
return
}
copy(dkgProposalParticipant.Response, request.Response) copy(dkgProposalParticipant.Response, request.Response)
dkgProposalParticipant.UpdatedAt = request.CreatedAt dkgProposalParticipant.UpdatedAt = request.CreatedAt
dkgProposalParticipant.Status = internal.ResponseConfirmed dkgProposalParticipant.Status = internal.ResponseConfirmed
@ -164,6 +329,51 @@ func (m *DKGProposalFSM) actionResponseConfirmationReceived(inEvent fsm.Event, a
return return
} }
func (m *DKGProposalFSM) actionValidateDkgProposalAwaitResponses(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
var (
isContainsError, isContainsExpired bool
)
m.payloadMu.Lock()
defer m.payloadMu.Unlock()
tm := time.Now()
unconfirmedParticipants := len(m.payload.DKGProposalPayload)
for _, participant := range m.payload.DKGProposalPayload {
if participant.Status == internal.ResponseAwaitConfirmation {
if participant.UpdatedAt.Add(config.SignatureProposalConfirmationDeadline).Before(tm) {
isContainsExpired = true
}
} else {
if participant.Status == internal.ResponseConfirmationError {
isContainsError = true
} else if participant.Status == internal.ResponseConfirmed {
unconfirmedParticipants--
}
}
}
if isContainsError {
outEvent = eventDKGResponseConfirmationCancelByErrorInternal
return
}
if isContainsExpired {
outEvent = eventDKGResponseConfirmationCancelByTimeoutInternal
return
}
// The are no declined and timed out participants, check for all confirmations
if unconfirmedParticipants > 0 {
return
}
outEvent = eventDKGResponsesConfirmedInternal
return
}
// Errors // Errors
func (m *DKGProposalFSM) actionConfirmationError(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { func (m *DKGProposalFSM) actionConfirmationError(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
m.payloadMu.Lock() m.payloadMu.Lock()
@ -196,7 +406,7 @@ func (m *DKGProposalFSM) actionConfirmationError(inEvent fsm.Event, args ...inte
switch inEvent { switch inEvent {
case EventDKGPubKeyConfirmationError: case EventDKGPubKeyConfirmationError:
switch dkgProposalParticipant.Status { switch dkgProposalParticipant.Status {
case internal.PubKeyConAwaitConfirmation: case internal.PubKeyAwaitConfirmation:
dkgProposalParticipant.Status = internal.PubKeyConfirmationError dkgProposalParticipant.Status = internal.PubKeyConfirmationError
case internal.PubKeyConfirmed: case internal.PubKeyConfirmed:
err = errors.New("{Status} already confirmed") err = errors.New("{Status} already confirmed")

View File

@ -12,61 +12,67 @@ const (
StateDkgInitial = StateDkgPubKeysAwaitConfirmations StateDkgInitial = StateDkgPubKeysAwaitConfirmations
StateDkgPubKeysAwaitConfirmations = fsm.State("state_dkg_pub_keys_await_confirmations") StateDkgPubKeysAwaitConfirmations = fsm.State("state_dkg_pub_keys_await_confirmations")
// Cancelled // Canceled
StateDkgPubKeysAwaitCancelled = fsm.State("state_dkg_pub_keys_await_cancelled") StateDkgPubKeysAwaitCanceled = fsm.State("state_dkg_pub_keys_await_canceled")
StateDkgPubKeysAwaitCancelledByTimeout = fsm.State("state_dkg_pub_keys_await_cancelled_by_timeout") StateDkgPubKeysAwaitCanceledByTimeout = fsm.State("state_dkg_pub_keys_await_canceled_by_timeout")
// Confirmed // Confirmed
StateDkgPubKeysAwaitConfirmed = fsm.State("state_dkg_pub_keys_await_confirmed") // StateDkgPubKeysAwaitConfirmed = fsm.State("state_dkg_pub_keys_await_confirmed")
// Sending dkg commits // Sending dkg commits
StateDkgCommitsAwaitConfirmations = fsm.State("state_dkg_commits_sending_await_confirmations") StateDkgCommitsAwaitConfirmations = fsm.State("state_dkg_commits_await_confirmations")
// Cancelled // Canceled
StateDkgCommitsAwaitCancelled = fsm.State("state_dkg_commits_await_cancelled") StateDkgCommitsAwaitCanceled = fsm.State("state_dkg_commits_await_canceled")
StateDkgCommitsAwaitCancelledByTimeout = fsm.State("state_dkg_commits_await_cancelled_by_timeout") StateDkgCommitsAwaitCanceledByTimeout = fsm.State("state_dkg_commits_await_canceled_by_timeout")
// Confirmed // Confirmed
StateDkgCommitsAwaitConfirmed = fsm.State("state_dkg_commits_await_confirmed") StateDkgCommitsAwaitConfirmed = fsm.State("state_dkg_commits_await_confirmed")
// Sending dkg deals // Sending dkg deals
StateDkgDealsAwaitConfirmations = fsm.State("state_dkg_deals_await_confirmations") StateDkgDealsAwaitConfirmations = fsm.State("state_dkg_deals_await_confirmations")
// Cancelled // Canceled
StateDkgDealsAwaitCancelled = fsm.State("state_dkg_deals_await_cancelled") StateDkgDealsAwaitCanceled = fsm.State("state_dkg_deals_await_canceled")
StateDkgDealsAwaitCancelledByTimeout = fsm.State("state_dkg_deals_sending_cancelled_by_timeout") StateDkgDealsAwaitCanceledByTimeout = fsm.State("state_dkg_deals_sending_canceled_by_timeout")
// Confirmed // Confirmed
StateDkgDealsAwaitConfirmed = fsm.State("state_dkg_deals_await_confirmed") //StateDkgDealsAwaitConfirmed = fsm.State("state_dkg_deals_await_confirmed")
StateDkgResponsesAwaitConfirmations = fsm.State("state_dkg_responses_await_confirmations") StateDkgResponsesAwaitConfirmations = fsm.State("state_dkg_responses_await_confirmations")
// Cancelled // Canceled
StateDkgResponsesAwaitCancelled = fsm.State("state_dkg_responses_await_cancelled") StateDkgResponsesAwaitCanceled = fsm.State("state_dkg_responses_await_canceled")
StateDkgResponsesAwaitCancelledByTimeout = fsm.State("state_dkg_responses_sending_cancelled_by_timeout") StateDkgResponsesAwaitCanceledByTimeout = fsm.State("state_dkg_responses_sending_canceled_by_timeout")
// Confirmed // Confirmed
StateDkgResponsesAwaitConfirmed = fsm.State("state_dkg_responses_await_confirmed") StateDkgResponsesAwaitConfirmed = fsm.State("state_dkg_responses_await_confirmed")
// Events // Events
eventDKGPubKeysSendingRequiredAuto = fsm.Event("event_dkg_pub_key_sending_required_internal")
EventDKGPubKeyConfirmationReceived = fsm.Event("event_dkg_pub_key_confirm_received") eventAutoDKGInitialInternal = fsm.Event("event_dkg_init_internal")
EventDKGPubKeyConfirmationError = fsm.Event("event_dkg_pub_key_confirm_canceled_by_error")
EventDKGPubKeysConfirmationCancelByTimeoutInternal = fsm.Event("event_dkg_pub_keys_confirm_canceled_by_timeout_internal")
EventDKGPubKeysConfirmedInternal = fsm.Event("event_dkg_pub_keys_confirmed_internal")
EventDKGCommitsSendingRequiredInternal = fsm.Event("event_dkg_commits_sending_required_internal") EventDKGPubKeyConfirmationReceived = fsm.Event("event_dkg_pub_key_confirm_received")
EventDKGPubKeyConfirmationError = fsm.Event("event_dkg_pub_key_confirm_canceled_by_error")
eventAutoValidatePubKeysInternal = fsm.Event("event_dkg_pub_keys_validate_internal")
eventDKGSetPubKeysConfirmationCanceledByTimeoutInternal = fsm.Event("event_dkg_pub_keys_confirm_canceled_by_timeout_internal")
eventDKGSetPubKeysConfirmationCanceledByErrorInternal = fsm.Event("event_dkg_pub_keys_confirm_canceled_by_error_internal")
eventDKGSetPubKeysConfirmedInternal = fsm.Event("event_dkg_pub_keys_confirmed_internal")
EventDKGCommitConfirmationReceived = fsm.Event("event_dkg_commit_confirm_received") EventDKGCommitConfirmationReceived = fsm.Event("event_dkg_commit_confirm_received")
EventDKGCommitConfirmationError = fsm.Event("event_dkg_commit_confirm_canceled_by_error") EventDKGCommitConfirmationError = fsm.Event("event_dkg_commit_confirm_canceled_by_error")
EventDKGCommitsConfirmationCancelByTimeoutInternal = fsm.Event("event_dkg_commits_confirm_canceled_by_timeout_internal") eventDKGCommitsConfirmationCancelByTimeoutInternal = fsm.Event("event_dkg_commits_confirm_canceled_by_timeout_internal")
EventDKGCommitsConfirmedInternal = fsm.Event("event_dkg_commits_confirmed_internal") eventDKGCommitsConfirmationCancelByErrorInternal = fsm.Event("event_dkg_commits_confirm_canceled_by_error_internal")
eventDKGCommitsConfirmedInternal = fsm.Event("event_dkg_commits_confirmed_internal")
EventDKGDealsSendingRequiredInternal = fsm.Event("event_dkg_deals_sending_required_internal") // EventDKGDealsSendingRequiredInternal = fsm.Event("event_dkg_deals_sending_required_internal")
EventDKGDealConfirmationReceived = fsm.Event("event_dkg_deal_confirm_received") EventDKGDealConfirmationReceived = fsm.Event("event_dkg_deal_confirm_received")
EventDKGDealConfirmationError = fsm.Event("event_dkg_deal_confirm_canceled_by_error") EventDKGDealConfirmationError = fsm.Event("event_dkg_deal_confirm_canceled_by_error")
EventDKGDealsConfirmationCancelByTimeoutInternal = fsm.Event("event_dkg_deals_confirm_canceled_by_timeout_internal") eventDKGDealsConfirmationCancelByTimeoutInternal = fsm.Event("event_dkg_deals_confirm_canceled_by_timeout_internal")
eventDKGDealsConfirmationCancelByErrorInternal = fsm.Event("event_dkg_deals_confirm_canceled_by_error_internal")
EventDKGResponsesSendingRequiredInternal = fsm.Event("event_dkg_responses_sending_required_internal") eventDKGDealsConfirmedInternal = fsm.Event("event_dkg_deals_confirmed_internal")
EventDKGResponseConfirmationReceived = fsm.Event("event_dkg_response_confirm_received") EventDKGResponseConfirmationReceived = fsm.Event("event_dkg_response_confirm_received")
EventDKGResponseConfirmationError = fsm.Event("event_dkg_response_confirm_canceled_by_error") EventDKGResponseConfirmationError = fsm.Event("event_dkg_response_confirm_canceled_by_error")
EventDKGResponseConfirmationCancelByTimeoutInternal = fsm.Event("event_dkg_response_confirm_canceled_by_timeout_internal") eventDKGResponseConfirmationCancelByTimeoutInternal = fsm.Event("event_dkg_response_confirm_canceled_by_timeout_internal")
eventDKGResponseConfirmationCancelByErrorInternal = fsm.Event("event_dkg_response_confirm_canceled_by_error_internal")
eventDKGResponsesConfirmedInternal = fsm.Event("event_dkg_responses_confirmed_internal")
EventDKGMasterKeyRequiredInternal = fsm.Event("event_dkg_master_key_required_internal") EventDKGMasterKeyRequiredInternal = fsm.Event("event_dkg_master_key_required_internal")
) )
@ -89,49 +95,57 @@ func New() internal.DumpedMachineProvider {
// Switch to pub keys required // Switch to pub keys required
// {Name: eventDKGPubKeysSendingRequiredAuto, SrcState: []fsm.State{StateDkgInitial}, DstState: StateDkgPubKeysAwaitConfirmations, IsInternal: true, IsAuto: true, AutoRunMode: fsm.EventRunAfter}, // {Name: eventDKGPubKeysSendingRequiredAuto, SrcState: []fsm.State{StateDkgInitial}, DstState: StateDkgPubKeysAwaitConfirmations, IsInternal: true, IsAuto: true, AutoRunMode: fsm.EventRunAfter},
// {Name: eventAutoDKGInitialInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitConfirmations, IsInternal: true, IsAuto: true, AutoRunMode: fsm.EventRunBefore},
// Pub keys sending // Pub keys sending
{Name: EventDKGPubKeyConfirmationReceived, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitConfirmations}, {Name: EventDKGPubKeyConfirmationReceived, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitConfirmations},
// Cancelled // Canceled
{Name: EventDKGPubKeyConfirmationError, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitCancelled}, {Name: EventDKGPubKeyConfirmationError, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitCanceled},
{Name: EventDKGPubKeysConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitCancelledByTimeout, IsInternal: true},
{Name: eventAutoValidatePubKeysInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitConfirmations, IsInternal: true, IsAuto: true},
{Name: eventDKGSetPubKeysConfirmationCanceledByTimeoutInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitCanceledByTimeout, IsInternal: true},
// Confirmed // Confirmed
{Name: EventDKGPubKeysConfirmedInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgPubKeysAwaitConfirmed, IsInternal: true}, {Name: eventDKGSetPubKeysConfirmedInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmations}, DstState: StateDkgCommitsAwaitConfirmations, IsInternal: true},
// Switch to commits required // Switch to commits required
{Name: EventDKGCommitsSendingRequiredInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmed}, DstState: StateDkgCommitsAwaitConfirmations, IsInternal: true}, //{Name: EventDKGCommitsSendingRequiredInternal, SrcState: []fsm.State{StateDkgPubKeysAwaitConfirmed}, DstState: StateDkgCommitsAwaitConfirmations, IsInternal: true},
// Commits // Commits
{Name: EventDKGCommitConfirmationReceived, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitConfirmations}, {Name: EventDKGCommitConfirmationReceived, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitConfirmations},
// Cancelled // Canceled
{Name: EventDKGCommitConfirmationError, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitCancelled}, {Name: EventDKGCommitConfirmationError, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitCanceled},
{Name: EventDKGCommitsConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitCancelledByTimeout, IsInternal: true}, {Name: eventDKGCommitsConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitCanceledByTimeout, IsInternal: true},
// Confirmed // Confirmed
{Name: EventDKGCommitsConfirmedInternal, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgCommitsAwaitConfirmed, IsInternal: true}, {Name: eventDKGCommitsConfirmedInternal, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgDealsAwaitConfirmations, IsInternal: true},
// Switch to deals required // Switch to deals required
{Name: EventDKGDealsSendingRequiredInternal, SrcState: []fsm.State{StateDkgDealsAwaitConfirmed}, DstState: StateDkgDealsAwaitConfirmations, IsInternal: true}, // {Name: EventDKGDealsSendingRequiredInternal, SrcState: []fsm.State{StateDkgDealsAwaitConfirmed}, DstState: StateDkgDealsAwaitConfirmations, IsInternal: true},
// Deals // Deals
{Name: EventDKGDealConfirmationReceived, SrcState: []fsm.State{StateDkgDealsAwaitConfirmations}, DstState: StateDkgDealsAwaitConfirmations}, {Name: EventDKGDealConfirmationReceived, SrcState: []fsm.State{StateDkgDealsAwaitConfirmations}, DstState: StateDkgDealsAwaitConfirmations},
// Cancelled // Canceled
{Name: EventDKGDealConfirmationError, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgDealsAwaitCancelled}, {Name: EventDKGDealConfirmationError, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgDealsAwaitCanceled},
{Name: EventDKGDealsConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgDealsAwaitCancelledByTimeout, IsInternal: true}, {Name: eventDKGDealsConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgCommitsAwaitConfirmations}, DstState: StateDkgDealsAwaitCanceledByTimeout, IsInternal: true},
// Switch to responses required // Switch to responses required
{Name: EventDKGResponsesSendingRequiredInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmed}, DstState: StateDkgResponsesAwaitConfirmations, IsInternal: true}, // {Name: eventDKGResponsesSendingRequiredInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmed}, DstState: StateDkgResponsesAwaitConfirmations, IsInternal: true},
// Deals // Deals
{Name: EventDKGResponseConfirmationReceived, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: StateDkgResponsesAwaitConfirmations}, {Name: EventDKGResponseConfirmationReceived, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: StateDkgResponsesAwaitConfirmations},
// Cancelled // Canceled
{Name: EventDKGResponseConfirmationError, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: StateDkgResponsesAwaitCancelled}, {Name: EventDKGResponseConfirmationError, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: StateDkgResponsesAwaitCanceled},
{Name: EventDKGResponseConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: StateDkgResponsesAwaitCancelledByTimeout, IsInternal: true}, {Name: eventDKGResponseConfirmationCancelByTimeoutInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: StateDkgResponsesAwaitCanceledByTimeout, IsInternal: true},
// Done // Done
{Name: EventDKGMasterKeyRequiredInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: fsm.StateGlobalDone, IsInternal: true}, {Name: EventDKGMasterKeyRequiredInternal, SrcState: []fsm.State{StateDkgResponsesAwaitConfirmations}, DstState: fsm.StateGlobalDone, IsInternal: true},
}, },
fsm.Callbacks{ fsm.Callbacks{
EventDKGPubKeyConfirmationReceived: machine.actionPubKeyConfirmationReceived, EventDKGPubKeyConfirmationReceived: machine.actionPubKeyConfirmationReceived,
EventDKGPubKeyConfirmationError: machine.actionConfirmationError, EventDKGPubKeyConfirmationError: machine.actionConfirmationError,
// actionValidateDkgProposalPubKeys
eventAutoValidatePubKeysInternal: machine.actionValidateDkgProposalPubKeys,
EventDKGCommitConfirmationReceived: machine.actionCommitConfirmationReceived, EventDKGCommitConfirmationReceived: machine.actionCommitConfirmationReceived,
EventDKGCommitConfirmationError: machine.actionConfirmationError, EventDKGCommitConfirmationError: machine.actionConfirmationError,

View File

@ -5,11 +5,6 @@ import (
"time" "time"
) )
const (
SignatureAwaitConfirmation SignatureProposalParticipantStatus = iota
SignatureConfirmed
)
type ConfirmationProposal struct { type ConfirmationProposal struct {
Quorum SignatureProposalQuorum Quorum SignatureProposalQuorum
CreatedAt *time.Time CreatedAt *time.Time
@ -23,22 +18,22 @@ type SignatureProposalParticipant struct {
PublicKey *rsa.PublicKey PublicKey *rsa.PublicKey
// For validation user confirmation: sign(InvitationSecret, PublicKey) => user // For validation user confirmation: sign(InvitationSecret, PublicKey) => user
InvitationSecret string InvitationSecret string
Status SignatureProposalParticipantStatus Status ParticipantStatus
UpdatedAt *time.Time UpdatedAt *time.Time
} }
// Unique alias for map iteration - Public Key Fingerprint // Unique alias for map iteration - Public Key Fingerprint
// Excludes array merge and rotate operations // Excludes array merge and rotate operations
type SignatureProposalQuorum map[string]SignatureProposalParticipant type SignatureProposalQuorum map[string]*SignatureProposalParticipant
type SignatureProposalParticipantStatus uint8 type ParticipantStatus uint8
const ( const (
SignatureConfirmationAwaitConfirmation DKGProposalParticipantStatus = iota SignatureConfirmationAwaitConfirmation ParticipantStatus = iota
SignatureConfirmationConfirmed SignatureConfirmationConfirmed
SignatureConfirmationDeclined SignatureConfirmationDeclined
SignatureConfirmationError SignatureConfirmationError
PubKeyConAwaitConfirmation PubKeyAwaitConfirmation
PubKeyConfirmed PubKeyConfirmed
PubKeyConfirmationError PubKeyConfirmationError
CommitAwaitConfirmation CommitAwaitConfirmation
@ -52,27 +47,21 @@ const (
ResponseConfirmationError ResponseConfirmationError
) )
type DKGProposal struct {
Quorum map[int]DKGProposalParticipant
CreatedAt *time.Time
ExpiresAt *time.Time
}
type DKGProposalParticipant struct { type DKGProposalParticipant struct {
Title string Title string
PublicKey []byte PubKey []byte
Commit []byte Commit []byte
Deal []byte Deal []byte
Response []byte Response []byte
Status DKGProposalParticipantStatus Status ParticipantStatus
UpdatedAt *time.Time UpdatedAt *time.Time
} }
type DKGProposalQuorum map[int]DKGProposalParticipant type DKGProposalQuorum map[int]*DKGProposalParticipant
type DKGProposalParticipantStatus uint8 type DKGProposalParticipantStatus uint8
func (s DKGProposalParticipantStatus) String() string { func (s ParticipantStatus) String() string {
var str = "undefined" var str = "undefined"
switch s { switch s {
case SignatureConfirmationAwaitConfirmation: case SignatureConfirmationAwaitConfirmation:
@ -83,8 +72,8 @@ func (s DKGProposalParticipantStatus) String() string {
str = "SignatureConfirmationDeclined" str = "SignatureConfirmationDeclined"
case SignatureConfirmationError: case SignatureConfirmationError:
str = "SignatureConfirmationError" str = "SignatureConfirmationError"
case PubKeyConAwaitConfirmation: case PubKeyAwaitConfirmation:
str = "PubKeyConAwaitConfirmation" str = "PubKeyAwaitConfirmation"
case PubKeyConfirmed: case PubKeyConfirmed:
str = "PubKeyConfirmed" str = "PubKeyConfirmed"
case PubKeyConfirmationError: case PubKeyConfirmationError:

View File

@ -149,6 +149,13 @@ func (i *FSMInstance) Id() string {
return "" return ""
} }
func (i *FSMInstance) Dump() ([]byte, error) {
if i.dump == nil {
return []byte{}, errors.New("dump is not initialized")
}
return i.dump.Marshal()
}
// TODO: Add encryption // TODO: Add encryption
func (d *FSMDump) Marshal() ([]byte, error) { func (d *FSMDump) Marshal() ([]byte, error) {
return json.Marshal(d) return json.Marshal(d)
@ -157,7 +164,7 @@ func (d *FSMDump) Marshal() ([]byte, error) {
// TODO: Add decryption // TODO: Add decryption
func (d *FSMDump) Unmarshal(data []byte) error { func (d *FSMDump) Unmarshal(data []byte) error {
if d == nil { if d == nil {
return errors.New("dump struct is not initialized") return errors.New("dump is not initialized")
} }
return json.Unmarshal(data, d) return json.Unmarshal(data, d)

View File

@ -36,6 +36,8 @@ var (
Participants: []*requests.SignatureProposalParticipantsEntry{}, Participants: []*requests.SignatureProposalParticipantsEntry{},
CreatedAt: &tm, CreatedAt: &tm,
} }
testFSMDump []byte
) )
func init() { func init() {
@ -118,21 +120,41 @@ func compareState(t *testing.T, expected fsm.State, got fsm.State) {
} }
} }
func Test_Workflow(t *testing.T) { // Test Workflow
testFSMInstance, err := Create()
log.Println(testFSMInstance.Id()) func Test_SignatureProposal_Init(t *testing.T) {
testFSMInstance, err := Create()
compareErrNil(t, err) compareErrNil(t, err)
compareFSMInstanceNotNil(t, testFSMInstance) compareFSMInstanceNotNil(t, testFSMInstance)
transactionId := testFSMInstance.Id()
if transactionId == "" {
t.Fatalf("expected {transactionId for dump}")
}
if testFSMInstance.machine.Name() != spf.FsmName { if testFSMInstance.machine.Name() != spf.FsmName {
t.Fatalf("expected machine name {%s}", spf.FsmName) t.Fatalf("expected machine name {%s}", spf.FsmName)
} }
compareState(t, spf.StateParticipantsConfirmationsInit, testFSMInstance.machine.State()) compareState(t, spf.StateParticipantsConfirmationsInit, testFSMInstance.machine.State())
testFSMDump, err = testFSMInstance.Dump()
compareErrNil(t, err)
compareDumpNotZero(t, testFSMDump)
}
func Test_SignatureProposal_Positive(t *testing.T) {
testFSMInstance, err := FromDump(testFSMDump)
compareErrNil(t, err)
compareFSMInstanceNotNil(t, testFSMInstance)
fsmResponse, dump, err := testFSMInstance.Do(spf.EventInitProposal, testParticipantsListRequest) fsmResponse, dump, err := testFSMInstance.Do(spf.EventInitProposal, testParticipantsListRequest)
compareErrNil(t, err) compareErrNil(t, err)
@ -143,10 +165,251 @@ func Test_Workflow(t *testing.T) {
compareState(t, spf.StateAwaitParticipantsConfirmations, fsmResponse.State) compareState(t, spf.StateAwaitParticipantsConfirmations, fsmResponse.State)
if testFSMInstance.Id() != testTransactionId { testParticipantsListResponse, ok := fsmResponse.Data.(responses.SignatureProposalParticipantInvitationsResponse)
t.Fatalf("expected {testTransactionId}")
if !ok {
t.Fatalf("expected response {SignatureProposalParticipantInvitationsResponse}")
} }
if len(testParticipantsListResponse) != len(testParticipantsListRequest.Participants) {
t.Fatalf("expected response len {%d}, got {%d}", len(testParticipantsListRequest.Participants), len(testParticipantsListResponse))
}
participantsMap := map[int]*responses.SignatureProposalParticipantInvitationEntry{}
for _, participant := range testParticipantsListResponse {
if _, ok := participantsMap[participant.ParticipantId]; ok {
t.Fatalf("expected unique {ParticipantId}")
}
if participant.Title == "" {
t.Fatalf("expected not empty {Title}")
}
if participant.EncryptedInvitation == "" {
t.Fatalf("expected not empty {DecryptedInvitation}")
}
if participant.PubKeyFingerprint == "" {
t.Fatalf("expected not empty {PubKeyFingerprint}")
}
participantsMap[participant.ParticipantId] = participant
}
tm = tm.Add(10 * time.Hour)
participantsCount := len(participantsMap)
participantCounter := participantsCount
for _, participant := range participantsMap {
participantCounter--
testFSMInstance, err = FromDump(dump)
compareErrNil(t, err)
compareFSMInstanceNotNil(t, testFSMInstance)
if _, ok := testParticipants[participant.PubKeyFingerprint]; !ok {
t.Fatalf("not found external user data for response fingerprint")
}
r := rand.Reader
encrypted, err := rsa.DecryptPKCS1v15(r, testParticipants[participant.PubKeyFingerprint].PrivKey, []byte(participant.EncryptedInvitation))
if err != nil {
t.Fatalf("cannot encrypt {DecryptedInvitation} with private key")
}
fsmResponse, dump, err = testFSMInstance.Do(spf.EventConfirmProposal, requests.SignatureProposalParticipantRequest{
PubKeyFingerprint: participant.PubKeyFingerprint,
DecryptedInvitation: string(encrypted),
CreatedAt: &tm,
})
compareErrNil(t, err)
compareDumpNotZero(t, dump)
compareFSMResponseNotNil(t, fsmResponse)
if participantCounter > 0 {
compareState(t, spf.StateAwaitParticipantsConfirmations, fsmResponse.State)
} else {
compareState(t, dpf.StateDkgInitial, fsmResponse.State)
}
}
// PubKeys
for _, participant := range participantsMap {
participantCounter--
testFSMInstance, err = FromDump(dump)
compareErrNil(t, err)
compareFSMInstanceNotNil(t, testFSMInstance)
if _, ok := testParticipants[participant.PubKeyFingerprint]; !ok {
t.Fatalf("not found external user data for response fingerprint")
}
pubKeyMock := make([]byte, 128)
_, err := rand.Read(pubKeyMock)
if err != nil {
compareErrNil(t, err)
}
fsmResponse, dump, err = testFSMInstance.Do(dpf.EventDKGPubKeyConfirmationReceived, requests.DKGProposalPubKeyConfirmationRequest{
ParticipantId: participant.ParticipantId,
PubKey: pubKeyMock,
CreatedAt: &tm,
})
compareErrNil(t, err)
compareDumpNotZero(t, dump)
compareFSMResponseNotNil(t, fsmResponse)
/*if participantCounter > 0 {
compareState(t, dpf.StateDkgPubKeysAwaitConfirmations, fsmResponse.State)
} else {
compareState(t, dpf.StateDkgCommitsAwaitConfirmations, fsmResponse.State)
}*/
}
compareState(t, dpf.StateDkgCommitsAwaitConfirmations, fsmResponse.State)
// Commits
for _, participant := range participantsMap {
participantCounter--
testFSMInstance, err = FromDump(dump)
compareErrNil(t, err)
compareFSMInstanceNotNil(t, testFSMInstance)
if _, ok := testParticipants[participant.PubKeyFingerprint]; !ok {
t.Fatalf("not found external user data for response fingerprint")
}
commitMock := make([]byte, 128)
_, err := rand.Read(commitMock)
if err != nil {
compareErrNil(t, err)
}
fsmResponse, dump, err = testFSMInstance.Do(dpf.EventDKGCommitConfirmationReceived, requests.DKGProposalCommitConfirmationRequest{
ParticipantId: participant.ParticipantId,
Commit: commitMock,
CreatedAt: &tm,
})
compareErrNil(t, err)
compareDumpNotZero(t, dump)
compareFSMResponseNotNil(t, fsmResponse)
}
compareState(t, dpf.StateDkgDealsAwaitConfirmations, fsmResponse.State)
// Deals
for _, participant := range participantsMap {
participantCounter--
testFSMInstance, err = FromDump(dump)
compareErrNil(t, err)
compareFSMInstanceNotNil(t, testFSMInstance)
if _, ok := testParticipants[participant.PubKeyFingerprint]; !ok {
t.Fatalf("not found external user data for response fingerprint")
}
dealMock := make([]byte, 128)
_, err := rand.Read(dealMock)
if err != nil {
compareErrNil(t, err)
}
fsmResponse, dump, err = testFSMInstance.Do(dpf.EventDKGDealConfirmationReceived, requests.DKGProposalDealConfirmationRequest{
ParticipantId: participant.ParticipantId,
Deal: dealMock,
CreatedAt: &tm,
})
compareErrNil(t, err)
compareDumpNotZero(t, dump)
compareFSMResponseNotNil(t, fsmResponse)
}
compareState(t, dpf.StateDkgResponsesAwaitConfirmations, fsmResponse.State)
// Responses
for _, participant := range participantsMap {
participantCounter--
testFSMInstance, err = FromDump(dump)
compareErrNil(t, err)
compareFSMInstanceNotNil(t, testFSMInstance)
if _, ok := testParticipants[participant.PubKeyFingerprint]; !ok {
t.Fatalf("not found external user data for response fingerprint")
}
responseMock := make([]byte, 128)
_, err := rand.Read(responseMock)
if err != nil {
compareErrNil(t, err)
}
fsmResponse, dump, err = testFSMInstance.Do(dpf.EventDKGDealConfirmationReceived, requests.DKGProposalDealConfirmationRequest{
ParticipantId: participant.ParticipantId,
Deal: responseMock,
CreatedAt: &tm,
})
compareErrNil(t, err)
compareDumpNotZero(t, dump)
compareFSMResponseNotNil(t, fsmResponse)
}
compareState(t, fsm.StateGlobalDone, fsmResponse.State)
}
/*
func Test_SignatureProposal_Negative_By_Decline(t *testing.T) {
testFSMInstance, err := FromDump(testFSMDump)
compareErrNil(t, err)
compareFSMInstanceNotNil(t, testFSMInstance)
fsmResponse, dump, err := testFSMInstance.Do(spf.EventInitProposal, testParticipantsListRequest)
compareErrNil(t, err)
compareDumpNotZero(t, dump)
compareFSMResponseNotNil(t, fsmResponse)
compareState(t, spf.StateAwaitParticipantsConfirmations, fsmResponse.State)
testParticipantsListResponse, ok := fsmResponse.Data.(responses.SignatureProposalParticipantInvitationsResponse) testParticipantsListResponse, ok := fsmResponse.Data.(responses.SignatureProposalParticipantInvitationsResponse)
if !ok { if !ok {
@ -204,7 +467,7 @@ func Test_Workflow(t *testing.T) {
t.Fatalf("cannot encrypt {DecryptedInvitation} with private key") t.Fatalf("cannot encrypt {DecryptedInvitation} with private key")
} }
fsmResponse, dump, err = testFSMInstance.Do(spf.EventConfirmProposal, requests.SignatureProposalParticipantRequest{ fsmResponse, dump, err = testFSMInstance.Do(spf.EventDeclineProposal, requests.SignatureProposalParticipantRequest{
PubKeyFingerprint: participant.PubKeyFingerprint, PubKeyFingerprint: participant.PubKeyFingerprint,
DecryptedInvitation: string(encrypted), DecryptedInvitation: string(encrypted),
CreatedAt: &tm, CreatedAt: &tm,
@ -216,19 +479,9 @@ func Test_Workflow(t *testing.T) {
compareFSMResponseNotNil(t, fsmResponse) compareFSMResponseNotNil(t, fsmResponse)
if participantCounter > 0 { compareState(t, spf.StateValidationCanceledByParticipant, fsmResponse.State)
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)
}
}
state, err := testFSMInstance.State()
log.Println(err, state)
} }
} }
*/

View File

@ -3,10 +3,13 @@ package signature_proposal_fsm
import ( import (
"crypto/x509" "crypto/x509"
"errors" "errors"
"fmt"
"github.com/depools/dc4bc/fsm/config"
"github.com/depools/dc4bc/fsm/fsm" "github.com/depools/dc4bc/fsm/fsm"
"github.com/depools/dc4bc/fsm/state_machines/internal" "github.com/depools/dc4bc/fsm/state_machines/internal"
"github.com/depools/dc4bc/fsm/types/requests" "github.com/depools/dc4bc/fsm/types/requests"
"github.com/depools/dc4bc/fsm/types/responses" "github.com/depools/dc4bc/fsm/types/responses"
"time"
) )
// init -> awaitingConfirmations // init -> awaitingConfirmations
@ -46,12 +49,12 @@ func (m *SignatureProposalFSM) actionInitProposal(inEvent fsm.Event, args ...int
return inEvent, nil, errors.New("cannot parse {PubKey}") return inEvent, nil, errors.New("cannot parse {PubKey}")
} }
m.payload.ConfirmationProposalPayload[participantId] = internal.SignatureProposalParticipant{ m.payload.ConfirmationProposalPayload[participantId] = &internal.SignatureProposalParticipant{
ParticipantId: index, ParticipantId: index,
Title: participant.Title, Title: participant.Title,
PublicKey: parsedPubKey, PublicKey: parsedPubKey,
InvitationSecret: secret, InvitationSecret: secret,
Status: internal.SignatureAwaitConfirmation, Status: internal.SignatureConfirmationAwaitConfirmation,
UpdatedAt: request.CreatedAt, UpdatedAt: request.CreatedAt,
} }
} }
@ -86,8 +89,8 @@ func (m *SignatureProposalFSM) actionInitProposal(inEvent fsm.Event, args ...int
// TODO: Add timeout checking // TODO: Add timeout checking
func (m *SignatureProposalFSM) actionProposalResponseByParticipant(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { func (m *SignatureProposalFSM) actionProposalResponseByParticipant(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
// m.payloadMu.Lock() m.payloadMu.Lock()
// defer m.payloadMu.Unlock() defer m.payloadMu.Unlock()
if len(args) != 1 { if len(args) != 1 {
err = errors.New("{arg0} required {SignatureProposalParticipantRequest}") err = errors.New("{arg0} required {SignatureProposalParticipantRequest}")
@ -117,30 +120,120 @@ func (m *SignatureProposalFSM) actionProposalResponseByParticipant(inEvent fsm.E
return return
} }
signatureProposalParticipant.Status = internal.SignatureConfirmed if signatureProposalParticipant.UpdatedAt.Add(config.SignatureProposalConfirmationDeadline).Before(*request.CreatedAt) {
outEvent = eventSetValidationCanceledByTimeout
return
}
if signatureProposalParticipant.Status != internal.SignatureConfirmationAwaitConfirmation {
err = errors.New(fmt.Sprintf("cannot apply reply participant with {Status} = {\"%s\"}", signatureProposalParticipant.Status))
return
}
switch inEvent {
case EventConfirmProposal:
signatureProposalParticipant.Status = internal.SignatureConfirmationConfirmed
case EventDeclineProposal:
signatureProposalParticipant.Status = internal.SignatureConfirmationDeclined
default:
err = errors.New("undefined {Event} for action")
return
}
signatureProposalParticipant.UpdatedAt = request.CreatedAt signatureProposalParticipant.UpdatedAt = request.CreatedAt
m.payload.ConfirmationProposalPayload[request.PubKeyFingerprint] = signatureProposalParticipant m.payload.ConfirmationProposalPayload[request.PubKeyFingerprint] = signatureProposalParticipant
outEvent, response, err = m.actionValidateProposal(eventValidateProposalInternal)
return return
} }
func (m *SignatureProposalFSM) actionValidateProposal(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) { func (m *SignatureProposalFSM) actionValidateSignatureProposal(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
// m.payloadMu.Lock() var (
// defer m.payloadMu.Unlock() isContainsDeclined, isContainsExpired bool
)
m.payloadMu.Lock()
defer m.payloadMu.Unlock()
tm := time.Now()
unconfirmedParticipants := len(m.payload.ConfirmationProposalPayload) unconfirmedParticipants := len(m.payload.ConfirmationProposalPayload)
for _, participant := range m.payload.ConfirmationProposalPayload { for _, participant := range m.payload.ConfirmationProposalPayload {
if participant.Status == internal.SignatureConfirmed { if participant.Status == internal.SignatureConfirmationAwaitConfirmation {
unconfirmedParticipants-- if participant.UpdatedAt.Add(config.SignatureProposalConfirmationDeadline).Before(tm) {
isContainsExpired = true
}
} else {
if participant.Status == internal.SignatureConfirmationConfirmed {
unconfirmedParticipants--
} else if participant.Status == internal.SignatureConfirmationDeclined {
isContainsDeclined = true
}
} }
} }
if isContainsDeclined {
outEvent = eventSetValidationCanceledByParticipant
return
}
if isContainsExpired {
outEvent = eventSetValidationCanceledByTimeout
return
}
// The are no declined and timed out participants, check for all confirmations
if unconfirmedParticipants > 0 { if unconfirmedParticipants > 0 {
return return
} }
outEvent = eventSetProposalValidatedInternal outEvent = eventSetProposalValidatedInternal
m.actionSetValidatedSignatureProposal(outEvent)
return return
} }
func (m *SignatureProposalFSM) actionSetValidatedSignatureProposal(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
// m.payloadMu.Lock()
// defer m.payloadMu.Unlock()
// TODO: Run once after validation
if m.payload.DKGProposalPayload != nil {
return
}
m.payload.DKGProposalPayload = make(internal.DKGProposalQuorum)
for _, participant := range m.payload.ConfirmationProposalPayload {
m.payload.DKGProposalPayload[participant.ParticipantId] = &internal.DKGProposalParticipant{
Title: participant.Title,
Status: internal.PubKeyAwaitConfirmation,
UpdatedAt: participant.UpdatedAt,
}
}
// Remove m.payload.ConfirmationProposalPayload?
return
}
func (m *SignatureProposalFSM) actionSignatureProposalCanceledByTimeout(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
m.payloadMu.Lock()
defer m.payloadMu.Unlock()
responseData := make(responses.SignatureProposalParticipantStatusResponse, 0)
for pubKeyFingerprint, participant := range m.payload.ConfirmationProposalPayload {
responseEntry := &responses.SignatureProposalParticipantStatusEntry{
ParticipantId: participant.ParticipantId,
Title: participant.Title,
PubKeyFingerprint: pubKeyFingerprint,
Status: uint8(participant.Status),
}
responseData = append(responseData, responseEntry)
}
return inEvent, responseData, nil
}

View File

@ -19,15 +19,15 @@ const (
StateValidationCanceledByTimeout = fsm.State("state_sig_proposal_canceled_by_timeout") StateValidationCanceledByTimeout = fsm.State("state_sig_proposal_canceled_by_timeout")
// Out state // Out state
StateValidationCompleted = fsm.State("state_sig_proposal_completed")
EventInitProposal = fsm.Event("event_sig_proposal_init") EventInitProposal = fsm.Event("event_sig_proposal_init")
EventConfirmProposal = fsm.Event("event_sig_proposal_confirm_by_participant") EventConfirmProposal = fsm.Event("event_sig_proposal_confirm_by_participant")
EventDeclineProposal = fsm.Event("event_sig_proposal_decline_by_participant") EventDeclineProposal = fsm.Event("event_sig_proposal_decline_by_participant")
eventValidateProposalInternal = fsm.Event("event_sig_proposal_validate") eventAutoValidateProposalInternal = fsm.Event("event_sig_proposal_validate")
eventSetProposalValidatedInternal = fsm.Event("event_sig_proposal_set_validated") eventSetProposalValidatedInternal = fsm.Event("event_sig_proposal_set_validated")
eventSetValidationCanceledByTimeout = fsm.Event("event_sig_proposal_canceled_timeout") eventSetValidationCanceledByTimeout = fsm.Event("event_sig_proposal_canceled_timeout")
eventSetValidationCanceledByParticipant = fsm.Event("event_sig_proposal_declined_timeout")
// Switch to next fsm // Switch to next fsm
@ -55,9 +55,10 @@ func New() internal.DumpedMachineProvider {
{Name: EventConfirmProposal, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateAwaitParticipantsConfirmations}, {Name: EventConfirmProposal, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateAwaitParticipantsConfirmations},
// Is decline event should auto change state to default, or it process will initiated by client (external emit)? // Is decline event should auto change state to default, or it process will initiated by client (external emit)?
// Now set for external emitting. // Now set for external emitting.
{Name: EventDeclineProposal, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateValidationCanceledByParticipant}, {Name: EventDeclineProposal, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateAwaitParticipantsConfirmations},
{Name: eventSetValidationCanceledByParticipant, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateValidationCanceledByParticipant, IsInternal: true},
{Name: eventValidateProposalInternal, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateAwaitParticipantsConfirmations, IsInternal: true}, {Name: eventAutoValidateProposalInternal, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateAwaitParticipantsConfirmations, IsInternal: true, IsAuto: true},
// eventProposalValidate internal or from client? // eventProposalValidate internal or from client?
// yay // yay
@ -67,10 +68,11 @@ func New() internal.DumpedMachineProvider {
{Name: eventSetValidationCanceledByTimeout, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateValidationCanceledByTimeout, IsInternal: true}, {Name: eventSetValidationCanceledByTimeout, SrcState: []fsm.State{StateAwaitParticipantsConfirmations}, DstState: StateValidationCanceledByTimeout, IsInternal: true},
}, },
fsm.Callbacks{ fsm.Callbacks{
EventInitProposal: machine.actionInitProposal, EventInitProposal: machine.actionInitProposal,
EventConfirmProposal: machine.actionProposalResponseByParticipant, EventConfirmProposal: machine.actionProposalResponseByParticipant,
EventDeclineProposal: machine.actionProposalResponseByParticipant, EventDeclineProposal: machine.actionProposalResponseByParticipant,
eventValidateProposalInternal: machine.actionValidateProposal, eventAutoValidateProposalInternal: machine.actionValidateSignatureProposal,
eventSetProposalValidatedInternal: machine.actionSetValidatedSignatureProposal,
}, },
) )
return machine return machine

View File

@ -2,14 +2,8 @@ package responses
// Response // Response
const ( // Event: "event_sig_proposal_init"
ProposalConfirmationStatusIdle = iota // States: "__idle"
ProposalConfirmationStatusAccepted
ProposalConfirmationStatusCanceled
ProposalConfirmationStatusTimeout
)
// States: "validate_proposal"
type SignatureProposalParticipantInvitationsResponse []*SignatureProposalParticipantInvitationEntry type SignatureProposalParticipantInvitationsResponse []*SignatureProposalParticipantInvitationEntry
@ -31,5 +25,5 @@ type SignatureProposalParticipantStatusEntry struct {
ParticipantId int ParticipantId int
Title string Title string
PubKeyFingerprint string PubKeyFingerprint string
Status int Status uint8
} }