mirror of https://github.com/certusone/dc4bc.git
Merge pull request #58 from depools/feat/fsm-visualization
Feat/fsm visualization
This commit is contained in:
commit
c1068f1532
23
README.md
23
README.md
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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
1
go.mod
|
@ -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
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 457 KiB |
Binary file not shown.
After Width: | Height: | Size: 110 KiB |
Binary file not shown.
After Width: | Height: | Size: 288 KiB |
Loading…
Reference in New Issue