Merge branch 'fsm-draft' into feat/airgapped-client-tests

This commit is contained in:
programmer10110 2020-08-21 14:54:25 +03:00
commit 826da7a0a6
13 changed files with 379 additions and 76 deletions

View File

@ -134,6 +134,8 @@ func (s DKGParticipantStatus) String() string {
// Signing proposal
type SigningConfirmation struct {
SigningId string
InitiatorId int
Quorum SigningProposalQuorum
RecoveredKey []byte
SrcPayload []byte
@ -152,8 +154,7 @@ type SigningProposalQuorum map[int]*SigningProposalParticipant
type SigningParticipantStatus uint8
const (
SigningIdle SigningParticipantStatus = iota
SigningAwaitConfirmation
SigningAwaitConfirmation SigningParticipantStatus = iota
SigningConfirmed
SigningDeclined
SigningAwaitPartialKeys
@ -165,8 +166,6 @@ const (
func (s SigningParticipantStatus) String() string {
var str = "undefined"
switch s {
case SigningIdle:
str = "SigningIdle"
case SigningAwaitConfirmation:
str = "SigningAwaitConfirmation"
case SigningConfirmed:

View File

@ -2,10 +2,9 @@ package state_machines
import (
"crypto/ed25519"
"crypto/rand"
"encoding/base64"
"encoding/json"
"errors"
"github.com/depools/dc4bc/fsm/state_machines/signing_proposal_fsm"
"strings"
"github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm"
@ -16,10 +15,6 @@ import (
"github.com/depools/dc4bc/fsm/state_machines/signature_proposal_fsm"
)
const (
dkgTransactionIdLength = 128
)
// Is machine state scope dump will be locked?
type FSMDump struct {
TransactionId string
@ -32,17 +27,6 @@ type FSMInstance struct {
dump *FSMDump
}
var (
fsmPoolProvider *fsm_pool.FSMPool
)
func init() {
fsmPoolProvider = fsm_pool.Init(
signature_proposal_fsm.New(),
dkg_proposal_fsm.New(),
)
}
// Create new fsm with unique id
// transactionId required for unique identify dump
func Create(dkgID string) (*FSMInstance, error) {
@ -56,6 +40,12 @@ func Create(dkgID string) (*FSMInstance, error) {
return nil, err
}
fsmPoolProvider := fsm_pool.Init(
signature_proposal_fsm.New(),
dkg_proposal_fsm.New(),
signing_proposal_fsm.New(),
)
machine, err := fsmPoolProvider.EntryPointMachine()
i.machine = machine.(internal.DumpedMachineProvider)
i.machine.SetUpPayload(i.dump.Payload)
@ -80,7 +70,16 @@ func FromDump(data []byte) (*FSMInstance, error) {
return nil, errors.New("cannot read machine dump")
}
fsmPoolProvider := fsm_pool.Init(
signature_proposal_fsm.New(),
dkg_proposal_fsm.New(),
signing_proposal_fsm.New(),
)
machine, err := fsmPoolProvider.MachineByState(i.dump.State)
if err != nil {
return nil, err
}
i.machine = machine.(internal.DumpedMachineProvider)
i.machine.SetUpPayload(i.dump.Payload)
return i, err
@ -173,13 +172,3 @@ func (d *FSMDump) Unmarshal(data []byte) error {
return json.Unmarshal(data, d)
}
func generateDkgTransactionId() (string, error) {
b := make([]byte, dkgTransactionIdLength)
_, err := rand.Read(b)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), err
}

View File

@ -5,6 +5,7 @@ import (
"crypto/rsa"
"crypto/x509"
"fmt"
sif "github.com/depools/dc4bc/fsm/state_machines/signing_proposal_fsm"
"log"
"testing"
"time"
@ -379,6 +380,63 @@ func Test_SignatureProposal_Positive(t *testing.T) {
}
compareState(t, dpf.StateDkgMasterKeyCollected, fsmResponse.State)
// Signing
testFSMInstance, err = FromDump(dump)
compareErrNil(t, err)
compareFSMInstanceNotNil(t, testFSMInstance)
fsmResponse, dump, err = testFSMInstance.Do(sif.EventSigningInit, requests.DefaultRequest{
CreatedAt: time.Now(),
})
compareErrNil(t, err)
compareDumpNotZero(t, dump)
compareFSMResponseNotNil(t, fsmResponse)
compareState(t, sif.StateSigningIdle, fsmResponse.State)
// Start
testFSMInstance, err = FromDump(dump)
compareErrNil(t, err)
compareFSMInstanceNotNil(t, testFSMInstance)
fsmResponse, dump, err = testFSMInstance.Do(sif.EventSigningStart, requests.SigningProposalStartRequest{
ParticipantId: 1,
SrcPayload: []byte("message to sign"),
CreatedAt: time.Now(),
})
compareErrNil(t, err)
compareDumpNotZero(t, dump)
compareFSMResponseNotNil(t, fsmResponse)
compareState(t, sif.StateSigningAwaitConfirmations, fsmResponse.State)
testSigningParticipantsListResponse, ok := fsmResponse.Data.(responses.SigningProposalParticipantInvitationsResponse)
if !ok {
t.Fatalf("expected response {SigningProposalParticipantInvitationsResponse}")
}
if len(testSigningParticipantsListResponse.Participants) != len(testParticipantsListRequest.Participants) {
t.Fatalf("expected response len {%d}, got {%d}", len(testParticipantsListRequest.Participants), len(testSigningParticipantsListResponse.Participants))
}
if testSigningParticipantsListResponse.SigningId == "" {
t.Fatalf("expected field {SigningId}")
}
}
func Test_DKGProposal_Positive(t *testing.T) {

View File

@ -116,11 +116,12 @@ func (m *SignatureProposalFSM) actionProposalResponseByParticipant(inEvent fsm.E
case EventDeclineProposal:
signatureProposalParticipant.Status = internal.SigConfirmationDeclined
default:
err = errors.New("undefined {Event} for action")
err = errors.New(fmt.Sprintf("unsupported event for action {inEvent} = {\"%s\"}", inEvent))
return
}
signatureProposalParticipant.UpdatedAt = request.CreatedAt
m.payload.SignatureProposalPayload.UpdatedAt = request.CreatedAt
m.payload.SigQuorumUpdate(request.ParticipantId, signatureProposalParticipant)
@ -129,7 +130,7 @@ func (m *SignatureProposalFSM) actionProposalResponseByParticipant(inEvent fsm.E
func (m *SignatureProposalFSM) actionValidateSignatureProposal(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
var (
isContainsDeclined bool
isContainsDecline bool
)
m.payloadMu.Lock()
@ -146,11 +147,11 @@ func (m *SignatureProposalFSM) actionValidateSignatureProposal(inEvent fsm.Event
if participant.Status == internal.SigConfirmationConfirmed {
unconfirmedParticipants--
} else if participant.Status == internal.SigConfirmationDeclined {
isContainsDeclined = true
isContainsDecline = true
}
}
if isContainsDeclined {
if isContainsDecline {
outEvent = eventSetValidationCanceledByParticipant
return
}
@ -166,6 +167,7 @@ func (m *SignatureProposalFSM) actionValidateSignatureProposal(inEvent fsm.Event
responseEntry := &responses.SignatureProposalParticipantStatusEntry{
ParticipantId: participantId,
Addr: participant.Addr,
DkgPubKey: participant.DkgPubKey,
Status: uint8(participant.Status),
}
responseData = append(responseData, responseEntry)
@ -184,6 +186,7 @@ func (m *SignatureProposalFSM) actionSignatureProposalCanceledByTimeout(inEvent
responseEntry := &responses.SignatureProposalParticipantStatusEntry{
ParticipantId: participantId,
Addr: participant.Addr,
DkgPubKey: participant.DkgPubKey,
Status: uint8(participant.Status),
}
responseData = append(responseData, responseEntry)

View File

@ -7,7 +7,7 @@ import (
"github.com/depools/dc4bc/fsm/fsm"
"github.com/depools/dc4bc/fsm/state_machines/internal"
"github.com/depools/dc4bc/fsm/types/requests"
"time"
"github.com/depools/dc4bc/fsm/types/responses"
)
func (m *SigningProposalFSM) actionInitSigningProposal(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
@ -36,14 +36,6 @@ func (m *SigningProposalFSM) actionInitSigningProposal(inEvent fsm.Event, args .
ExpiresAt: request.CreatedAt.Add(config.SigningConfirmationDeadline),
}
for participantId, participant := range m.payload.SignatureProposalPayload.Quorum {
m.payload.SigningProposalPayload.Quorum[participantId] = &internal.SigningProposalParticipant{
Addr: participant.Addr,
Status: internal.SigningIdle,
UpdatedAt: participant.UpdatedAt,
}
}
return
}
@ -67,9 +59,47 @@ func (m *SigningProposalFSM) actionStartSigningProposal(inEvent fsm.Event, args
return
}
m.payload.SigningProposalPayload.SigningId, err = generateSigningId()
if err != nil {
err = errors.New("cannot generate {SigningId}")
return
}
m.payload.SigningProposalPayload.InitiatorId = request.ParticipantId
m.payload.SigningProposalPayload.SrcPayload = request.SrcPayload
m.payload.SigningProposalPayload.Quorum = make(internal.SigningProposalQuorum)
// Initialize new quorum
for id, dkgEntry := range m.payload.DKGProposalPayload.Quorum {
m.payload.SigningProposalPayload.Quorum[id] = &internal.SigningProposalParticipant{
Addr: dkgEntry.Addr,
Status: internal.SigningAwaitConfirmation,
UpdatedAt: request.CreatedAt,
}
}
m.payload.SigningProposalPayload.Quorum[request.ParticipantId].Status = internal.SigningConfirmed
m.payload.SigningProposalPayload.CreatedAt = request.CreatedAt
return
// Make response
responseData := responses.SigningProposalParticipantInvitationsResponse{
SigningId: m.payload.SigningProposalPayload.SigningId,
InitiatorId: m.payload.SigningProposalPayload.InitiatorId,
Participants: make([]*responses.SigningProposalParticipantInvitationEntry, 0),
}
for participantId, proposal := range m.payload.SigningProposalPayload.Quorum {
responseEntry := &responses.SigningProposalParticipantInvitationEntry{
ParticipantId: participantId,
Addr: proposal.Addr,
}
responseData.Participants = append(responseData.Participants, responseEntry)
}
return inEvent, responseData, nil
}
func (m *SigningProposalFSM) actionProposalResponseByParticipant(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
@ -105,9 +135,20 @@ func (m *SigningProposalFSM) actionProposalResponseByParticipant(inEvent fsm.Eve
}
// copy(signingProposalParticipant.Commit, request.Commit)
signingProposalParticipant.UpdatedAt = request.CreatedAt
switch inEvent {
case EventConfirmSigningConfirmation:
signingProposalParticipant.Status = internal.SigningConfirmed
case EventDeclineSigningConfirmation:
signingProposalParticipant.Status = internal.SigningDeclined
default:
err = errors.New(fmt.Sprintf("unsupported event for action {inEvent} = {\"%s\"}", inEvent))
return
}
signingProposalParticipant.Status = internal.SigningConfirmed
signingProposalParticipant.UpdatedAt = request.CreatedAt
m.payload.SigningProposalPayload.UpdatedAt = request.CreatedAt
m.payload.SigningQuorumUpdate(request.ParticipantId, signingProposalParticipant)
return
@ -115,35 +156,27 @@ func (m *SigningProposalFSM) actionProposalResponseByParticipant(inEvent fsm.Eve
func (m *SigningProposalFSM) actionValidateSigningProposalConfirmations(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
var (
isContainsError, isContainsExpired bool
isContainsDecline bool
)
m.payloadMu.Lock()
defer m.payloadMu.Unlock()
tm := time.Now()
unconfirmedParticipants := m.payload.SigningQuorumCount()
for _, participant := range m.payload.SigningProposalPayload.Quorum {
if participant.Status == internal.SigningAwaitConfirmation {
if participant.UpdatedAt.Add(config.SigningConfirmationDeadline).Before(tm) {
isContainsExpired = true
}
} else {
if participant.Status == internal.SigningDeclined {
isContainsError = true
} else if participant.Status == internal.SigningConfirmed {
unconfirmedParticipants--
}
}
}
if isContainsError {
outEvent = eventSetSigningConfirmCanceledByTimeoutInternal
if m.payload.SigningProposalPayload.IsExpired() {
outEvent = eventSetSigningConfirmCanceledByParticipantInternal
return
}
if isContainsExpired {
unconfirmedParticipants := m.payload.SigningQuorumCount()
for _, participant := range m.payload.SigningProposalPayload.Quorum {
if participant.Status == internal.SigningDeclined {
isContainsDecline = true
} else if participant.Status == internal.SigningConfirmed {
unconfirmedParticipants--
}
}
if isContainsDecline {
outEvent = eventSetSigningConfirmCanceledByParticipantInternal
return
}
@ -161,3 +194,150 @@ func (m *SigningProposalFSM) actionValidateSigningProposalConfirmations(inEvent
return
}
func (m *SigningProposalFSM) actionPartialKeyConfirmationReceived(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 {SigningProposalPartialKeyRequest}")
return
}
request, ok := args[0].(requests.SigningProposalPartialKeyRequest)
if !ok {
err = errors.New("cannot cast {arg0} to type {SigningProposalPartialKeyRequest}")
return
}
if err = request.Validate(); err != nil {
return
}
if !m.payload.SigningQuorumExists(request.ParticipantId) {
err = errors.New("{ParticipantId} not exist in quorum")
return
}
signingProposalParticipant := m.payload.SigningQuorumGet(request.ParticipantId)
if signingProposalParticipant.Status != internal.SigningAwaitPartialKeys {
err = errors.New(fmt.Sprintf("cannot confirm response with {Status} = {\"%s\"}", signingProposalParticipant.Status))
return
}
copy(signingProposalParticipant.PartialKey, request.PartialKey)
signingProposalParticipant.Status = internal.SigningPartialKeysConfirmed
signingProposalParticipant.UpdatedAt = request.CreatedAt
m.payload.SignatureProposalPayload.UpdatedAt = request.CreatedAt
m.payload.SigningQuorumUpdate(request.ParticipantId, signingProposalParticipant)
return
}
func (m *SigningProposalFSM) actionValidateSigningPartialKeyAwaitConfirmations(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
var (
isContainsError bool
)
m.payloadMu.Lock()
defer m.payloadMu.Unlock()
if m.payload.SigningProposalPayload.IsExpired() {
outEvent = eventSigningPartialKeyCancelByTimeoutInternal
return
}
unconfirmedParticipants := m.payload.SigningQuorumCount()
for _, participant := range m.payload.SigningProposalPayload.Quorum {
if participant.Status == internal.SigningError {
isContainsError = true
} else if participant.Status == internal.SigningPartialKeysConfirmed {
unconfirmedParticipants--
}
}
if isContainsError {
outEvent = eventSigningPartialKeyCancelByErrorInternal
return
}
// The are no declined and timed out participants, check for all confirmations
if unconfirmedParticipants > 0 {
return
}
outEvent = eventSigningPartialKeysConfirmedInternal
for _, participant := range m.payload.SigningProposalPayload.Quorum {
participant.Status = internal.SigningProcess
}
return
}
// Errors
func (m *SigningProposalFSM) 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 {SignatureProposalConfirmationErrorRequest}")
return
}
request, ok := args[0].(requests.SignatureProposalConfirmationErrorRequest)
if !ok {
err = errors.New("cannot cast {arg0} to type {SignatureProposalConfirmationErrorRequest}")
return
}
if err = request.Validate(); err != nil {
return
}
if !m.payload.SigningQuorumExists(request.ParticipantId) {
err = errors.New("{ParticipantId} not exist in quorum")
return
}
signingProposalParticipant := m.payload.SigningQuorumGet(request.ParticipantId)
// TODO: Move to methods
switch inEvent {
case EventSigningPartialKeyError:
switch signingProposalParticipant.Status {
case internal.SigningAwaitPartialKeys:
signingProposalParticipant.Status = internal.SigningError
case internal.SigningPartialKeysConfirmed:
err = errors.New("{Status} already confirmed")
case internal.SigningError:
err = errors.New(fmt.Sprintf("{Status} already has {\"%s\"}", internal.SigningError))
default:
err = errors.New(fmt.Sprintf(
"{Status} now is \"%s\" and cannot set to {\"%s\"}",
signingProposalParticipant.Status,
internal.SigningError,
))
}
default:
err = errors.New(fmt.Sprintf("{%s} event cannot be used for action {actionConfirmationError}", inEvent))
}
if err != nil {
return
}
signingProposalParticipant.Error = request.Error
signingProposalParticipant.UpdatedAt = request.CreatedAt
m.payload.SignatureProposalPayload.UpdatedAt = request.CreatedAt
m.payload.SigningQuorumUpdate(request.ParticipantId, signingProposalParticipant)
return
}

View File

@ -0,0 +1,20 @@
package signing_proposal_fsm
import (
"crypto/rand"
"encoding/base64"
)
const (
dkgTransactionIdLength = 128
)
func generateSigningId() (string, error) {
b := make([]byte, dkgTransactionIdLength)
_, err := rand.Read(b)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), err
}

View File

@ -28,10 +28,6 @@ const (
StateSigningPartialKeysCollected = fsm.State("state_signing_partial_signatures_collected")
// await full
//
// Events
EventSigningInit = fsm.Event("event_signing_init")
@ -49,7 +45,7 @@ const (
eventSigningPartialKeyCancelByTimeoutInternal = fsm.Event("event_signing_partial_key_canceled_by_timeout_internal")
eventSigningPartialKeyCancelByErrorInternal = fsm.Event("event_signing_partial_key_canceled_by_error_internal")
eventSigningPartialKeysConfirmedInternal = fsm.Event("event_signing_partial_keys_confirmed_internal")
EventSigningFinish = fsm.Event("event_signing_finish")
EventSigningRestart = fsm.Event("event_signing_restart")
)
type SigningProposalFSM struct {
@ -82,7 +78,7 @@ func New() internal.DumpedMachineProvider {
// Validate
{Name: eventAutoValidateProposalInternal, SrcState: []fsm.State{StateSigningAwaitConfirmations}, DstState: StateSigningAwaitConfirmations, IsInternal: true, IsAuto: true},
{Name: eventSetProposalValidatedInternal, SrcState: []fsm.State{StateSigningAwaitConfirmations}, DstState: StateSigningAwaitConfirmations, IsInternal: true, IsAuto: true},
{Name: eventSetProposalValidatedInternal, SrcState: []fsm.State{StateSigningAwaitConfirmations}, DstState: StateSigningAwaitPartialKeys, IsInternal: true},
// Canceled
{Name: EventSigningPartialKeyReceived, SrcState: []fsm.State{StateSigningAwaitPartialKeys}, DstState: StateSigningAwaitPartialKeys},
@ -92,10 +88,17 @@ func New() internal.DumpedMachineProvider {
{Name: eventSigningPartialKeysConfirmedInternal, SrcState: []fsm.State{StateSigningAwaitPartialKeys}, DstState: StateSigningPartialKeysCollected, IsInternal: true},
{Name: EventSigningFinish, SrcState: []fsm.State{StateSigningPartialKeysCollected}, DstState: StateSigningIdle, IsInternal: true},
{Name: EventSigningRestart, SrcState: []fsm.State{StateSigningPartialKeysCollected}, DstState: StateSigningIdle, IsInternal: true},
},
fsm.Callbacks{
EventSigningInit: machine.actionInitSigningProposal,
EventSigningInit: machine.actionInitSigningProposal,
EventSigningStart: machine.actionStartSigningProposal,
EventConfirmSigningConfirmation: machine.actionProposalResponseByParticipant,
EventDeclineSigningConfirmation: machine.actionProposalResponseByParticipant,
eventAutoValidateProposalInternal: machine.actionValidateSigningProposalConfirmations,
EventSigningPartialKeyReceived: machine.actionPartialKeyConfirmationReceived,
EventSigningPartialKeyError: machine.actionValidateSigningPartialKeyAwaitConfirmations,
// actionConfirmationError
},
)

View File

@ -23,7 +23,12 @@ type SignatureProposalParticipantsEntry struct {
// Events: "event_sig_proposal_confirm_by_participant"
// "event_sig_proposal_decline_by_participant"
type SignatureProposalParticipantRequest struct {
// Key for link invitations to participants
ParticipantId int
CreatedAt time.Time
}
type SignatureProposalConfirmationErrorRequest struct {
ParticipantId int
Error error
CreatedAt time.Time
}

View File

@ -55,3 +55,19 @@ func (r *SignatureProposalParticipantRequest) Validate() error {
}
return nil
}
func (r *SignatureProposalConfirmationErrorRequest) Validate() error {
if r.ParticipantId < 0 {
return errors.New("{ParticipantId} cannot be a negative number")
}
if r.Error == nil {
return errors.New("{Error} cannot be a nil")
}
if r.CreatedAt.IsZero() {
return errors.New("{CreatedAt} is not set")
}
return nil
}

View File

@ -14,6 +14,7 @@ type SigningProposalStartRequest struct {
// Events: "event_signing_proposal_confirm_by_participant"
// "event_signing_proposal_decline_by_participant"
type SigningProposalParticipantRequest struct {
SigningId string
ParticipantId int
CreatedAt time.Time
}
@ -21,6 +22,7 @@ type SigningProposalParticipantRequest struct {
// States: "state_signing_await_partial_keys"
// Events: "event_signing_partial_key_received"
type SigningProposalPartialKeyRequest struct {
SigningId string
ParticipantId int
PartialKey []byte
CreatedAt time.Time

View File

@ -19,6 +19,10 @@ func (r *SigningProposalStartRequest) Validate() error {
}
func (r *SigningProposalParticipantRequest) Validate() error {
if r.SigningId == "" {
return errors.New("{SigningId} cannot be empty")
}
if r.ParticipantId < 0 {
return errors.New("{ParticipantId} cannot be a negative number")
}
@ -31,6 +35,10 @@ func (r *SigningProposalParticipantRequest) Validate() error {
}
func (r *SigningProposalPartialKeyRequest) Validate() error {
if r.SigningId == "" {
return errors.New("{SigningId} cannot be empty")
}
if r.ParticipantId < 0 {
return errors.New("{ParticipantId} cannot be a negative number")
}

View File

@ -20,6 +20,7 @@ type SignatureProposalParticipantStatusResponse []*SignatureProposalParticipantS
type SignatureProposalParticipantStatusEntry struct {
ParticipantId int
Addr string
DkgPubKey []byte
Status uint8
DkgPubKey []byte
}

View File

@ -1 +1,20 @@
package responses
type SigningProposalParticipantInvitationsResponse struct {
InitiatorId int
Participants []*SigningProposalParticipantInvitationEntry
SigningId string
}
type SigningProposalParticipantInvitationEntry struct {
ParticipantId int
Addr string
}
type SigningProposalParticipantStatusResponse []*SignatureProposalParticipantStatusEntry
type SigningProposalParticipantStatusEntry struct {
ParticipantId int
Addr string
Status uint8
}