mirror of https://github.com/certusone/dc4bc.git
Merge branch 'fsm-draft' into feat/airgapped-client-tests
This commit is contained in:
commit
826da7a0a6
|
@ -134,6 +134,8 @@ func (s DKGParticipantStatus) String() string {
|
||||||
// Signing proposal
|
// Signing proposal
|
||||||
|
|
||||||
type SigningConfirmation struct {
|
type SigningConfirmation struct {
|
||||||
|
SigningId string
|
||||||
|
InitiatorId int
|
||||||
Quorum SigningProposalQuorum
|
Quorum SigningProposalQuorum
|
||||||
RecoveredKey []byte
|
RecoveredKey []byte
|
||||||
SrcPayload []byte
|
SrcPayload []byte
|
||||||
|
@ -152,8 +154,7 @@ type SigningProposalQuorum map[int]*SigningProposalParticipant
|
||||||
type SigningParticipantStatus uint8
|
type SigningParticipantStatus uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SigningIdle SigningParticipantStatus = iota
|
SigningAwaitConfirmation SigningParticipantStatus = iota
|
||||||
SigningAwaitConfirmation
|
|
||||||
SigningConfirmed
|
SigningConfirmed
|
||||||
SigningDeclined
|
SigningDeclined
|
||||||
SigningAwaitPartialKeys
|
SigningAwaitPartialKeys
|
||||||
|
@ -165,8 +166,6 @@ const (
|
||||||
func (s SigningParticipantStatus) String() string {
|
func (s SigningParticipantStatus) String() string {
|
||||||
var str = "undefined"
|
var str = "undefined"
|
||||||
switch s {
|
switch s {
|
||||||
case SigningIdle:
|
|
||||||
str = "SigningIdle"
|
|
||||||
case SigningAwaitConfirmation:
|
case SigningAwaitConfirmation:
|
||||||
str = "SigningAwaitConfirmation"
|
str = "SigningAwaitConfirmation"
|
||||||
case SigningConfirmed:
|
case SigningConfirmed:
|
||||||
|
|
|
@ -2,10 +2,9 @@ package state_machines
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"crypto/rand"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/depools/dc4bc/fsm/state_machines/signing_proposal_fsm"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm"
|
"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"
|
"github.com/depools/dc4bc/fsm/state_machines/signature_proposal_fsm"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
dkgTransactionIdLength = 128
|
|
||||||
)
|
|
||||||
|
|
||||||
// Is machine state scope dump will be locked?
|
// Is machine state scope dump will be locked?
|
||||||
type FSMDump struct {
|
type FSMDump struct {
|
||||||
TransactionId string
|
TransactionId string
|
||||||
|
@ -32,17 +27,6 @@ type FSMInstance struct {
|
||||||
dump *FSMDump
|
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
|
// Create new fsm with unique id
|
||||||
// transactionId required for unique identify dump
|
// transactionId required for unique identify dump
|
||||||
func Create(dkgID string) (*FSMInstance, error) {
|
func Create(dkgID string) (*FSMInstance, error) {
|
||||||
|
@ -56,6 +40,12 @@ func Create(dkgID string) (*FSMInstance, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fsmPoolProvider := fsm_pool.Init(
|
||||||
|
signature_proposal_fsm.New(),
|
||||||
|
dkg_proposal_fsm.New(),
|
||||||
|
signing_proposal_fsm.New(),
|
||||||
|
)
|
||||||
|
|
||||||
machine, err := fsmPoolProvider.EntryPointMachine()
|
machine, err := fsmPoolProvider.EntryPointMachine()
|
||||||
i.machine = machine.(internal.DumpedMachineProvider)
|
i.machine = machine.(internal.DumpedMachineProvider)
|
||||||
i.machine.SetUpPayload(i.dump.Payload)
|
i.machine.SetUpPayload(i.dump.Payload)
|
||||||
|
@ -80,7 +70,16 @@ func FromDump(data []byte) (*FSMInstance, error) {
|
||||||
return nil, errors.New("cannot read machine dump")
|
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)
|
machine, err := fsmPoolProvider.MachineByState(i.dump.State)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
i.machine = machine.(internal.DumpedMachineProvider)
|
i.machine = machine.(internal.DumpedMachineProvider)
|
||||||
i.machine.SetUpPayload(i.dump.Payload)
|
i.machine.SetUpPayload(i.dump.Payload)
|
||||||
return i, err
|
return i, err
|
||||||
|
@ -173,13 +172,3 @@ func (d *FSMDump) Unmarshal(data []byte) error {
|
||||||
|
|
||||||
return json.Unmarshal(data, d)
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
sif "github.com/depools/dc4bc/fsm/state_machines/signing_proposal_fsm"
|
||||||
"log"
|
"log"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -379,6 +380,63 @@ func Test_SignatureProposal_Positive(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
compareState(t, dpf.StateDkgMasterKeyCollected, fsmResponse.State)
|
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) {
|
func Test_DKGProposal_Positive(t *testing.T) {
|
||||||
|
|
|
@ -116,11 +116,12 @@ func (m *SignatureProposalFSM) actionProposalResponseByParticipant(inEvent fsm.E
|
||||||
case EventDeclineProposal:
|
case EventDeclineProposal:
|
||||||
signatureProposalParticipant.Status = internal.SigConfirmationDeclined
|
signatureProposalParticipant.Status = internal.SigConfirmationDeclined
|
||||||
default:
|
default:
|
||||||
err = errors.New("undefined {Event} for action")
|
err = errors.New(fmt.Sprintf("unsupported event for action {inEvent} = {\"%s\"}", inEvent))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
signatureProposalParticipant.UpdatedAt = request.CreatedAt
|
signatureProposalParticipant.UpdatedAt = request.CreatedAt
|
||||||
|
m.payload.SignatureProposalPayload.UpdatedAt = request.CreatedAt
|
||||||
|
|
||||||
m.payload.SigQuorumUpdate(request.ParticipantId, signatureProposalParticipant)
|
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) {
|
func (m *SignatureProposalFSM) actionValidateSignatureProposal(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
|
||||||
var (
|
var (
|
||||||
isContainsDeclined bool
|
isContainsDecline bool
|
||||||
)
|
)
|
||||||
|
|
||||||
m.payloadMu.Lock()
|
m.payloadMu.Lock()
|
||||||
|
@ -146,11 +147,11 @@ func (m *SignatureProposalFSM) actionValidateSignatureProposal(inEvent fsm.Event
|
||||||
if participant.Status == internal.SigConfirmationConfirmed {
|
if participant.Status == internal.SigConfirmationConfirmed {
|
||||||
unconfirmedParticipants--
|
unconfirmedParticipants--
|
||||||
} else if participant.Status == internal.SigConfirmationDeclined {
|
} else if participant.Status == internal.SigConfirmationDeclined {
|
||||||
isContainsDeclined = true
|
isContainsDecline = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isContainsDeclined {
|
if isContainsDecline {
|
||||||
outEvent = eventSetValidationCanceledByParticipant
|
outEvent = eventSetValidationCanceledByParticipant
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -166,6 +167,7 @@ func (m *SignatureProposalFSM) actionValidateSignatureProposal(inEvent fsm.Event
|
||||||
responseEntry := &responses.SignatureProposalParticipantStatusEntry{
|
responseEntry := &responses.SignatureProposalParticipantStatusEntry{
|
||||||
ParticipantId: participantId,
|
ParticipantId: participantId,
|
||||||
Addr: participant.Addr,
|
Addr: participant.Addr,
|
||||||
|
DkgPubKey: participant.DkgPubKey,
|
||||||
Status: uint8(participant.Status),
|
Status: uint8(participant.Status),
|
||||||
}
|
}
|
||||||
responseData = append(responseData, responseEntry)
|
responseData = append(responseData, responseEntry)
|
||||||
|
@ -184,6 +186,7 @@ func (m *SignatureProposalFSM) actionSignatureProposalCanceledByTimeout(inEvent
|
||||||
responseEntry := &responses.SignatureProposalParticipantStatusEntry{
|
responseEntry := &responses.SignatureProposalParticipantStatusEntry{
|
||||||
ParticipantId: participantId,
|
ParticipantId: participantId,
|
||||||
Addr: participant.Addr,
|
Addr: participant.Addr,
|
||||||
|
DkgPubKey: participant.DkgPubKey,
|
||||||
Status: uint8(participant.Status),
|
Status: uint8(participant.Status),
|
||||||
}
|
}
|
||||||
responseData = append(responseData, responseEntry)
|
responseData = append(responseData, responseEntry)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"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"
|
"github.com/depools/dc4bc/fsm/types/responses"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m *SigningProposalFSM) actionInitSigningProposal(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
|
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),
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,9 +59,47 @@ func (m *SigningProposalFSM) actionStartSigningProposal(inEvent fsm.Event, args
|
||||||
return
|
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
|
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) {
|
func (m *SigningProposalFSM) actionProposalResponseByParticipant(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
|
||||||
|
@ -105,8 +135,19 @@ func (m *SigningProposalFSM) actionProposalResponseByParticipant(inEvent fsm.Eve
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy(signingProposalParticipant.Commit, request.Commit)
|
// copy(signingProposalParticipant.Commit, request.Commit)
|
||||||
signingProposalParticipant.UpdatedAt = request.CreatedAt
|
switch inEvent {
|
||||||
|
case EventConfirmSigningConfirmation:
|
||||||
signingProposalParticipant.Status = internal.SigningConfirmed
|
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)
|
m.payload.SigningQuorumUpdate(request.ParticipantId, signingProposalParticipant)
|
||||||
|
|
||||||
|
@ -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) {
|
func (m *SigningProposalFSM) actionValidateSigningProposalConfirmations(inEvent fsm.Event, args ...interface{}) (outEvent fsm.Event, response interface{}, err error) {
|
||||||
var (
|
var (
|
||||||
isContainsError, isContainsExpired bool
|
isContainsDecline bool
|
||||||
)
|
)
|
||||||
|
|
||||||
m.payloadMu.Lock()
|
m.payloadMu.Lock()
|
||||||
defer m.payloadMu.Unlock()
|
defer m.payloadMu.Unlock()
|
||||||
|
|
||||||
tm := time.Now()
|
if m.payload.SigningProposalPayload.IsExpired() {
|
||||||
|
outEvent = eventSetSigningConfirmCanceledByParticipantInternal
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
unconfirmedParticipants := m.payload.SigningQuorumCount()
|
unconfirmedParticipants := m.payload.SigningQuorumCount()
|
||||||
for _, participant := range m.payload.SigningProposalPayload.Quorum {
|
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 {
|
if participant.Status == internal.SigningDeclined {
|
||||||
isContainsError = true
|
isContainsDecline = true
|
||||||
} else if participant.Status == internal.SigningConfirmed {
|
} else if participant.Status == internal.SigningConfirmed {
|
||||||
unconfirmedParticipants--
|
unconfirmedParticipants--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if isContainsError {
|
if isContainsDecline {
|
||||||
outEvent = eventSetSigningConfirmCanceledByTimeoutInternal
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if isContainsExpired {
|
|
||||||
outEvent = eventSetSigningConfirmCanceledByParticipantInternal
|
outEvent = eventSetSigningConfirmCanceledByParticipantInternal
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -161,3 +194,150 @@ func (m *SigningProposalFSM) actionValidateSigningProposalConfirmations(inEvent
|
||||||
|
|
||||||
return
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -28,10 +28,6 @@ const (
|
||||||
|
|
||||||
StateSigningPartialKeysCollected = fsm.State("state_signing_partial_signatures_collected")
|
StateSigningPartialKeysCollected = fsm.State("state_signing_partial_signatures_collected")
|
||||||
|
|
||||||
// await full
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
// Events
|
// Events
|
||||||
|
|
||||||
EventSigningInit = fsm.Event("event_signing_init")
|
EventSigningInit = fsm.Event("event_signing_init")
|
||||||
|
@ -49,7 +45,7 @@ const (
|
||||||
eventSigningPartialKeyCancelByTimeoutInternal = fsm.Event("event_signing_partial_key_canceled_by_timeout_internal")
|
eventSigningPartialKeyCancelByTimeoutInternal = fsm.Event("event_signing_partial_key_canceled_by_timeout_internal")
|
||||||
eventSigningPartialKeyCancelByErrorInternal = fsm.Event("event_signing_partial_key_canceled_by_error_internal")
|
eventSigningPartialKeyCancelByErrorInternal = fsm.Event("event_signing_partial_key_canceled_by_error_internal")
|
||||||
eventSigningPartialKeysConfirmedInternal = fsm.Event("event_signing_partial_keys_confirmed_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 {
|
type SigningProposalFSM struct {
|
||||||
|
@ -82,7 +78,7 @@ func New() internal.DumpedMachineProvider {
|
||||||
// Validate
|
// Validate
|
||||||
{Name: eventAutoValidateProposalInternal, SrcState: []fsm.State{StateSigningAwaitConfirmations}, DstState: StateSigningAwaitConfirmations, IsInternal: true, IsAuto: true},
|
{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
|
// Canceled
|
||||||
{Name: EventSigningPartialKeyReceived, SrcState: []fsm.State{StateSigningAwaitPartialKeys}, DstState: StateSigningAwaitPartialKeys},
|
{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: 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{
|
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
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,12 @@ type SignatureProposalParticipantsEntry struct {
|
||||||
// Events: "event_sig_proposal_confirm_by_participant"
|
// Events: "event_sig_proposal_confirm_by_participant"
|
||||||
// "event_sig_proposal_decline_by_participant"
|
// "event_sig_proposal_decline_by_participant"
|
||||||
type SignatureProposalParticipantRequest struct {
|
type SignatureProposalParticipantRequest struct {
|
||||||
// Key for link invitations to participants
|
|
||||||
ParticipantId int
|
ParticipantId int
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SignatureProposalConfirmationErrorRequest struct {
|
||||||
|
ParticipantId int
|
||||||
|
Error error
|
||||||
|
CreatedAt time.Time
|
||||||
|
}
|
||||||
|
|
|
@ -55,3 +55,19 @@ func (r *SignatureProposalParticipantRequest) Validate() error {
|
||||||
}
|
}
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ type SigningProposalStartRequest struct {
|
||||||
// Events: "event_signing_proposal_confirm_by_participant"
|
// Events: "event_signing_proposal_confirm_by_participant"
|
||||||
// "event_signing_proposal_decline_by_participant"
|
// "event_signing_proposal_decline_by_participant"
|
||||||
type SigningProposalParticipantRequest struct {
|
type SigningProposalParticipantRequest struct {
|
||||||
|
SigningId string
|
||||||
ParticipantId int
|
ParticipantId int
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
}
|
}
|
||||||
|
@ -21,6 +22,7 @@ type SigningProposalParticipantRequest struct {
|
||||||
// States: "state_signing_await_partial_keys"
|
// States: "state_signing_await_partial_keys"
|
||||||
// Events: "event_signing_partial_key_received"
|
// Events: "event_signing_partial_key_received"
|
||||||
type SigningProposalPartialKeyRequest struct {
|
type SigningProposalPartialKeyRequest struct {
|
||||||
|
SigningId string
|
||||||
ParticipantId int
|
ParticipantId int
|
||||||
PartialKey []byte
|
PartialKey []byte
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
|
|
|
@ -19,6 +19,10 @@ func (r *SigningProposalStartRequest) Validate() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SigningProposalParticipantRequest) Validate() error {
|
func (r *SigningProposalParticipantRequest) Validate() error {
|
||||||
|
if r.SigningId == "" {
|
||||||
|
return errors.New("{SigningId} cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
if r.ParticipantId < 0 {
|
if r.ParticipantId < 0 {
|
||||||
return errors.New("{ParticipantId} cannot be a negative number")
|
return errors.New("{ParticipantId} cannot be a negative number")
|
||||||
}
|
}
|
||||||
|
@ -31,6 +35,10 @@ func (r *SigningProposalParticipantRequest) Validate() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *SigningProposalPartialKeyRequest) Validate() error {
|
func (r *SigningProposalPartialKeyRequest) Validate() error {
|
||||||
|
if r.SigningId == "" {
|
||||||
|
return errors.New("{SigningId} cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
if r.ParticipantId < 0 {
|
if r.ParticipantId < 0 {
|
||||||
return errors.New("{ParticipantId} cannot be a negative number")
|
return errors.New("{ParticipantId} cannot be a negative number")
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ type SignatureProposalParticipantStatusResponse []*SignatureProposalParticipantS
|
||||||
type SignatureProposalParticipantStatusEntry struct {
|
type SignatureProposalParticipantStatusEntry struct {
|
||||||
ParticipantId int
|
ParticipantId int
|
||||||
Addr string
|
Addr string
|
||||||
|
DkgPubKey []byte
|
||||||
Status uint8
|
Status uint8
|
||||||
DkgPubKey []byte
|
DkgPubKey []byte
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,20 @@
|
||||||
package responses
|
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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue