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
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)
|
|
}
|