diff --git a/apitest/scripts/mainnet-test.sh b/apitest/scripts/mainnet-test.sh index 9c5889ff3a0..bfab9fc3b5f 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.8" ] + [ "$output" = "1.3.9" ] } @test "test getversion" { run ./bisq-cli --password=xyz getversion [ "$status" -eq 0 ] echo "actual output: $output" >&2 - [ "$output" = "1.3.8" ] + [ "$output" = "1.3.9" ] } @test "test setwalletpassword \"a b c\"" { @@ -166,15 +166,15 @@ [ "$output" = "Error: address bogus not found in wallet" ] } -@test "test createpaymentacct PerfectMoneyDummy (missing nbr, ccy params)" { - run ./bisq-cli --password=xyz createpaymentacct PerfectMoneyDummy +@test "test createpaymentacct PerfectMoneyDummy (missing name, nbr, ccy params)" { + run ./bisq-cli --password=xyz createpaymentacct PERFECT_MONEY [ "$status" -eq 1 ] echo "actual output: $output" >&2 - [ "$output" = "Error: incorrect parameter count, expecting account name, account number, currency code" ] + [ "$output" = "Error: incorrect parameter count, expecting payment method id, account name, account number, currency code" ] } -@test "test createpaymentacct PerfectMoneyDummy 0123456789 USD" { - run ./bisq-cli --password=xyz createpaymentacct PerfectMoneyDummy 0123456789 USD +@test "test createpaymentacct PERFECT_MONEY PerfectMoneyDummy 0123456789 USD" { + run ./bisq-cli --password=xyz createpaymentacct PERFECT_MONEY PerfectMoneyDummy 0123456789 USD [ "$status" -eq 0 ] } diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java index f229fcd4538..341420666d2 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) { @@ -114,9 +116,9 @@ public static void run(String[] args) { } var methodName = nonOptionArgs.get(0); - final Method method; + Method method; try { - method = Method.valueOf(methodName); + method = getMethodFromCmd(methodName); } catch (IllegalArgumentException ex) { throw new IllegalArgumentException(format("'%s' is not a supported method", methodName)); } @@ -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,34 +169,38 @@ 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); var request = GetOffersRequest.newBuilder() .setDirection(direction) - .setFiatCurrencyCode(fiatCurrency) + .setCurrencyCode(fiatCurrency) .build(); var reply = offersService.getOffers(request); out.println(formatOfferTable(reply.getOffersList(), fiatCurrency)); return; } case createpaymentacct: { - if (nonOptionArgs.size() < 4) + if (nonOptionArgs.size() < 5) throw new IllegalArgumentException( - "incorrect parameter count, expecting account name, account number, currency code"); + "incorrect parameter count, expecting payment method id," + + " account name, account number, currency code"); - var accountName = nonOptionArgs.get(1); - var accountNumber = nonOptionArgs.get(2); - var fiatCurrencyCode = nonOptionArgs.get(3); + var paymentMethodId = nonOptionArgs.get(1); + var accountName = nonOptionArgs.get(2); + var accountNumber = nonOptionArgs.get(3); + var currencyCode = nonOptionArgs.get(4); var request = CreatePaymentAccountRequest.newBuilder() + .setPaymentMethodId(paymentMethodId) .setAccountName(accountName) .setAccountNumber(accountNumber) - .setFiatCurrencyCode(fiatCurrencyCode).build(); + .setCurrencyCode(currencyCode).build(); paymentAccountsService.createPaymentAccount(request); - out.println(format("payment account %s saved", accountName)); + out.printf("payment account %s saved", accountName); return; } case getpaymentaccts: { @@ -232,7 +239,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 +249,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 +258,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)); } @@ -260,6 +282,13 @@ public static void run(String[] args) { } } + private static Method getMethodFromCmd(String methodName) { + // TODO if we use const type for enum we need add some mapping. Even if we don't + // change now it is handy to have flexibility in case we change internal code + // and don't want to break user commands. + return Method.valueOf(methodName.toLowerCase()); + } + private static void printHelp(OptionParser parser, PrintStream stream) { try { stream.println("Bisq RPC Client"); diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index 35af589f3d5..81af1fae0f3 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -139,8 +139,14 @@ public void createOffer(String offerId, // PaymentAccounts /////////////////////////////////////////////////////////////////////////////////////////// - public void createPaymentAccount(String accountName, String accountNumber, String fiatCurrencyCode) { - paymentAccountsService.createPaymentAccount(accountName, accountNumber, fiatCurrencyCode); + public void createPaymentAccount(String paymentMethodId, + String accountName, + String accountNumber, + String currencyCode) { + paymentAccountsService.createPaymentAccount(paymentMethodId, + accountName, + accountNumber, + currencyCode); } public Set getPaymentAccounts() { diff --git a/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java b/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java index 29e51b88535..5aabdd822f7 100644 --- a/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java +++ b/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java @@ -68,7 +68,7 @@ public CoreDisputeAgentsService(Config config, this.languageCodes = Arrays.asList("de", "en", "es", "fr"); } - public void registerDisputeAgent(String disputeAgentType, String registrationKey) { + void registerDisputeAgent(String disputeAgentType, String registrationKey) { if (!p2PService.isBootstrapped()) throw new IllegalStateException("p2p service is not bootstrapped yet"); diff --git a/core/src/main/java/bisq/core/api/CoreOffersService.java b/core/src/main/java/bisq/core/api/CoreOffersService.java index c5fac7442af..61ee0e006db 100644 --- a/core/src/main/java/bisq/core/api/CoreOffersService.java +++ b/core/src/main/java/bisq/core/api/CoreOffersService.java @@ -58,7 +58,7 @@ public CoreOffersService(CreateOfferService createOfferService, this.user = user; } - public List getOffers(String direction, String fiatCurrencyCode) { + List getOffers(String direction, String fiatCurrencyCode) { List offers = offerBookService.getOffers().stream() .filter(o -> { var offerOfWantedDirection = o.getDirection().name().equalsIgnoreCase(direction); @@ -77,16 +77,16 @@ public List getOffers(String direction, String fiatCurrencyCode) { return offers; } - public void createOffer(String currencyCode, - String directionAsString, - long priceAsLong, - boolean useMarketBasedPrice, - double marketPriceMargin, - long amountAsLong, - long minAmountAsLong, - double buyerSecurityDeposit, - String paymentAccountId, - TransactionResultHandler resultHandler) { + void createOffer(String currencyCode, + String directionAsString, + long priceAsLong, + boolean useMarketBasedPrice, + double marketPriceMargin, + long amountAsLong, + long minAmountAsLong, + double buyerSecurityDeposit, + String paymentAccountId, + TransactionResultHandler resultHandler) { String offerId = createOfferService.getRandomOfferId(); OfferPayload.Direction direction = OfferPayload.Direction.valueOf(directionAsString); Price price = Price.valueOf(currencyCode, priceAsLong); @@ -111,18 +111,18 @@ public void createOffer(String currencyCode, resultHandler); } - public void createOffer(String offerId, - String currencyCode, - OfferPayload.Direction direction, - Price price, - boolean useMarketBasedPrice, - double marketPriceMargin, - Coin amount, - Coin minAmount, - double buyerSecurityDeposit, - PaymentAccount paymentAccount, - boolean useSavingsWallet, - TransactionResultHandler resultHandler) { + void createOffer(String offerId, + String currencyCode, + OfferPayload.Direction direction, + Price price, + boolean useMarketBasedPrice, + double marketPriceMargin, + Coin amount, + Coin minAmount, + double buyerSecurityDeposit, + PaymentAccount paymentAccount, + boolean useSavingsWallet, + TransactionResultHandler resultHandler) { Coin useDefaultTxFee = Coin.ZERO; Offer offer = createOfferService.createAndGetOffer(offerId, direction, diff --git a/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java b/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java index f40c56c20d7..b49c57a23f9 100644 --- a/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java +++ b/core/src/main/java/bisq/core/api/CorePaymentAccountsService.java @@ -33,6 +33,9 @@ import lombok.extern.slf4j.Slf4j; +import static bisq.core.payment.payload.PaymentMethod.*; +import static com.google.common.base.Preconditions.checkNotNull; + @Slf4j class CorePaymentAccountsService { @@ -49,17 +52,19 @@ public CorePaymentAccountsService(Config config, this.user = user; } - public void createPaymentAccount(String accountName, String accountNumber, String fiatCurrencyCode) { - // Create and persist a PerfectMoney dummy payment account. There is no guard - // against creating accounts with duplicate names & numbers, only the uuid and - // creation date are unique. - PaymentMethod dummyPaymentMethod = PaymentMethod.getDummyPaymentMethod(PaymentMethod.PERFECT_MONEY_ID); - PaymentAccount paymentAccount = PaymentAccountFactory.getPaymentAccount(dummyPaymentMethod); - paymentAccount.init(); - paymentAccount.setAccountName(accountName); - ((PerfectMoneyAccount) paymentAccount).setAccountNr(accountNumber); - paymentAccount.setSingleTradeCurrency(new FiatCurrency(fiatCurrencyCode.toUpperCase())); - user.addPaymentAccount(paymentAccount); + void createPaymentAccount(String paymentMethodId, + String accountName, + String accountNumber, + String currencyCode) { + + PaymentAccount paymentAccount = getNewPaymentAccount(paymentMethodId, + accountName, + accountNumber, + currencyCode); + + // TODO not sure if there is more to do at account creation. + // Need to check all the flow when its created from UI. + user.addPaymentAccountIfNotExists(paymentAccount); // Don't do this on mainnet until thoroughly tested. if (config.baseCurrencyNetwork.isRegtest()) @@ -68,7 +73,64 @@ public void createPaymentAccount(String accountName, String accountNumber, Strin log.info("Payment account {} saved", paymentAccount.getId()); } - public Set getPaymentAccounts() { + Set getPaymentAccounts() { return user.getPaymentAccounts(); } + + private PaymentAccount getNewPaymentAccount(String paymentMethodId, + String accountName, + String accountNumber, + String currencyCode) { + PaymentAccount paymentAccount = null; + PaymentMethod paymentMethod = getPaymentMethodById(paymentMethodId); + + switch (paymentMethod.getId()) { + case UPHOLD_ID: + case MONEY_BEAM_ID: + case POPMONEY_ID: + case REVOLUT_ID: + //noinspection DuplicateBranchesInSwitch + log.error("PaymentMethod {} not supported yet.", paymentMethod); + break; + case PERFECT_MONEY_ID: + // Create and persist a PerfectMoney dummy payment account. There is no + // guard against creating accounts with duplicate names & numbers, only + // the uuid and creation date are unique. + paymentAccount = PaymentAccountFactory.getPaymentAccount(paymentMethod); + paymentAccount.init(); + paymentAccount.setAccountName(accountName); + ((PerfectMoneyAccount) paymentAccount).setAccountNr(accountNumber); + paymentAccount.setSingleTradeCurrency(new FiatCurrency(currencyCode)); + break; + case SEPA_ID: + case SEPA_INSTANT_ID: + case FASTER_PAYMENTS_ID: + case NATIONAL_BANK_ID: + case SAME_BANK_ID: + case SPECIFIC_BANKS_ID: + case JAPAN_BANK_ID: + case ALI_PAY_ID: + case WECHAT_PAY_ID: + case SWISH_ID: + case CLEAR_X_CHANGE_ID: + case CHASE_QUICK_PAY_ID: + case INTERAC_E_TRANSFER_ID: + case US_POSTAL_MONEY_ORDER_ID: + case MONEY_GRAM_ID: + case WESTERN_UNION_ID: + case CASH_DEPOSIT_ID: + case HAL_CASH_ID: + case F2F_ID: + case PROMPT_PAY_ID: + case ADVANCED_CASH_ID: + default: + log.error("PaymentMethod {} not supported yet.", paymentMethod); + break; + } + + checkNotNull(paymentAccount, + "Could not create payment account with paymentMethodId " + + paymentMethodId + "."); + return paymentAccount; + } } diff --git a/core/src/main/java/bisq/core/api/CoreWalletsService.java b/core/src/main/java/bisq/core/api/CoreWalletsService.java index 10feea5b8e6..4ae4efe7a16 100644 --- a/core/src/main/java/bisq/core/api/CoreWalletsService.java +++ b/core/src/main/java/bisq/core/api/CoreWalletsService.java @@ -71,7 +71,7 @@ public CoreWalletsService(Balances balances, this.btcWalletService = btcWalletService; } - public long getAvailableBalance() { + long getAvailableBalance() { verifyWalletsAreAvailable(); verifyEncryptedWalletIsUnlocked(); @@ -82,40 +82,38 @@ public long getAvailableBalance() { return balance.getValue(); } - public long getAddressBalance(String addressString) { + long getAddressBalance(String addressString) { Address address = getAddressEntry(addressString).getAddress(); return btcWalletService.getBalanceForAddress(address).value; } - public AddressBalanceInfo getAddressBalanceInfo(String addressString) { + AddressBalanceInfo getAddressBalanceInfo(String addressString) { var satoshiBalance = getAddressBalance(addressString); var numConfirmations = getNumConfirmationsForMostRecentTransaction(addressString); return new AddressBalanceInfo(addressString, satoshiBalance, numConfirmations); } - public List getFundingAddresses() { + List getFundingAddresses() { verifyWalletsAreAvailable(); verifyEncryptedWalletIsUnlocked(); // Create a new funding address if none exists. - if (btcWalletService.getAvailableAddressEntries().size() == 0) + if (btcWalletService.getAvailableAddressEntries().isEmpty()) btcWalletService.getFreshAddressEntry(); - List addressStrings = - btcWalletService - .getAvailableAddressEntries() - .stream() - .map(AddressEntry::getAddressString) - .collect(Collectors.toList()); + List addressStrings = btcWalletService + .getAvailableAddressEntries() + .stream() + .map(AddressEntry::getAddressString) + .collect(Collectors.toList()); // getAddressBalance is memoized, because we'll map it over addresses twice. // To get the balances, we'll be using .getUnchecked, because we know that // this::getAddressBalance cannot return null. var balances = memoize(this::getAddressBalance); - boolean noAddressHasZeroBalance = - addressStrings.stream() - .allMatch(addressString -> balances.getUnchecked(addressString) != 0); + boolean noAddressHasZeroBalance = addressStrings.stream() + .allMatch(addressString -> balances.getUnchecked(addressString) != 0); if (noAddressHasZeroBalance) { var newZeroBalanceAddress = btcWalletService.getFreshAddressEntry(); @@ -129,13 +127,13 @@ public List getFundingAddresses() { .collect(Collectors.toList()); } - public int getNumConfirmationsForMostRecentTransaction(String addressString) { + int getNumConfirmationsForMostRecentTransaction(String addressString) { Address address = getAddressEntry(addressString).getAddress(); TransactionConfidence confidence = btcWalletService.getConfidenceForAddress(address); return confidence == null ? 0 : confidence.getDepthInBlocks(); } - public void setWalletPassword(String password, String newPassword) { + void setWalletPassword(String password, String newPassword) { verifyWalletsAreAvailable(); KeyCrypterScrypt keyCrypterScrypt = getKeyCrypterScrypt(); @@ -165,7 +163,7 @@ public void setWalletPassword(String password, String newPassword) { walletsManager.backupWallets(); } - public void lockWallet() { + void lockWallet() { if (!walletsManager.areWalletsEncrypted()) throw new IllegalStateException("wallet is not encrypted with a password"); @@ -175,7 +173,7 @@ public void lockWallet() { tempAesKey = null; } - public void unlockWallet(String password, long timeout) { + void unlockWallet(String password, long timeout) { verifyWalletIsAvailableAndEncrypted(); KeyCrypterScrypt keyCrypterScrypt = getKeyCrypterScrypt(); @@ -213,7 +211,7 @@ public void run() { // Provided for automated wallet protection method testing, despite the // security risks exposed by providing users the ability to decrypt their wallets. - public void removeWalletPassword(String password) { + void removeWalletPassword(String password) { verifyWalletIsAvailableAndEncrypted(); KeyCrypterScrypt keyCrypterScrypt = getKeyCrypterScrypt(); diff --git a/core/src/main/java/bisq/core/btc/wallet/WalletService.java b/core/src/main/java/bisq/core/btc/wallet/WalletService.java index d56b34a7c95..7013d529f73 100644 --- a/core/src/main/java/bisq/core/btc/wallet/WalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/WalletService.java @@ -734,6 +734,9 @@ public static Transaction maybeAddTxToWallet(Transaction transaction, return maybeAddTxToWallet(transaction.bitcoinSerialize(), wallet, source); } + public Address getAddress(String addressString) { + return Address.fromBase58(params, addressString); + } /////////////////////////////////////////////////////////////////////////////////////////// // bisqWalletEventListener diff --git a/core/src/main/java/bisq/core/user/User.java b/core/src/main/java/bisq/core/user/User.java index d9adac9a09c..86cb5048b78 100644 --- a/core/src/main/java/bisq/core/user/User.java +++ b/core/src/main/java/bisq/core/user/User.java @@ -189,6 +189,12 @@ public boolean hasPaymentAccountForCurrency(TradeCurrency tradeCurrency) { // Collection operations /////////////////////////////////////////////////////////////////////////////////////////// + public void addPaymentAccountIfNotExists(PaymentAccount paymentAccount) { + if (!paymentAccountExists(paymentAccount)) { + addPaymentAccount(paymentAccount); + } + } + public void addPaymentAccount(PaymentAccount paymentAccount) { paymentAccount.onAddToUser(); @@ -493,4 +499,8 @@ public PriceAlertFilter getPriceAlertFilter() { public boolean isPaymentAccountImport() { return isPaymentAccountImport; } + + private boolean paymentAccountExists(PaymentAccount paymentAccount) { + return getPaymentAccountsAsObservable().stream().anyMatch(e -> e.equals(paymentAccount)); + } } diff --git a/daemon/src/main/java/bisq/daemon/app/BisqDaemonMain.java b/daemon/src/main/java/bisq/daemon/app/BisqDaemonMain.java index f7a5dac3d6b..9bc34648d60 100644 --- a/daemon/src/main/java/bisq/daemon/app/BisqDaemonMain.java +++ b/daemon/src/main/java/bisq/daemon/app/BisqDaemonMain.java @@ -23,6 +23,7 @@ import bisq.common.UserThread; import bisq.common.app.AppModule; +import bisq.common.handlers.ResultHandler; import bisq.common.setup.CommonSetup; import com.google.common.util.concurrent.ThreadFactoryBuilder; @@ -39,13 +40,15 @@ @Slf4j public class BisqDaemonMain extends BisqHeadlessAppMain implements BisqSetup.BisqSetupListener { + private GrpcServer grpcServer; + public static void main(String[] args) { new BisqDaemonMain().execute(args); } - /////////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// // First synchronous execution tasks - /////////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// @Override protected void configUserThread() { @@ -70,9 +73,9 @@ protected void onApplicationLaunched() { headlessApp.setGracefulShutDownHandler(this); } - /////////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// // We continue with a series of synchronous execution tasks - /////////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// @Override protected AppModule getModule() { @@ -91,7 +94,8 @@ protected void startApplication() { // We need to be in user thread! We mapped at launchApplication already... headlessApp.startApplication(); - // In headless mode we don't have an async behaviour so we trigger the setup by calling onApplicationStarted + // In headless mode we don't have an async behaviour so we trigger the setup by + // calling onApplicationStarted. onApplicationStarted(); } @@ -99,7 +103,14 @@ protected void startApplication() { protected void onApplicationStarted() { super.onApplicationStarted(); - GrpcServer grpcServer = injector.getInstance(GrpcServer.class); + grpcServer = injector.getInstance(GrpcServer.class); grpcServer.start(); } + + @Override + public void gracefulShutDown(ResultHandler resultHandler) { + super.gracefulShutDown(resultHandler); + + grpcServer.shutdown(); + } } diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcGetTradeStatisticsService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcGetTradeStatisticsService.java new file mode 100644 index 00000000000..281fc121185 --- /dev/null +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcGetTradeStatisticsService.java @@ -0,0 +1,37 @@ +package bisq.daemon.grpc; + +import bisq.core.api.CoreApi; +import bisq.core.trade.statistics.TradeStatistics2; + +import bisq.proto.grpc.GetTradeStatisticsGrpc; +import bisq.proto.grpc.GetTradeStatisticsReply; +import bisq.proto.grpc.GetTradeStatisticsRequest; + +import io.grpc.stub.StreamObserver; + +import javax.inject.Inject; + +import java.util.stream.Collectors; + +class GrpcGetTradeStatisticsService extends GetTradeStatisticsGrpc.GetTradeStatisticsImplBase { + + private final CoreApi coreApi; + + @Inject + public GrpcGetTradeStatisticsService(CoreApi coreApi) { + this.coreApi = coreApi; + } + + @Override + public void getTradeStatistics(GetTradeStatisticsRequest req, + StreamObserver responseObserver) { + + var tradeStatistics = coreApi.getTradeStatistics().stream() + .map(TradeStatistics2::toProtoTradeStatistics2) + .collect(Collectors.toList()); + + var reply = GetTradeStatisticsReply.newBuilder().addAllTradeStatistics(tradeStatistics).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } +} diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java index 8a3fa548d92..b8b00f6d123 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java @@ -52,7 +52,7 @@ public void getOffers(GetOffersRequest req, // The client cannot see bisq.core.Offer or its fromProto method. // We use the lighter weight OfferInfo proto wrapper instead, containing just // enough fields to view and create offers. - List result = coreApi.getOffers(req.getDirection(), req.getFiatCurrencyCode()) + List result = coreApi.getOffers(req.getDirection(), req.getCurrencyCode()) .stream().map(offer -> new OfferInfo.OfferInfoBuilder() .withId(offer.getId()) .withDirection(offer.getDirection().name()) diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java index e630b30fc50..91060cbc829 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcPaymentAccountsService.java @@ -45,7 +45,10 @@ public GrpcPaymentAccountsService(CoreApi coreApi) { @Override public void createPaymentAccount(CreatePaymentAccountRequest req, StreamObserver responseObserver) { - coreApi.createPaymentAccount(req.getAccountName(), req.getAccountNumber(), req.getFiatCurrencyCode()); + coreApi.createPaymentAccount(req.getPaymentMethodId(), + req.getAccountName(), + req.getAccountNumber(), + req.getCurrencyCode()); var reply = CreatePaymentAccountReply.newBuilder().build(); responseObserver.onNext(reply); responseObserver.onCompleted(); @@ -54,10 +57,11 @@ public void createPaymentAccount(CreatePaymentAccountRequest req, @Override public void getPaymentAccounts(GetPaymentAccountsRequest req, StreamObserver responseObserver) { - var tradeStatistics = coreApi.getPaymentAccounts().stream() + var paymentAccounts = coreApi.getPaymentAccounts().stream() .map(PaymentAccount::toProtoMessage) .collect(Collectors.toList()); - var reply = GetPaymentAccountsReply.newBuilder().addAllPaymentAccounts(tradeStatistics).build(); + var reply = GetPaymentAccountsReply.newBuilder() + .addAllPaymentAccounts(paymentAccounts).build(); responseObserver.onNext(reply); responseObserver.onCompleted(); } diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcServer.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcServer.java index 0c0e2f5cb3f..3090dce14a6 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcServer.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcServer.java @@ -18,30 +18,21 @@ package bisq.daemon.grpc; import bisq.core.api.CoreApi; -import bisq.core.trade.statistics.TradeStatistics2; import bisq.common.config.Config; -import bisq.proto.grpc.GetTradeStatisticsGrpc; -import bisq.proto.grpc.GetTradeStatisticsReply; -import bisq.proto.grpc.GetTradeStatisticsRequest; -import bisq.proto.grpc.GetVersionGrpc; -import bisq.proto.grpc.GetVersionReply; -import bisq.proto.grpc.GetVersionRequest; - import io.grpc.Server; import io.grpc.ServerBuilder; -import io.grpc.stub.StreamObserver; import javax.inject.Inject; +import javax.inject.Singleton; import java.io.IOException; import java.io.UncheckedIOException; -import java.util.stream.Collectors; - import lombok.extern.slf4j.Slf4j; +@Singleton @Slf4j public class GrpcServer { @@ -51,19 +42,22 @@ public class GrpcServer { @Inject public GrpcServer(Config config, CoreApi coreApi, + PasswordAuthInterceptor passwordAuthInterceptor, GrpcDisputeAgentsService disputeAgentsService, GrpcOffersService offersService, GrpcPaymentAccountsService paymentAccountsService, + GrpcVersionService versionService, + GrpcGetTradeStatisticsService tradeStatisticsService, GrpcWalletsService walletsService) { this.coreApi = coreApi; this.server = ServerBuilder.forPort(config.apiPort) .addService(disputeAgentsService) - .addService(new GetVersionService()) - .addService(new GetTradeStatisticsService()) .addService(offersService) .addService(paymentAccountsService) + .addService(tradeStatisticsService) + .addService(versionService) .addService(walletsService) - .intercept(new PasswordAuthInterceptor(config.apiPassword)) + .intercept(passwordAuthInterceptor) .build(); } @@ -71,36 +65,14 @@ public void start() { try { server.start(); log.info("listening on port {}", server.getPort()); - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - server.shutdown(); - log.info("shutdown complete"); - })); } catch (IOException ex) { throw new UncheckedIOException(ex); } } - class GetVersionService extends GetVersionGrpc.GetVersionImplBase { - @Override - public void getVersion(GetVersionRequest req, StreamObserver responseObserver) { - var reply = GetVersionReply.newBuilder().setVersion(coreApi.getVersion()).build(); - responseObserver.onNext(reply); - responseObserver.onCompleted(); - } - } - - class GetTradeStatisticsService extends GetTradeStatisticsGrpc.GetTradeStatisticsImplBase { - @Override - public void getTradeStatistics(GetTradeStatisticsRequest req, - StreamObserver responseObserver) { - - var tradeStatistics = coreApi.getTradeStatistics().stream() - .map(TradeStatistics2::toProtoTradeStatistics2) - .collect(Collectors.toList()); - - var reply = GetTradeStatisticsReply.newBuilder().addAllTradeStatistics(tradeStatistics).build(); - responseObserver.onNext(reply); - responseObserver.onCompleted(); - } + public void shutdown() { + log.info("Server shutdown started"); + server.shutdown(); + log.info("Server shutdown complete"); } } diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcVersionService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcVersionService.java new file mode 100644 index 00000000000..658ef10e27f --- /dev/null +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcVersionService.java @@ -0,0 +1,28 @@ +package bisq.daemon.grpc; + +import bisq.core.api.CoreApi; + +import bisq.proto.grpc.GetVersionGrpc; +import bisq.proto.grpc.GetVersionReply; +import bisq.proto.grpc.GetVersionRequest; + +import io.grpc.stub.StreamObserver; + +import javax.inject.Inject; + +class GrpcVersionService extends GetVersionGrpc.GetVersionImplBase { + + private final CoreApi coreApi; + + @Inject + public GrpcVersionService(CoreApi coreApi) { + this.coreApi = coreApi; + } + + @Override + public void getVersion(GetVersionRequest req, StreamObserver responseObserver) { + var reply = GetVersionReply.newBuilder().setVersion(coreApi.getVersion()).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } +} diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcWalletsService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcWalletsService.java index 04dd3460234..1b5cb42e4cc 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcWalletsService.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcWalletsService.java @@ -54,11 +54,17 @@ public GrpcWalletsService(CoreApi coreApi) { this.coreApi = coreApi; } + // TODO we need to support 3 or 4 balance types: available, reserved, lockedInTrade + // and maybe total wallet balance (available+reserved). To not duplicate the methods, + // we should pass an enum type. Enums in proto are a bit cumbersome as they are + // global so you quickly run into namespace conflicts if not always prefixes which + // makes it more verbose. In the core code base we move to the strategy to store the + // enum name and map it. This gives also more flexibility with updates. @Override public void getBalance(GetBalanceRequest req, StreamObserver responseObserver) { try { - long result = coreApi.getAvailableBalance(); - var reply = GetBalanceReply.newBuilder().setBalance(result).build(); + long availableBalance = coreApi.getAvailableBalance(); + var reply = GetBalanceReply.newBuilder().setBalance(availableBalance).build(); responseObserver.onNext(reply); responseObserver.onCompleted(); } catch (IllegalStateException cause) { @@ -72,8 +78,9 @@ public void getBalance(GetBalanceRequest req, StreamObserver re public void getAddressBalance(GetAddressBalanceRequest req, StreamObserver responseObserver) { try { - AddressBalanceInfo result = coreApi.getAddressBalanceInfo(req.getAddress()); - var reply = GetAddressBalanceReply.newBuilder().setAddressBalanceInfo(result.toProtoMessage()).build(); + AddressBalanceInfo balanceInfo = coreApi.getAddressBalanceInfo(req.getAddress()); + var reply = GetAddressBalanceReply.newBuilder() + .setAddressBalanceInfo(balanceInfo.toProtoMessage()).build(); responseObserver.onNext(reply); responseObserver.onCompleted(); } catch (IllegalStateException cause) { @@ -87,10 +94,10 @@ public void getAddressBalance(GetAddressBalanceRequest req, public void getFundingAddresses(GetFundingAddressesRequest req, StreamObserver responseObserver) { try { - List result = coreApi.getFundingAddresses(); + List balanceInfo = coreApi.getFundingAddresses(); var reply = GetFundingAddressesReply.newBuilder() .addAllAddressBalanceInfo( - result.stream() + balanceInfo.stream() .map(AddressBalanceInfo::toProtoMessage) .collect(Collectors.toList())) .build(); diff --git a/daemon/src/main/java/bisq/daemon/grpc/PasswordAuthInterceptor.java b/daemon/src/main/java/bisq/daemon/grpc/PasswordAuthInterceptor.java index 7798857dd2a..1bf542d7fe7 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/PasswordAuthInterceptor.java +++ b/daemon/src/main/java/bisq/daemon/grpc/PasswordAuthInterceptor.java @@ -17,12 +17,16 @@ package bisq.daemon.grpc; +import bisq.common.config.Config; + import io.grpc.Metadata; import io.grpc.ServerCall; import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptor; import io.grpc.StatusRuntimeException; +import javax.inject.Inject; + import static io.grpc.Metadata.ASCII_STRING_MARSHALLER; import static io.grpc.Metadata.Key; import static io.grpc.Status.UNAUTHENTICATED; @@ -36,12 +40,13 @@ */ class PasswordAuthInterceptor implements ServerInterceptor { - public static final String PASSWORD_KEY = "password"; + private static final String PASSWORD_KEY = "password"; private final String expectedPasswordValue; - public PasswordAuthInterceptor(String expectedPasswordValue) { - this.expectedPasswordValue = expectedPasswordValue; + @Inject + public PasswordAuthInterceptor(Config config) { + this.expectedPasswordValue = config.apiPassword; } @Override diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index ed2cb4fd61b..21c59b8643d 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -53,7 +53,7 @@ service Offers { message GetOffersRequest { string direction = 1; - string fiatCurrencyCode = 2; + string currencyCode = 2; } message GetOffersReply { @@ -61,7 +61,7 @@ message GetOffersReply { } message CreateOfferRequest { - string currencyCode = 1; // TODO switch order w/ direction field in next PR + string currencyCode = 1; string direction = 2; uint64 price = 3; bool useMarketBasedPrice = 4; @@ -107,9 +107,11 @@ service PaymentAccounts { } message CreatePaymentAccountRequest { - string accountName = 1; - string accountNumber = 2; - string fiatCurrencyCode = 3; +string paymentMethodId = 1; + string accountName = 2; + string accountNumber = 3; + // TODO Support all currencies. Maybe add a repeated and if only one is used its a singletonList. + string currencyCode = 4; } message CreatePaymentAccountReply { @@ -123,7 +125,7 @@ message GetPaymentAccountsReply { } /////////////////////////////////////////////////////////////////////////////////////////// -// TradeStatistics +// GetTradeStatistics /////////////////////////////////////////////////////////////////////////////////////////// service GetTradeStatistics {