diff --git a/client/asset/eth/eth.go b/client/asset/eth/eth.go index 2d46c6f1f4..13d71aff2c 100644 --- a/client/asset/eth/eth.go +++ b/client/asset/eth/eth.go @@ -11,6 +11,7 @@ import ( "crypto/sha256" "encoding/binary" "encoding/hex" + "encoding/json" "errors" "fmt" "math" @@ -73,7 +74,7 @@ const ( walletTypeGeth = "geth" walletTypeRPC = "rpc" - providersKey = "providers" + providersKey = "providersv1" // confCheckTimeout is the amount of time allowed to check for // confirmations. Testing on testnet has shown spikes up to 2.5 @@ -101,13 +102,13 @@ var ( } RPCOpts = []*asset.ConfigOption{ { - Key: providersKey, - DisplayName: "Provider", - Description: "Specify one or more providers. For infrastructure " + + Key: providersKey, + RepeatableDisplayName: []string{"Provider", "JWT secret"}, + RepeatableDescription: []string{"Specify one or more providers. For infrastructure " + "providers, use an https address. Only url-based authentication " + "is supported. For a local node, use the filepath to an IPC file.", - Repeatable: providerDelimiter, - Required: true, + "Specify a jwt secret if communication with a geth full node over ws."}, + Required: true, }, } // WalletInfo defines some general information about a Ethereum wallet. @@ -515,6 +516,31 @@ func CreateWallet(cfg *asset.CreateWalletParams) error { return createWallet(cfg, false) } +// endpointsFromSettings parses endpoints from the setting map. Endpoints are +// stored as and array of and array of strings. +func endpointsFromSettings(settings map[string]string) ([]endpoint, error) { + providerDef := settings[providersKey] + if len(providerDef) == 0 { + return nil, errors.New("no providers specified") + } + var values [][]string + err := json.Unmarshal([]byte(providerDef), &values) + if err != nil { + return nil, err + } + endpoints := make([]endpoint, len(values)) + for i, v := range values { + switch len(v) { + case 2: + endpoints[i].jwt = v[1] + fallthrough + case 1: + endpoints[i].addr = v[0] + } + } + return endpoints, nil +} + func createWallet(createWalletParams *asset.CreateWalletParams, skipConnect bool) error { switch createWalletParams.Type { case walletTypeGeth: @@ -557,12 +583,10 @@ func createWallet(createWalletParams *asset.CreateWalletParams, skipConnect bool case walletTypeRPC: // Check that we can connect to all endpoints. - providerDef := createWalletParams.Settings[providersKey] - if len(providerDef) == 0 { - return errors.New("no providers specified") + endpoints, err := endpointsFromSettings(createWalletParams.Settings) + if err != nil { + return fmt.Errorf("unable to read endpoints: %v", err) } - endpoints := strings.Split(providerDef, providerDelimiter) - n := len(endpoints) // TODO: This procedure may actually work for walletTypeGeth too. ks := keystore.NewKeyStore(filepath.Join(walletDir, "keystore"), keystore.LightScryptN, keystore.LightScryptP) @@ -576,14 +600,15 @@ func createWallet(createWalletParams *asset.CreateWalletParams, skipConnect bool ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() - var unknownEndpoints []string + var unknownEndpoints []endpoint - for _, endpoint := range endpoints { - known, compliant := providerIsCompliant(endpoint) + for _, ep := range endpoints { + addr := ep.addr + known, compliant := providerIsCompliant(addr) if known && !compliant { - return fmt.Errorf("provider %q is known to have an insufficient API for DEX", endpoint) + return fmt.Errorf("provider %q is known to have an insufficient API for DEX", addr) } else if !known { - unknownEndpoints = append(unknownEndpoints, endpoint) + unknownEndpoints = append(unknownEndpoints, ep) } } @@ -597,8 +622,8 @@ func createWallet(createWalletParams *asset.CreateWalletParams, skipConnect bool p.ec.Close() } }() - if len(providers) != n { - return fmt.Errorf("Could not connect to all providers") + if len(providers) != len(endpoints) { + return errors.New("could not connect to all providers") } if err := checkProvidersCompliance(ctx, walletDir, providers, createWalletParams.Logger); err != nil { return err @@ -727,7 +752,10 @@ func (w *ETHWallet) Connect(ctx context.Context) (_ *sync.WaitGroup, err error) // } return nil, asset.ErrWalletTypeDisabled case walletTypeRPC: - endpoints := strings.Split(w.settings[providersKey], " ") + endpoints, err := endpointsFromSettings(w.settings) + if err != nil { + return nil, fmt.Errorf("unable to read endpoints: %v", err) + } ethCfg, err := ethChainConfig(w.net) if err != nil { return nil, err @@ -737,7 +765,7 @@ func (w *ETHWallet) Connect(ctx context.Context) (_ *sync.WaitGroup, err error) // Point to a harness node on simnet, if not specified. if w.net == dex.Simnet && len(endpoints) == 0 { u, _ := user.Current() - endpoints = append(endpoints, filepath.Join(u.HomeDir, "dextest", "eth", "beta", "node", "geth.ipc")) + endpoints = append(endpoints, endpoint{addr: filepath.Join(u.HomeDir, "dextest", "eth", "beta", "node", "geth.ipc")}) } cl, err = newMultiRPCClient(w.dir, endpoints, w.log.SubLogger("RPC"), chainConfig, big.NewInt(chainIDs[w.net]), w.net) diff --git a/client/asset/eth/eth_test.go b/client/asset/eth/eth_test.go index c10a88e3e1..5251bfe86d 100644 --- a/client/asset/eth/eth_test.go +++ b/client/asset/eth/eth_test.go @@ -2938,7 +2938,7 @@ func TestDriverOpen(t *testing.T) { logger := dex.StdOutLogger("ETHTEST", dex.LevelOff) tmpDir := t.TempDir() - settings := map[string]string{providersKey: "a.ipc"} + settings := map[string]string{providersKey: `[["a.ipc",""]]`} err := createWallet(&asset.CreateWalletParams{ Type: walletTypeRPC, Seed: encode.RandomBytes(32), @@ -2989,7 +2989,7 @@ func TestDriverExists(t *testing.T) { drv := &Driver{} tmpDir := t.TempDir() - settings := map[string]string{providersKey: "a.ipc"} + settings := map[string]string{providersKey: `[["a.ipc",""]]`} // no wallet exists, err := drv.Exists(walletTypeRPC, tmpDir, settings, dex.Simnet) @@ -4556,7 +4556,7 @@ func testMaxSwapRedeemLots(t *testing.T, assetID uint32) { logger := dex.StdOutLogger("ETHTEST", dex.LevelOff) tmpDir := t.TempDir() - settings := map[string]string{providersKey: "a.ipc"} + settings := map[string]string{providersKey: `[["a.ipc",""]]`} err := createWallet(&asset.CreateWalletParams{ Type: walletTypeRPC, Seed: encode.RandomBytes(32), diff --git a/client/asset/eth/multirpc.go b/client/asset/eth/multirpc.go index f7247197d9..b4e09b5fac 100644 --- a/client/asset/eth/multirpc.go +++ b/client/asset/eth/multirpc.go @@ -14,6 +14,7 @@ import ( "fmt" "math/big" "math/rand" + "net/http" "net/url" "os" "path/filepath" @@ -206,7 +207,7 @@ func (p *provider) subscribeHeaders(ctx context.Context, sub ethereum.Subscripti return sub, nil } if time.Since(lastWarning) > 5*time.Minute { - log.Warnf("can't resubscribe to %q headers: %v", err) + log.Warnf("can't resubscribe to %q headers: %v", p.host, err) } select { case <-time.After(time.Second * 30): @@ -254,7 +255,7 @@ func (p *provider) subscribeHeaders(ctx context.Context, sub ethereum.Subscripti return } log.Errorf("%q header subscription error: %v", p.host, err) - log.Info("Attempting to resubscribe to %q block headers", p.host) + log.Infof("Attempting to resubscribe to %q block headers", p.host) sub, err = newSub() if err != nil { // context cancelled return @@ -282,6 +283,11 @@ type receiptRecord struct { confirmed bool } +type endpoint struct { + addr string + jwt string +} + // multiRPCClient is an ethFetcher backed by one or more public RPC providers. type multiRPCClient struct { cfg *params.ChainConfig @@ -290,7 +296,7 @@ type multiRPCClient struct { chainID *big.Int providerMtx sync.Mutex - endpoints []string + endpoints []endpoint providers []*provider lastNonce struct { @@ -316,7 +322,7 @@ type multiRPCClient struct { var _ ethFetcher = (*multiRPCClient)(nil) -func newMultiRPCClient(dir string, endpoints []string, log dex.Logger, cfg *params.ChainConfig, chainID *big.Int, net dex.Network) (*multiRPCClient, error) { +func newMultiRPCClient(dir string, endpoints []endpoint, log dex.Logger, cfg *params.ChainConfig, chainID *big.Int, net dex.Network) (*multiRPCClient, error) { walletDir := getWalletDir(dir, net) creds, err := pathCredentials(filepath.Join(walletDir, "keystore")) if err != nil { @@ -340,7 +346,7 @@ func newMultiRPCClient(dir string, endpoints []string, log dex.Logger, cfg *para // list of providers that were successfully connected. It is not an error for a // connection to fail. The caller can infer failed connections from the length // and contents of the returned provider list. -func connectProviders(ctx context.Context, endpoints []string, log dex.Logger, chainID *big.Int) ([]*provider, error) { +func connectProviders(ctx context.Context, endpoints []endpoint, log dex.Logger, chainID *big.Int) ([]*provider, error) { providers := make([]*provider, 0, len(endpoints)) var success bool @@ -352,7 +358,7 @@ func connectProviders(ctx context.Context, endpoints []string, log dex.Logger, c } }() - for _, endpoint := range endpoints { + for _, ep := range endpoints { // First try to get a websocket connection. Websockets have a header // feed, so are much preferred to http connections. So much so, that // we'll do some path inspection here and make an attempt to find a @@ -362,10 +368,14 @@ func connectProviders(ctx context.Context, endpoints []string, log dex.Logger, c var sub ethereum.Subscription var h chan *types.Header host := providerIPC - if !strings.HasSuffix(endpoint, ".ipc") { - wsURL, err := url.Parse(endpoint) + addr := ep.addr + if strings.HasSuffix(addr, ".ipc") { + // Clean file path. + addr = dex.CleanAndExpandPath(addr) + } else { + wsURL, err := url.Parse(addr) if err != nil { - return nil, fmt.Errorf("Failed to parse url %q", endpoint) + return nil, fmt.Errorf("Failed to parse url %q", addr) } host = wsURL.Host ogScheme := wsURL.Scheme @@ -376,7 +386,7 @@ func connectProviders(ctx context.Context, endpoints []string, log dex.Logger, c wsURL.Scheme = "ws" case "ws", "wss": default: - return nil, fmt.Errorf("unknown scheme for endpoint %q: %q", endpoint, wsURL.Scheme) + return nil, fmt.Errorf("unknown scheme for endpoint %q: %q", addr, wsURL.Scheme) } replaced := ogScheme != wsURL.Scheme @@ -392,7 +402,18 @@ func connectProviders(ctx context.Context, endpoints []string, log dex.Logger, c host = providerRivetCloud } - rpcClient, err = rpc.DialWebsocket(ctx, wsURL.String(), "") + if ep.jwt == "" { + rpcClient, err = rpc.DialWebsocket(ctx, wsURL.String(), "") + } else { + // Geth clients should always be able to get a + // websocket connection, making http unnecessary. + var authFn func(h http.Header) error + authFn, err = dexeth.JWTHTTPAuthFn(ep.jwt) + if err != nil { + return nil, fmt.Errorf("unable to create auth function: %v", err) + } + rpcClient, err = rpc.DialOptions(ctx, wsURL.String(), rpc.WithHTTPAuth(authFn)) + } if err == nil { ec = ethclient.NewClient(rpcClient) h = make(chan *types.Header, 8) @@ -410,7 +431,7 @@ func connectProviders(ctx context.Context, endpoints []string, log dex.Logger, c if replaced { log.Debugf("couldn't get a websocket connection for %q (original scheme: %q) (OK)", wsURL, ogScheme) } else { - log.Errorf("failed to get websocket connection to %q. attempting http(s) fallback: error = %v", endpoint, err) + log.Errorf("failed to get websocket connection to %q. attempting http(s) fallback: error = %v", addr, err) } } } @@ -418,9 +439,9 @@ func connectProviders(ctx context.Context, endpoints []string, log dex.Logger, c // path discrimination, so I won't even try to validate the protocol. if ec == nil { var err error - rpcClient, err = rpc.Dial(endpoint) + rpcClient, err = rpc.Dial(addr) if err != nil { - log.Errorf("error creating http client for %q: %v", endpoint, err) + log.Errorf("error creating http client for %q: %v", addr, err) continue } ec = ethclient.NewClient(rpcClient) @@ -431,12 +452,12 @@ func connectProviders(ctx context.Context, endpoints []string, log dex.Logger, c if err != nil { // If we can't get a header, don't use this provider. ec.Close() - log.Errorf("Failed to get chain ID from %q: %v", endpoint, err) + log.Errorf("Failed to get chain ID from %q: %v", addr, err) continue } if chainID.Cmp(reportedChainID) != 0 { ec.Close() - log.Errorf("%q reported wrong chain ID. expected %d, got %d", endpoint, chainID, reportedChainID) + log.Errorf("%q reported wrong chain ID. expected %d, got %d", addr, chainID, reportedChainID) continue } @@ -444,7 +465,7 @@ func connectProviders(ctx context.Context, endpoints []string, log dex.Logger, c if err != nil { // If we can't get a header, don't use this provider. ec.Close() - log.Errorf("Failed to get header from %q: %v", endpoint, err) + log.Errorf("Failed to get header from %q: %v", addr, err) continue } @@ -547,11 +568,10 @@ func (m *multiRPCClient) voidUnusedNonce() { } func (m *multiRPCClient) reconfigure(ctx context.Context, settings map[string]string) error { - providerDef := settings[providersKey] - if len(providerDef) == 0 { - return errors.New("no providers specified") + endpoints, err := endpointsFromSettings(settings) + if err != nil { + return fmt.Errorf("unable to read endpoints: %v", err) } - endpoints := strings.Split(providerDef, " ") providers, err := connectProviders(ctx, endpoints, m.log, m.chainID) if err != nil { return err diff --git a/client/asset/eth/multirpc_live_test.go b/client/asset/eth/multirpc_live_test.go index 5dfdfb6f71..e6294c11ff 100644 --- a/client/asset/eth/multirpc_live_test.go +++ b/client/asset/eth/multirpc_live_test.go @@ -11,7 +11,6 @@ import ( "os" "os/exec" "path/filepath" - "strings" "testing" "time" @@ -25,8 +24,9 @@ import ( ) const ( - deltaHTTPPort = "38556" - deltaWSPort = "38557" + alphaAuthedPort = "8552" + betaAuthedPort = "8553" + jwtSecret = "0x45747261485f394e52346574347a4d78527941734f30512d4e32383dbabababa" ) var ( @@ -46,7 +46,7 @@ func mine(ctx context.Context) error { return err } -func testEndpoint(endpoints []string, syncBlocks uint64, tFunc func(context.Context, *multiRPCClient)) error { +func testEndpoint(endpoints []endpoint, syncBlocks uint64, tFunc func(context.Context, *multiRPCClient)) error { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() @@ -102,20 +102,21 @@ func testEndpoint(endpoints []string, syncBlocks uint64, tFunc func(context.Cont return nil } +// NOTE: This will be upgraded to websocket and does not test a http path in earnest. func TestHTTP(t *testing.T) { - if err := testEndpoint([]string{"http://localhost:" + deltaHTTPPort}, 2, nil); err != nil { + if err := testEndpoint([]endpoint{{addr: "http://localhost:" + alphaAuthedPort, jwt: jwtSecret}}, 2, nil); err != nil { t.Fatal(err) } } func TestWS(t *testing.T) { - if err := testEndpoint([]string{"ws://localhost:" + deltaWSPort}, 2, nil); err != nil { + if err := testEndpoint([]endpoint{{addr: "ws://localhost:" + betaAuthedPort, jwt: jwtSecret}}, 2, nil); err != nil { t.Fatal(err) } } func TestWSTxLogs(t *testing.T) { - if err := testEndpoint([]string{"ws://localhost:" + deltaWSPort}, 2, func(ctx context.Context, cl *multiRPCClient) { + if err := testEndpoint([]endpoint{{addr: "ws://localhost:" + alphaAuthedPort, jwt: jwtSecret}}, 2, func(ctx context.Context, cl *multiRPCClient) { for i := 0; i < 3; i++ { time.Sleep(time.Second) harnessCmd(ctx, "./sendtoaddress", cl.creds.addr.String(), "1") @@ -130,9 +131,9 @@ func TestWSTxLogs(t *testing.T) { } func TestSimnetMultiRPCClient(t *testing.T) { - endpoints := []string{ - "ws://localhost:" + deltaWSPort, - "http://localhost:" + deltaHTTPPort, + endpoints := []endpoint{ + {addr: "ws://localhost:" + alphaAuthedPort, jwt: jwtSecret}, + {addr: "http://localhost:" + betaAuthedPort, jwt: jwtSecret}, // NOTE: Will be upgraded to a websocket. } nonceProviderStickiness = time.Second / 2 @@ -238,9 +239,13 @@ func TestMonitorMainnet(t *testing.T) { func testMonitorNet(t *testing.T, net dex.Network) { providerFile := readProviderFile(t, net) + endpoints := make([]endpoint, 0, 1) + for _, addr := range providerFile.Providers { + endpoints = append(endpoints, endpoint{addr: addr}) + } dir, _ := os.MkdirTemp("", "") - cl, err := tRPCClient(dir, providerFile.Seed, providerFile.Providers, net, true) + cl, err := tRPCClient(dir, providerFile.Seed, endpoints, net, true) if err != nil { t.Fatal(err) } @@ -255,13 +260,13 @@ func testMonitorNet(t *testing.T, net dex.Network) { } func TestRPC(t *testing.T) { - endpoint := os.Getenv("PROVIDER") - if endpoint == "" { + addr := os.Getenv("PROVIDER") + if addr == "" { t.Fatalf("specify a provider in the PROVIDER environmental variable") } dir, _ := os.MkdirTemp("", "") defer os.RemoveAll(dir) - cl, err := tRPCClient(dir, encode.RandomBytes(32), []string{endpoint}, dex.Mainnet, true) + cl, err := tRPCClient(dir, encode.RandomBytes(32), []endpoint{{addr: addr}}, dex.Mainnet, true) if err != nil { t.Fatal(err) } @@ -292,12 +297,12 @@ var freeServers = []string{ } func TestFreeServers(t *testing.T) { - runTest := func(endpoint string) error { + runTest := func(ep endpoint) error { dir, _ := os.MkdirTemp("", "") defer os.RemoveAll(dir) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - cl, err := tRPCClient(dir, encode.RandomBytes(32), []string{endpoint}, dex.Mainnet, true) + cl, err := tRPCClient(dir, encode.RandomBytes(32), []endpoint{ep}, dex.Mainnet, true) if err != nil { return fmt.Errorf("tRPCClient error: %v", err) } @@ -309,18 +314,19 @@ func TestFreeServers(t *testing.T) { if err := tt.f(p); err != nil { return fmt.Errorf("%q error: %v", tt.name, err) } - fmt.Printf("#### %q passed %q \n", endpoint, tt.name) + fmt.Printf("#### %q passed %q \n", ep.addr, tt.name) } return nil }) } passes, fails := make([]string, 0), make(map[string]error, 0) - for _, endpoint := range freeServers { - if err := runTest(endpoint); err != nil { - fails[endpoint] = err + for _, addr := range freeServers { + ep := endpoint{addr: addr} + if err := runTest(ep); err != nil { + fails[addr] = err } else { - passes = append(passes, endpoint) + passes = append(passes, addr) } } for _, pass := range passes { @@ -333,12 +339,16 @@ func TestFreeServers(t *testing.T) { func TestMainnetCompliance(t *testing.T) { providerFile := readProviderFile(t, dex.Mainnet) + endpoints := make([]endpoint, 0, len(providerFile.Providers)) + for _, addr := range providerFile.Providers { + endpoints = append(endpoints, endpoint{addr: addr}) + } dir, _ := os.MkdirTemp("", "") ctx, cancel := context.WithCancel(context.Background()) defer cancel() log := dex.StdOutLogger("T", dex.LevelTrace) - providers, err := connectProviders(ctx, providerFile.Providers, log, big.NewInt(chainIDs[dex.Mainnet])) + providers, err := connectProviders(ctx, endpoints, log, big.NewInt(chainIDs[dex.Mainnet])) if err != nil { t.Fatal(err) } @@ -348,14 +358,23 @@ func TestMainnetCompliance(t *testing.T) { } } -func tRPCClient(dir string, seed []byte, endpoints []string, net dex.Network, skipConnect bool) (*multiRPCClient, error) { +func tRPCClient(dir string, seed []byte, endpoints []endpoint, net dex.Network, skipConnect bool) (*multiRPCClient, error) { log := dex.StdOutLogger("T", dex.LevelTrace) + var providersSlice [][]string + for _, ep := range endpoints { + valueSet := []string{ep.addr, ep.jwt} + providersSlice = append(providersSlice, valueSet) + } + providers, err := json.Marshal(providersSlice) + if err != nil { + return nil, err + } if err := createWallet(&asset.CreateWalletParams{ Type: walletTypeRPC, Seed: seed, Pass: []byte("abc"), Settings: map[string]string{ - "providers": strings.Join(endpoints, " "), + providersKey: string(providers), }, DataDir: dir, Net: net, diff --git a/client/asset/eth/nodeclient_harness_test.go b/client/asset/eth/nodeclient_harness_test.go index 0be1371ce5..e51ab1b409 100644 --- a/client/asset/eth/nodeclient_harness_test.go +++ b/client/asset/eth/nodeclient_harness_test.go @@ -1,7 +1,18 @@ //go:build harness && lgpl -// This test requires that the simnet harness be running. Some tests will -// alternatively work on testnet. +// This test requires that the simnet harness be running. Tests also work on +// siment but require some extra setup. +// +// If you are testing on testnet, you must specify the provider and/or jwt secret +// in the testnet-credentials.json file. +// +// example of testnet-credentials.json file: +// { +// "key0": "0000000000000000000000000000000000000000000000000000000000000000", +// "key1": "1111111111111111111111111111111111111111111111111111111111111111", +// "provider": "ws://127.0.0.1:8551", +// "jwt": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" +// } // // NOTE: These test reuse a light node that lives in the dextest folders. // However, when recreating the test database for every test, the nonce used @@ -11,10 +22,6 @@ // fail, and sometimes the redeem and refund functions to also fail. This could // be a problem in the future if a user restores from seed. Punting on this // particular problem for now. -// -// TODO: Running these tests many times on simnet eventually results in all -// transactions returning "unexpected error for test ok: exceeds block gas -// limit". Find out why that is. package eth @@ -106,11 +113,9 @@ var ( // block currently. Is set in code to testnetSecPerBlock if runing on // testnet. secPerBlock = time.Second - // If you are testing on testnet, you must specify the rpcNode. You can also - // specify it in the testnet-credentials.json file. - rpcNode string // useRPC can be set to true to test the RPC clients. - useRPC bool + useRPC bool + rpcNode endpoint // isTestnet can be set to true to perform tests on the goerli testnet. // May need some setup including sending testnet coins to the addresses @@ -269,14 +274,14 @@ out: return nil } -func prepareRPCClient(name, dataDir, endpoint string, net dex.Network) (*multiRPCClient, *accounts.Account, error) { +func prepareRPCClient(name, dataDir string, ep *endpoint, net dex.Network) (*multiRPCClient, *accounts.Account, error) { ethCfg, err := ethChainConfig(net) if err != nil { return nil, nil, err } cfg := ethCfg.Genesis.Config - c, err := newMultiRPCClient(dataDir, []string{endpoint}, tLogger.SubLogger(name), cfg, big.NewInt(chainIDs[net]), net) + c, err := newMultiRPCClient(dataDir, []endpoint{{addr: ep.addr, jwt: ep.jwt}}, tLogger.SubLogger(name), cfg, big.NewInt(chainIDs[net]), net) if err != nil { return nil, nil, fmt.Errorf("(%s) newNodeClient error: %v", name, err) } @@ -286,11 +291,11 @@ func prepareRPCClient(name, dataDir, endpoint string, net dex.Network) (*multiRP return c, c.creds.acct, nil } -func rpcEndpoints(net dex.Network) (string, string) { +func rpcEndpoints(net dex.Network) (initiator, participant *endpoint) { if net == dex.Testnet { - return rpcNode, rpcNode + return &rpcNode, &rpcNode } - return alphaIPCFile, betaIPCFile + return &endpoint{addr: alphaIPCFile}, &endpoint{addr: betaIPCFile} } func prepareTestRPCClients(initiatorDir, participantDir string, net dex.Network) (err error) { @@ -606,25 +611,24 @@ func useTestnet() error { isTestnet = true b, err := os.ReadFile(testnetCredentialsPath) if err != nil { - if !os.IsNotExist(err) { - return fmt.Errorf("error reading credentials file: %v", err) - } - } else { - var creds struct { - Key0 string `json:"key0"` - Key1 string `json:"key1"` - Provider string `json:"provider"` - } - if err := json.Unmarshal(b, &creds); err != nil { - return fmt.Errorf("error parsing credentials file: %v", err) - } - if creds.Key0 == "" || creds.Key1 == "" { - return fmt.Errorf("must provide both keys in testnet credentials file") - } - testnetWalletSeed = creds.Key0 - testnetParticipantWalletSeed = creds.Key1 - rpcNode = creds.Provider + return fmt.Errorf("error reading credentials file: %v", err) + } + var creds struct { + Key0 string `json:"key0"` + Key1 string `json:"key1"` + Provider string `json:"provider"` + JWT string `json:"jwt"` + } + if err := json.Unmarshal(b, &creds); err != nil { + return fmt.Errorf("error parsing credentials file: %v", err) + } + if creds.Key0 == "" || creds.Key1 == "" { + return fmt.Errorf("must provide both keys in testnet credentials file") } + testnetWalletSeed = creds.Key0 + testnetParticipantWalletSeed = creds.Key1 + rpcNode.addr = creds.Provider + rpcNode.jwt = creds.JWT return nil } @@ -632,7 +636,9 @@ func TestMain(m *testing.M) { dexeth.MaybeReadSimnetAddrs() flag.BoolVar(&isTestnet, "testnet", false, "use testnet") - flag.BoolVar(&useRPC, "rpc", false, "use RPC") + // NOTE: Internal clients are currently disabled because light clients + // do not work since the merge. Default this to false when they are fixed. + flag.BoolVar(&useRPC, "rpc", true, "use RPC") flag.Parse() if isTestnet { @@ -667,7 +673,7 @@ func TestMain(m *testing.M) { os.Exit(exitCode) } -func setupWallet(walletDir, seed, listenAddress, rpcAddr string, net dex.Network) error { +func setupWallet(walletDir, seed, listenAddress string, ep *endpoint, net dex.Network) error { walletType := walletTypeGeth settings := map[string]string{ "nodelistenaddr": listenAddress, @@ -675,7 +681,7 @@ func setupWallet(walletDir, seed, listenAddress, rpcAddr string, net dex.Network if useRPC { walletType = walletTypeRPC settings = map[string]string{ - providersKey: rpcAddr, + providersKey: fmt.Sprintf("[[\"%v\",\"%v\"]]", ep.addr, ep.jwt), } } seedB, _ := hex.DecodeString(seed) diff --git a/client/asset/interface.go b/client/asset/interface.go index a5b53368a4..3198972329 100644 --- a/client/asset/interface.go +++ b/client/asset/interface.go @@ -250,11 +250,11 @@ type ConfigOption struct { IsDate bool `json:"isdate"` DisableWhenActive bool `json:"disablewhenactive"` IsBirthdayConfig bool `json:"isBirthdayConfig"` - // Repeatable signals a text input that can be duplicated and submitted - // multiple times, with the specified delimiter used to encode the data - // in the settings map. - Repeatable string `json:"repeatable"` - Required bool `json:"required"` + // Repeatable arguments signal a text input that can be duplicated and + // submitted multiple times. They must have the same length. + RepeatableDisplayName []string `json:"repeatableDisplayName"` + RepeatableDescription []string `json:"repeatableDescription"` + Required bool `json:"required"` // ShowByDefault to show or not options on "hide advanced options". ShowByDefault bool `json:"showByDefault,omitempty"` diff --git a/client/cmd/dexcctl/simnet-setup.sh b/client/cmd/dexcctl/simnet-setup.sh index 3361d387c4..c4fccd6f70 100755 --- a/client/cmd/dexcctl/simnet-setup.sh +++ b/client/cmd/dexcctl/simnet-setup.sh @@ -42,7 +42,7 @@ fi if [ $ETH_ON -eq 0 ]; then echo configuring Eth wallet - ./dexcctl -p abc -p "" --simnet newwallet 60 geth + ./dexcctl -p abc -p "" --simnet newwallet 60 rpc "" '{"providersv1":"[[\"~/dextest/eth/alpha/node/geth.ipc\"]]"}' fi echo checking if we have an account already diff --git a/client/webserver/site/src/html/forms.tmpl b/client/webserver/site/src/html/forms.tmpl index ce438c640d..41fe5c6dc1 100644 --- a/client/webserver/site/src/html/forms.tmpl +++ b/client/webserver/site/src/html/forms.tmpl @@ -21,11 +21,13 @@ -