You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

381 lines
7.6 KiB

package main
import (
"context"
"encoding/base32"
"encoding/json"
"errors"
"fmt"
"git.wownero.com/onionltd/monero-multisig-client-poc/clients/api"
"git.wownero.com/onionltd/monero-multisig-client-poc/clients/rpc"
"github.com/urfave/cli/v2"
"log"
"strconv"
"time"
)
const Version = "0.1"
type secret struct {
Host string `json:"host"`
Topic string `json:"topic"`
Token string `json:"token"`
Nickname string `json:"nickname"`
}
type Application struct {
app *cli.App
secret secret
peersMultisigInfo []string
rpcCli *rpc.Client
apiCli *api.Client
}
func (a *Application) prepare() cli.BeforeFunc {
decodeMemberSecret := func(s string) error {
b, err := base32.StdEncoding.DecodeString(s)
if err != nil {
return err
}
if err := json.Unmarshal(b, &a.secret); err != nil {
return err
}
return nil
}
setupWallet := func(language string) error {
name := a.secret.Topic
if err := a.rpcCli.OpenWallet(name); err != nil {
if err := a.rpcCli.CreateWallet(name, language); err != nil {
return err
}
}
return nil
}
setupAPIClient := func() *api.Client {
c := api.NewClient("http://"+a.secret.Host, 15*time.Second)
c.SetToken(a.secret.Token)
return c
}
setupRPCClient := func(uri string) *rpc.Client {
return rpc.NewClient(uri)
}
return func(c *cli.Context) error {
if err := decodeMemberSecret(c.String("secret")); err != nil {
return fmt.Errorf("failed to decode the secret: %s", err)
}
a.apiCli = setupAPIClient()
a.rpcCli = setupRPCClient(c.String("wallet-rpc"))
if err := setupWallet(c.String("wallet-language")); err != nil {
return fmt.Errorf("failed to setup wallet: %s", err)
}
return nil
}
}
func (a *Application) handleTransfer() cli.ActionFunc {
isMultisigWallet := func() error {
isMs, err := a.rpcCli.IsMultisig()
if err != nil {
return err
}
if !isMs {
return errors.New("wallet is not multisig wallet")
}
return nil
}
return func(c *cli.Context) error {
if c.Args().Len() < 2 {
return errors.New("Not enough arguments")
}
amount := c.Args().First()
address := c.Args().Slice()[1]
amountXmr, err := strconv.ParseUint(amount, 10, 64)
if err != nil {
return fmt.Errorf("failed to parse the amount argument: %s", err)
}
state := "init"
loop:
for {
switch state {
case "init":
if err := isMultisigWallet(); err != nil {
return err
}
state = "push_prepare_multisig"
case "done":
break loop
default:
return errors.New("undefined state")
}
}
message := api.NewTransferMessage("")
if err := a.rpcCli.Transfer(address, uint(amountXmr), &message); err != nil {
return err
}
return nil
}
}
func (a *Application) handlePrepareWallet() cli.ActionFunc {
isMultisigWallet := func() error {
isMs, err := a.rpcCli.IsMultisig()
if err != nil {
return err
}
if !isMs {
return errors.New("wallet is not multisig wallet")
}
return nil
}
pushPrepareMultisig := func() error {
log.Println("pushPrepareMultisig")
message := api.NewPrepareMultisig("")
if err := a.rpcCli.PrepareMultisig(&message); err != nil {
return err
}
ctx := context.TODO()
if err := a.apiCli.PushMessage(ctx, a.secret.Topic, message); err != nil {
return err
}
return nil
}
waitPeersPrepareMultisig := func() error {
log.Println("waitPeersPrepareMultisig")
ctx := context.TODO()
peersMultisigInfo := map[string]string{}
for {
messages, err := a.apiCli.ListMessages(ctx, a.secret.Topic)
if err != nil {
return err
}
for _, message := range messages {
if message.RPCName != api.PrepareMultisig {
continue
}
peersMultisigInfo[message.Sender] = message.MultisigInfo
}
if len(peersMultisigInfo) == 3 {
break
}
log.Printf("%+v", peersMultisigInfo)
time.Sleep(10 * time.Second)
}
a.peersMultisigInfo = make([]string, 0, 2)
for sender, multisigInfo := range peersMultisigInfo {
if sender == a.secret.Nickname {
continue
}
a.peersMultisigInfo = append(a.peersMultisigInfo, multisigInfo)
}
return nil
}
pushMakeMultisig := func() error {
log.Println("pushMakeMultisig")
message := api.NewMakeMultisig("")
if err := a.rpcCli.MakeMultisig(2, a.peersMultisigInfo, &message); err != nil {
return err
}
ctx := context.TODO()
if err := a.apiCli.PushMessage(ctx, a.secret.Topic, message); err != nil {
return err
}
return nil
}
waitPeersMakeMultisig := func() error {
log.Println("waitPeersMakeMultisig")
ctx := context.TODO()
peersMultisigInfo := map[string]string{}
for {
messages, err := a.apiCli.ListMessages(ctx, a.secret.Topic)
if err != nil {
return err
}
for _, message := range messages {
if message.RPCName != api.MakeMultisig {
continue
}
peersMultisigInfo[message.Sender] = message.MultisigInfo
}
if len(peersMultisigInfo) == 3 {
break
}
log.Printf("%+v", peersMultisigInfo)
time.Sleep(10 * time.Second)
}
a.peersMultisigInfo = make([]string, 0, 2)
for sender, multisigInfo := range peersMultisigInfo {
if sender == a.secret.Nickname {
continue
}
a.peersMultisigInfo = append(a.peersMultisigInfo, multisigInfo)
}
return nil
}
pushMultisigAddress := func() error {
log.Println("pushMultisigAddress")
message := api.NewFinalizeMultisig("")
if err := a.rpcCli.FinalizeMultisig(a.peersMultisigInfo, &message); err != nil {
return err
}
ctx := context.TODO()
if err := a.apiCli.PushMessage(ctx, a.secret.Topic, message); err != nil {
return err
}
return nil
}
waitPeersMultisigAddress := func() error {
log.Println("waitPeersMultisigAddress")
ctx := context.TODO()
peersMultisigAddress := map[string]string{}
for {
messages, err := a.apiCli.ListMessages(ctx, a.secret.Topic)
if err != nil {
return err
}
for _, message := range messages {
if message.RPCName != api.FinalizeMultisig {
continue
}
peersMultisigAddress[message.Sender] = message.Address
}
if len(peersMultisigAddress) == 3 {
break
}
log.Printf("%+v", peersMultisigAddress)
time.Sleep(10 * time.Second)
}
previousAddress := ""
for _, address := range peersMultisigAddress {
if previousAddress == "" {
previousAddress = address
continue
}
if address != previousAddress {
return errors.New("multisig addresses do not match")
}
}
return nil
}
return func(c *cli.Context) error {
state := "init"
loop:
for {
switch state {
case "init":
if err := isMultisigWallet(); err == nil {
return nil
}
state = "push_prepare_multisig"
case "push_prepare_multisig":
if err := pushPrepareMultisig(); err != nil {
return err
}
state = "wait_peers_prepare_multisig"
case "wait_peers_prepare_multisig":
if err := waitPeersPrepareMultisig(); err != nil {
return err
}
state = "push_make_multisig"
case "push_make_multisig":
if err := pushMakeMultisig(); err != nil {
return err
}
state = "wait_peers_make_multisig"
case "wait_peers_make_multisig":
if err := waitPeersMakeMultisig(); err != nil {
return err
}
state = "push_multisig_address"
case "push_multisig_address":
if err := pushMultisigAddress(); err != nil {
return err
}
state = "wait_peers_multisig_address"
case "wait_peers_multisig_address":
if err := waitPeersMultisigAddress(); err != nil {
return err
}
state = "done"
case "done":
if err := isMultisigWallet(); err != nil {
return err
}
break loop
default:
return errors.New("undefined state")
}
}
return nil
}
}
func (a *Application) Run(args []string) error {
return a.app.Run(args)
}