mirror of https://github.com/certusone/dc4bc.git
594 lines
15 KiB
Go
594 lines
15 KiB
Go
package state_machines
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
sif "github.com/depools/dc4bc/fsm/state_machines/signing_proposal_fsm"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/depools/dc4bc/fsm/fsm"
|
|
dpf "github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm"
|
|
spf "github.com/depools/dc4bc/fsm/state_machines/signature_proposal_fsm"
|
|
"github.com/depools/dc4bc/fsm/types/requests"
|
|
"github.com/depools/dc4bc/fsm/types/responses"
|
|
)
|
|
|
|
const (
|
|
addrMockLen = 32
|
|
keysMockLen = 128
|
|
)
|
|
|
|
type testParticipantsPayload struct {
|
|
Addr string
|
|
HotPrivKey ed25519.PrivateKey
|
|
HotPubKey ed25519.PublicKey
|
|
DkgPubKey []byte
|
|
DkgCommit []byte
|
|
DkgDeal []byte
|
|
DkgResponse []byte
|
|
DkgPartialKey []byte
|
|
}
|
|
|
|
var (
|
|
tm = time.Now()
|
|
|
|
dkgId = "1b7a6382afe0fbe2ff127a5779f5e9b042e685cabefeadcf4ef27c6089a56bfb"
|
|
|
|
// map {addr} -> {participant}
|
|
testAddrMapParticipants = map[string]*testParticipantsPayload{}
|
|
// map {dkg_queue_id} -> {participant}
|
|
testIdMapParticipants = map[int]*testParticipantsPayload{}
|
|
|
|
testParticipantsListRequest = requests.SignatureProposalParticipantsListRequest{
|
|
Participants: []*requests.SignatureProposalParticipantsEntry{},
|
|
CreatedAt: tm,
|
|
}
|
|
|
|
testFSMDump []byte
|
|
)
|
|
|
|
func init() {
|
|
for i := 0; i < 3; i++ {
|
|
|
|
participant := &testParticipantsPayload{
|
|
Addr: base64.StdEncoding.EncodeToString(genDataMock(addrMockLen)),
|
|
HotPrivKey: genDataMock(keysMockLen),
|
|
HotPubKey: genDataMock(keysMockLen),
|
|
DkgPubKey: genDataMock(keysMockLen),
|
|
DkgCommit: genDataMock(keysMockLen),
|
|
DkgDeal: genDataMock(keysMockLen),
|
|
DkgResponse: genDataMock(keysMockLen),
|
|
DkgPartialKey: genDataMock(keysMockLen),
|
|
}
|
|
testAddrMapParticipants[participant.Addr] = participant
|
|
}
|
|
}
|
|
|
|
func TestCreate_Positive(t *testing.T) {
|
|
testFSMInstance, err := Create(dkgId)
|
|
if err != nil {
|
|
t.Fatalf("expected nil error, got {%s}", err)
|
|
}
|
|
|
|
if testFSMInstance == nil {
|
|
t.Fatalf("expected {*FSMInstance}")
|
|
}
|
|
}
|
|
|
|
func genDataMock(len int) []byte {
|
|
data := make([]byte, len)
|
|
rand.Read(data)
|
|
return data
|
|
}
|
|
|
|
func compareErrNil(t *testing.T, got error) {
|
|
if got != nil {
|
|
t.Fatalf("expected nil error, got {%s}", got)
|
|
}
|
|
}
|
|
|
|
func compareFSMInstanceNotNil(t *testing.T, got *FSMInstance) {
|
|
if got == nil {
|
|
t.Fatalf("expected {*FSMInstance}")
|
|
}
|
|
}
|
|
|
|
func compareDumpNotZero(t *testing.T, got []byte) {
|
|
if len(got) == 0 {
|
|
t.Fatalf("expected non zero dump, when executed without error")
|
|
}
|
|
}
|
|
|
|
func compareFSMResponseNotNil(t *testing.T, got *fsm.Response) {
|
|
if got == nil {
|
|
t.Fatalf("expected {*fsm.FSMResponse} got nil")
|
|
}
|
|
}
|
|
|
|
func compareState(t *testing.T, expected fsm.State, got fsm.State) {
|
|
if got != expected {
|
|
t.Fatalf("expected state {%s} got {%s}", expected, got)
|
|
}
|
|
}
|
|
|
|
// Test Workflow
|
|
func Test_SignatureProposal_Init(t *testing.T) {
|
|
testFSMInstance, err := Create(dkgId)
|
|
|
|
compareErrNil(t, err)
|
|
|
|
compareFSMInstanceNotNil(t, testFSMInstance)
|
|
|
|
transactionId := testFSMInstance.Id()
|
|
|
|
if transactionId == "" {
|
|
t.Fatalf("expected {transactionId for dump}")
|
|
}
|
|
|
|
if testFSMInstance.machine.Name() != spf.FsmName {
|
|
t.Fatalf("expected machine name {%s}", spf.FsmName)
|
|
}
|
|
compareState(t, spf.StateParticipantsConfirmationsInit, testFSMInstance.machine.State())
|
|
|
|
testFSMDump, err = testFSMInstance.Dump()
|
|
|
|
compareErrNil(t, err)
|
|
}
|
|
|
|
// EventInitProposal
|
|
func Test_SignatureProposal_EventInitProposal(t *testing.T) {
|
|
var fsmResponse *fsm.Response
|
|
|
|
testFSMInstance, err := FromDump(testFSMDump)
|
|
|
|
compareErrNil(t, err)
|
|
|
|
compareFSMInstanceNotNil(t, testFSMInstance)
|
|
|
|
// Make request
|
|
request := make([]*requests.SignatureProposalParticipantsEntry, 0)
|
|
|
|
for _, participant := range testAddrMapParticipants {
|
|
|
|
request = append(request, &requests.SignatureProposalParticipantsEntry{
|
|
Addr: participant.Addr,
|
|
PubKey: participant.HotPubKey,
|
|
DkgPubKey: participant.DkgPubKey,
|
|
})
|
|
}
|
|
testParticipantsListRequest.Participants = request
|
|
testParticipantsListRequest.SigningThreshold = len(request)
|
|
|
|
fsmResponse, testFSMDump, err = testFSMInstance.Do(spf.EventInitProposal, testParticipantsListRequest)
|
|
|
|
compareErrNil(t, err)
|
|
|
|
compareDumpNotZero(t, testFSMDump)
|
|
|
|
compareFSMResponseNotNil(t, fsmResponse)
|
|
|
|
compareState(t, spf.StateAwaitParticipantsConfirmations, fsmResponse.State)
|
|
|
|
testParticipantsListResponse, ok := fsmResponse.Data.(responses.SignatureProposalParticipantInvitationsResponse)
|
|
|
|
if !ok {
|
|
t.Fatalf("expected response {SignatureProposalParticipantInvitationsResponse}")
|
|
}
|
|
|
|
if len(testParticipantsListResponse) != len(testParticipantsListRequest.Participants) {
|
|
t.Fatalf("expected response len {%d}, got {%d}", len(testParticipantsListRequest.Participants), len(testParticipantsListResponse))
|
|
}
|
|
|
|
for _, participant := range testParticipantsListResponse {
|
|
if _, ok := testIdMapParticipants[participant.ParticipantId]; ok {
|
|
t.Fatalf("expected unique {ParticipantId}")
|
|
}
|
|
|
|
if participant.Addr == "" {
|
|
t.Fatalf("expected not empty {Addr}")
|
|
}
|
|
|
|
participantEntry, ok := testAddrMapParticipants[participant.Addr]
|
|
|
|
if !ok {
|
|
t.Fatalf("expected exist {Addr}")
|
|
}
|
|
|
|
testIdMapParticipants[participant.ParticipantId] = participantEntry
|
|
}
|
|
|
|
}
|
|
|
|
// EventConfirmSignatureProposal
|
|
func Test_SignatureProposal_EventConfirmSignatureProposal(t *testing.T) {
|
|
var fsmResponse *fsm.Response
|
|
|
|
participantsCount := len(testIdMapParticipants)
|
|
|
|
participantCounter := participantsCount
|
|
|
|
for participantId, _ := range testIdMapParticipants {
|
|
participantCounter--
|
|
testFSMInstance, err := FromDump(testFSMDump)
|
|
|
|
compareErrNil(t, err)
|
|
|
|
compareFSMInstanceNotNil(t, testFSMInstance)
|
|
|
|
fsmResponse, testFSMDump, err = testFSMInstance.Do(spf.EventConfirmSignatureProposal, requests.SignatureProposalParticipantRequest{
|
|
ParticipantId: participantId,
|
|
CreatedAt: time.Now(),
|
|
})
|
|
|
|
compareErrNil(t, err)
|
|
|
|
compareDumpNotZero(t, testFSMDump)
|
|
|
|
compareFSMResponseNotNil(t, fsmResponse)
|
|
|
|
if participantCounter > 0 {
|
|
compareState(t, spf.StateAwaitParticipantsConfirmations, fsmResponse.State)
|
|
}
|
|
|
|
}
|
|
|
|
compareState(t, spf.StateSignatureProposalCollected, fsmResponse.State)
|
|
}
|
|
|
|
func Test_DkgProposal_Positive(t *testing.T) {
|
|
var fsmResponse *fsm.Response
|
|
|
|
testFSMInstance, err := FromDump(testFSMDump)
|
|
|
|
compareErrNil(t, err)
|
|
|
|
compareFSMInstanceNotNil(t, testFSMInstance)
|
|
|
|
fsmResponse, testFSMDump, err = testFSMInstance.Do(dpf.EventDKGInitProcess, requests.DefaultRequest{
|
|
CreatedAt: time.Now(),
|
|
})
|
|
|
|
compareErrNil(t, err)
|
|
|
|
compareDumpNotZero(t, testFSMDump)
|
|
|
|
compareFSMResponseNotNil(t, fsmResponse)
|
|
|
|
response, ok := fsmResponse.Data.(responses.DKGProposalPubKeysParticipantResponse)
|
|
|
|
if !ok {
|
|
t.Fatalf("expected response {DKGProposalPubKeysParticipantResponse}")
|
|
}
|
|
|
|
if len(response) != len(testParticipantsListRequest.Participants) {
|
|
t.Fatalf("expected response len {%d}, got {%d}", len(testParticipantsListRequest.Participants), len(response))
|
|
}
|
|
|
|
for _, responseEntry := range response {
|
|
if _, ok := testIdMapParticipants[responseEntry.ParticipantId]; !ok {
|
|
t.Fatalf("expected exist {ParticipantId}")
|
|
}
|
|
|
|
if len(responseEntry.DkgPubKey) == 0 {
|
|
t.Fatalf("expected {DkgPubKey} non zero length")
|
|
}
|
|
|
|
if !reflect.DeepEqual(testIdMapParticipants[responseEntry.ParticipantId].DkgPubKey, responseEntry.DkgPubKey) {
|
|
t.Fatalf("expected valid {DkgPubKey}")
|
|
}
|
|
}
|
|
|
|
compareState(t, dpf.StateDkgCommitsAwaitConfirmations, fsmResponse.State)
|
|
|
|
// Commits
|
|
}
|
|
|
|
func Test_DkgProposal_EventDKGCommitConfirmationReceived(t *testing.T) {
|
|
var fsmResponse *fsm.Response
|
|
|
|
pCounter := 0
|
|
for participantId, participant := range testIdMapParticipants {
|
|
pCounter--
|
|
testFSMInstance, err := FromDump(testFSMDump)
|
|
|
|
compareErrNil(t, err)
|
|
|
|
compareFSMInstanceNotNil(t, testFSMInstance)
|
|
|
|
fsmResponse, testFSMDump, err = testFSMInstance.Do(dpf.EventDKGCommitConfirmationReceived, requests.DKGProposalCommitConfirmationRequest{
|
|
ParticipantId: participantId,
|
|
Commit: participant.DkgCommit,
|
|
CreatedAt: tm,
|
|
})
|
|
|
|
compareErrNil(t, err)
|
|
|
|
compareDumpNotZero(t, testFSMDump)
|
|
|
|
compareFSMResponseNotNil(t, fsmResponse)
|
|
|
|
}
|
|
|
|
compareState(t, dpf.StateDkgDealsAwaitConfirmations, fsmResponse.State)
|
|
|
|
response, ok := fsmResponse.Data.(responses.DKGProposalCommitParticipantResponse)
|
|
|
|
if !ok {
|
|
t.Fatalf("expected response {DKGProposalCommitParticipantResponse}")
|
|
}
|
|
|
|
if len(response) != len(testParticipantsListRequest.Participants) {
|
|
t.Fatalf("expected response len {%d}, got {%d}", len(testParticipantsListRequest.Participants), len(response))
|
|
}
|
|
|
|
for _, responseEntry := range response {
|
|
if _, ok := testIdMapParticipants[responseEntry.ParticipantId]; !ok {
|
|
t.Fatalf("expected exist {ParticipantId}")
|
|
}
|
|
|
|
if len(responseEntry.DkgCommit) == 0 {
|
|
t.Fatalf("expected {DkgCommit} non zero length")
|
|
}
|
|
|
|
if !reflect.DeepEqual(testIdMapParticipants[responseEntry.ParticipantId].DkgCommit, responseEntry.DkgCommit) {
|
|
t.Fatalf("expected valid {DkgCommit}")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Deals
|
|
func Test_DkgProposal_EventDKGDealConfirmationReceived(t *testing.T) {
|
|
var fsmResponse *fsm.Response
|
|
|
|
pCounter := 0
|
|
for participantId, participant := range testIdMapParticipants {
|
|
pCounter--
|
|
testFSMInstance, err := FromDump(testFSMDump)
|
|
|
|
compareErrNil(t, err)
|
|
|
|
compareFSMInstanceNotNil(t, testFSMInstance)
|
|
|
|
fsmResponse, testFSMDump, err = testFSMInstance.Do(dpf.EventDKGDealConfirmationReceived, requests.DKGProposalDealConfirmationRequest{
|
|
ParticipantId: participantId,
|
|
Deal: participant.DkgDeal,
|
|
CreatedAt: tm,
|
|
})
|
|
|
|
compareErrNil(t, err)
|
|
|
|
compareDumpNotZero(t, testFSMDump)
|
|
|
|
compareFSMResponseNotNil(t, fsmResponse)
|
|
|
|
}
|
|
|
|
compareState(t, dpf.StateDkgResponsesAwaitConfirmations, fsmResponse.State)
|
|
|
|
response, ok := fsmResponse.Data.(responses.DKGProposalDealParticipantResponse)
|
|
|
|
if !ok {
|
|
t.Fatalf("expected response {DKGProposalDealParticipantResponse}")
|
|
}
|
|
|
|
if len(response) != len(testParticipantsListRequest.Participants) {
|
|
t.Fatalf("expected response len {%d}, got {%d}", len(testParticipantsListRequest.Participants), len(response))
|
|
}
|
|
|
|
for _, responseEntry := range response {
|
|
if _, ok := testIdMapParticipants[responseEntry.ParticipantId]; !ok {
|
|
t.Fatalf("expected exist {ParticipantId}")
|
|
}
|
|
|
|
if len(responseEntry.DkgDeal) == 0 {
|
|
t.Fatalf("expected {DkgDeal} non zero length")
|
|
}
|
|
|
|
if !reflect.DeepEqual(testIdMapParticipants[responseEntry.ParticipantId].DkgDeal, responseEntry.DkgDeal) {
|
|
t.Fatalf("expected valid {DkgDeal}")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Responses
|
|
func Test_DkgProposal_EventDKGResponseConfirmationReceived(t *testing.T) {
|
|
var fsmResponse *fsm.Response
|
|
|
|
pCounter := 0
|
|
for participantId, participant := range testIdMapParticipants {
|
|
pCounter--
|
|
testFSMInstance, err := FromDump(testFSMDump)
|
|
|
|
compareErrNil(t, err)
|
|
|
|
compareFSMInstanceNotNil(t, testFSMInstance)
|
|
|
|
fsmResponse, testFSMDump, err = testFSMInstance.Do(dpf.EventDKGResponseConfirmationReceived, requests.DKGProposalResponseConfirmationRequest{
|
|
ParticipantId: participantId,
|
|
Response: participant.DkgResponse,
|
|
CreatedAt: tm,
|
|
})
|
|
|
|
compareErrNil(t, err)
|
|
|
|
compareDumpNotZero(t, testFSMDump)
|
|
|
|
compareFSMResponseNotNil(t, fsmResponse)
|
|
|
|
}
|
|
|
|
compareState(t, dpf.StateDkgMasterKeyAwaitConfirmations, fsmResponse.State)
|
|
|
|
response, ok := fsmResponse.Data.(responses.DKGProposalResponseParticipantResponse)
|
|
|
|
if !ok {
|
|
t.Fatalf("expected response {DKGProposalResponseParticipantResponse}")
|
|
}
|
|
|
|
if len(response) != len(testParticipantsListRequest.Participants) {
|
|
t.Fatalf("expected response len {%d}, got {%d}", len(testParticipantsListRequest.Participants), len(response))
|
|
}
|
|
|
|
for _, responseEntry := range response {
|
|
if _, ok := testIdMapParticipants[responseEntry.ParticipantId]; !ok {
|
|
t.Fatalf("expected exist {ParticipantId}")
|
|
}
|
|
|
|
if len(responseEntry.DkgResponse) == 0 {
|
|
t.Fatalf("expected {DkgResponse} non zero length")
|
|
}
|
|
|
|
if !reflect.DeepEqual(testIdMapParticipants[responseEntry.ParticipantId].DkgResponse, responseEntry.DkgResponse) {
|
|
t.Fatalf("expected valid {DkgResponse}")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Master keys
|
|
func Test_DkgProposal_EventDKGMasterKeyConfirmationReceived(t *testing.T) {
|
|
var fsmResponse *fsm.Response
|
|
|
|
pCounter := 0
|
|
for participantId, participant := range testIdMapParticipants {
|
|
pCounter--
|
|
testFSMInstance, err := FromDump(testFSMDump)
|
|
|
|
compareErrNil(t, err)
|
|
|
|
compareFSMInstanceNotNil(t, testFSMInstance)
|
|
|
|
fsmResponse, testFSMDump, err = testFSMInstance.Do(dpf.EventDKGMasterKeyConfirmationReceived, requests.DKGProposalMasterKeyConfirmationRequest{
|
|
ParticipantId: participantId,
|
|
MasterKey: participant.DkgPartialKey,
|
|
CreatedAt: tm,
|
|
})
|
|
|
|
compareErrNil(t, err)
|
|
|
|
compareDumpNotZero(t, testFSMDump)
|
|
|
|
compareFSMResponseNotNil(t, fsmResponse)
|
|
|
|
}
|
|
|
|
compareState(t, dpf.StateDkgMasterKeyCollected, fsmResponse.State)
|
|
return
|
|
}
|
|
|
|
// Signing
|
|
func Test_SigningProposal_Positive(t *testing.T) {
|
|
var fsmResponse *fsm.Response
|
|
|
|
testFSMInstance, err := FromDump(testFSMDump)
|
|
|
|
compareErrNil(t, err)
|
|
|
|
compareFSMInstanceNotNil(t, testFSMInstance)
|
|
|
|
fsmResponse, testFSMDump, err = testFSMInstance.Do(sif.EventSigningInit, requests.DefaultRequest{
|
|
CreatedAt: time.Now(),
|
|
})
|
|
|
|
compareErrNil(t, err)
|
|
|
|
compareDumpNotZero(t, testFSMDump)
|
|
|
|
compareFSMResponseNotNil(t, fsmResponse)
|
|
|
|
compareState(t, sif.StateSigningIdle, fsmResponse.State)
|
|
|
|
// Start
|
|
|
|
testFSMInstance, err = FromDump(testFSMDump)
|
|
|
|
compareErrNil(t, err)
|
|
|
|
compareFSMInstanceNotNil(t, testFSMInstance)
|
|
|
|
fsmResponse, testFSMDump, err = testFSMInstance.Do(sif.EventSigningStart, requests.SigningProposalStartRequest{
|
|
ParticipantId: 1,
|
|
SrcPayload: []byte("message to sign"),
|
|
CreatedAt: time.Now(),
|
|
})
|
|
|
|
compareErrNil(t, err)
|
|
|
|
compareDumpNotZero(t, testFSMDump)
|
|
|
|
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_Parallel(t *testing.T) {
|
|
var (
|
|
id1 = "123"
|
|
id2 = "456"
|
|
)
|
|
testFSMInstance1, err := Create(id1)
|
|
compareErrNil(t, err)
|
|
|
|
compareFSMInstanceNotNil(t, testFSMInstance1)
|
|
|
|
compareState(t, spf.StateParticipantsConfirmationsInit, testFSMInstance1.machine.State())
|
|
|
|
testFSMDump1, err := testFSMInstance1.Dump()
|
|
|
|
compareErrNil(t, err)
|
|
|
|
compareDumpNotZero(t, testFSMDump1)
|
|
|
|
/// fsm2
|
|
testFSMInstance2, err := Create(id2)
|
|
compareErrNil(t, err)
|
|
|
|
compareFSMInstanceNotNil(t, testFSMInstance2)
|
|
|
|
compareState(t, spf.StateParticipantsConfirmationsInit, testFSMInstance2.machine.State())
|
|
|
|
testFSMDump2, err := testFSMInstance2.Dump()
|
|
|
|
compareErrNil(t, err)
|
|
|
|
compareDumpNotZero(t, testFSMDump2)
|
|
|
|
testFSMInstance1, err = FromDump(testFSMDump1)
|
|
|
|
compareErrNil(t, err)
|
|
|
|
testFSMInstance2, err = FromDump(testFSMDump2)
|
|
|
|
compareErrNil(t, err)
|
|
|
|
_, _, err = testFSMInstance1.Do(spf.EventInitProposal, testParticipantsListRequest)
|
|
|
|
s1, err := testFSMInstance1.State()
|
|
|
|
compareErrNil(t, err)
|
|
|
|
s2, err := testFSMInstance2.State()
|
|
|
|
if s1 == s2 {
|
|
t.Fatalf("MATCH STATES {%s}", s1)
|
|
}
|
|
|
|
}
|