diff --git a/cli/test.sh b/apitest/scripts/mainnet-test.sh similarity index 94% rename from cli/test.sh rename to apitest/scripts/mainnet-test.sh index 9878d93fd02..ae3afd73d2d 100755 --- a/cli/test.sh +++ b/apitest/scripts/mainnet-test.sh @@ -1,11 +1,11 @@ #!/usr/bin/env bats # -# Integration tests for bisq-cli running against a live bisq-daemon +# Smoke tests for bisq-cli running against a live bisq-daemon (on mainnet) # # Prerequisites: # -# - bats v0.4.0 must be installed (brew install bats on macOS) -# see https://github.com/sstephenson/bats/tree/v0.4.0 +# - bats-core 1.2.0+ must be installed (brew install bats-core on macOS) +# see https://github.com/bats-core/bats-core # # - Run `./bisq-daemon --apiPassword=xyz --appDataDir=$TESTDIR` where $TESTDIR # is empty or otherwise contains an unencrypted wallet with a 0 BTC balance @@ -48,14 +48,14 @@ run ./bisq-cli --password="xyz" getversion [ "$status" -eq 0 ] echo "actual output: $output" >&2 - [ "$output" = "1.3.5" ] + [ "$output" = "1.3.7" ] } @test "test getversion" { run ./bisq-cli --password=xyz getversion [ "$status" -eq 0 ] echo "actual output: $output" >&2 - [ "$output" = "1.3.5" ] + [ "$output" = "1.3.7" ] } @test "test setwalletpassword \"a b c\"" { @@ -190,8 +190,8 @@ [ "$output" = "Error: incorrect parameter count, expecting direction (buy|sell), currency code" ] } -@test "test getoffers buy eur check return status" { - run ./bisq-cli --password=xyz getoffers buy eur +@test "test getoffers sell eur check return status" { + run ./bisq-cli --password=xyz getoffers sell eur [ "$status" -eq 0 ] } diff --git a/apitest/src/main/java/bisq/apitest/Scaffold.java b/apitest/src/main/java/bisq/apitest/Scaffold.java index bf0e4c771dd..d195b787015 100644 --- a/apitest/src/main/java/bisq/apitest/Scaffold.java +++ b/apitest/src/main/java/bisq/apitest/Scaffold.java @@ -311,17 +311,25 @@ private void startBackgroundProcesses(ExecutorService executor, bitcoinDaemon.verifyBitcoindRunning(); } + // Start Bisq apps defined by the supportingApps option, in the in proper order. + if (config.hasSupportingApp(seednode.name())) startBisqApp(seednode, executor, countdownLatch); - if (config.hasSupportingApp(arbdaemon.name(), arbdesktop.name())) - startBisqApp(config.runArbNodeAsDesktop ? arbdesktop : arbdaemon, executor, countdownLatch); + if (config.hasSupportingApp(arbdaemon.name())) + startBisqApp(arbdaemon, executor, countdownLatch); + else if (config.hasSupportingApp(arbdesktop.name())) + startBisqApp(arbdesktop, executor, countdownLatch); - if (config.hasSupportingApp(alicedaemon.name(), alicedesktop.name())) - startBisqApp(config.runAliceNodeAsDesktop ? alicedesktop : alicedaemon, executor, countdownLatch); + if (config.hasSupportingApp(alicedaemon.name())) + startBisqApp(alicedaemon, executor, countdownLatch); + else if (config.hasSupportingApp(alicedesktop.name())) + startBisqApp(alicedesktop, executor, countdownLatch); - if (config.hasSupportingApp(bobdaemon.name(), bobdesktop.name())) - startBisqApp(config.runBobNodeAsDesktop ? bobdesktop : bobdaemon, executor, countdownLatch); + if (config.hasSupportingApp(bobdaemon.name())) + startBisqApp(bobdaemon, executor, countdownLatch); + else if (config.hasSupportingApp(bobdesktop.name())) + startBisqApp(bobdesktop, executor, countdownLatch); } private void startBisqApp(BisqAppConfig bisqAppConfig, @@ -329,28 +337,24 @@ private void startBisqApp(BisqAppConfig bisqAppConfig, CountDownLatch countdownLatch) throws IOException, InterruptedException { - BisqApp bisqApp; + BisqApp bisqApp = createBisqApp(bisqAppConfig); switch (bisqAppConfig) { case seednode: - bisqApp = createBisqApp(seednode); seedNodeTask = new SetupTask(bisqApp, countdownLatch); seedNodeTaskFuture = executor.submit(seedNodeTask); break; case arbdaemon: case arbdesktop: - bisqApp = createBisqApp(config.runArbNodeAsDesktop ? arbdesktop : arbdaemon); arbNodeTask = new SetupTask(bisqApp, countdownLatch); arbNodeTaskFuture = executor.submit(arbNodeTask); break; case alicedaemon: case alicedesktop: - bisqApp = createBisqApp(config.runAliceNodeAsDesktop ? alicedesktop : alicedaemon); aliceNodeTask = new SetupTask(bisqApp, countdownLatch); aliceNodeTaskFuture = executor.submit(aliceNodeTask); break; case bobdaemon: case bobdesktop: - bisqApp = createBisqApp(config.runBobNodeAsDesktop ? bobdesktop : bobdaemon); bobNodeTask = new SetupTask(bisqApp, countdownLatch); bobNodeTaskFuture = executor.submit(bobNodeTask); break; diff --git a/apitest/src/main/java/bisq/apitest/config/ApiTestConfig.java b/apitest/src/main/java/bisq/apitest/config/ApiTestConfig.java index 6a9e9a6448b..5197a35634c 100644 --- a/apitest/src/main/java/bisq/apitest/config/ApiTestConfig.java +++ b/apitest/src/main/java/bisq/apitest/config/ApiTestConfig.java @@ -65,13 +65,11 @@ public class ApiTestConfig { static final String ROOT_APP_DATA_DIR = "rootAppDataDir"; static final String API_PASSWORD = "apiPassword"; static final String RUN_SUBPROJECT_JARS = "runSubprojectJars"; - static final String RUN_ARB_NODE_AS_DESKTOP = "runArbNodeAsDesktop"; - static final String RUN_ALICE_NODE_AS_DESKTOP = "runAliceNodeAsDesktop"; - static final String RUN_BOB_NODE_AS_DESKTOP = "runBobNodeAsDesktop"; static final String BISQ_APP_INIT_TIME = "bisqAppInitTime"; static final String SKIP_TESTS = "skipTests"; static final String SHUTDOWN_AFTER_TESTS = "shutdownAfterTests"; static final String SUPPORTING_APPS = "supportingApps"; + static final String ENABLE_BISQ_DEBUGGING = "enableBisqDebugging"; // Default values for certain options static final String DEFAULT_CONFIG_FILE_NAME = "apitest.properties"; @@ -98,13 +96,11 @@ public class ApiTestConfig { // Daemon instances can use same gRPC password, but each needs a different apiPort. public final String apiPassword; public final boolean runSubprojectJars; - public final boolean runArbNodeAsDesktop; - public final boolean runAliceNodeAsDesktop; - public final boolean runBobNodeAsDesktop; public final long bisqAppInitTime; public final boolean skipTests; public final boolean shutdownAfterTests; public final List supportingApps; + public final boolean enableBisqDebugging; // Immutable system configurations set in the constructor. public final String bitcoinDatadir; @@ -202,27 +198,6 @@ public ApiTestConfig(String... args) { .ofType(Boolean.class) .defaultsTo(false); - ArgumentAcceptingOptionSpec runArbNodeAsDesktopOpt = - parser.accepts(RUN_ARB_NODE_AS_DESKTOP, - "Run Arbitration node as desktop") - .withRequiredArg() - .ofType(Boolean.class) - .defaultsTo(false); // TODO how do I register mediator? - - ArgumentAcceptingOptionSpec runAliceNodeAsDesktopOpt = - parser.accepts(RUN_ALICE_NODE_AS_DESKTOP, - "Run Alice node as desktop") - .withRequiredArg() - .ofType(Boolean.class) - .defaultsTo(false); - - ArgumentAcceptingOptionSpec runBobNodeAsDesktopOpt = - parser.accepts(RUN_BOB_NODE_AS_DESKTOP, - "Run Bob node as desktop") - .withRequiredArg() - .ofType(Boolean.class) - .defaultsTo(false); - ArgumentAcceptingOptionSpec bisqAppInitTimeOpt = parser.accepts(BISQ_APP_INIT_TIME, "Amount of time (ms) to wait on a Bisq instance's initialization") @@ -251,6 +226,12 @@ public ApiTestConfig(String... args) { .ofType(String.class) .defaultsTo("bitcoind,seednode,arbdaemon,alicedaemon,bobdaemon"); + ArgumentAcceptingOptionSpec enableBisqDebuggingOpt = + parser.accepts(ENABLE_BISQ_DEBUGGING, + "Start Bisq apps with remote debug options") + .withRequiredArg() + .ofType(Boolean.class) + .defaultsTo(false); try { CompositeOptionSet options = new CompositeOptionSet(); @@ -302,13 +283,11 @@ public ApiTestConfig(String... args) { this.bitcoinRpcPassword = options.valueOf(bitcoinRpcPasswordOpt); this.apiPassword = options.valueOf(apiPasswordOpt); this.runSubprojectJars = options.valueOf(runSubprojectJarsOpt); - this.runArbNodeAsDesktop = options.valueOf(runArbNodeAsDesktopOpt); - this.runAliceNodeAsDesktop = options.valueOf(runAliceNodeAsDesktopOpt); - this.runBobNodeAsDesktop = options.valueOf(runBobNodeAsDesktopOpt); this.bisqAppInitTime = options.valueOf(bisqAppInitTimeOpt); this.skipTests = options.valueOf(skipTestsOpt); this.shutdownAfterTests = options.valueOf(shutdownAfterTestsOpt); this.supportingApps = asList(options.valueOf(supportingAppsOpt).split(",")); + this.enableBisqDebugging = options.valueOf(enableBisqDebuggingOpt); // Assign values to special-case static fields. BASH_PATH_VALUE = bashPath; diff --git a/apitest/src/main/java/bisq/apitest/config/BisqAppConfig.java b/apitest/src/main/java/bisq/apitest/config/BisqAppConfig.java index 662956462ed..08a7531ca30 100644 --- a/apitest/src/main/java/bisq/apitest/config/BisqAppConfig.java +++ b/apitest/src/main/java/bisq/apitest/config/BisqAppConfig.java @@ -30,58 +30,64 @@ @see dev-setup.md @see dao-setup.md */ -@SuppressWarnings("unused") public enum BisqAppConfig { seednode("bisq-BTC_REGTEST_Seed_2002", "bisq-seednode", - "\"-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml\"", + "-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml", SeedNodeMain.class.getName(), 2002, 5120, - -1), + -1, + 49996), arbdaemon("bisq-BTC_REGTEST_Arb_dao", "bisq-daemon", - "\"-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml\"", + "-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml", BisqDaemonMain.class.getName(), 4444, 5121, - 9997), + 9997, + 49997), arbdesktop("bisq-BTC_REGTEST_Arb_dao", "bisq-desktop", - "\"-XX:MaxRAM=3g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml\"", + "-XX:MaxRAM=3g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml", BisqAppMain.class.getName(), 4444, 5121, - -1), + -1, + 49997), alicedaemon("bisq-BTC_REGTEST_Alice_dao", "bisq-daemon", - "\"-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml\"", + "-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml", BisqDaemonMain.class.getName(), 7777, 5122, - 9998), + 9998, + 49998), alicedesktop("bisq-BTC_REGTEST_Alice_dao", "bisq-desktop", - "\"-XX:MaxRAM=4g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml\"", + "-XX:MaxRAM=4g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml", BisqAppMain.class.getName(), 7777, 5122, - -1), + -1, + 49998), bobdaemon("bisq-BTC_REGTEST_Bob_dao", "bisq-daemon", - "\"-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml\"", + "-XX:MaxRAM=2g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml", BisqDaemonMain.class.getName(), 8888, 5123, - 9999), + 9999, + 49999), bobdesktop("bisq-BTC_REGTEST_Bob_dao", "bisq-desktop", - "\"-XX:MaxRAM=4g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml\"", + "-XX:MaxRAM=4g -Dlogback.configurationFile=apitest/build/resources/main/logback.xml", BisqAppMain.class.getName(), 8888, 5123, - -1); + -1, + 49999); public final String appName; public final String startupScript; @@ -91,6 +97,7 @@ public enum BisqAppConfig { public final int rpcBlockNotificationPort; // Daemons can use a global gRPC password, but each needs a unique apiPort. public final int apiPort; + public final int remoteDebugPort; BisqAppConfig(String appName, String startupScript, @@ -98,7 +105,8 @@ public enum BisqAppConfig { String mainClassName, int nodePort, int rpcBlockNotificationPort, - int apiPort) { + int apiPort, + int remoteDebugPort) { this.appName = appName; this.startupScript = startupScript; this.javaOpts = javaOpts; @@ -106,6 +114,7 @@ public enum BisqAppConfig { this.nodePort = nodePort; this.rpcBlockNotificationPort = rpcBlockNotificationPort; this.apiPort = apiPort; + this.remoteDebugPort = remoteDebugPort; } @Override @@ -118,6 +127,7 @@ public String toString() { ", nodePort=" + nodePort + "\n" + ", rpcBlockNotificationPort=" + rpcBlockNotificationPort + "\n" + ", apiPort=" + apiPort + "\n" + + ", remoteDebugPort=" + remoteDebugPort + "\n" + '}'; } } diff --git a/apitest/src/main/java/bisq/apitest/linux/BisqApp.java b/apitest/src/main/java/bisq/apitest/linux/BisqApp.java index f449d0b98f1..7c66746f3df 100644 --- a/apitest/src/main/java/bisq/apitest/linux/BisqApp.java +++ b/apitest/src/main/java/bisq/apitest/linux/BisqApp.java @@ -53,6 +53,7 @@ public class BisqApp extends AbstractLinuxProcess implements LinuxProcess { private final boolean useLocalhostForP2P; public final boolean useDevPrivilegeKeys; private final String findBisqPidScript; + private final String debugOpts; public BisqApp(BisqAppConfig bisqAppConfig, ApiTestConfig config) { super(bisqAppConfig.appName, config); @@ -67,6 +68,9 @@ public BisqApp(BisqAppConfig bisqAppConfig, ApiTestConfig config) { this.useDevPrivilegeKeys = true; this.findBisqPidScript = (config.isRunningTest ? "." : "./apitest") + "/scripts/get-bisq-pid.sh"; + this.debugOpts = config.enableBisqDebugging + ? " -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:" + bisqAppConfig.remoteDebugPort + : ""; } @Override @@ -112,7 +116,6 @@ public void shutdown() { if (isAlive(pid)) { this.shutdownExceptions.add(new IllegalStateException(format("%s shutdown did not work", bisqAppConfig.appName))); - return; } } catch (Exception e) { @@ -209,7 +212,7 @@ private long findBisqAppPid() throws IOException, InterruptedException { } private String getJavaOptsSpec() { - return "export JAVA_OPTS=" + bisqAppConfig.javaOpts + "; "; + return "export JAVA_OPTS=\"" + bisqAppConfig.javaOpts + debugOpts + "\"; "; } private List getOptsList() { diff --git a/apitest/src/test/java/bisq/apitest/ApiTestCase.java b/apitest/src/test/java/bisq/apitest/ApiTestCase.java index 286e7f8c206..f9100bee96c 100644 --- a/apitest/src/test/java/bisq/apitest/ApiTestCase.java +++ b/apitest/src/test/java/bisq/apitest/ApiTestCase.java @@ -28,6 +28,7 @@ import bisq.apitest.config.ApiTestConfig; import bisq.apitest.method.BitcoinCliHelper; +import bisq.cli.GrpcStubs; /** * Base class for all test types: 'method', 'scenario' and 'e2e'. @@ -65,19 +66,19 @@ public class ApiTestCase { public static void setUpScaffold(String supportingApps) throws InterruptedException, ExecutionException, IOException { - // The supportingApps argument is a comma delimited string of supporting app - // names, e.g. "bitcoind,seednode,arbdaemon,alicedaemon,bobdaemon" scaffold = new Scaffold(supportingApps).setUp(); config = scaffold.config; bitcoinCli = new BitcoinCliHelper((config)); - grpcStubs = new GrpcStubs(alicedaemon, config).init(); + // For now, all grpc requests are sent to the alicedaemon, but this will need to + // be made configurable for new test cases that call arb or bob node daemons. + grpcStubs = new GrpcStubs("localhost", alicedaemon.apiPort, config.apiPassword); } - public static void setUpScaffold() + public static void setUpScaffold(String[] params) throws InterruptedException, ExecutionException, IOException { - scaffold = new Scaffold(new String[]{}).setUp(); + scaffold = new Scaffold(params).setUp(); config = scaffold.config; - grpcStubs = new GrpcStubs(alicedaemon, config).init(); + grpcStubs = new GrpcStubs("localhost", alicedaemon.apiPort, config.apiPassword); } public static void tearDownScaffold() { diff --git a/apitest/src/test/java/bisq/apitest/GrpcStubs.java b/apitest/src/test/java/bisq/apitest/GrpcStubs.java deleted file mode 100644 index 6279c61489f..00000000000 --- a/apitest/src/test/java/bisq/apitest/GrpcStubs.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.apitest; - -import bisq.proto.grpc.GetVersionGrpc; -import bisq.proto.grpc.OffersGrpc; -import bisq.proto.grpc.PaymentAccountsGrpc; -import bisq.proto.grpc.WalletsGrpc; - -import io.grpc.CallCredentials; -import io.grpc.ManagedChannelBuilder; -import io.grpc.Metadata; - -import java.util.concurrent.Executor; - -import static io.grpc.Metadata.ASCII_STRING_MARSHALLER; -import static io.grpc.Status.UNAUTHENTICATED; -import static java.lang.String.format; -import static java.util.concurrent.TimeUnit.SECONDS; - - - -import bisq.apitest.config.ApiTestConfig; -import bisq.apitest.config.BisqAppConfig; - -public class GrpcStubs { - - public final CallCredentials credentials; - public final String host; - public final int port; - - public GetVersionGrpc.GetVersionBlockingStub versionService; - public OffersGrpc.OffersBlockingStub offersService; - public PaymentAccountsGrpc.PaymentAccountsBlockingStub paymentAccountsService; - public WalletsGrpc.WalletsBlockingStub walletsService; - - public GrpcStubs(BisqAppConfig bisqAppConfig, ApiTestConfig config) { - this.credentials = new PasswordCallCredentials(config.apiPassword); - this.host = "localhost"; - this.port = bisqAppConfig.apiPort; - } - - public GrpcStubs init() { - var channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build(); - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - try { - channel.shutdown().awaitTermination(1, SECONDS); - } catch (InterruptedException ex) { - throw new IllegalStateException(ex); - } - })); - - this.versionService = GetVersionGrpc.newBlockingStub(channel).withCallCredentials(credentials); - this.offersService = OffersGrpc.newBlockingStub(channel).withCallCredentials(credentials); - this.paymentAccountsService = PaymentAccountsGrpc.newBlockingStub(channel).withCallCredentials(credentials); - this.walletsService = WalletsGrpc.newBlockingStub(channel).withCallCredentials(credentials); - - return this; - } - - static class PasswordCallCredentials extends CallCredentials { - - public static final String PASSWORD_KEY = "password"; - private final String passwordValue; - - public PasswordCallCredentials(String passwordValue) { - if (passwordValue == null) - throw new IllegalArgumentException(format("'%s' value must not be null", PASSWORD_KEY)); - this.passwordValue = passwordValue; - } - - @Override - public void applyRequestMetadata(RequestInfo requestInfo, - Executor appExecutor, - MetadataApplier metadataApplier) { - appExecutor.execute(() -> { - try { - var headers = new Metadata(); - var passwordKey = Metadata.Key.of(PASSWORD_KEY, ASCII_STRING_MARSHALLER); - headers.put(passwordKey, passwordValue); - metadataApplier.apply(headers); - } catch (Throwable ex) { - metadataApplier.fail(UNAUTHENTICATED.withCause(ex)); - } - }); - } - - @Override - public void thisUsesUnstableApi() { - // An experimental api. A noop but never called; tries to make it clearer to - // implementors that they may break in the future. - } - } -} diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java index 37dbe3f52d6..46dfee10726 100644 --- a/cli/src/main/java/bisq/cli/CliMain.java +++ b/cli/src/main/java/bisq/cli/CliMain.java @@ -133,21 +133,11 @@ public static void run(String[] args) { if (password == null) throw new IllegalArgumentException("missing required 'password' option"); - var credentials = new PasswordCallCredentials(password); - - var channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build(); - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - try { - channel.shutdown().awaitTermination(1, TimeUnit.SECONDS); - } catch (InterruptedException ex) { - throw new RuntimeException(ex); - } - })); - - var versionService = GetVersionGrpc.newBlockingStub(channel).withCallCredentials(credentials); - var offersService = OffersGrpc.newBlockingStub(channel).withCallCredentials(credentials); - var paymentAccountsService = PaymentAccountsGrpc.newBlockingStub(channel).withCallCredentials(credentials); - var walletsService = WalletsGrpc.newBlockingStub(channel).withCallCredentials(credentials); + GrpcStubs grpcStubs = new GrpcStubs(host, port, password); + var versionService = grpcStubs.versionService; + var offersService = grpcStubs.offersService; + var paymentAccountsService = grpcStubs.paymentAccountsService; + var walletsService = grpcStubs.walletsService; try { switch (method) { diff --git a/cli/src/main/java/bisq/cli/GrpcStubs.java b/cli/src/main/java/bisq/cli/GrpcStubs.java new file mode 100644 index 00000000000..e12a6efa7c7 --- /dev/null +++ b/cli/src/main/java/bisq/cli/GrpcStubs.java @@ -0,0 +1,54 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.cli; + +import bisq.proto.grpc.GetVersionGrpc; +import bisq.proto.grpc.OffersGrpc; +import bisq.proto.grpc.PaymentAccountsGrpc; +import bisq.proto.grpc.WalletsGrpc; + +import io.grpc.CallCredentials; +import io.grpc.ManagedChannelBuilder; + +import static java.util.concurrent.TimeUnit.SECONDS; + +public class GrpcStubs { + + public final GetVersionGrpc.GetVersionBlockingStub versionService; + public final OffersGrpc.OffersBlockingStub offersService; + public final PaymentAccountsGrpc.PaymentAccountsBlockingStub paymentAccountsService; + public final WalletsGrpc.WalletsBlockingStub walletsService; + + public GrpcStubs(String apiHost, int apiPort, String apiPassword) { + CallCredentials credentials = new PasswordCallCredentials(apiPassword); + + var channel = ManagedChannelBuilder.forAddress(apiHost, apiPort).usePlaintext().build(); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + channel.shutdown().awaitTermination(1, SECONDS); + } catch (InterruptedException ex) { + throw new IllegalStateException(ex); + } + })); + + this.versionService = GetVersionGrpc.newBlockingStub(channel).withCallCredentials(credentials); + this.offersService = OffersGrpc.newBlockingStub(channel).withCallCredentials(credentials); + this.paymentAccountsService = PaymentAccountsGrpc.newBlockingStub(channel).withCallCredentials(credentials); + this.walletsService = WalletsGrpc.newBlockingStub(channel).withCallCredentials(credentials); + } +}