From 359037a3ba9358282b9cf6ba687656a809a4a78a Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 14 Sep 2020 10:43:31 -0300 Subject: [PATCH 01/10] Move version service proto def to bottom of grpc.proto --- proto/src/main/proto/grpc.proto | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index 730920b0622..234b5e77622 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -23,22 +23,6 @@ import "pb.proto"; option java_package = "bisq.proto.grpc"; option java_multiple_files = true; -/////////////////////////////////////////////////////////////////////////////////////////// -// Version -/////////////////////////////////////////////////////////////////////////////////////////// - -service GetVersion { - rpc GetVersion (GetVersionRequest) returns (GetVersionReply) { - } -} - -message GetVersionRequest { -} - -message GetVersionReply { - string version = 1; -} - /////////////////////////////////////////////////////////////////////////////////////////// // Offers /////////////////////////////////////////////////////////////////////////////////////////// @@ -214,3 +198,20 @@ message AddressBalanceInfo { int64 balance = 2; int64 numConfirmations = 3; } + +/////////////////////////////////////////////////////////////////////////////////////////// +// Version +/////////////////////////////////////////////////////////////////////////////////////////// + +service GetVersion { + rpc GetVersion (GetVersionRequest) returns (GetVersionReply) { + } +} + +message GetVersionRequest { +} + +message GetVersionReply { + string version = 1; +} + From 15b60445873304e614be6d9ba20c9e86181f6cba Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 14 Sep 2020 10:44:19 -0300 Subject: [PATCH 02/10] Add dispute agents service proto def to grpc.proto --- proto/src/main/proto/grpc.proto | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index 234b5e77622..ed2cb4fd61b 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -23,6 +23,23 @@ import "pb.proto"; option java_package = "bisq.proto.grpc"; option java_multiple_files = true; +/////////////////////////////////////////////////////////////////////////////////////////// +// DisputeAgents +/////////////////////////////////////////////////////////////////////////////////////////// + +service DisputeAgents { + rpc RegisterDisputeAgent (RegisterDisputeAgentRequest) returns (RegisterDisputeAgentReply) { + } +} + +message RegisterDisputeAgentRequest { + string disputeAgentType = 1; + string registrationKey = 2; +} + +message RegisterDisputeAgentReply { +} + /////////////////////////////////////////////////////////////////////////////////////////// // Offers /////////////////////////////////////////////////////////////////////////////////////////// From bbf4f09181746759961f5404f241fa183dd6c5ec Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 14 Sep 2020 11:04:10 -0300 Subject: [PATCH 03/10] Add core impl for registering dispute agents This change supports registering mediators and refund agents on daemons running on regest or testnet chains. Registering arbitrators is not supported. --- core/src/main/java/bisq/core/api/CoreApi.java | 14 +- .../core/api/CoreDisputeAgentsService.java | 145 ++++++++++++++++++ .../daemon/grpc/GrpcDisputeAgentsService.java | 45 ++++++ 3 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java create mode 100644 daemon/src/main/java/bisq/daemon/grpc/GrpcDisputeAgentsService.java diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index 30cedeb3b93..35af589f3d5 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -47,26 +47,38 @@ @Slf4j public class CoreApi { + private final CoreDisputeAgentsService coreDisputeAgentsService; private final CoreOffersService coreOffersService; private final CorePaymentAccountsService paymentAccountsService; private final CoreWalletsService walletsService; private final TradeStatisticsManager tradeStatisticsManager; @Inject - public CoreApi(CoreOffersService coreOffersService, + public CoreApi(CoreDisputeAgentsService coreDisputeAgentsService, + CoreOffersService coreOffersService, CorePaymentAccountsService paymentAccountsService, CoreWalletsService walletsService, TradeStatisticsManager tradeStatisticsManager) { + this.coreDisputeAgentsService = coreDisputeAgentsService; this.coreOffersService = coreOffersService; this.paymentAccountsService = paymentAccountsService; this.walletsService = walletsService; this.tradeStatisticsManager = tradeStatisticsManager; } + @SuppressWarnings("SameReturnValue") public String getVersion() { return Version.VERSION; } + /////////////////////////////////////////////////////////////////////////////////////////// + // Dispute Agents + /////////////////////////////////////////////////////////////////////////////////////////// + + public void registerDisputeAgent(String disputeAgentType, String registrationKey) { + coreDisputeAgentsService.registerDisputeAgent(disputeAgentType, registrationKey); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Offers /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java b/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java new file mode 100644 index 00000000000..a1071ab3b2b --- /dev/null +++ b/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java @@ -0,0 +1,145 @@ +/* + * 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.core.api; + +import bisq.core.support.dispute.mediation.mediator.Mediator; +import bisq.core.support.dispute.mediation.mediator.MediatorManager; +import bisq.core.support.dispute.refund.refundagent.RefundAgent; +import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; + +import bisq.network.p2p.NodeAddress; +import bisq.network.p2p.P2PService; + +import bisq.common.config.Config; +import bisq.common.crypto.KeyRing; + +import org.bitcoinj.core.ECKey; + +import javax.inject.Inject; + +import java.net.InetAddress; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Objects; + +import lombok.extern.slf4j.Slf4j; + +import static bisq.common.app.DevEnv.DEV_PRIVILEGE_PRIV_KEY; + +@Slf4j +class CoreDisputeAgentsService { + + private final Config config; + private final KeyRing keyRing; + private final MediatorManager mediatorManager; + private final RefundAgentManager refundAgentManager; + private final P2PService p2PService; + + @Inject + public CoreDisputeAgentsService(Config config, + KeyRing keyRing, + MediatorManager mediatorManager, + RefundAgentManager refundAgentManager, + P2PService p2PService) { + this.config = config; + this.keyRing = keyRing; + this.mediatorManager = mediatorManager; + this.refundAgentManager = refundAgentManager; + this.p2PService = p2PService; + } + + public void registerDisputeAgent(String disputeAgentType, String registrationKey) { + if (!p2PService.isBootstrapped()) + throw new IllegalStateException("p2p service is not bootstrapped yet"); + + if (config.baseCurrencyNetwork.isMainnet() + || config.baseCurrencyNetwork.isDaoBetaNet() + || !config.useLocalhostForP2P) + throw new IllegalStateException("dispute agents must be registered in a Bisq UI"); + + if (!registrationKey.equals(DEV_PRIVILEGE_PRIV_KEY)) + throw new IllegalArgumentException("invalid registration key"); + + switch (disputeAgentType) { + case "arbitrator": + throw new IllegalArgumentException("arbitrators must be registered in a Bisq UI"); + case "mediator": + case "refundagent": + NodeAddress nodeAddress = new NodeAddress( + InetAddress.getLoopbackAddress().getHostAddress(), config.nodePort); + List languageCodes = Arrays.asList("de", "en", "es", "fr"); + ECKey ecKey = mediatorManager.getRegistrationKey(registrationKey); + String signature = mediatorManager.signStorageSignaturePubKey(Objects.requireNonNull(ecKey)); + + if (disputeAgentType.equals("mediator")) + registerMediator(nodeAddress, languageCodes, ecKey, signature); + else + registerRefundAgent(nodeAddress, languageCodes, ecKey, signature); + + return; + default: + throw new IllegalArgumentException("unknown dispute agent type " + disputeAgentType); + } + } + + private void registerMediator(NodeAddress nodeAddress, + List languageCodes, + ECKey ecKey, + String signature) { + Mediator mediator = new Mediator( + nodeAddress, + keyRing.getPubKeyRing(), + languageCodes, + new Date().getTime(), + ecKey.getPubKey(), + signature, + null, + null, + null + ); + mediatorManager.addDisputeAgent(mediator, () -> { + }, errorMessage -> { + }); + mediatorManager.getDisputeAgentByNodeAddress(nodeAddress).orElseThrow(() -> + new IllegalStateException("could not register mediator")); + } + + private void registerRefundAgent(NodeAddress nodeAddress, + List languageCodes, + ECKey ecKey, + String signature) { + RefundAgent refundAgent = new RefundAgent( + nodeAddress, + keyRing.getPubKeyRing(), + languageCodes, + new Date().getTime(), + ecKey.getPubKey(), + signature, + null, + null, + null + ); + refundAgentManager.addDisputeAgent(refundAgent, () -> { + }, errorMessage -> { + }); + refundAgentManager.getDisputeAgentByNodeAddress(nodeAddress).orElseThrow(() -> + new IllegalStateException("could not register refund agent")); + } +} diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcDisputeAgentsService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcDisputeAgentsService.java new file mode 100644 index 00000000000..24fd192fea8 --- /dev/null +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcDisputeAgentsService.java @@ -0,0 +1,45 @@ +package bisq.daemon.grpc; + +import bisq.core.api.CoreApi; + +import bisq.proto.grpc.DisputeAgentsGrpc; +import bisq.proto.grpc.RegisterDisputeAgentReply; +import bisq.proto.grpc.RegisterDisputeAgentRequest; + +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import io.grpc.stub.StreamObserver; + +import javax.inject.Inject; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +class GrpcDisputeAgentsService extends DisputeAgentsGrpc.DisputeAgentsImplBase { + + private final CoreApi coreApi; + + @Inject + public GrpcDisputeAgentsService(CoreApi coreApi) { + this.coreApi = coreApi; + } + + @Override + public void registerDisputeAgent(RegisterDisputeAgentRequest req, + StreamObserver responseObserver) { + try { + coreApi.registerDisputeAgent(req.getDisputeAgentType(), req.getRegistrationKey()); + var reply = RegisterDisputeAgentReply.newBuilder().build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } catch (IllegalArgumentException cause) { + var ex = new StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription(cause.getMessage())); + responseObserver.onError(ex); + throw ex; + } catch (IllegalStateException cause) { + var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage())); + responseObserver.onError(ex); + throw ex; + } + } +} From 3386b43e52de5a0eae01ac3612c63e422300d293 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 14 Sep 2020 11:39:36 -0300 Subject: [PATCH 04/10] Add GrpcDisputeAgentsService to GrpcServer --- daemon/src/main/java/bisq/daemon/grpc/GrpcServer.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcServer.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcServer.java index a1293dfa0ac..0c0e2f5cb3f 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcServer.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcServer.java @@ -51,11 +51,13 @@ public class GrpcServer { @Inject public GrpcServer(Config config, CoreApi coreApi, + GrpcDisputeAgentsService disputeAgentsService, GrpcOffersService offersService, GrpcPaymentAccountsService paymentAccountsService, GrpcWalletsService walletsService) { this.coreApi = coreApi; this.server = ServerBuilder.forPort(config.apiPort) + .addService(disputeAgentsService) .addService(new GetVersionService()) .addService(new GetTradeStatisticsService()) .addService(offersService) From 304047eacaae2f95760d3b336a9d2affcc8bc4be Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 14 Sep 2020 11:40:22 -0300 Subject: [PATCH 05/10] Create GrpcDisputeAgentsService stub --- cli/src/main/java/bisq/cli/GrpcStubs.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cli/src/main/java/bisq/cli/GrpcStubs.java b/cli/src/main/java/bisq/cli/GrpcStubs.java index e12a6efa7c7..2ef5efb75b7 100644 --- a/cli/src/main/java/bisq/cli/GrpcStubs.java +++ b/cli/src/main/java/bisq/cli/GrpcStubs.java @@ -17,6 +17,7 @@ package bisq.cli; +import bisq.proto.grpc.DisputeAgentsGrpc; import bisq.proto.grpc.GetVersionGrpc; import bisq.proto.grpc.OffersGrpc; import bisq.proto.grpc.PaymentAccountsGrpc; @@ -29,6 +30,7 @@ public class GrpcStubs { + public final DisputeAgentsGrpc.DisputeAgentsBlockingStub disputeAgentsService; public final GetVersionGrpc.GetVersionBlockingStub versionService; public final OffersGrpc.OffersBlockingStub offersService; public final PaymentAccountsGrpc.PaymentAccountsBlockingStub paymentAccountsService; @@ -46,6 +48,7 @@ public GrpcStubs(String apiHost, int apiPort, String apiPassword) { } })); + this.disputeAgentsService = DisputeAgentsGrpc.newBlockingStub(channel).withCallCredentials(credentials); this.versionService = GetVersionGrpc.newBlockingStub(channel).withCallCredentials(credentials); this.offersService = OffersGrpc.newBlockingStub(channel).withCallCredentials(credentials); this.paymentAccountsService = PaymentAccountsGrpc.newBlockingStub(channel).withCallCredentials(credentials); From 899bea8df5cbd42e85a2ace4b61d2e9ebf2e709f Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 14 Sep 2020 11:53:02 -0300 Subject: [PATCH 06/10] Replace "localhost" strings with InetAddress.getLoopbackAddress calls --- .../main/java/bisq/apitest/config/ApiTestConfig.java | 4 +++- apitest/src/test/java/bisq/apitest/ApiTestCase.java | 10 ++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apitest/src/main/java/bisq/apitest/config/ApiTestConfig.java b/apitest/src/main/java/bisq/apitest/config/ApiTestConfig.java index 5197a35634c..b0ce2c548e6 100644 --- a/apitest/src/main/java/bisq/apitest/config/ApiTestConfig.java +++ b/apitest/src/main/java/bisq/apitest/config/ApiTestConfig.java @@ -27,6 +27,8 @@ import joptsimple.OptionSet; import joptsimple.OptionSpec; +import java.net.InetAddress; + import java.nio.file.Paths; import java.io.File; @@ -169,7 +171,7 @@ public ApiTestConfig(String... args) { ArgumentAcceptingOptionSpec bitcoinRegtestHostOpt = parser.accepts(BITCOIN_REGTEST_HOST, "Bitcoin Core regtest host") .withRequiredArg() - .ofType(String.class).defaultsTo("localhost"); + .ofType(String.class).defaultsTo(InetAddress.getLoopbackAddress().getHostAddress()); ArgumentAcceptingOptionSpec bitcoinRpcPortOpt = parser.accepts(BITCOIN_RPC_PORT, "Bitcoin Core rpc port (non-default)") diff --git a/apitest/src/test/java/bisq/apitest/ApiTestCase.java b/apitest/src/test/java/bisq/apitest/ApiTestCase.java index f9100bee96c..3001c5b64dd 100644 --- a/apitest/src/test/java/bisq/apitest/ApiTestCase.java +++ b/apitest/src/test/java/bisq/apitest/ApiTestCase.java @@ -17,6 +17,8 @@ package bisq.apitest; +import java.net.InetAddress; + import java.io.IOException; import java.util.concurrent.ExecutionException; @@ -69,16 +71,16 @@ public static void setUpScaffold(String supportingApps) scaffold = new Scaffold(supportingApps).setUp(); config = scaffold.config; bitcoinCli = new BitcoinCliHelper((config)); - // 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); + grpcStubs = new GrpcStubs(InetAddress.getLoopbackAddress().getHostAddress(), + alicedaemon.apiPort, config.apiPassword); } public static void setUpScaffold(String[] params) throws InterruptedException, ExecutionException, IOException { scaffold = new Scaffold(params).setUp(); config = scaffold.config; - grpcStubs = new GrpcStubs("localhost", alicedaemon.apiPort, config.apiPassword); + grpcStubs = new GrpcStubs(InetAddress.getLoopbackAddress().getHostAddress(), + alicedaemon.apiPort, config.apiPassword); } public static void tearDownScaffold() { From 148a0f120015e6923a5bc517aa114c879467a384 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 14 Sep 2020 12:09:04 -0300 Subject: [PATCH 07/10] Refactor test cases to use arbitrary grpc stubs Most test cases send requests to the alicedaemon, but new test cases will need to be able to send requests to arbitration and bob daemons. --- .../test/java/bisq/apitest/ApiTestCase.java | 25 ++++++--- .../bisq/apitest/method/GetBalanceTest.java | 4 +- .../bisq/apitest/method/GetVersionTest.java | 3 +- .../java/bisq/apitest/method/MethodTest.java | 27 +++++++--- .../apitest/method/WalletProtectionTest.java | 51 +++++++++---------- .../scenario/FundWalletScenarioTest.java | 8 +-- 6 files changed, 70 insertions(+), 48 deletions(-) diff --git a/apitest/src/test/java/bisq/apitest/ApiTestCase.java b/apitest/src/test/java/bisq/apitest/ApiTestCase.java index 3001c5b64dd..30e7472e2ac 100644 --- a/apitest/src/test/java/bisq/apitest/ApiTestCase.java +++ b/apitest/src/test/java/bisq/apitest/ApiTestCase.java @@ -21,14 +21,16 @@ import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.ExecutionException; -import static bisq.apitest.config.BisqAppConfig.alicedaemon; import static java.util.concurrent.TimeUnit.MILLISECONDS; import bisq.apitest.config.ApiTestConfig; +import bisq.apitest.config.BisqAppConfig; import bisq.apitest.method.BitcoinCliHelper; import bisq.cli.GrpcStubs; @@ -59,34 +61,41 @@ */ public class ApiTestCase { - // The gRPC service stubs are used by method & scenario tests, but not e2e tests. - protected static GrpcStubs grpcStubs; - protected static Scaffold scaffold; protected static ApiTestConfig config; protected static BitcoinCliHelper bitcoinCli; + // gRPC service stubs are used by method & scenario tests, but not e2e tests. + private static final Map grpcStubsCache = new HashMap<>(); + public static void setUpScaffold(String supportingApps) throws InterruptedException, ExecutionException, IOException { scaffold = new Scaffold(supportingApps).setUp(); config = scaffold.config; bitcoinCli = new BitcoinCliHelper((config)); - grpcStubs = new GrpcStubs(InetAddress.getLoopbackAddress().getHostAddress(), - alicedaemon.apiPort, config.apiPassword); } public static void setUpScaffold(String[] params) throws InterruptedException, ExecutionException, IOException { scaffold = new Scaffold(params).setUp(); config = scaffold.config; - grpcStubs = new GrpcStubs(InetAddress.getLoopbackAddress().getHostAddress(), - alicedaemon.apiPort, config.apiPassword); } public static void tearDownScaffold() { scaffold.tearDown(); } + protected static GrpcStubs grpcStubs(BisqAppConfig bisqAppConfig) { + if (grpcStubsCache.containsKey(bisqAppConfig)) { + return grpcStubsCache.get(bisqAppConfig); + } else { + GrpcStubs stubs = new GrpcStubs(InetAddress.getLoopbackAddress().getHostAddress(), + bisqAppConfig.apiPort, config.apiPassword); + grpcStubsCache.put(bisqAppConfig, stubs); + return stubs; + } + } + protected void sleep(long ms) { try { MILLISECONDS.sleep(ms); diff --git a/apitest/src/test/java/bisq/apitest/method/GetBalanceTest.java b/apitest/src/test/java/bisq/apitest/method/GetBalanceTest.java index 2cf4e8ae1cd..a77fe633ea6 100644 --- a/apitest/src/test/java/bisq/apitest/method/GetBalanceTest.java +++ b/apitest/src/test/java/bisq/apitest/method/GetBalanceTest.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; +import static bisq.apitest.config.BisqAppConfig.alicedaemon; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; @@ -57,7 +58,8 @@ public static void setUp() { public void testGetBalance() { // All tests depend on the DAO / regtest environment, and Alice's wallet is // initialized with 10 BTC during the scaffolding setup. - var balance = grpcStubs.walletsService.getBalance(GetBalanceRequest.newBuilder().build()).getBalance(); + var balance = grpcStubs(alicedaemon).walletsService + .getBalance(GetBalanceRequest.newBuilder().build()).getBalance(); assertEquals(1000000000, balance); } diff --git a/apitest/src/test/java/bisq/apitest/method/GetVersionTest.java b/apitest/src/test/java/bisq/apitest/method/GetVersionTest.java index 22413cf9d3c..ed6083c8d3d 100644 --- a/apitest/src/test/java/bisq/apitest/method/GetVersionTest.java +++ b/apitest/src/test/java/bisq/apitest/method/GetVersionTest.java @@ -50,7 +50,8 @@ public static void setUp() { @Test @Order(1) public void testGetVersion() { - var version = grpcStubs.versionService.getVersion(GetVersionRequest.newBuilder().build()).getVersion(); + var version = grpcStubs(alicedaemon).versionService + .getVersion(GetVersionRequest.newBuilder().build()).getVersion(); assertEquals(VERSION, version); } diff --git a/apitest/src/test/java/bisq/apitest/method/MethodTest.java b/apitest/src/test/java/bisq/apitest/method/MethodTest.java index 694aa6806e3..3437ac59c94 100644 --- a/apitest/src/test/java/bisq/apitest/method/MethodTest.java +++ b/apitest/src/test/java/bisq/apitest/method/MethodTest.java @@ -20,13 +20,17 @@ import bisq.proto.grpc.GetBalanceRequest; import bisq.proto.grpc.GetFundingAddressesRequest; import bisq.proto.grpc.LockWalletRequest; +import bisq.proto.grpc.RegisterDisputeAgentRequest; import bisq.proto.grpc.RemoveWalletPasswordRequest; import bisq.proto.grpc.SetWalletPasswordRequest; import bisq.proto.grpc.UnlockWalletRequest; +import static bisq.common.app.DevEnv.DEV_PRIVILEGE_PRIV_KEY; + import bisq.apitest.ApiTestCase; +import bisq.apitest.config.BisqAppConfig; public class MethodTest extends ApiTestCase { @@ -60,24 +64,31 @@ protected final GetFundingAddressesRequest createGetFundingAddressesRequest() { return GetFundingAddressesRequest.newBuilder().build(); } + protected final RegisterDisputeAgentRequest createRegisterDisputeAgentRequest(String disputeAgentType) { + return RegisterDisputeAgentRequest.newBuilder() + .setDisputeAgentType(disputeAgentType) + .setRegistrationKey(DEV_PRIVILEGE_PRIV_KEY).build(); + } + // Convenience methods for calling frequently used & thoroughly tested gRPC services. - protected final long getBalance() { - return grpcStubs.walletsService.getBalance(createBalanceRequest()).getBalance(); + protected final long getBalance(BisqAppConfig bisqAppConfig) { + return grpcStubs(bisqAppConfig).walletsService.getBalance(createBalanceRequest()).getBalance(); } - protected final void unlockWallet(String password, long timeout) { + protected final void unlockWallet(BisqAppConfig bisqAppConfig, String password, long timeout) { //noinspection ResultOfMethodCallIgnored - grpcStubs.walletsService.unlockWallet(createUnlockWalletRequest(password, timeout)); + grpcStubs(bisqAppConfig).walletsService.unlockWallet(createUnlockWalletRequest(password, timeout)); } - protected final void lockWallet() { + protected final void lockWallet(BisqAppConfig bisqAppConfig) { //noinspection ResultOfMethodCallIgnored - grpcStubs.walletsService.lockWallet(createLockWalletRequest()); + grpcStubs(bisqAppConfig).walletsService.lockWallet(createLockWalletRequest()); } - protected final String getUnusedBtcAddress() { - return grpcStubs.walletsService.getFundingAddresses(createGetFundingAddressesRequest()) + protected final String getUnusedBtcAddress(BisqAppConfig bisqAppConfig) { + //noinspection OptionalGetWithoutIsPresent + return grpcStubs(bisqAppConfig).walletsService.getFundingAddresses(createGetFundingAddressesRequest()) .getAddressBalanceInfoList() .stream() .filter(a -> a.getBalance() == 0 && a.getNumConfirmations() == 0) diff --git a/apitest/src/test/java/bisq/apitest/method/WalletProtectionTest.java b/apitest/src/test/java/bisq/apitest/method/WalletProtectionTest.java index 450fb58e010..f74c8e705c3 100644 --- a/apitest/src/test/java/bisq/apitest/method/WalletProtectionTest.java +++ b/apitest/src/test/java/bisq/apitest/method/WalletProtectionTest.java @@ -36,13 +36,13 @@ public static void setUp() { @Order(1) public void testSetWalletPassword() { var request = createSetWalletPasswordRequest("first-password"); - grpcStubs.walletsService.setWalletPassword(request); + grpcStubs(alicedaemon).walletsService.setWalletPassword(request); } @Test @Order(2) public void testGetBalanceOnEncryptedWalletShouldThrowException() { - Throwable exception = assertThrows(StatusRuntimeException.class, this::getBalance); + Throwable exception = assertThrows(StatusRuntimeException.class, () -> getBalance(alicedaemon)); assertEquals("UNKNOWN: wallet is locked", exception.getMessage()); } @@ -50,11 +50,10 @@ public void testGetBalanceOnEncryptedWalletShouldThrowException() { @Order(3) public void testUnlockWalletFor4Seconds() { var request = createUnlockWalletRequest("first-password", 4); - grpcStubs.walletsService.unlockWallet(request); - getBalance(); // should not throw 'wallet locked' exception - + grpcStubs(alicedaemon).walletsService.unlockWallet(request); + getBalance(alicedaemon); // should not throw 'wallet locked' exception sleep(4500); // let unlock timeout expire - Throwable exception = assertThrows(StatusRuntimeException.class, this::getBalance); + Throwable exception = assertThrows(StatusRuntimeException.class, () -> getBalance(alicedaemon)); assertEquals("UNKNOWN: wallet is locked", exception.getMessage()); } @@ -62,20 +61,19 @@ public void testUnlockWalletFor4Seconds() { @Order(4) public void testGetBalanceAfterUnlockTimeExpiryShouldThrowException() { var request = createUnlockWalletRequest("first-password", 3); - grpcStubs.walletsService.unlockWallet(request); + grpcStubs(alicedaemon).walletsService.unlockWallet(request); sleep(4000); // let unlock timeout expire - Throwable exception = assertThrows(StatusRuntimeException.class, this::getBalance); + Throwable exception = assertThrows(StatusRuntimeException.class, () -> getBalance(alicedaemon)); assertEquals("UNKNOWN: wallet is locked", exception.getMessage()); } @Test @Order(5) public void testLockWalletBeforeUnlockTimeoutExpiry() { - unlockWallet("first-password", 60); + unlockWallet(alicedaemon, "first-password", 60); var request = createLockWalletRequest(); - grpcStubs.walletsService.lockWallet(request); - - Throwable exception = assertThrows(StatusRuntimeException.class, this::getBalance); + grpcStubs(alicedaemon).walletsService.lockWallet(request); + Throwable exception = assertThrows(StatusRuntimeException.class, () -> getBalance(alicedaemon)); assertEquals("UNKNOWN: wallet is locked", exception.getMessage()); } @@ -83,40 +81,39 @@ public void testLockWalletBeforeUnlockTimeoutExpiry() { @Order(6) public void testLockWalletWhenWalletAlreadyLockedShouldThrowException() { var request = createLockWalletRequest(); - Throwable exception = assertThrows(StatusRuntimeException.class, () -> - grpcStubs.walletsService.lockWallet(request)); + grpcStubs(alicedaemon).walletsService.lockWallet(request)); assertEquals("UNKNOWN: wallet is already locked", exception.getMessage()); } @Test @Order(7) public void testUnlockWalletTimeoutOverride() { - unlockWallet("first-password", 2); + unlockWallet(alicedaemon, "first-password", 2); sleep(500); // override unlock timeout after 0.5s - unlockWallet("first-password", 6); + unlockWallet(alicedaemon, "first-password", 6); sleep(5000); - getBalance(); // getbalance 5s after resetting unlock timeout to 6s + getBalance(alicedaemon); // getbalance 5s after resetting unlock timeout to 6s } @Test @Order(8) public void testSetNewWalletPassword() { - var request = createSetWalletPasswordRequest("first-password", "second-password"); - grpcStubs.walletsService.setWalletPassword(request); - - unlockWallet("second-password", 2); - getBalance(); + var request = createSetWalletPasswordRequest( + "first-password", "second-password"); + grpcStubs(alicedaemon).walletsService.setWalletPassword(request); + unlockWallet(alicedaemon, "second-password", 2); + getBalance(alicedaemon); sleep(2500); // allow time for wallet save } @Test @Order(9) public void testSetNewWalletPasswordWithIncorrectNewPasswordShouldThrowException() { - var request = createSetWalletPasswordRequest("bad old password", "irrelevant"); - + var request = createSetWalletPasswordRequest( + "bad old password", "irrelevant"); Throwable exception = assertThrows(StatusRuntimeException.class, () -> - grpcStubs.walletsService.setWalletPassword(request)); + grpcStubs(alicedaemon).walletsService.setWalletPassword(request)); assertEquals("UNKNOWN: incorrect old password", exception.getMessage()); } @@ -124,8 +121,8 @@ public void testSetNewWalletPasswordWithIncorrectNewPasswordShouldThrowException @Order(10) public void testRemoveNewWalletPassword() { var request = createRemoveWalletPasswordRequest("second-password"); - grpcStubs.walletsService.removeWalletPassword(request); - getBalance(); // should not throw 'wallet locked' exception + grpcStubs(alicedaemon).walletsService.removeWalletPassword(request); + getBalance(alicedaemon); // should not throw 'wallet locked' exception } @AfterAll diff --git a/apitest/src/test/java/bisq/apitest/scenario/FundWalletScenarioTest.java b/apitest/src/test/java/bisq/apitest/scenario/FundWalletScenarioTest.java index e95e310eb58..0b30d72c1cf 100644 --- a/apitest/src/test/java/bisq/apitest/scenario/FundWalletScenarioTest.java +++ b/apitest/src/test/java/bisq/apitest/scenario/FundWalletScenarioTest.java @@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; +import static bisq.apitest.config.BisqAppConfig.alicedaemon; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; @@ -48,16 +49,17 @@ public static void setUp() { @Test @Order(1) public void testFundWallet() { - long balance = getBalance(); // bisq wallet was initialized with 10 btc + // bisq wallet was initialized with 10 btc + long balance = getBalance(alicedaemon); assertEquals(1000000000, balance); - String unusedAddress = getUnusedBtcAddress(); + String unusedAddress = getUnusedBtcAddress(alicedaemon); bitcoinCli.sendToAddress(unusedAddress, "2.5"); bitcoinCli.generateBlocks(1); sleep(1500); - balance = getBalance(); + balance = getBalance(alicedaemon); assertEquals(1250000000L, balance); // new balance is 12.5 btc } From 8384dd80049a5e1f05212e78b15551eb2c3f81e1 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 14 Sep 2020 12:13:36 -0300 Subject: [PATCH 08/10] Add api dispute agent registration test case This test case checks that mediators and refund agents can be registered over grpc, but not on mainnet. --- .../method/RegisterDisputeAgentsTest.java | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 apitest/src/test/java/bisq/apitest/method/RegisterDisputeAgentsTest.java diff --git a/apitest/src/test/java/bisq/apitest/method/RegisterDisputeAgentsTest.java b/apitest/src/test/java/bisq/apitest/method/RegisterDisputeAgentsTest.java new file mode 100644 index 00000000000..1ad3a36f16a --- /dev/null +++ b/apitest/src/test/java/bisq/apitest/method/RegisterDisputeAgentsTest.java @@ -0,0 +1,108 @@ +/* + * 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.method; + +import bisq.proto.grpc.RegisterDisputeAgentRequest; + +import io.grpc.StatusRuntimeException; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +import static bisq.apitest.config.BisqAppConfig.arbdaemon; +import static bisq.common.app.DevEnv.DEV_PRIVILEGE_PRIV_KEY; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation; + + +@SuppressWarnings("ResultOfMethodCallIgnored") +@Slf4j +@TestMethodOrder(OrderAnnotation.class) +public class RegisterDisputeAgentsTest extends MethodTest { + + @BeforeAll + public static void setUp() { + try { + setUpScaffold("bitcoind,seednode,arbdaemon"); + } catch (Exception ex) { + fail(ex); + } + } + + @Test + @Order(1) + public void testRegisterArbitratorShouldThrowException() { + var req = + createRegisterDisputeAgentRequest("arbitrator"); + Throwable exception = assertThrows(StatusRuntimeException.class, () -> + grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req)); + assertEquals("INVALID_ARGUMENT: arbitrators must be registered in a Bisq UI", + exception.getMessage()); + } + + @Test + @Order(2) + public void testInvalidDisputeAgentTypeArgShouldThrowException() { + var req = + createRegisterDisputeAgentRequest("badagent"); + Throwable exception = assertThrows(StatusRuntimeException.class, () -> + grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req)); + assertEquals("INVALID_ARGUMENT: unknown dispute agent type badagent", + exception.getMessage()); + } + + @Test + @Order(3) + public void testInvalidRegistrationKeyArgShouldThrowException() { + var req = RegisterDisputeAgentRequest.newBuilder() + .setDisputeAgentType("refundagent") + .setRegistrationKey("invalid" + DEV_PRIVILEGE_PRIV_KEY).build(); + Throwable exception = assertThrows(StatusRuntimeException.class, () -> + grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req)); + assertEquals("INVALID_ARGUMENT: invalid registration key", + exception.getMessage()); + } + + @Test + @Order(4) + public void testRegisterMediator() { + var req = + createRegisterDisputeAgentRequest("mediator"); + grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req); + } + + @Test + @Order(5) + public void testRegisterRefundAgent() { + var req = + createRegisterDisputeAgentRequest("refundagent"); + grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req); + } + + @AfterAll + public static void tearDown() { + tearDownScaffold(); + } +} From 3f0394f722904c3130a36ef60740197dbb68cb88 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 14 Sep 2020 12:14:37 -0300 Subject: [PATCH 09/10] Bump version to 1.3.8 --- apitest/scripts/mainnet-test.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apitest/scripts/mainnet-test.sh b/apitest/scripts/mainnet-test.sh index ae3afd73d2d..9c5889ff3a0 100755 --- a/apitest/scripts/mainnet-test.sh +++ b/apitest/scripts/mainnet-test.sh @@ -48,14 +48,14 @@ run ./bisq-cli --password="xyz" getversion [ "$status" -eq 0 ] echo "actual output: $output" >&2 - [ "$output" = "1.3.7" ] + [ "$output" = "1.3.8" ] } @test "test getversion" { run ./bisq-cli --password=xyz getversion [ "$status" -eq 0 ] echo "actual output: $output" >&2 - [ "$output" = "1.3.7" ] + [ "$output" = "1.3.8" ] } @test "test setwalletpassword \"a b c\"" { From d0fa24f220f76e44f584e946ddd4fe8ac8b009d5 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 14 Sep 2020 12:31:24 -0300 Subject: [PATCH 10/10] Add registerdisputeagent method to CLI This change finishes the work to support registration of mediators and refund agents on arbitration daemons running on regtest. This method cannot be used to register dispute agents on mainnet; users will see an error msg if they try. Some minor formatting changes are included in this change. --- cli/src/main/java/bisq/cli/CliMain.java | 29 ++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java index f229fcd4538..8e9c3136928 100644 --- a/cli/src/main/java/bisq/cli/CliMain.java +++ b/cli/src/main/java/bisq/cli/CliMain.java @@ -25,6 +25,7 @@ import bisq.proto.grpc.GetPaymentAccountsRequest; import bisq.proto.grpc.GetVersionRequest; import bisq.proto.grpc.LockWalletRequest; +import bisq.proto.grpc.RegisterDisputeAgentRequest; import bisq.proto.grpc.RemoveWalletPasswordRequest; import bisq.proto.grpc.SetWalletPasswordRequest; import bisq.proto.grpc.UnlockWalletRequest; @@ -69,7 +70,8 @@ private enum Method { lockwallet, unlockwallet, removewalletpassword, - setwalletpassword + setwalletpassword, + registerdisputeagent } public static void main(String[] args) { @@ -128,6 +130,7 @@ public static void run(String[] args) { throw new IllegalArgumentException("missing required 'password' option"); GrpcStubs grpcStubs = new GrpcStubs(host, port, password); + var disputeAgentsService = grpcStubs.disputeAgentsService; var versionService = grpcStubs.versionService; var offersService = grpcStubs.offersService; var paymentAccountsService = grpcStubs.paymentAccountsService; @@ -166,7 +169,8 @@ public static void run(String[] args) { } case getoffers: { if (nonOptionArgs.size() < 3) - throw new IllegalArgumentException("incorrect parameter count, expecting direction (buy|sell), currency code"); + throw new IllegalArgumentException("incorrect parameter count," + + " expecting direction (buy|sell), currency code"); var direction = nonOptionArgs.get(1); var fiatCurrency = nonOptionArgs.get(2); @@ -193,7 +197,7 @@ public static void run(String[] args) { .setAccountNumber(accountNumber) .setFiatCurrencyCode(fiatCurrencyCode).build(); paymentAccountsService.createPaymentAccount(request); - out.println(format("payment account %s saved", accountName)); + out.printf("payment account %s saved", accountName); return; } case getpaymentaccts: { @@ -232,7 +236,8 @@ public static void run(String[] args) { if (nonOptionArgs.size() < 2) throw new IllegalArgumentException("no password specified"); - var request = RemoveWalletPasswordRequest.newBuilder().setPassword(nonOptionArgs.get(1)).build(); + var request = RemoveWalletPasswordRequest.newBuilder() + .setPassword(nonOptionArgs.get(1)).build(); walletsService.removeWalletPassword(request); out.println("wallet decrypted"); return; @@ -241,7 +246,8 @@ public static void run(String[] args) { if (nonOptionArgs.size() < 2) throw new IllegalArgumentException("no password specified"); - var requestBuilder = SetWalletPasswordRequest.newBuilder().setPassword(nonOptionArgs.get(1)); + var requestBuilder = SetWalletPasswordRequest.newBuilder() + .setPassword(nonOptionArgs.get(1)); var hasNewPassword = nonOptionArgs.size() == 3; if (hasNewPassword) requestBuilder.setNewPassword(nonOptionArgs.get(2)); @@ -249,6 +255,19 @@ public static void run(String[] args) { out.println("wallet encrypted" + (hasNewPassword ? " with new password" : "")); return; } + case registerdisputeagent: { + if (nonOptionArgs.size() < 3) + throw new IllegalArgumentException( + "incorrect parameter count, expecting dispute agent type, registration key"); + + var disputeAgentType = nonOptionArgs.get(1); + var registrationKey = nonOptionArgs.get(2); + var requestBuilder = RegisterDisputeAgentRequest.newBuilder() + .setDisputeAgentType(disputeAgentType).setRegistrationKey(registrationKey); + disputeAgentsService.registerDisputeAgent(requestBuilder.build()); + out.println(disputeAgentType + " registered"); + return; + } default: { throw new RuntimeException(format("unhandled method '%s'", method)); }