Merge pull request #58 from depools/feat/fsm-visualization

Feat/fsm visualization
This commit is contained in:
Andrew Zavgorodny 2020-10-15 18:15:03 +03:00 committed by GitHub
commit c1068f1532
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 87 additions and 88 deletions

View File

@ -103,3 +103,26 @@ If at any point something goes wrong (timeout reached, the deal is invalid, publ
If not enough participants signal their willingness to sign within a timeout or signal their rejection to sign, signature process is aborted.
We organize logic in the hot node as a set of simple state machines that change state only by external trigger, such as CLI command, message from cold node, or a new message on Bulletin Board. That way it can be easily tested and audited.
# Finite-state machines description
We moved away from the idea of one large state machine that would perform all tasks, so we divided the functionality into three separate state machines:
* SignatureProposalFSM - responsible for collecting agreements to participate in a specific DKG round
* DKGProposalFSM - responsible for collecting a neccessary data (pubkeys, commits, deals, responses and reconstructed pubkeys) for a DKG process
* SigningProposalFSM - responsible for signature process (collecting agreements to sign a message, collecting partial signs and reconstructed full signature)
We implemented a FSMPoolProvider containing all three state machines that we can switch between each other by hand calling necessary events.
For example, when SignatureProposalFSM collected all agreements from every participant it's state becomes *state_sig_proposal_collected*.
That means it's time to start a new DKG round to create shared public key. We can do it by sending *event_dkg_init_process* event to the FSM.
# Visual representation of FSMs
### SignatureProposalFSM
![SignatureProposalFSM](images/sigFSM.png)
### DKGProposalFSM
![DKGProposalFSM](images/dkgFSM.png)
### SigningProposalFSM
![SigningProposalFSM](images/signingFSM.png)

0
fsm-states.md Normal file
View File

View File

@ -2,96 +2,29 @@ package main
import (
"fmt"
"github.com/looplab/fsm"
"github.com/depools/dc4bc/fsm/fsm"
"github.com/depools/dc4bc/fsm/state_machines/dkg_proposal_fsm"
"github.com/depools/dc4bc/fsm/state_machines/signature_proposal_fsm"
"github.com/depools/dc4bc/fsm/state_machines/signing_proposal_fsm"
"log"
)
func main() {
signatureProposalFSM := fsm.NewFSM(
"idle",
fsm.Events{
{Name: "proposal_spotted", Src: []string{"idle"}, Dst: "validate_proposal"},
{Name: "proposal_valid", Src: []string{"validate_proposal"}, Dst: "proposed"},
{Name: "proposal_invalid", Src: []string{"validate_proposal"}, Dst: "idle"},
{Name: "recieve_yay", Src: []string{"proposed"}, Dst: "process_yay"},
{Name: "receive_nay", Src: []string{"proposed"}, Dst: "process_nay"},
{Name: "send_nay", Src: []string{"proposed"}, Dst: "proposed"},
{Name: "send_yay", Src: []string{"proposed"}, Dst: "proposed"},
{Name: "enough_yays", Src: []string{"process_yay"}, Dst: "signing"},
{Name: "enough_nays", Src: []string{"process_nay"}, Dst: "abort"},
{Name: "not_enough_yays", Src: []string{"process_yay"}, Dst: "proposed"},
{Name: "not_enough_nays", Src: []string{"process_nay"}, Dst: "proposed"},
},
fsm.Callbacks{},
)
fmt.Print(fsm.Visualize(signatureProposalFSM))
dkgFSM, ok := dkg_proposal_fsm.New().(*dkg_proposal_fsm.DKGProposalFSM)
if !ok {
log.Fatal("invalid type")
}
fmt.Println(fsm.Visualize(dkgFSM.FSM))
signatureConstructFSM := fsm.NewFSM(
"idle",
fsm.Events{
{Name: "request_airgapped_sig", Src: []string{"signing"}, Dst: "signing"},
{Name: "transmit_airgapped_sig", Src: []string{"signing"}, Dst: "signing"},
{Name: "receive_sig", Src: []string{"signing"}, Dst: "process_sig"},
{Name: "enough_signature_shares", Src: []string{"process_sig"}, Dst: "reconstruct_signature"},
{Name: "not_enough_signature_shares", Src: []string{"process_sig"}, Dst: "signing"},
{Name: "signature_reconstucted", Src: []string{"reconstruct_signature"}, Dst: "publish_signature"},
{Name: "signature_published", Src: []string{"publish_signature"}, Dst: "fin"},
{Name: "failed_to_reconstuct_signature", Src: []string{"reconstruct_signature"}, Dst: "signing"},
},
fsm.Callbacks{},
)
fmt.Print(fsm.Visualize(signatureConstructFSM))
DkgProposeFSM := fsm.NewFSM(
"idle",
fsm.Events{
{Name: "proposal_spotted", Src: []string{"idle"}, Dst: "validate_proposal"},
{Name: "proposal_valid", Src: []string{"validate_proposal"}, Dst: "proposed"},
{Name: "proposal_invalid", Src: []string{"validate_proposal"}, Dst: "idle"},
{Name: "recieve_yay", Src: []string{"proposed"}, Dst: "process_yay"},
{Name: "receive_nay", Src: []string{"proposed"}, Dst: "abort"},
{Name: "send_nay", Src: []string{"proposed"}, Dst: "proposed"},
{Name: "send_yay", Src: []string{"proposed"}, Dst: "proposed"},
{Name: "not_enough_yays", Src: []string{"process_yay"}, Dst: "proposed"},
{Name: "all_yays", Src: []string{"process_yay"}, Dst: "dkg_commitments"},
{Name: "timeout", Src: []string{"proposed"}, Dst: "abort"},
},
fsm.Callbacks{},
)
fmt.Print(fsm.Visualize(DkgProposeFSM))
DkgCommitFSM := fsm.NewFSM(
"dkg_commitments",
fsm.Events{
{Name: "request_airgapped_commitment", Src: []string{"dkg_commitments"}, Dst: "dkg_commitments"},
{Name: "transmit_airgapped_commitment", Src: []string{"dkg_commitments"}, Dst: "dkg_commitments"},
{Name: "recieve_commitment", Src: []string{"dkg_commitments"}, Dst: "process_commitment"},
{Name: "invalid_commitment", Src: []string{"process_commitment"}, Dst: "abort"},
{Name: "all_commitments", Src: []string{"process_commitment"}, Dst: "dkg_deals"},
{Name: "not_enough_commitments", Src: []string{"process_commitment"}, Dst: "dkg_commitments"},
{Name: "timeout", Src: []string{"dkg_commitments"}, Dst: "abort"},
},
fsm.Callbacks{},
)
fmt.Print(fsm.Visualize(DkgCommitFSM))
DkgDealsFSM := fsm.NewFSM(
"dkg_deals",
fsm.Events{
{Name: "pass_commitements_and_request_airgapped_deals", Src: []string{"dkg_deals"}, Dst: "dkg_deals"},
{Name: "transmit_airgapped_deals", Src: []string{"dkg_deals"}, Dst: "dkg_deals"},
{Name: "transmit_airgapped_error", Src: []string{"dkg_deals"}, Dst: "abort"},
{Name: "recieve_deal", Src: []string{"dkg_deals"}, Dst: "process_deal"},
{Name: "not_my_deal", Src: []string{"process_deal"}, Dst: "dkg_deals"},
{Name: "invalid_deal", Src: []string{"process_deal"}, Dst: "abort"},
{Name: "enough_deals", Src: []string{"process_deal"}, Dst: "dkg_construct_tss"},
{Name: "not_enough_deals", Src: []string{"process_deal"}, Dst: "dkg_deals"},
{Name: "pass_deals_and_request_airgapped_public_key", Src: []string{"dkg_construct_tss"}, Dst: "dkg_construct_tss"},
{Name: "transmit_airgapped_public_key", Src: []string{"dkg_construct_tss"}, Dst: "fin"},
{Name: "transmit_airgapped_error", Src: []string{"dkg_construct_tss"}, Dst: "abort"},
{Name: "timeout", Src: []string{"dkg_deals"}, Dst: "abort"},
},
fsm.Callbacks{},
)
fmt.Print(fsm.Visualize(DkgDealsFSM))
sigFSM, ok := signature_proposal_fsm.New().(*signature_proposal_fsm.SignatureProposalFSM)
if !ok {
log.Fatal("invalid type")
}
fmt.Println(fsm.Visualize(sigFSM.FSM))
signingFSM, ok := signing_proposal_fsm.New().(*signing_proposal_fsm.SigningProposalFSM)
if !ok {
log.Fatal("invalid type")
}
fmt.Println(fsm.Visualize(signingFSM.FSM))
}

44
fsm/fsm/utils.go Normal file
View File

@ -0,0 +1,44 @@
package fsm
import (
"bytes"
"fmt"
)
func Visualize(fsm *FSM) string {
var buf bytes.Buffer
states := make(map[string]int)
buf.WriteString(fmt.Sprintf(`digraph fsm {`))
buf.WriteString("\n")
// make sure the initial state is at top
for k, v := range fsm.transitions {
if k.source == fsm.currentState {
states[string(k.source)]++
states[string(v.dstState)]++
buf.WriteString(fmt.Sprintf(` "%s" -> "%s" [ label = "%s" ];`, k.source, v.dstState, k.event))
buf.WriteString("\n")
}
}
for k, v := range fsm.transitions {
if k.source != fsm.currentState {
states[string(k.source)]++
states[string(v.dstState)]++
buf.WriteString(fmt.Sprintf(` "%s" -> "%s" [ label = "%s" ];`, k.source, v.dstState, k.event))
buf.WriteString("\n")
}
}
buf.WriteString("\n")
for k := range states {
buf.WriteString(fmt.Sprintf(` "%s";`, k))
buf.WriteString("\n")
}
buf.WriteString(fmt.Sprintln("}"))
return buf.String()
}

1
go.mod
View File

@ -8,7 +8,6 @@ require (
github.com/google/go-cmp v0.5.0
github.com/google/uuid v1.1.1
github.com/juju/fslock v0.0.0-20160525022230-4d5c94c67b4b
github.com/looplab/fsm v0.1.0
github.com/makiuchi-d/gozxing v0.0.0-20190830103442-eaff64b1ceb7
github.com/prysmaticlabs/prysm v1.0.0-alpha.29.0.20201014075528-022b6667e5d0
github.com/segmentio/kafka-go v0.4.2

BIN
images/dkgFSM.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

BIN
images/sigFSM.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
images/signingFSM.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB