diff --git a/.gitignore b/.gitignore index f26121387e..9fef2c840e 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,4 @@ server/cmd/dexadm/dexadm server/cmd/geogame/geogame internal/libsecp256k1/secp256k1 internal/cmd/xmrswap/xmrswap +internal/cmd/xmrswap/config.json diff --git a/internal/cmd/xmrswap/example-config.json b/internal/cmd/xmrswap/example-config.json new file mode 100644 index 0000000000..3277df0739 --- /dev/null +++ b/internal/cmd/xmrswap/example-config.json @@ -0,0 +1,11 @@ +{ + "alice": { + "xmrhost": "http://127.0.0.1:28284/json_rpc", + "dcrconf": "/home/me/dextest/dcr/trading1/trading1.conf" + }, + "bob": { + "xmrhost": "http://127.0.0.1:28184/json_rpc", + "dcrconf": "/home/me/dextest/dcr/trading2/trading2.conf" + }, + "extraxmrhost": "http://127.0.0.1:28484/json_rpc" + } diff --git a/internal/cmd/xmrswap/main.go b/internal/cmd/xmrswap/main.go index c25efb79a6..c8d499ce7d 100644 --- a/internal/cmd/xmrswap/main.go +++ b/internal/cmd/xmrswap/main.go @@ -4,7 +4,9 @@ import ( "bytes" "context" "encoding/hex" + "encoding/json" "errors" + "flag" "fmt" "math" "math/big" @@ -46,6 +48,7 @@ const ( dcrAmt = 7_000_000 // atoms xmrAmt = 1_000 // 1e12 units dumbFee = int64(6000) + configName = "config.json" ) var ( @@ -53,8 +56,23 @@ var ( dextestDir = filepath.Join(homeDir, "dextest") bobDir = filepath.Join(dextestDir, "xmr", "wallets", "bob") curve = edwards.Edwards() + + // These should be wallets with funds. + alicexmr = "http://127.0.0.1:28284/json_rpc" + bobxmr = "http://127.0.0.1:28184/json_rpc" + alicedcr = filepath.Join(dextestDir, "dcr", "trading1", "trading1.conf") + bobdcr = filepath.Join(dextestDir, "dcr", "trading2", "trading2.conf") + + // This wallet does not need funds or to be loaded. + extraxmr = "http://127.0.0.1:28484/json_rpc" + + testnet bool ) +func init() { + flag.BoolVar(&testnet, "testnet", false, "use testnet") +} + func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -147,19 +165,48 @@ func newRPCWallet(settings map[string]string, logger dex.Logger, net dex.Network return newCombinedClient(nodeRPCClient, params), nil } -func newClient(ctx context.Context, xmrAddr, dcrNode string) (*client, error) { +func newClient(ctx context.Context, xmrAddr, dcrConf string) (*client, error) { xmr := rpc.New(rpc.Config{ Address: xmrAddr, Client: &http.Client{}, }) - settings, err := config.Parse(filepath.Join(dextestDir, "dcr", dcrNode, fmt.Sprintf("%s.conf", dcrNode))) + ticker := time.NewTicker(time.Second * 5) + defer ticker.Stop() + balReq := rpc.GetBalanceRequest{} + i := 0 +out: + for { + select { + case <-ticker.C: + bal, err := xmr.GetBalance(ctx, &balReq) + if err != nil { + return nil, err + } + if bal.UnlockedBalance > xmrAmt*2 { + break out + } + if i%5 == 0 { + fmt.Println("xmr walet has no unlocked funds. Waiting...") + } + i++ + case <-ctx.Done(): + return nil, ctx.Err() + } + + } + + settings, err := config.Parse(dcrConf) if err != nil { return nil, err } settings["account"] = "default" - dcr, err := newRPCWallet(settings, dex.StdOutLogger(dcrNode, slog.LevelTrace), dex.Simnet) + net := dex.Simnet + if testnet { + net = dex.Testnet + } + dcr, err := newRPCWallet(settings, dex.StdOutLogger("client", slog.LevelTrace), net) if err != nil { return nil, err } @@ -265,7 +312,7 @@ func toAtoms(v float64) uint64 { // and open it. Can only create one wallet at a time. func createNewXMRWallet(ctx context.Context, genReq rpc.GenerateFromKeysRequest) (*rpc.Client, error) { xmrChecker := rpc.New(rpc.Config{ - Address: "http://127.0.0.1:28484/json_rpc", + Address: extraxmr, Client: &http.Client{}, }) @@ -294,6 +341,10 @@ func (cl prettyLogger) Write(p []byte) (n int, err error) { } func run(ctx context.Context) error { + if err := parseConfig(); err != nil { + return err + } + pl := prettyLogger{c: color.New(color.FgGreen)} log := dex.NewLogger("T", dex.LevelInfo, pl) @@ -323,6 +374,51 @@ func run(ctx context.Context) error { return nil } +type clientJSON struct { + XMRHost string `json:"xmrhost"` + DCRConf string `json:"dcrconf"` +} + +type configJSON struct { + Alice clientJSON `json:"alice"` + Bob clientJSON `json:"bob"` + ExtraXMRHost string `json:"extraxmrhost"` +} + +func parseConfig() error { + flag.Parse() + + if !testnet { + return nil + } + + ex, err := os.Executable() + if err != nil { + return err + } + + exPath := filepath.Dir(ex) + configPath := filepath.Join(exPath, configName) + + b, err := os.ReadFile(configPath) + if err != nil { + return err + } + + var cj configJSON + if err := json.Unmarshal(b, &cj); err != nil { + return err + } + + alicexmr = cj.Alice.XMRHost + bobxmr = cj.Bob.XMRHost + alicedcr = cj.Alice.DCRConf + bobdcr = cj.Bob.DCRConf + extraxmr = cj.ExtraXMRHost + + return nil +} + // generateDleag starts the trade by creating some keys. func (c *partClient) generateDleag(ctx context.Context) (pubSpendKeyf *edwards.PublicKey, kbvf *edwards.PrivateKey, pubPartSignKeyHalf *secp256k1.PublicKey, dleag []byte, err error) { @@ -623,7 +719,11 @@ func (c *partClient) initXmr(ctx context.Context, viewKey *edwards.PrivateKey, p fullPubKey = append(fullPubKey, c.pubSpendKey.SerializeCompressed()...) fullPubKey = append(fullPubKey, c.viewKey.PubKey().SerializeCompressed()...) - sharedAddr := base58.EncodeAddr(18, fullPubKey) + tag := uint64(18) + if testnet { + tag = 53 + } + sharedAddr := base58.EncodeAddr(tag, fullPubKey) dest := rpc.Destination{ Amount: xmrAmt, @@ -733,7 +833,11 @@ func (c *initClient) redeemXmr(ctx context.Context, initSignKeyHalfSig []byte) ( var fullPubKey []byte fullPubKey = append(fullPubKey, vkbs.PubKey().Serialize()...) fullPubKey = append(fullPubKey, c.viewKey.PubKey().Serialize()...) - walletAddr := base58.EncodeAddr(18, fullPubKey) + tag := uint64(18) + if testnet { + tag = 53 + } + walletAddr := base58.EncodeAddr(tag, fullPubKey) walletFileName := fmt.Sprintf("%s_spend", walletAddr) var viewKeyBytes [32]byte @@ -848,7 +952,11 @@ func (c *partClient) refundXmr(ctx context.Context, partSignKeyHalfSig []byte, e var fullPubKey []byte fullPubKey = append(fullPubKey, vkbs.PubKey().Serialize()...) fullPubKey = append(fullPubKey, c.viewKey.PubKey().Serialize()...) - walletAddr := base58.EncodeAddr(18, fullPubKey) + tag := uint64(18) + if testnet { + tag = 53 + } + walletAddr := base58.EncodeAddr(tag, fullPubKey) walletFileName := fmt.Sprintf("%s_spend", walletAddr) var viewKeyBytes [32]byte @@ -912,14 +1020,12 @@ func (c *partClient) takeDcr(ctx context.Context, lockRefundTxScript []byte, spe // success is a successful trade. func success(ctx context.Context) error { - pc, err := newClient(ctx, "http://127.0.0.1:28284/json_rpc", "trading1") + pc, err := newClient(ctx, alicexmr, alicedcr) if err != nil { return err } alice := partClient{client: pc} - balReq := rpc.GetBalanceRequest{ - AccountIndex: 0, - } + balReq := rpc.GetBalanceRequest{} xmrBal, err := alice.xmr.GetBalance(ctx, &balReq) if err != nil { return err @@ -933,7 +1039,7 @@ func success(ctx context.Context) error { dcrBeforeBal := toAtoms(dcrBal.Balances[0].Total) fmt.Printf("alice dcr balance %v\n", dcrBeforeBal) - ic, err := newClient(ctx, "http://127.0.0.1:28184/json_rpc", "trading2") + ic, err := newClient(ctx, bobxmr, bobdcr) if err != nil { return err } @@ -1001,14 +1107,28 @@ func success(ctx context.Context) error { return err } - // NOTE: This wallet must sync so may take a long time on mainnet. - // TODO: Wait for wallet sync rather than a dumb sleep. - time.Sleep(time.Second * 40) + ticker := time.NewTicker(time.Second * 5) + defer ticker.Stop() + timeout := time.After(time.Minute) +out: + for { + select { + case <-ticker.C: + xmrBal, err = xmrChecker.GetBalance(ctx, &balReq) + if err != nil { + return err + } + if xmrBal.Balance > 0 { + break out + } + case <-timeout: + return errors.New("xmr wallet not synced after one minute") + case <-ctx.Done(): + return ctx.Err() + } - xmrBal, err = xmrChecker.GetBalance(ctx, &balReq) - if err != nil { - return err } + if xmrBal.Balance != xmrAmt { return fmt.Errorf("expected redeem xmr balance of %d but got %d", xmrAmt, xmrBal.Balance) } @@ -1029,13 +1149,13 @@ func success(ctx context.Context) error { // aliceBailsBeforeXmrInit is a trade that fails because alice does nothing after // Bob inits. func aliceBailsBeforeXmrInit(ctx context.Context) error { - pc, err := newClient(ctx, "http://127.0.0.1:28284/json_rpc", "trading1") + pc, err := newClient(ctx, alicexmr, alicedcr) if err != nil { return err } alice := partClient{client: pc} - ic, err := newClient(ctx, "http://127.0.0.1:28184/json_rpc", "trading2") + ic, err := newClient(ctx, alicexmr, alicedcr) if err != nil { return err } @@ -1116,13 +1236,13 @@ func aliceBailsBeforeXmrInit(ctx context.Context) error { // refund is a failed trade where both parties have sent their initial funds and // both get them back minus fees. func refund(ctx context.Context) error { - pc, err := newClient(ctx, "http://127.0.0.1:28284/json_rpc", "trading1") + pc, err := newClient(ctx, alicexmr, alicedcr) if err != nil { return err } alice := partClient{client: pc} - ic, err := newClient(ctx, "http://127.0.0.1:28184/json_rpc", "trading2") + ic, err := newClient(ctx, bobxmr, bobdcr) if err != nil { return err } @@ -1178,15 +1298,30 @@ func refund(ctx context.Context) error { return err } - // NOTE: This wallet must sync so may take a long time on mainnet. - // TODO: Wait for wallet sync rather than a dumb sleep. - time.Sleep(time.Second * 40) - + var bal *rpc.GetBalanceResponse balReq := rpc.GetBalanceRequest{} - bal, err := xmrChecker.GetBalance(ctx, &balReq) - if err != nil { - return err + ticker := time.NewTicker(time.Second * 5) + defer ticker.Stop() + timeout := time.After(time.Minute) +out: + for { + select { + case <-ticker.C: + bal, err = xmrChecker.GetBalance(ctx, &balReq) + if err != nil { + return err + } + if bal.Balance > 0 { + break out + } + case <-timeout: + return errors.New("xmr wallet not synced after one minute") + case <-ctx.Done(): + return ctx.Err() + } + } + if bal.Balance != xmrAmt { return fmt.Errorf("expected refund xmr balance of %d but got %d", xmrAmt, bal.Balance) } @@ -1199,13 +1334,13 @@ func refund(ctx context.Context) error { // bobBailsAfterXmrInit is a failed trade where bob disappears after both parties // init and alice takes all his dcr while losing her xmr. Bob gets nothing. func bobBailsAfterXmrInit(ctx context.Context) error { - pc, err := newClient(ctx, "http://127.0.0.1:28284/json_rpc", "trading1") + pc, err := newClient(ctx, alicexmr, alicedcr) if err != nil { return err } alice := partClient{client: pc} - ic, err := newClient(ctx, "http://127.0.0.1:28184/json_rpc", "trading2") + ic, err := newClient(ctx, bobxmr, bobdcr) if err != nil { return err }