From cb65de6d2e1497eb8eaf86404fc3285729aba87c Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Fri, 23 Oct 2020 12:47:06 -0300 Subject: [PATCH 01/30] Block on tx-fee-request in core TakeOfferModel init Added license comment too. --- .../core/offer/takeoffer/TakeOfferModel.java | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/bisq/core/offer/takeoffer/TakeOfferModel.java b/core/src/main/java/bisq/core/offer/takeoffer/TakeOfferModel.java index efd0523ec19..c48b3c8ad4c 100644 --- a/core/src/main/java/bisq/core/offer/takeoffer/TakeOfferModel.java +++ b/core/src/main/java/bisq/core/offer/takeoffer/TakeOfferModel.java @@ -1,3 +1,20 @@ +/* + * 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.offer.takeoffer; import bisq.core.account.witness.AccountAgeWitnessService; @@ -20,6 +37,8 @@ import javax.inject.Inject; import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -141,12 +160,19 @@ private void calculateTxFees() { // Payout tx: 371 bytes // Disputed payout tx: 408 bytes - // Request actual fees: - log.info("Start requestTxFee: txFeeFromFeeService={}", txFeeFromFeeService); - feeService.requestFees(() -> { - txFeePerByteFromFeeService = feeService.getTxFeePerByte(); - txFeeFromFeeService = offerUtil.getTxFeeBySize(txFeePerByteFromFeeService, feeTxSize); - }); + txFeePerByteFromFeeService = getTxFeePerByte(); + txFeeFromFeeService = offerUtil.getTxFeeBySize(txFeePerByteFromFeeService, feeTxSize); + log.info("{} txFeePerByte = {}", feeService.getClass().getSimpleName(), txFeePerByteFromFeeService); + } + + private Coin getTxFeePerByte() { + try { + CompletableFuture feeRequestFuture = CompletableFuture.runAsync(feeService::requestFees); + feeRequestFuture.get(); // Block until async fee request is complete. + return feeService.getTxFeePerByte(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException("Could not request fees from fee service.", e); + } } private void calculateTotalToPay() { From ab20225cd2d50003a8f7f45feca6227a2370a9fb Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Sat, 24 Oct 2020 16:25:20 -0300 Subject: [PATCH 02/30] Add compiler warning suppression, remove comment --- core/src/main/java/bisq/core/api/model/OfferInfo.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/bisq/core/api/model/OfferInfo.java b/core/src/main/java/bisq/core/api/model/OfferInfo.java index ad2389e438a..219045b2762 100644 --- a/core/src/main/java/bisq/core/api/model/OfferInfo.java +++ b/core/src/main/java/bisq/core/api/model/OfferInfo.java @@ -125,13 +125,9 @@ public bisq.proto.grpc.OfferInfo toProtoMessage() { .build(); } + @SuppressWarnings({"unused", "SameReturnValue"}) public static OfferInfo fromProto(bisq.proto.grpc.OfferInfo proto) { - /* - TODO? - return new OfferInfo(proto.getOfferPayload().getId(), - proto.getOfferPayload().getDate()); - */ - return null; + return null; // TODO } /* From 63cf4369904855013ac5b8e37de43462c02e1551 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Sat, 24 Oct 2020 16:27:03 -0300 Subject: [PATCH 03/30] Add fields to grpc TradeInfo proto & wrapper --- .../java/bisq/core/api/model/TradeInfo.java | 133 +++++++++++++++++- proto/src/main/proto/grpc.proto | 29 ++-- 2 files changed, 149 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/bisq/core/api/model/TradeInfo.java b/core/src/main/java/bisq/core/api/model/TradeInfo.java index 33b8059f9b5..6e4adeca4ae 100644 --- a/core/src/main/java/bisq/core/api/model/TradeInfo.java +++ b/core/src/main/java/bisq/core/api/model/TradeInfo.java @@ -21,6 +21,8 @@ import bisq.common.Payload; +import java.util.Objects; + import lombok.EqualsAndHashCode; import lombok.Getter; @@ -37,6 +39,16 @@ public class TradeInfo implements Payload { private final OfferInfo offer; private final String tradeId; private final String shortId; + private final long date; + private final boolean isCurrencyForTakerFeeBtc; + private final long txFeeAsLong; + private final long takerFeeAsLong; + private final String takerFeeTxId; + private final String depositTxId; + private final String payoutTxId; + private final long tradeAmountAsLong; + private final long tradePrice; + private final String tradingPeerNodeAddress; private final String state; private final String phase; private final String tradePeriodState; @@ -46,11 +58,22 @@ public class TradeInfo implements Payload { private final boolean isFiatReceived; private final boolean isPayoutPublished; private final boolean isWithdrawn; + private final String contractAsJson; public TradeInfo(TradeInfoBuilder builder) { this.offer = builder.offer; this.tradeId = builder.tradeId; this.shortId = builder.shortId; + this.date = builder.date; + this.isCurrencyForTakerFeeBtc = builder.isCurrencyForTakerFeeBtc; + this.txFeeAsLong = builder.txFeeAsLong; + this.takerFeeAsLong = builder.takerFeeAsLong; + this.takerFeeTxId = builder.takerFeeTxId; + this.depositTxId = builder.depositTxId; + this.payoutTxId = builder.payoutTxId; + this.tradeAmountAsLong = builder.tradeAmountAsLong; + this.tradePrice = builder.tradePrice; + this.tradingPeerNodeAddress = builder.tradingPeerNodeAddress; this.state = builder.state; this.phase = builder.phase; this.tradePeriodState = builder.tradePeriodState; @@ -60,6 +83,7 @@ public TradeInfo(TradeInfoBuilder builder) { this.isFiatReceived = builder.isFiatReceived; this.isPayoutPublished = builder.isPayoutPublished; this.isWithdrawn = builder.isWithdrawn; + this.contractAsJson = builder.contractAsJson; } public static TradeInfo toTradeInfo(Trade trade) { @@ -67,6 +91,18 @@ public static TradeInfo toTradeInfo(Trade trade) { .withOffer(toOfferInfo(trade.getOffer())) .withTradeId(trade.getId()) .withShortId(trade.getShortId()) + .withDate(trade.getDate().getTime()) + .withIsCurrencyForTakerFeeBtc(trade.isCurrencyForTakerFeeBtc()) + .withTxFeeAsLong(trade.getTxFeeAsLong()) + .withTakerFeeAsLong(trade.getTakerFeeAsLong()) + .withTakerFeeAsLong(trade.getTakerFeeAsLong()) + .withTakerFeeTxId(trade.getTakerFeeTxId()) + .withDepositTxId(trade.getDepositTxId()) + .withPayoutTxId(trade.getPayoutTxId()) + .withTradeAmountAsLong(trade.getTradeAmountAsLong()) + .withTradePrice(trade.getTradePrice().getValue()) + .withTradingPeerNodeAddress(Objects.requireNonNull( + trade.getTradingPeerNodeAddress()).getHostNameWithoutPostFix()) .withState(trade.getState().name()) .withPhase(trade.getPhase().name()) .withTradePeriodState(trade.getTradePeriodState().name()) @@ -76,6 +112,7 @@ public static TradeInfo toTradeInfo(Trade trade) { .withIsFiatReceived(trade.isFiatReceived()) .withIsPayoutPublished(trade.isPayoutPublished()) .withIsWithdrawn(trade.isWithdrawn()) + .withContractAsJson(trade.getContractAsJson()) .build(); } @@ -89,6 +126,16 @@ public bisq.proto.grpc.TradeInfo toProtoMessage() { .setOffer(offer.toProtoMessage()) .setTradeId(tradeId) .setShortId(shortId) + .setDate(date) + .setIsCurrencyForTakerFeeBtc(isCurrencyForTakerFeeBtc) + .setTxFeeAsLong(txFeeAsLong) + .setTakerFeeAsLong(takerFeeAsLong) + .setTakerFeeTxId(takerFeeTxId == null ? "" : takerFeeTxId) + .setDepositTxId(depositTxId == null ? "" : depositTxId) + .setPayoutTxId(payoutTxId == null ? "" : payoutTxId) + .setTradeAmountAsLong(tradeAmountAsLong) + .setTradePrice(tradePrice) + .setTradingPeerNodeAddress(tradingPeerNodeAddress) .setState(state) .setPhase(phase) .setTradePeriodState(tradePeriodState) @@ -98,12 +145,13 @@ public bisq.proto.grpc.TradeInfo toProtoMessage() { .setIsFiatReceived(isFiatReceived) .setIsPayoutPublished(isPayoutPublished) .setIsWithdrawn(isWithdrawn) + .setContractAsJson(contractAsJson == null ? "" : contractAsJson) .build(); } + @SuppressWarnings({"unused", "SameReturnValue"}) public static TradeInfo fromProto(bisq.proto.grpc.TradeInfo proto) { - // TODO - return null; + return null; // TODO } /* @@ -116,6 +164,16 @@ public static class TradeInfoBuilder { private OfferInfo offer; private String tradeId; private String shortId; + private long date; + private boolean isCurrencyForTakerFeeBtc; + private long txFeeAsLong; + private long takerFeeAsLong; + private String takerFeeTxId; + private String depositTxId; + private String payoutTxId; + private long tradeAmountAsLong; + private long tradePrice; + private String tradingPeerNodeAddress; private String state; private String phase; private String tradePeriodState; @@ -125,6 +183,7 @@ public static class TradeInfoBuilder { private boolean isFiatReceived; private boolean isPayoutPublished; private boolean isWithdrawn; + private String contractAsJson; public TradeInfoBuilder withOffer(OfferInfo offer) { this.offer = offer; @@ -141,6 +200,56 @@ public TradeInfoBuilder withShortId(String shortId) { return this; } + public TradeInfoBuilder withDate(long date) { + this.date = date; + return this; + } + + public TradeInfoBuilder withIsCurrencyForTakerFeeBtc(boolean isCurrencyForTakerFeeBtc) { + this.isCurrencyForTakerFeeBtc = isCurrencyForTakerFeeBtc; + return this; + } + + public TradeInfoBuilder withTxFeeAsLong(long txFeeAsLong) { + this.txFeeAsLong = txFeeAsLong; + return this; + } + + public TradeInfoBuilder withTakerFeeAsLong(long takerFeeAsLong) { + this.takerFeeAsLong = takerFeeAsLong; + return this; + } + + public TradeInfoBuilder withTakerFeeTxId(String takerFeeTxId) { + this.takerFeeTxId = takerFeeTxId; + return this; + } + + public TradeInfoBuilder withDepositTxId(String depositTxId) { + this.depositTxId = depositTxId; + return this; + } + + public TradeInfoBuilder withPayoutTxId(String payoutTxId) { + this.payoutTxId = payoutTxId; + return this; + } + + public TradeInfoBuilder withTradeAmountAsLong(long tradeAmountAsLong) { + this.tradeAmountAsLong = tradeAmountAsLong; + return this; + } + + public TradeInfoBuilder withTradePrice(long tradePrice) { + this.tradePrice = tradePrice; + return this; + } + + public TradeInfoBuilder withTradePeriodState(String tradePeriodState) { + this.tradePeriodState = tradePeriodState; + return this; + } + public TradeInfoBuilder withState(String state) { this.state = state; return this; @@ -151,8 +260,8 @@ public TradeInfoBuilder withPhase(String phase) { return this; } - public TradeInfoBuilder withTradePeriodState(String tradePeriodState) { - this.tradePeriodState = tradePeriodState; + public TradeInfoBuilder withTradingPeerNodeAddress(String tradingPeerNodeAddress) { + this.tradingPeerNodeAddress = tradingPeerNodeAddress; return this; } @@ -186,6 +295,11 @@ public TradeInfoBuilder withIsWithdrawn(boolean isWithdrawn) { return this; } + public TradeInfoBuilder withContractAsJson(String contractAsJson) { + this.contractAsJson = contractAsJson; + return this; + } + public TradeInfo build() { return new TradeInfo(this); } @@ -196,6 +310,16 @@ public String toString() { return "TradeInfo{" + " tradeId='" + tradeId + '\'' + "\n" + ", shortId='" + shortId + '\'' + "\n" + + ", date='" + date + '\'' + "\n" + + ", isCurrencyForTakerFeeBtc='" + isCurrencyForTakerFeeBtc + '\'' + "\n" + + ", txFeeAsLong='" + txFeeAsLong + '\'' + "\n" + + ", takerFeeAsLong='" + takerFeeAsLong + '\'' + "\n" + + ", takerFeeTxId='" + takerFeeTxId + '\'' + "\n" + + ", depositTxId='" + depositTxId + '\'' + "\n" + + ", payoutTxId='" + payoutTxId + '\'' + "\n" + + ", tradeAmountAsLong='" + tradeAmountAsLong + '\'' + "\n" + + ", tradePrice='" + tradePrice + '\'' + "\n" + + ", tradingPeerNodeAddress='" + tradingPeerNodeAddress + '\'' + "\n" + ", state='" + state + '\'' + "\n" + ", phase='" + phase + '\'' + "\n" + ", tradePeriodState='" + tradePeriodState + '\'' + "\n" + @@ -206,6 +330,7 @@ public String toString() { ", isPayoutPublished=" + isPayoutPublished + "\n" + ", isWithdrawn=" + isWithdrawn + "\n" + ", offer=" + offer + "\n" + + ", contractAsJson=" + contractAsJson + "\n" + '}'; } } diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index 38ddd4a9dbb..68121d00596 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -217,15 +217,26 @@ message TradeInfo { OfferInfo offer = 1; string tradeId = 2; string shortId = 3; - string state = 4; - string phase = 5; - string tradePeriodState = 6; - bool isDepositPublished = 7; - bool isDepositConfirmed = 8; - bool isFiatSent = 9; - bool isFiatReceived = 10; - bool isPayoutPublished = 11; - bool isWithdrawn = 12; + uint64 date = 4; + bool isCurrencyForTakerFeeBtc = 5; + uint64 txFeeAsLong = 6; + uint64 takerFeeAsLong = 7; + string takerFeeTxId = 8; + string depositTxId = 9; + string payoutTxId = 10; + uint64 tradeAmountAsLong = 11; + uint64 tradePrice = 12; + string tradingPeerNodeAddress = 13; + string state = 14; + string phase = 15; + string tradePeriodState = 16; + bool isDepositPublished = 17; + bool isDepositConfirmed = 18; + bool isFiatSent = 19; + bool isFiatReceived = 20; + bool isPayoutPublished = 21; + bool isWithdrawn = 22; + string contractAsJson = 23; } /////////////////////////////////////////////////////////////////////////////////////////// From 296e4f98cb6231ed2226e72d95c1317981a9d008 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Sat, 24 Oct 2020 17:26:10 -0300 Subject: [PATCH 04/30] Replace static TradeUtil with singleton TradeUtil The API is going to need some desktop trade utilities, which should be shared between :desktop and :core.api. --- .../java/bisq/core/trade/TradeManager.java | 5 +- .../trade/{TradeUtils.java => TradeUtil.java} | 60 ++++++++++++++----- .../trade/failed/FailedTradesManager.java | 7 ++- 3 files changed, 55 insertions(+), 17 deletions(-) rename core/src/main/java/bisq/core/trade/{TradeUtils.java => TradeUtil.java} (57%) diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index 0d8d02a2414..d8278018d56 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -120,6 +120,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi private final P2PService p2PService; private final PriceFeedService priceFeedService; private final TradeStatisticsManager tradeStatisticsManager; + private final TradeUtil tradeUtil; @Getter private final ArbitratorManager arbitratorManager; private final MediatorManager mediatorManager; @@ -157,6 +158,7 @@ public TradeManager(User user, P2PService p2PService, PriceFeedService priceFeedService, TradeStatisticsManager tradeStatisticsManager, + TradeUtil tradeUtil, ArbitratorManager arbitratorManager, MediatorManager mediatorManager, ProcessModelServiceProvider processModelServiceProvider, @@ -175,6 +177,7 @@ public TradeManager(User user, this.p2PService = p2PService; this.priceFeedService = priceFeedService; this.tradeStatisticsManager = tradeStatisticsManager; + this.tradeUtil = tradeUtil; this.arbitratorManager = arbitratorManager; this.mediatorManager = mediatorManager; this.processModelServiceProvider = processModelServiceProvider; @@ -634,7 +637,7 @@ private boolean unFailTrade(Trade trade) { // the relevant entries are changed, otherwise it's not added and no address entries are changed private boolean recoverAddresses(Trade trade) { // Find addresses associated with this trade. - var entries = TradeUtils.getAvailableAddresses(trade, btcWalletService, keyRing); + var entries = tradeUtil.getAvailableAddresses(trade); if (entries == null) return false; diff --git a/core/src/main/java/bisq/core/trade/TradeUtils.java b/core/src/main/java/bisq/core/trade/TradeUtil.java similarity index 57% rename from core/src/main/java/bisq/core/trade/TradeUtils.java rename to core/src/main/java/bisq/core/trade/TradeUtil.java index 10a5da8fdf8..001dbd21217 100644 --- a/core/src/main/java/bisq/core/trade/TradeUtils.java +++ b/core/src/main/java/bisq/core/trade/TradeUtil.java @@ -1,18 +1,18 @@ /* * This file is part of Bisq. * - * Bisq is free software: you can redistribute it and/or modify it + * 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 + * 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 . + * along with bisq. If not, see . */ package bisq.core.trade; @@ -23,20 +23,44 @@ import bisq.common.util.Tuple2; import bisq.common.util.Utilities; +import javax.inject.Inject; +import javax.inject.Singleton; + import java.util.Objects; -public class TradeUtils { +import lombok.extern.slf4j.Slf4j; + +/** + * This class contains trade utility methods. + */ +@Slf4j +@Singleton +public class TradeUtil { + + private final BtcWalletService btcWalletService; + private final KeyRing keyRing; - // Returns if both are AVAILABLE, otherwise null - static Tuple2 getAvailableAddresses(Trade trade, BtcWalletService btcWalletService, - KeyRing keyRing) { - var addresses = getTradeAddresses(trade, btcWalletService, keyRing); + @Inject + public TradeUtil(BtcWalletService btcWalletService, KeyRing keyRing) { + this.btcWalletService = btcWalletService; + this.keyRing = keyRing; + } + + /** + * Returns if and only if both are AVAILABLE, + * otherwise null. + * @param trade the trade being queried for MULTI_SIG, TRADE_PAYOUT addresses + * @return Tuple2 tuple containing MULTI_SIG, TRADE_PAYOUT addresses for trade + */ + public Tuple2 getAvailableAddresses(Trade trade) { + var addresses = getTradeAddresses(trade); if (addresses == null) return null; if (btcWalletService.getAvailableAddressEntries().stream() .noneMatch(e -> Objects.equals(e.getAddressString(), addresses.first))) return null; + if (btcWalletService.getAvailableAddressEntries().stream() .noneMatch(e -> Objects.equals(e.getAddressString(), addresses.second))) return null; @@ -44,18 +68,25 @@ static Tuple2 getAvailableAddresses(Trade trade, BtcWalletServic return new Tuple2<>(addresses.first, addresses.second); } - // Returns addresses as strings if they're known by the wallet - public static Tuple2 getTradeAddresses(Trade trade, BtcWalletService btcWalletService, - KeyRing keyRing) { + /** + * Returns addresses as strings if they're known by the + * wallet. + * @param trade the trade being queried for MULTI_SIG, TRADE_PAYOUT addresses + * @return Tuple2 tuple containing MULTI_SIG, TRADE_PAYOUT addresses for trade + */ + public Tuple2 getTradeAddresses(Trade trade) { var contract = trade.getContract(); if (contract == null) return null; // Get multisig address var isMyRoleBuyer = contract.isMyRoleBuyer(keyRing.getPubKeyRing()); - var multiSigPubKey = isMyRoleBuyer ? contract.getBuyerMultiSigPubKey() : contract.getSellerMultiSigPubKey(); + var multiSigPubKey = isMyRoleBuyer + ? contract.getBuyerMultiSigPubKey() + : contract.getSellerMultiSigPubKey(); if (multiSigPubKey == null) return null; + var multiSigPubKeyString = Utilities.bytesAsHexString(multiSigPubKey); var multiSigAddress = btcWalletService.getAddressEntryListAsImmutableList().stream() .filter(e -> e.getKeyPair().getPublicKeyAsHex().equals(multiSigPubKeyString)) @@ -65,8 +96,9 @@ public static Tuple2 getTradeAddresses(Trade trade, BtcWalletSer return null; // Get payout address - var payoutAddress = isMyRoleBuyer ? - contract.getBuyerPayoutAddressString() : contract.getSellerPayoutAddressString(); + var payoutAddress = isMyRoleBuyer + ? contract.getBuyerPayoutAddressString() + : contract.getSellerPayoutAddressString(); var payoutAddressEntry = btcWalletService.getAddressEntryListAsImmutableList().stream() .filter(e -> Objects.equals(e.getAddressString(), payoutAddress)) .findAny() diff --git a/core/src/main/java/bisq/core/trade/failed/FailedTradesManager.java b/core/src/main/java/bisq/core/trade/failed/FailedTradesManager.java index a584f6b783f..4782ef080d2 100644 --- a/core/src/main/java/bisq/core/trade/failed/FailedTradesManager.java +++ b/core/src/main/java/bisq/core/trade/failed/FailedTradesManager.java @@ -24,7 +24,7 @@ import bisq.core.trade.DumpDelayedPayoutTx; import bisq.core.trade.TradableList; import bisq.core.trade.Trade; -import bisq.core.trade.TradeUtils; +import bisq.core.trade.TradeUtil; import bisq.common.crypto.KeyRing; import bisq.common.persistence.PersistenceManager; @@ -50,6 +50,7 @@ public class FailedTradesManager implements PersistedDataHost { private final PriceFeedService priceFeedService; private final BtcWalletService btcWalletService; private final PersistenceManager> persistenceManager; + private final TradeUtil tradeUtil; private final DumpDelayedPayoutTx dumpDelayedPayoutTx; @Setter private Function unFailTradeCallback; @@ -59,12 +60,14 @@ public FailedTradesManager(KeyRing keyRing, PriceFeedService priceFeedService, BtcWalletService btcWalletService, PersistenceManager> persistenceManager, + TradeUtil tradeUtil, DumpDelayedPayoutTx dumpDelayedPayoutTx) { this.keyRing = keyRing; this.priceFeedService = priceFeedService; this.btcWalletService = btcWalletService; this.dumpDelayedPayoutTx = dumpDelayedPayoutTx; this.persistenceManager = persistenceManager; + this.tradeUtil = tradeUtil; this.persistenceManager.initialize(failedTrades, "FailedTrades", PersistenceManager.Source.PRIVATE); } @@ -127,7 +130,7 @@ public void unFailTrade(Trade trade) { } public String checkUnFail(Trade trade) { - var addresses = TradeUtils.getTradeAddresses(trade, btcWalletService, keyRing); + var addresses = tradeUtil.getTradeAddresses(trade); if (addresses == null) { return "Addresses not found"; } From ccd3c99f2e5b9cf276e6b70bceb34e73fb0a6858 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Sat, 24 Oct 2020 17:45:01 -0300 Subject: [PATCH 05/30] Fix comment typos --- core/src/main/java/bisq/core/trade/TradeUtil.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/TradeUtil.java b/core/src/main/java/bisq/core/trade/TradeUtil.java index 001dbd21217..c93e5e2be3b 100644 --- a/core/src/main/java/bisq/core/trade/TradeUtil.java +++ b/core/src/main/java/bisq/core/trade/TradeUtil.java @@ -1,18 +1,18 @@ /* * This file is part of Bisq. * - * bisq is free software: you can redistribute it and/or modify it + * 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 + * 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 . + * along with Bisq. If not, see . */ package bisq.core.trade; From 24ba9215cd12f65605b23c2f5d53037258afd019 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Sun, 25 Oct 2020 10:41:19 -0300 Subject: [PATCH 06/30] Refactor PendingTradesViewModel methods -> TradeUtil & OfferUtil --- .../main/java/bisq/core/offer/OfferUtil.java | 4 + .../main/java/bisq/core/trade/TradeUtil.java | 96 +++++++++++++++ .../pendingtrades/PendingTradesViewModel.java | 112 ++++++------------ 3 files changed, 137 insertions(+), 75 deletions(-) diff --git a/core/src/main/java/bisq/core/offer/OfferUtil.java b/core/src/main/java/bisq/core/offer/OfferUtil.java index 373df679073..84e783f5b14 100644 --- a/core/src/main/java/bisq/core/offer/OfferUtil.java +++ b/core/src/main/java/bisq/core/offer/OfferUtil.java @@ -261,6 +261,10 @@ public boolean isBsqForTakerFeeAvailable(@Nullable Coin amount) { return !availableBalance.subtract(takerFee).isNegative(); } + public boolean isBlockChainPaymentMethod(Offer offer) { + return offer != null && offer.getPaymentMethod().isAsset(); + } + public Optional getFeeInUserFiatCurrency(Coin makerFee, boolean isCurrencyForMakerFeeBtc, CoinFormatter bsqFormatter) { diff --git a/core/src/main/java/bisq/core/trade/TradeUtil.java b/core/src/main/java/bisq/core/trade/TradeUtil.java index c93e5e2be3b..a89dec32459 100644 --- a/core/src/main/java/bisq/core/trade/TradeUtil.java +++ b/core/src/main/java/bisq/core/trade/TradeUtil.java @@ -18,6 +18,8 @@ package bisq.core.trade; import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.locale.Res; +import bisq.core.offer.Offer; import bisq.common.crypto.KeyRing; import bisq.common.util.Tuple2; @@ -26,10 +28,18 @@ import javax.inject.Inject; import javax.inject.Singleton; +import java.util.Date; import java.util.Objects; import lombok.extern.slf4j.Slf4j; +import javax.annotation.Nullable; + +import static bisq.core.locale.CurrencyUtil.getCurrencyPair; +import static bisq.core.locale.CurrencyUtil.isFiatCurrency; +import static bisq.core.util.FormattingUtils.formatDurationAsWords; +import static com.google.common.base.Preconditions.checkNotNull; + /** * This class contains trade utility methods. */ @@ -108,4 +118,90 @@ public Tuple2 getTradeAddresses(Trade trade) { return new Tuple2<>(multiSigAddress.getAddressString(), payoutAddress); } + + public long getRemainingTradeDuration(Trade trade) { + return trade.getMaxTradePeriodDate() != null + ? trade.getMaxTradePeriodDate().getTime() - new Date().getTime() + : getMaxTradePeriod(trade); + } + + public long getMaxTradePeriod(Trade trade) { + return trade.getOffer() != null + ? trade.getOffer().getPaymentMethod().getMaxTradePeriod() + : 0; + } + + public double getRemainingTradeDurationAsPercentage(Trade trade) { + long maxPeriod = getMaxTradePeriod(trade); + long remaining = getRemainingTradeDuration(trade); + if (maxPeriod != 0) { + return 1 - (double) remaining / (double) maxPeriod; + } else + return 0; + } + + public String getRemainingTradeDurationAsWords(Trade trade) { + return formatDurationAsWords(Math.max(0, getRemainingTradeDuration(trade))); + } + + @Nullable + public Date getHalfTradePeriodDate(Trade trade) { + return trade != null ? trade.getHalfTradePeriodDate() : null; + } + + public Date getDateForOpenDispute(Trade trade) { + return new Date(new Date().getTime() + getRemainingTradeDuration(trade)); + } + + public String getMarketDescription(Trade trade) { + if (trade == null) + return ""; + + checkNotNull(trade.getOffer()); + checkNotNull(trade.getOffer().getCurrencyCode()); + return getCurrencyPair(trade.getOffer().getCurrencyCode()); + } + + public String getPaymentMethodNameWithCountryCode(Trade trade) { + String paymentMethodDescription = ""; + if (trade != null) { + Offer offer = trade.getOffer(); + checkNotNull(offer); + checkNotNull(offer.getPaymentMethod()); + paymentMethodDescription = offer.getPaymentMethodNameWithCountryCode(); + } + return paymentMethodDescription; + } + + /** + * Returns a string describing a trader's role. + * + * @param isBuyerMakerAndSellerTaker boolean + * @param isMaker boolean + * @param currencyCode String + * @return String describing a trader's role + */ + public String getRole(boolean isBuyerMakerAndSellerTaker, boolean isMaker, String currencyCode) { + if (isFiatCurrency(currencyCode)) { + String baseCurrencyCode = Res.getBaseCurrencyCode(); + if (isBuyerMakerAndSellerTaker) + return isMaker + ? Res.get("formatter.asMaker", baseCurrencyCode, Res.get("shared.buyer")) + : Res.get("formatter.asTaker", baseCurrencyCode, Res.get("shared.seller")); + else + return isMaker + ? Res.get("formatter.asMaker", baseCurrencyCode, Res.get("shared.seller")) + : Res.get("formatter.asTaker", baseCurrencyCode, Res.get("shared.buyer")); + } else { + if (isBuyerMakerAndSellerTaker) + return isMaker + ? Res.get("formatter.asMaker", currencyCode, Res.get("shared.seller")) + : Res.get("formatter.asTaker", currencyCode, Res.get("shared.buyer")); + else + return isMaker + ? Res.get("formatter.asMaker", currencyCode, Res.get("shared.buyer")) + : Res.get("formatter.asTaker", currencyCode, Res.get("shared.seller")); + } + + } } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java index 35934419fdf..53369839bd0 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java @@ -24,13 +24,13 @@ import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.btc.wallet.Restrictions; -import bisq.core.locale.CurrencyUtil; -import bisq.core.locale.Res; import bisq.core.network.MessageState; import bisq.core.offer.Offer; +import bisq.core.offer.OfferUtil; import bisq.core.provider.fee.FeeService; import bisq.core.trade.Contract; import bisq.core.trade.Trade; +import bisq.core.trade.TradeUtil; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.user.User; import bisq.core.util.FormattingUtils; @@ -97,6 +97,8 @@ enum SellerState implements State { final AccountAgeWitnessService accountAgeWitnessService; public final P2PService p2PService; private final ClosedTradableManager closedTradableManager; + private final OfferUtil offerUtil; + private final TradeUtil tradeUtil; public final ClockWatcher clockWatcher; @Getter private final User user; @@ -120,6 +122,8 @@ public PendingTradesViewModel(PendingTradesDataModel dataModel, BtcAddressValidator btcAddressValidator, P2PService p2PService, ClosedTradableManager closedTradableManager, + OfferUtil offerUtil, + TradeUtil tradeUtil, AccountAgeWitnessService accountAgeWitnessService, ClockWatcher clockWatcher, User user) { @@ -130,6 +134,8 @@ public PendingTradesViewModel(PendingTradesDataModel dataModel, this.btcAddressValidator = btcAddressValidator; this.p2PService = p2PService; this.closedTradableManager = closedTradableManager; + this.offerUtil = offerUtil; + this.tradeUtil = tradeUtil; this.accountAgeWitnessService = accountAgeWitnessService; this.clockWatcher = clockWatcher; this.user = user; @@ -199,55 +205,30 @@ ReadOnlyObjectProperty getSellerState() { } public String getPayoutAmount() { - return dataModel.getTrade() != null ? btcFormatter.formatCoinWithCode(dataModel.getTrade().getPayoutAmount()) : ""; + return dataModel.getTrade() != null + ? btcFormatter.formatCoinWithCode(dataModel.getTrade().getPayoutAmount()) + : ""; } String getMarketLabel(PendingTradesListItem item) { - if ((item == null)) - return ""; - - checkNotNull(item.getTrade().getOffer()); - checkNotNull(item.getTrade().getOffer().getCurrencyCode()); - return CurrencyUtil.getCurrencyPair(item.getTrade().getOffer().getCurrencyCode()); - } - - private long getMaxTradePeriod() { - return dataModel.getOffer() != null ? dataModel.getOffer().getPaymentMethod().getMaxTradePeriod() : 0; - } - - @Nullable - private Date getMaxTradePeriodDate() { - return dataModel.getTrade() != null ? dataModel.getTrade().getMaxTradePeriodDate() : null; - } - - @Nullable - private Date getHalfTradePeriodDate() { - return dataModel.getTrade() != null ? dataModel.getTrade().getHalfTradePeriodDate() : null; - } - - private long getRemainingTradeDuration() { - return getMaxTradePeriodDate() != null ? getMaxTradePeriodDate().getTime() - new Date().getTime() : getMaxTradePeriod(); + return item == null ? "" : tradeUtil.getMarketDescription(item.getTrade()); } public String getRemainingTradeDurationAsWords() { - return FormattingUtils.formatDurationAsWords(Math.max(0, getRemainingTradeDuration())); + return tradeUtil.getRemainingTradeDurationAsWords(dataModel.getTrade()); } public double getRemainingTradeDurationAsPercentage() { - long maxPeriod = getMaxTradePeriod(); - long remaining = getRemainingTradeDuration(); - if (maxPeriod != 0) { - return 1 - (double) remaining / (double) maxPeriod; - } else - return 0; + return tradeUtil.getRemainingTradeDurationAsPercentage(dataModel.getTrade()); } public String getDateForOpenDispute() { - return DisplayUtils.formatDateTime(new Date(new Date().getTime() + getRemainingTradeDuration())); + return DisplayUtils.formatDateTime(tradeUtil.getDateForOpenDispute(dataModel.getTrade())); } public boolean showWarning() { - return getHalfTradePeriodDate() != null && new Date().after(getHalfTradePeriodDate()); + Date halfTradePeriodDate = tradeUtil.getHalfTradePeriodDate(dataModel.getTrade()); + return halfTradePeriodDate != null && new Date().after(halfTradePeriodDate); } public boolean showDispute() { @@ -263,36 +244,36 @@ String getMyRole(PendingTradesListItem item) { Offer offer = trade.getOffer(); checkNotNull(offer); checkNotNull(offer.getCurrencyCode()); - return getRole(contract.isBuyerMakerAndSellerTaker(), dataModel.isMaker(offer), offer.getCurrencyCode()); + return tradeUtil.getRole(contract.isBuyerMakerAndSellerTaker(), + dataModel.isMaker(offer), + offer.getCurrencyCode()); } else { return ""; } } String getPaymentMethod(PendingTradesListItem item) { - String result = ""; - if (item != null) { - Offer offer = item.getTrade().getOffer(); - checkNotNull(offer); - checkNotNull(offer.getPaymentMethod()); - result = offer.getPaymentMethodNameWithCountryCode(); - } - return result; + return item == null ? "" : tradeUtil.getPaymentMethodNameWithCountryCode(item.getTrade()); } // summary public String getTradeVolume() { - return dataModel.getTrade() != null ? btcFormatter.formatCoinWithCode(dataModel.getTrade().getTradeAmount()) : ""; + return dataModel.getTrade() != null + ? btcFormatter.formatCoinWithCode(dataModel.getTrade().getTradeAmount()) + : ""; } public String getFiatVolume() { - return dataModel.getTrade() != null ? DisplayUtils.formatVolumeWithCode(dataModel.getTrade().getTradeVolume()) : ""; + return dataModel.getTrade() != null + ? DisplayUtils.formatVolumeWithCode(dataModel.getTrade().getTradeVolume()) + : ""; } public String getTxFee() { if (trade != null && trade.getTradeAmount() != null) { Coin txFee = dataModel.getTxFee(); - String percentage = GUIUtil.getPercentageOfTradeAmount(txFee, trade.getTradeAmount(), + String percentage = GUIUtil.getPercentageOfTradeAmount(txFee, + trade.getTradeAmount(), Coin.ZERO); return btcFormatter.formatCoinWithCode(txFee) + percentage; } else { @@ -344,7 +325,7 @@ public String getSecurityDeposit() { } public boolean isBlockChainMethod() { - return dataModel.getOffer() != null && dataModel.getOffer().getPaymentMethod().isAsset(); + return offerUtil.isBlockChainPaymentMethod(dataModel.getOffer()); } public int getNumPastTrades(Trade trade) { @@ -362,7 +343,13 @@ public int getNumPastTrades(Trade trade) { .collect(Collectors.toSet()) .size(); } - + + @Nullable + private Date getMaxTradePeriodDate() { + return dataModel.getTrade() != null + ? dataModel.getTrade().getMaxTradePeriodDate() + : null; + } /////////////////////////////////////////////////////////////////////////////////////////// // States @@ -479,29 +466,4 @@ private void onTradeStateChanged(Trade.State tradeState) { break; } } - - private static String getRole(boolean isBuyerMakerAndSellerTaker, boolean isMaker, String currencyCode) { - if (CurrencyUtil.isFiatCurrency(currencyCode)) { - String baseCurrencyCode = Res.getBaseCurrencyCode(); - if (isBuyerMakerAndSellerTaker) - return isMaker ? - Res.get("formatter.asMaker", baseCurrencyCode, Res.get("shared.buyer")) : - Res.get("formatter.asTaker", baseCurrencyCode, Res.get("shared.seller")); - else - return isMaker ? - Res.get("formatter.asMaker", baseCurrencyCode, Res.get("shared.seller")) : - Res.get("formatter.asTaker", baseCurrencyCode, Res.get("shared.buyer")); - } else { - if (isBuyerMakerAndSellerTaker) - return isMaker ? - Res.get("formatter.asMaker", currencyCode, Res.get("shared.seller")) : - Res.get("formatter.asTaker", currencyCode, Res.get("shared.buyer")); - else - return isMaker ? - Res.get("formatter.asMaker", currencyCode, Res.get("shared.buyer")) : - Res.get("formatter.asTaker", currencyCode, Res.get("shared.seller")); - } - - } - } From 36ad1379496d29bf26d6d3c90c4cc50b8c74b928 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Sun, 25 Oct 2020 10:54:40 -0300 Subject: [PATCH 07/30] Remove trailing spaces for codacy --- .../main/portfolio/pendingtrades/PendingTradesViewModel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java index 53369839bd0..6a9b4dff06c 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java @@ -343,7 +343,7 @@ public int getNumPastTrades(Trade trade) { .collect(Collectors.toSet()) .size(); } - + @Nullable private Date getMaxTradePeriodDate() { return dataModel.getTrade() != null From 95bcb1ef9c74c6fa48f946ebc29b9af55bd1ed70 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Sun, 25 Oct 2020 11:27:30 -0300 Subject: [PATCH 08/30] Refactor PendingTradesDataModel methods -> TradeUtil Scope of this refactoring is small; more can be done, but the short term goal is to share trade util logic with core api. - Removed unused method getCurrencyCode() - Made minor style changes - Removed duplicated code block --- .../main/java/bisq/core/trade/TradeUtil.java | 2 + .../pendingtrades/PendingTradesDataModel.java | 83 +++++++------------ 2 files changed, 34 insertions(+), 51 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/TradeUtil.java b/core/src/main/java/bisq/core/trade/TradeUtil.java index a89dec32459..2837e4e9319 100644 --- a/core/src/main/java/bisq/core/trade/TradeUtil.java +++ b/core/src/main/java/bisq/core/trade/TradeUtil.java @@ -25,6 +25,8 @@ import bisq.common.util.Tuple2; import bisq.common.util.Utilities; +import org.bitcoinj.core.Coin; + import javax.inject.Inject; import javax.inject.Singleton; diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java index fa000d8e6f7..bce37344b80 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java @@ -35,6 +35,7 @@ import bisq.core.locale.Res; import bisq.core.offer.Offer; import bisq.core.offer.OfferPayload; +import bisq.core.offer.OfferUtil; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.support.SupportType; import bisq.core.support.dispute.Dispute; @@ -107,6 +108,7 @@ public class PendingTradesDataModel extends ActivatableDataModel { public final Navigation navigation; public final WalletPasswordWindow walletPasswordWindow; private final NotificationCenter notificationCenter; + private final OfferUtil offerUtil; final ObservableList list = FXCollections.observableArrayList(); private final ListChangeListener tradesListChangeListener; @@ -142,7 +144,8 @@ public PendingTradesDataModel(TradeManager tradeManager, DaoFacade daoFacade, Navigation navigation, WalletPasswordWindow walletPasswordWindow, - NotificationCenter notificationCenter) { + NotificationCenter notificationCenter, + OfferUtil offerUtil) { this.tradeManager = tradeManager; this.btcWalletService = btcWalletService; this.pubKeyRing = pubKeyRing; @@ -157,6 +160,7 @@ public PendingTradesDataModel(TradeManager tradeManager, this.navigation = navigation; this.walletPasswordWindow = walletPasswordWindow; this.notificationCenter = notificationCenter; + this.offerUtil = offerUtil; tradesListChangeListener = change -> onListChanged(); notificationCenter.setSelectItemByTradeIdConsumer(this::selectItemByTradeId); @@ -253,7 +257,7 @@ Offer getOffer() { } private boolean isBuyOffer() { - return getOffer() != null && getOffer().getDirection() == OfferPayload.Direction.BUY; + return getOffer() != null && offerUtil.isBuyOffer(getOffer().getDirection()); } boolean isBuyer() { @@ -348,11 +352,6 @@ Coin getTradeFeeAsBsq() { } } - public String getCurrencyCode() { - return getOffer() != null ? getOffer().getCurrencyCode() : ""; - } - - @Nullable public PaymentAccountPayload getSellersPaymentAccountPayload() { if (getTrade() != null && getTrade().getContract() != null) @@ -555,26 +554,7 @@ private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { } trade.setDisputeState(Trade.DisputeState.MEDIATION_REQUESTED); - disputeManager.sendOpenNewDisputeMessage(dispute, - false, - resultHandler, - (errorMessage, throwable) -> { - if ((throwable instanceof DisputeAlreadyOpenException)) { - errorMessage += "\n\n" + Res.get("portfolio.pending.openAgainDispute.msg"); - new Popup().warning(errorMessage) - .actionButtonText(Res.get("portfolio.pending.openAgainDispute.button")) - .onAction(() -> disputeManager.sendOpenNewDisputeMessage(dispute, - true, - resultHandler, - (e, t) -> { - log.error(e); - })) - .closeButtonText(Res.get("shared.cancel")) - .show(); - } else { - new Popup().warning(errorMessage).show(); - } - }); + sendOpenDisputeMessage(disputeManager, resultHandler, dispute); } else if (useRefundAgent) { resultHandler = () -> navigation.navigateTo(MainView.class, SupportView.class, RefundClientView.class); @@ -643,35 +623,13 @@ private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { dispute.setDonationAddressOfDelayedPayoutTx(donationAddressString.get()); dispute.setDelayedPayoutTxId(delayedPayoutTx.getTxId().toString()); - trade.setDisputeState(Trade.DisputeState.REFUND_REQUESTED); ((DisputeProtocol) tradeManager.getTradeProtocol(trade)).onPublishDelayedPayoutTx(() -> { log.info("DelayedPayoutTx published and message sent to peer"); - disputeManager.sendOpenNewDisputeMessage(dispute, - false, - resultHandler, - (errorMessage, throwable) -> { - if ((throwable instanceof DisputeAlreadyOpenException)) { - errorMessage += "\n\n" + Res.get("portfolio.pending.openAgainDispute.msg"); - new Popup().warning(errorMessage) - .actionButtonText(Res.get("portfolio.pending.openAgainDispute.button")) - .onAction(() -> disputeManager.sendOpenNewDisputeMessage(dispute, - true, - resultHandler, - (e, t) -> { - log.error(e); - })) - .closeButtonText(Res.get("shared.cancel")) - .show(); - } else { - new Popup().warning(errorMessage).show(); - } - }); + sendOpenDisputeMessage(disputeManager, resultHandler, dispute); }, - errorMessage -> { - new Popup().error(errorMessage).show(); - }); + errorMessage -> new Popup().error(errorMessage).show()); } else { log.warn("Invalid dispute state {}", disputeState.name()); @@ -694,5 +652,28 @@ public void onMoveInvalidTradeToFailedTrades(Trade trade) { public boolean isSignWitnessTrade() { return accountAgeWitnessService.isSignWitnessTrade(selectedTrade); } + + private void sendOpenDisputeMessage(DisputeManager> disputeManager, + ResultHandler resultHandler, + Dispute dispute) { + disputeManager.sendOpenNewDisputeMessage(dispute, + false, + resultHandler, + (errorMessage, throwable) -> { + if ((throwable instanceof DisputeAlreadyOpenException)) { + errorMessage += "\n\n" + Res.get("portfolio.pending.openAgainDispute.msg"); + new Popup().warning(errorMessage) + .actionButtonText(Res.get("portfolio.pending.openAgainDispute.button")) + .onAction(() -> disputeManager.sendOpenNewDisputeMessage(dispute, + true, + resultHandler, + (e, t) -> log.error(e))) + .closeButtonText(Res.get("shared.cancel")) + .show(); + } else { + new Popup().warning(errorMessage).show(); + } + }); + } } From bbd7a31c88907e7c29ce473e5a71603f976ace21 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Sun, 25 Oct 2020 11:38:03 -0300 Subject: [PATCH 09/30] Remove unused import --- core/src/main/java/bisq/core/trade/TradeUtil.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/TradeUtil.java b/core/src/main/java/bisq/core/trade/TradeUtil.java index 2837e4e9319..a89dec32459 100644 --- a/core/src/main/java/bisq/core/trade/TradeUtil.java +++ b/core/src/main/java/bisq/core/trade/TradeUtil.java @@ -25,8 +25,6 @@ import bisq.common.util.Tuple2; import bisq.common.util.Utilities; -import org.bitcoinj.core.Coin; - import javax.inject.Inject; import javax.inject.Singleton; From 161dbade0d04dcd7439e3ed94929a4c4ce3fdab0 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Sun, 25 Oct 2020 16:22:20 -0300 Subject: [PATCH 10/30] Add getRole(tradeId) to core api API users will need to see their role as maker/taker when looking at trade details. - Add getRole(trade) to TradeUtil. - Add getTradeRole(tradeId) to CoreApi, CoreTradesService. - Add role field to TradeInfo proto and its wrapper class. --- core/src/main/java/bisq/core/api/CoreApi.java | 4 ++ .../java/bisq/core/api/CoreTradesService.java | 9 +++++ .../java/bisq/core/api/model/TradeInfo.java | 15 +++++++ .../main/java/bisq/core/trade/TradeUtil.java | 23 ++++++++++- .../bisq/daemon/grpc/GrpcTradesService.java | 3 +- proto/src/main/proto/grpc.proto | 39 ++++++++++--------- 6 files changed, 72 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index 01f70d312f4..05886dd6e9e 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -193,6 +193,10 @@ public Trade getTrade(String tradeId) { return coreTradesService.getTrade(tradeId); } + public String getTradeRole(String tradeId) { + return coreTradesService.getTradeRole(tradeId); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Wallets /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/api/CoreTradesService.java b/core/src/main/java/bisq/core/api/CoreTradesService.java index d0200a78ce5..85877488315 100644 --- a/core/src/main/java/bisq/core/api/CoreTradesService.java +++ b/core/src/main/java/bisq/core/api/CoreTradesService.java @@ -21,6 +21,7 @@ import bisq.core.offer.takeoffer.TakeOfferModel; import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; +import bisq.core.trade.TradeUtil; import bisq.core.trade.protocol.BuyerProtocol; import bisq.core.trade.protocol.SellerProtocol; import bisq.core.user.User; @@ -38,14 +39,17 @@ class CoreTradesService { private final TakeOfferModel takeOfferModel; private final TradeManager tradeManager; + private final TradeUtil tradeUtil; private final User user; @Inject public CoreTradesService(TakeOfferModel takeOfferModel, TradeManager tradeManager, + TradeUtil tradeUtil, User user) { this.takeOfferModel = takeOfferModel; this.tradeManager = tradeManager; + this.tradeUtil = tradeUtil; this.user = user; } @@ -57,6 +61,7 @@ void takeOffer(Offer offer, throw new IllegalArgumentException(format("payment account with id '%s' not found", paymentAccountId)); var useSavingsWallet = true; + //noinspection ConstantConditions takeOfferModel.initModel(offer, paymentAccount, useSavingsWallet); log.info("Initiating take {} offer, {}", offer.isBuyOffer() ? "buy" : "sell", @@ -111,6 +116,10 @@ void confirmPaymentReceived(String tradeId) { } } + String getTradeRole(String tradeId) { + return tradeUtil.getRole(getTrade(tradeId)); + } + Trade getTrade(String tradeId) { return tradeManager.getTradeById(tradeId).orElseThrow(() -> new IllegalArgumentException(format("trade with id '%s' not found", tradeId))); diff --git a/core/src/main/java/bisq/core/api/model/TradeInfo.java b/core/src/main/java/bisq/core/api/model/TradeInfo.java index 6e4adeca4ae..1a717a7672e 100644 --- a/core/src/main/java/bisq/core/api/model/TradeInfo.java +++ b/core/src/main/java/bisq/core/api/model/TradeInfo.java @@ -40,6 +40,7 @@ public class TradeInfo implements Payload { private final String tradeId; private final String shortId; private final long date; + private final String role; private final boolean isCurrencyForTakerFeeBtc; private final long txFeeAsLong; private final long takerFeeAsLong; @@ -65,6 +66,7 @@ public TradeInfo(TradeInfoBuilder builder) { this.tradeId = builder.tradeId; this.shortId = builder.shortId; this.date = builder.date; + this.role = builder.role; this.isCurrencyForTakerFeeBtc = builder.isCurrencyForTakerFeeBtc; this.txFeeAsLong = builder.txFeeAsLong; this.takerFeeAsLong = builder.takerFeeAsLong; @@ -87,11 +89,16 @@ public TradeInfo(TradeInfoBuilder builder) { } public static TradeInfo toTradeInfo(Trade trade) { + return toTradeInfo(trade, null); + } + + public static TradeInfo toTradeInfo(Trade trade, String role) { return new TradeInfo.TradeInfoBuilder() .withOffer(toOfferInfo(trade.getOffer())) .withTradeId(trade.getId()) .withShortId(trade.getShortId()) .withDate(trade.getDate().getTime()) + .withRole(role == null ? "" : role) .withIsCurrencyForTakerFeeBtc(trade.isCurrencyForTakerFeeBtc()) .withTxFeeAsLong(trade.getTxFeeAsLong()) .withTakerFeeAsLong(trade.getTakerFeeAsLong()) @@ -127,6 +134,7 @@ public bisq.proto.grpc.TradeInfo toProtoMessage() { .setTradeId(tradeId) .setShortId(shortId) .setDate(date) + .setRole(role) .setIsCurrencyForTakerFeeBtc(isCurrencyForTakerFeeBtc) .setTxFeeAsLong(txFeeAsLong) .setTakerFeeAsLong(takerFeeAsLong) @@ -165,6 +173,7 @@ public static class TradeInfoBuilder { private String tradeId; private String shortId; private long date; + private String role; private boolean isCurrencyForTakerFeeBtc; private long txFeeAsLong; private long takerFeeAsLong; @@ -205,6 +214,11 @@ public TradeInfoBuilder withDate(long date) { return this; } + public TradeInfoBuilder withRole(String role) { + this.role = role; + return this; + } + public TradeInfoBuilder withIsCurrencyForTakerFeeBtc(boolean isCurrencyForTakerFeeBtc) { this.isCurrencyForTakerFeeBtc = isCurrencyForTakerFeeBtc; return this; @@ -311,6 +325,7 @@ public String toString() { " tradeId='" + tradeId + '\'' + "\n" + ", shortId='" + shortId + '\'' + "\n" + ", date='" + date + '\'' + "\n" + + ", role='" + role + '\'' + "\n" + ", isCurrencyForTakerFeeBtc='" + isCurrencyForTakerFeeBtc + '\'' + "\n" + ", txFeeAsLong='" + txFeeAsLong + '\'' + "\n" + ", takerFeeAsLong='" + takerFeeAsLong + '\'' + "\n" + diff --git a/core/src/main/java/bisq/core/trade/TradeUtil.java b/core/src/main/java/bisq/core/trade/TradeUtil.java index a89dec32459..cd4fd074aad 100644 --- a/core/src/main/java/bisq/core/trade/TradeUtil.java +++ b/core/src/main/java/bisq/core/trade/TradeUtil.java @@ -39,6 +39,7 @@ import static bisq.core.locale.CurrencyUtil.isFiatCurrency; import static bisq.core.util.FormattingUtils.formatDurationAsWords; import static com.google.common.base.Preconditions.checkNotNull; +import static java.lang.String.format; /** * This class contains trade utility methods. @@ -173,6 +174,27 @@ public String getPaymentMethodNameWithCountryCode(Trade trade) { return paymentMethodDescription; } + /** + * Returns a string describing a trader's role for a given trade. + * @param trade Trade + * @return String describing a trader's role for a given trade + */ + public String getRole(Trade trade) { + Contract contract = trade.getContract(); + if (contract == null) + throw new IllegalStateException(format("could not get role because no contract was found for trade '%s'", + trade.getShortId())); + + Offer offer = trade.getOffer(); + if (offer == null) + throw new IllegalStateException(format("could not get role because no offer was found for trade '%s'", + trade.getShortId())); + + return getRole(contract.isBuyerMakerAndSellerTaker(), + offer.isMyOffer(keyRing), + offer.getCurrencyCode()); + } + /** * Returns a string describing a trader's role. * @@ -202,6 +224,5 @@ public String getRole(boolean isBuyerMakerAndSellerTaker, boolean isMaker, Strin ? Res.get("formatter.asMaker", currencyCode, Res.get("shared.buyer")) : Res.get("formatter.asTaker", currencyCode, Res.get("shared.seller")); } - } } diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java index 0ffbd71f044..e23651bb207 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java @@ -56,8 +56,9 @@ public void getTrade(GetTradeRequest req, StreamObserver responseObserver) { try { Trade trade = coreApi.getTrade(req.getTradeId()); + String role = coreApi.getTradeRole(req.getTradeId()); var reply = GetTradeReply.newBuilder() - .setTrade(toTradeInfo(trade).toProtoMessage()) + .setTrade(toTradeInfo(trade, role).toProtoMessage()) .build(); responseObserver.onNext(reply); responseObserver.onCompleted(); diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index 68121d00596..041f1278ea2 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -218,25 +218,26 @@ message TradeInfo { string tradeId = 2; string shortId = 3; uint64 date = 4; - bool isCurrencyForTakerFeeBtc = 5; - uint64 txFeeAsLong = 6; - uint64 takerFeeAsLong = 7; - string takerFeeTxId = 8; - string depositTxId = 9; - string payoutTxId = 10; - uint64 tradeAmountAsLong = 11; - uint64 tradePrice = 12; - string tradingPeerNodeAddress = 13; - string state = 14; - string phase = 15; - string tradePeriodState = 16; - bool isDepositPublished = 17; - bool isDepositConfirmed = 18; - bool isFiatSent = 19; - bool isFiatReceived = 20; - bool isPayoutPublished = 21; - bool isWithdrawn = 22; - string contractAsJson = 23; + string role = 5; + bool isCurrencyForTakerFeeBtc = 6; + uint64 txFeeAsLong = 7; + uint64 takerFeeAsLong = 8; + string takerFeeTxId = 9; + string depositTxId = 10; + string payoutTxId = 11; + uint64 tradeAmountAsLong = 12; + uint64 tradePrice = 13; + string tradingPeerNodeAddress = 14; + string state = 15; + string phase = 16; + string tradePeriodState = 17; + bool isDepositPublished = 18; + bool isDepositConfirmed = 19; + bool isFiatSent = 20; + bool isFiatReceived = 21; + bool isPayoutPublished = 22; + bool isWithdrawn = 23; + string contractAsJson = 24; } /////////////////////////////////////////////////////////////////////////////////////////// From 3379376babab645af1f65cf595aca06768fd40c1 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Sun, 25 Oct 2020 16:50:22 -0300 Subject: [PATCH 11/30] Refactor CLI output formatting code & add trade formatter - Move output column header specs to its own shared constants class. - Add new TradeFormat class for printing trade details in the console. - Print formatted trade in api trade tests -- to see output before using formatter in CLI (in next PR). --- .../method/trade/AbstractTradeTest.java | 1 - .../method/trade/TakeBuyBTCOfferTest.java | 6 + .../method/trade/TakeSellBTCOfferTest.java | 7 ++ .../java/bisq/cli/ColumnHeaderConstants.java | 38 ++++++ cli/src/main/java/bisq/cli/TableFormat.java | 26 +--- cli/src/main/java/bisq/cli/TradeFormat.java | 119 ++++++++++++++++++ 6 files changed, 173 insertions(+), 24 deletions(-) create mode 100644 cli/src/main/java/bisq/cli/ColumnHeaderConstants.java create mode 100644 cli/src/main/java/bisq/cli/TradeFormat.java diff --git a/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java b/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java index 5364de7f7d8..094007abd40 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java @@ -28,5 +28,4 @@ protected final void verifyExpectedTradeStateAndPhase(TradeInfo trade, assertEquals(expectedState.name(), trade.getState()); assertEquals(expectedPhase.name(), trade.getPhase()); } - } diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java index 040cee5a18d..4aba67fc715 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java @@ -31,6 +31,7 @@ import static bisq.apitest.config.BisqAppConfig.alicedaemon; import static bisq.apitest.config.BisqAppConfig.bobdaemon; +import static bisq.cli.TradeFormat.format; import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED; import static bisq.core.trade.Trade.Phase.DEPOSIT_PUBLISHED; import static bisq.core.trade.Trade.Phase.FIAT_SENT; @@ -39,6 +40,7 @@ import static bisq.core.trade.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN; import static bisq.core.trade.Trade.State.SELLER_PUBLISHED_DEPOSIT_TX; import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG; +import static java.lang.System.out; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; @@ -85,10 +87,12 @@ public void testTakeAlicesBuyOffer() { trade = getTrade(bobdaemon, trade.getTradeId()); verifyExpectedTradeStateAndPhase(trade, SELLER_PUBLISHED_DEPOSIT_TX, DEPOSIT_PUBLISHED); + out.println(format(trade)); genBtcBlocksThenWait(1, 2250); trade = getTrade(bobdaemon, trade.getTradeId()); verifyExpectedTradeStateAndPhase(trade, DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, DEPOSIT_CONFIRMED); + out.println(format(trade)); } catch (StatusRuntimeException e) { fail(e); } @@ -107,6 +111,7 @@ public void testAlicesConfirmPaymentStarted() { trade = getTrade(alicedaemon, tradeId); assertEquals(OFFER_FEE_PAID.name(), trade.getOffer().getState()); verifyExpectedTradeStateAndPhase(trade, BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG, FIAT_SENT); + out.println(format(trade)); } catch (StatusRuntimeException e) { fail(e); } @@ -125,5 +130,6 @@ public void testBobsConfirmPaymentReceived() { // TODO is this a bug? Why is offer.state == available? assertEquals(AVAILABLE.name(), trade.getOffer().getState()); verifyExpectedTradeStateAndPhase(trade, SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG, PAYOUT_PUBLISHED); + out.println(format(trade)); } } diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java index 5347159daae..658b4083231 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java @@ -31,6 +31,7 @@ import static bisq.apitest.config.BisqAppConfig.alicedaemon; import static bisq.apitest.config.BisqAppConfig.bobdaemon; +import static bisq.cli.TradeFormat.format; import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED; import static bisq.core.trade.Trade.Phase.DEPOSIT_PUBLISHED; import static bisq.core.trade.Trade.Phase.FIAT_SENT; @@ -39,6 +40,7 @@ import static bisq.core.trade.Trade.State.BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG; import static bisq.core.trade.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN; import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG; +import static java.lang.System.out; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; @@ -86,10 +88,12 @@ public void testTakeAlicesSellOffer() { trade = getTrade(bobdaemon, trade.getTradeId()); verifyExpectedTradeStateAndPhase(trade, BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG, DEPOSIT_PUBLISHED); + out.println(format(trade)); genBtcBlocksThenWait(1, 2250); trade = getTrade(bobdaemon, trade.getTradeId()); verifyExpectedTradeStateAndPhase(trade, DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, DEPOSIT_CONFIRMED); + out.println(format(trade)); } catch (StatusRuntimeException e) { fail(e); @@ -110,6 +114,8 @@ public void testBobsConfirmPaymentStarted() { // TODO is this a bug? Why is offer.state == available? assertEquals(AVAILABLE.name(), trade.getOffer().getState()); verifyExpectedTradeStateAndPhase(trade, BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG, FIAT_SENT); + + out.println(format(trade)); } catch (StatusRuntimeException e) { fail(e); } @@ -127,5 +133,6 @@ public void testAlicesConfirmPaymentReceived() { trade = getTrade(alicedaemon, tradeId); assertEquals(OFFER_FEE_PAID.name(), trade.getOffer().getState()); verifyExpectedTradeStateAndPhase(trade, SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG, PAYOUT_PUBLISHED); + out.println(format(trade)); } } diff --git a/cli/src/main/java/bisq/cli/ColumnHeaderConstants.java b/cli/src/main/java/bisq/cli/ColumnHeaderConstants.java new file mode 100644 index 00000000000..f5744f6cc71 --- /dev/null +++ b/cli/src/main/java/bisq/cli/ColumnHeaderConstants.java @@ -0,0 +1,38 @@ +package bisq.cli; + +import static com.google.common.base.Strings.padEnd; +import static com.google.common.base.Strings.padStart; + +class ColumnHeaderConstants { + + // For inserting 2 spaces between column headers. + static final String COL_HEADER_DELIMITER = " "; + + // Table column header format specs, right padded with two spaces. In some cases + // such as COL_HEADER_CREATION_DATE, COL_HEADER_VOLUME and COL_HEADER_UUID, the + // expected max data string length is accounted for. In others, the column header length + // are expected to be greater than any column value length. + static final String COL_HEADER_ADDRESS = padEnd("Address", 34, ' '); + static final String COL_HEADER_AMOUNT = padEnd("BTC(min - max)", 24, ' '); + static final String COL_HEADER_BALANCE = padStart("Balance", 12, ' '); + static final String COL_HEADER_CONFIRMATIONS = "Confirmations"; + static final String COL_HEADER_CREATION_DATE = padEnd("Creation Date (UTC)", 20, ' '); + static final String COL_HEADER_CURRENCY = "Currency"; + static final String COL_HEADER_DIRECTION = "Buy/Sell"; + static final String COL_HEADER_NAME = "Name"; + static final String COL_HEADER_PAYMENT_METHOD = "Payment Method"; + static final String COL_HEADER_PRICE = "Price in %-3s for 1 BTC"; + static final String COL_HEADER_TRADE_AMOUNT = padStart("Amount(%-3s)", 12, ' '); + static final String COL_HEADER_TRADE_DEPOSIT_CONFIRMED = "Deposit Confirmed"; + static final String COL_HEADER_TRADE_DEPOSIT_PUBLISHED = "Deposit Published"; + static final String COL_HEADER_TRADE_FIAT_SENT = "Fiat Sent"; + static final String COL_HEADER_TRADE_FIAT_RECEIVED = "Fiat Received"; + static final String COL_HEADER_TRADE_PAYOUT_PUBLISHED = "Payout Published"; + static final String COL_HEADER_TRADE_WITHDRAWN = "Withdrawn"; + static final String COL_HEADER_TRADE_ROLE = "My Role"; + static final String COL_HEADER_TRADE_SHORT_ID = "ID"; + static final String COL_HEADER_TRADE_TX_FEE = "Tx Fee(%-3s)"; + static final String COL_HEADER_TRADE_TAKER_FEE = "Taker Fee(%-3s)"; + static final String COL_HEADER_VOLUME = padEnd("%-3s(min - max)", 15, ' '); + static final String COL_HEADER_UUID = padEnd("ID", 52, ' '); +} diff --git a/cli/src/main/java/bisq/cli/TableFormat.java b/cli/src/main/java/bisq/cli/TableFormat.java index 7d02af562f5..e9868f336e4 100644 --- a/cli/src/main/java/bisq/cli/TableFormat.java +++ b/cli/src/main/java/bisq/cli/TableFormat.java @@ -29,12 +29,12 @@ import java.util.TimeZone; import java.util.stream.Collectors; +import static bisq.cli.ColumnHeaderConstants.*; import static bisq.cli.CurrencyFormat.formatAmountRange; import static bisq.cli.CurrencyFormat.formatOfferPrice; import static bisq.cli.CurrencyFormat.formatSatoshis; import static bisq.cli.CurrencyFormat.formatVolumeRange; import static com.google.common.base.Strings.padEnd; -import static com.google.common.base.Strings.padStart; import static java.lang.String.format; import static java.util.Collections.max; import static java.util.Comparator.comparing; @@ -42,28 +42,8 @@ class TableFormat { - private static final TimeZone TZ_UTC = getTimeZone("UTC"); - private static final SimpleDateFormat DATE_FORMAT_ISO_8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - - // For inserting 2 spaces between column headers. - private static final String COL_HEADER_DELIMITER = " "; - - // Table column header format specs, right padded with two spaces. In some cases - // such as COL_HEADER_CREATION_DATE, COL_HEADER_VOLUME and COL_HEADER_UUID, the - // expected max data string length is accounted for. In others, the column header length - // are expected to be greater than any column value length. - private static final String COL_HEADER_ADDRESS = padEnd("Address", 34, ' '); - private static final String COL_HEADER_AMOUNT = padEnd("BTC(min - max)", 24, ' '); - private static final String COL_HEADER_BALANCE = padStart("Balance", 12, ' '); - private static final String COL_HEADER_CONFIRMATIONS = "Confirmations"; - private static final String COL_HEADER_CREATION_DATE = padEnd("Creation Date (UTC)", 20, ' '); - private static final String COL_HEADER_CURRENCY = "Currency"; - private static final String COL_HEADER_DIRECTION = "Buy/Sell"; // TODO "Take Offer to - private static final String COL_HEADER_NAME = "Name"; - private static final String COL_HEADER_PAYMENT_METHOD = "Payment Method"; - private static final String COL_HEADER_PRICE = "Price in %-3s for 1 BTC"; - private static final String COL_HEADER_VOLUME = padEnd("%-3s(min - max)", 15, ' '); - private static final String COL_HEADER_UUID = padEnd("ID", 52, ' '); + static final TimeZone TZ_UTC = getTimeZone("UTC"); + static final SimpleDateFormat DATE_FORMAT_ISO_8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); static String formatAddressBalanceTbl(List addressBalanceInfo) { String headerLine = (COL_HEADER_ADDRESS + COL_HEADER_DELIMITER diff --git a/cli/src/main/java/bisq/cli/TradeFormat.java b/cli/src/main/java/bisq/cli/TradeFormat.java new file mode 100644 index 00000000000..4eadfa9008c --- /dev/null +++ b/cli/src/main/java/bisq/cli/TradeFormat.java @@ -0,0 +1,119 @@ +/* + * 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.TradeInfo; + +import com.google.common.annotations.VisibleForTesting; + +import java.util.function.Supplier; + +import static bisq.cli.ColumnHeaderConstants.*; +import static bisq.cli.CurrencyFormat.formatOfferPrice; +import static bisq.cli.CurrencyFormat.formatSatoshis; +import static com.google.common.base.Strings.padEnd; + +@VisibleForTesting +public class TradeFormat { + + @VisibleForTesting + public static String format(TradeInfo tradeInfo) { + // Some column values might be longer than header, so we need to calculated them. + int shortIdColWidth = Math.max(COL_HEADER_TRADE_SHORT_ID.length(), tradeInfo.getShortId().length()); + int roleColWidth = Math.max(COL_HEADER_TRADE_ROLE.length(), tradeInfo.getRole().length()); + + // We only show taker fee under its header when user is the taker. + boolean isTaker = tradeInfo.getRole().toLowerCase().contains("taker"); + Supplier takerFeeHeaderFormat = () -> isTaker ? + padEnd(COL_HEADER_TRADE_TAKER_FEE, 12, ' ') + COL_HEADER_DELIMITER + : ""; + Supplier takerFeeHeader = () -> isTaker ? + "%" + (COL_HEADER_TRADE_TAKER_FEE.length() + 1) + "s" + : ""; + + String headersFormat = padEnd(COL_HEADER_TRADE_SHORT_ID, shortIdColWidth, ' ') + COL_HEADER_DELIMITER + + padEnd(COL_HEADER_TRADE_ROLE, roleColWidth, ' ') + COL_HEADER_DELIMITER + + COL_HEADER_PRICE + COL_HEADER_DELIMITER // includes %s -> currencyCode + + padEnd(COL_HEADER_TRADE_AMOUNT, 12, ' ') + COL_HEADER_DELIMITER + + padEnd(COL_HEADER_TRADE_TX_FEE, 12, ' ') + COL_HEADER_DELIMITER + + takerFeeHeaderFormat.get() + + COL_HEADER_TRADE_DEPOSIT_PUBLISHED + COL_HEADER_DELIMITER + + COL_HEADER_TRADE_DEPOSIT_CONFIRMED + COL_HEADER_DELIMITER + + COL_HEADER_TRADE_FIAT_SENT + COL_HEADER_DELIMITER + + COL_HEADER_TRADE_FIAT_RECEIVED + COL_HEADER_DELIMITER + + COL_HEADER_TRADE_PAYOUT_PUBLISHED + COL_HEADER_DELIMITER + + COL_HEADER_TRADE_WITHDRAWN + COL_HEADER_DELIMITER + + "%n"; + + String counterCurrencyCode = tradeInfo.getOffer().getCounterCurrencyCode(); + String baseCurrencyCode = tradeInfo.getOffer().getBaseCurrencyCode(); + String headerLine = isTaker + ? String.format(headersFormat, counterCurrencyCode, baseCurrencyCode, baseCurrencyCode, baseCurrencyCode) + : String.format(headersFormat, counterCurrencyCode, baseCurrencyCode, baseCurrencyCode); + + String colDataFormat = "%-" + shortIdColWidth + "s" // left justify + + " %-" + (roleColWidth + COL_HEADER_DELIMITER.length()) + "s" // left justify + + "%" + (COL_HEADER_PRICE.length() - 1) + "s" // right justify + + "%" + (COL_HEADER_TRADE_AMOUNT.length() + 1) + "s" // right justify + + "%" + (COL_HEADER_TRADE_TX_FEE.length() + 1) + "s" // right justify + + takerFeeHeader.get() // right justify + + " %-" + COL_HEADER_TRADE_DEPOSIT_PUBLISHED.length() + "s" // left justify + + " %-" + COL_HEADER_TRADE_DEPOSIT_CONFIRMED.length() + "s" // left justify + + " %-" + COL_HEADER_TRADE_FIAT_SENT.length() + "s" // left justify + + " %-" + COL_HEADER_TRADE_FIAT_RECEIVED.length() + "s" // left justify + + " %-" + COL_HEADER_TRADE_PAYOUT_PUBLISHED.length() + "s" // left justify + + " %-" + COL_HEADER_TRADE_WITHDRAWN.length() + "s" // left justify + ; + + return headerLine + + (isTaker + ? formatTradeForTaker(colDataFormat, tradeInfo) + : formatTradeForMaker(colDataFormat, tradeInfo)); + } + + private static String formatTradeForMaker(String format, TradeInfo tradeInfo) { + return String.format(format, + tradeInfo.getShortId(), + tradeInfo.getRole(), + formatOfferPrice(tradeInfo.getTradePrice()), + formatSatoshis(tradeInfo.getTradeAmountAsLong()), + formatSatoshis(tradeInfo.getTxFeeAsLong()), + tradeInfo.getIsDepositPublished() ? "YES" : "NO", + tradeInfo.getIsDepositConfirmed() ? "YES" : "NO", + tradeInfo.getIsFiatSent() ? "YES" : "NO", + tradeInfo.getIsFiatReceived() ? "YES" : "NO", + tradeInfo.getIsPayoutPublished() ? "YES" : "NO", + tradeInfo.getIsWithdrawn() ? "YES" : "NO"); + } + + private static String formatTradeForTaker(String format, TradeInfo tradeInfo) { + return String.format(format, + tradeInfo.getShortId(), + tradeInfo.getRole(), + formatOfferPrice(tradeInfo.getTradePrice()), + formatSatoshis(tradeInfo.getTradeAmountAsLong()), + formatSatoshis(tradeInfo.getTxFeeAsLong()), + formatSatoshis(tradeInfo.getTakerFeeAsLong()), + tradeInfo.getIsDepositPublished() ? "YES" : "NO", + tradeInfo.getIsDepositConfirmed() ? "YES" : "NO", + tradeInfo.getIsFiatSent() ? "YES" : "NO", + tradeInfo.getIsFiatReceived() ? "YES" : "NO", + tradeInfo.getIsPayoutPublished() ? "YES" : "NO", + tradeInfo.getIsWithdrawn() ? "YES" : "NO"); + } +} From d8bc26588c90d35436380143988e485b62a19e83 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Sun, 25 Oct 2020 16:52:45 -0300 Subject: [PATCH 12/30] Add license comment --- .../java/bisq/cli/ColumnHeaderConstants.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/cli/src/main/java/bisq/cli/ColumnHeaderConstants.java b/cli/src/main/java/bisq/cli/ColumnHeaderConstants.java index f5744f6cc71..03500e4f47a 100644 --- a/cli/src/main/java/bisq/cli/ColumnHeaderConstants.java +++ b/cli/src/main/java/bisq/cli/ColumnHeaderConstants.java @@ -1,3 +1,20 @@ +/* + * 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 static com.google.common.base.Strings.padEnd; From 31435bba51162f2ba352a7c055f5af9106010e5f Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Sun, 25 Oct 2020 17:02:40 -0300 Subject: [PATCH 13/30] Move semicolon up from blank line --- cli/src/main/java/bisq/cli/TradeFormat.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cli/src/main/java/bisq/cli/TradeFormat.java b/cli/src/main/java/bisq/cli/TradeFormat.java index 4eadfa9008c..ab9075fe341 100644 --- a/cli/src/main/java/bisq/cli/TradeFormat.java +++ b/cli/src/main/java/bisq/cli/TradeFormat.java @@ -77,8 +77,7 @@ public static String format(TradeInfo tradeInfo) { + " %-" + COL_HEADER_TRADE_FIAT_SENT.length() + "s" // left justify + " %-" + COL_HEADER_TRADE_FIAT_RECEIVED.length() + "s" // left justify + " %-" + COL_HEADER_TRADE_PAYOUT_PUBLISHED.length() + "s" // left justify - + " %-" + COL_HEADER_TRADE_WITHDRAWN.length() + "s" // left justify - ; + + " %-" + COL_HEADER_TRADE_WITHDRAWN.length() + "s"; // left justify return headerLine + (isTaker From 2b23704b5af862f947b64b1ca30234f8641b0114 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Sun, 25 Oct 2020 17:15:02 -0300 Subject: [PATCH 14/30] Add 'gettrade' to api method CLI --- cli/src/main/java/bisq/cli/CliMain.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java index d8f66dabbef..b7a5d80e216 100644 --- a/cli/src/main/java/bisq/cli/CliMain.java +++ b/cli/src/main/java/bisq/cli/CliMain.java @@ -27,6 +27,7 @@ import bisq.proto.grpc.GetOfferRequest; import bisq.proto.grpc.GetOffersRequest; import bisq.proto.grpc.GetPaymentAccountsRequest; +import bisq.proto.grpc.GetTradeRequest; import bisq.proto.grpc.GetVersionRequest; import bisq.proto.grpc.LockWalletRequest; import bisq.proto.grpc.RegisterDisputeAgentRequest; @@ -74,6 +75,7 @@ private enum Method { getoffer, getoffers, takeoffer, + gettrade, confirmpaymentstarted, confirmpaymentreceived, createpaymentacct, @@ -275,6 +277,18 @@ public static void run(String[] args) { out.printf("trade '%s' successfully taken", reply.getTrade().getShortId()); return; } + case gettrade: { + if (nonOptionArgs.size() < 2) + throw new IllegalArgumentException("incorrect parameter count, expecting trade id"); + + var tradeId = nonOptionArgs.get(1); + var request = GetTradeRequest.newBuilder() + .setTradeId(tradeId) + .build(); + var reply = tradesService.getTrade(request); + out.println(TradeFormat.format(reply.getTrade())); + return; + } case confirmpaymentstarted: { if (nonOptionArgs.size() < 2) throw new IllegalArgumentException("incorrect parameter count, expecting trade id"); @@ -427,6 +441,7 @@ private static void printHelp(OptionParser parser, PrintStream stream) { stream.format(rowFormat, "getoffer", "offer id", "Get current offer with id"); stream.format(rowFormat, "getoffers", "buy | sell, currency code", "Get current offers"); stream.format(rowFormat, "takeoffer", "offer id", "Take offer with id"); + stream.format(rowFormat, "gettrade", "trade id", "Get trade details and protocol status"); stream.format(rowFormat, "confirmpaymentstarted", "trade id", "Confirm payment started"); stream.format(rowFormat, "confirmpaymentreceived", "trade id", "Confirm payment received"); stream.format(rowFormat, "createpaymentacct", "account name, account number, currency code", "Create PerfectMoney dummy account"); From a2b292318cfbe753f8aca403051a59d7ddc8b8d2 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Sun, 25 Oct 2020 17:44:18 -0300 Subject: [PATCH 15/30] Add boolean 'showcontract' argument to api's 'gettrade' Optionally print the json contract for a given trade id. --- cli/src/main/java/bisq/cli/CliMain.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java index b7a5d80e216..f72fd26eb58 100644 --- a/cli/src/main/java/bisq/cli/CliMain.java +++ b/cli/src/main/java/bisq/cli/CliMain.java @@ -279,14 +279,21 @@ public static void run(String[] args) { } case gettrade: { if (nonOptionArgs.size() < 2) - throw new IllegalArgumentException("incorrect parameter count, expecting trade id"); + throw new IllegalArgumentException("incorrect parameter count, expecting trade id, [,showcontract = true|false]"); var tradeId = nonOptionArgs.get(1); + var showContract = false; + if (nonOptionArgs.size() == 3) + showContract = Boolean.getBoolean(nonOptionArgs.get(2)); + var request = GetTradeRequest.newBuilder() .setTradeId(tradeId) .build(); var reply = tradesService.getTrade(request); - out.println(TradeFormat.format(reply.getTrade())); + if (showContract) + out.println(reply.getTrade().getContractAsJson()); + else + out.println(TradeFormat.format(reply.getTrade())); return; } case confirmpaymentstarted: { @@ -441,7 +448,7 @@ private static void printHelp(OptionParser parser, PrintStream stream) { stream.format(rowFormat, "getoffer", "offer id", "Get current offer with id"); stream.format(rowFormat, "getoffers", "buy | sell, currency code", "Get current offers"); stream.format(rowFormat, "takeoffer", "offer id", "Take offer with id"); - stream.format(rowFormat, "gettrade", "trade id", "Get trade details and protocol status"); + stream.format(rowFormat, "gettrade", "trade id [,showcontract]", "Get trade summary or full contract"); stream.format(rowFormat, "confirmpaymentstarted", "trade id", "Confirm payment started"); stream.format(rowFormat, "confirmpaymentreceived", "trade id", "Confirm payment received"); stream.format(rowFormat, "createpaymentacct", "account name, account number, currency code", "Create PerfectMoney dummy account"); From a8decafc2ffd9956c273d35470d86866a29d810f Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 26 Oct 2020 17:43:08 -0300 Subject: [PATCH 16/30] Stub out api methods 'keepfunds', 'withdrawfunds' This PR adds trade closing method stubs to keep funds in the Bisq wallet or send them to an external BTC wallet. - Add grpc protos - Add new methods to GrpcTradesService, CoreApi - Stub out implementations in CoreTradesService - Add methods to CLI --- cli/src/main/java/bisq/cli/CliMain.java | 32 +++++++++++++++++ core/src/main/java/bisq/core/api/CoreApi.java | 8 +++++ .../java/bisq/core/api/CoreTradesService.java | 10 ++++++ .../bisq/daemon/grpc/GrpcTradesService.java | 34 +++++++++++++++++++ proto/src/main/proto/grpc.proto | 19 +++++++++++ 5 files changed, 103 insertions(+) diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java index f72fd26eb58..cecaadf0d56 100644 --- a/cli/src/main/java/bisq/cli/CliMain.java +++ b/cli/src/main/java/bisq/cli/CliMain.java @@ -29,12 +29,14 @@ import bisq.proto.grpc.GetPaymentAccountsRequest; import bisq.proto.grpc.GetTradeRequest; import bisq.proto.grpc.GetVersionRequest; +import bisq.proto.grpc.KeepFundsRequest; import bisq.proto.grpc.LockWalletRequest; import bisq.proto.grpc.RegisterDisputeAgentRequest; import bisq.proto.grpc.RemoveWalletPasswordRequest; import bisq.proto.grpc.SetWalletPasswordRequest; import bisq.proto.grpc.TakeOfferRequest; import bisq.proto.grpc.UnlockWalletRequest; +import bisq.proto.grpc.WithdrawFundsRequest; import io.grpc.StatusRuntimeException; @@ -78,6 +80,8 @@ private enum Method { gettrade, confirmpaymentstarted, confirmpaymentreceived, + keepfunds, + withdrawfunds, createpaymentacct, getpaymentaccts, getversion, @@ -320,6 +324,32 @@ public static void run(String[] args) { out.printf("trade '%s' payment received message sent", tradeId); return; } + case keepfunds: { + if (nonOptionArgs.size() < 2) + throw new IllegalArgumentException("incorrect parameter count, expecting trade id"); + + var tradeId = nonOptionArgs.get(1); + var request = KeepFundsRequest.newBuilder() + .setTradeId(tradeId) + .build(); + tradesService.keepFunds(request); + out.printf("funds from trade '%s' saved in bisq wallet", tradeId); + return; + } + case withdrawfunds: { + if (nonOptionArgs.size() < 3) + throw new IllegalArgumentException("incorrect parameter count, expecting trade id, bitcoin wallet address"); + + var tradeId = nonOptionArgs.get(1); + var address = nonOptionArgs.get(2); + var request = WithdrawFundsRequest.newBuilder() + .setTradeId(tradeId) + .setAddress(address) + .build(); + tradesService.withdrawFunds(request); + out.printf("funds from trade '%s' sent to btc address '%s'", tradeId, address); + return; + } case createpaymentacct: { if (nonOptionArgs.size() < 5) throw new IllegalArgumentException( @@ -451,6 +481,8 @@ private static void printHelp(OptionParser parser, PrintStream stream) { stream.format(rowFormat, "gettrade", "trade id [,showcontract]", "Get trade summary or full contract"); stream.format(rowFormat, "confirmpaymentstarted", "trade id", "Confirm payment started"); stream.format(rowFormat, "confirmpaymentreceived", "trade id", "Confirm payment received"); + stream.format(rowFormat, "keepfunds", "trade id", "Keep received funds in Bisq wallet"); + stream.format(rowFormat, "withdrawfunds", "trade id, bitcoin wallet address", "Withdraw received funds to external wallet address"); stream.format(rowFormat, "createpaymentacct", "account name, account number, currency code", "Create PerfectMoney dummy account"); stream.format(rowFormat, "getpaymentaccts", "", "Get user payment accounts"); stream.format(rowFormat, "lockwallet", "", "Remove wallet password from memory, locking the wallet"); diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index 05886dd6e9e..001bf4773bc 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -189,6 +189,14 @@ public void confirmPaymentReceived(String tradeId) { coreTradesService.confirmPaymentReceived(tradeId); } + public void keepFunds(String tradeId) { + coreTradesService.keepFunds(tradeId); + } + + public void withdrawFunds(String tradeId, String address) { + coreTradesService.withdrawFunds(tradeId, address); + } + public Trade getTrade(String tradeId) { return coreTradesService.getTrade(tradeId); } diff --git a/core/src/main/java/bisq/core/api/CoreTradesService.java b/core/src/main/java/bisq/core/api/CoreTradesService.java index 85877488315..88ab0128cd2 100644 --- a/core/src/main/java/bisq/core/api/CoreTradesService.java +++ b/core/src/main/java/bisq/core/api/CoreTradesService.java @@ -116,6 +116,16 @@ void confirmPaymentReceived(String tradeId) { } } + @SuppressWarnings("unused") + void keepFunds(String tradeId) { + log.info("TODO"); + } + + @SuppressWarnings("unused") + void withdrawFunds(String tradeId, String address) { + log.info("TODO"); + } + String getTradeRole(String tradeId) { return tradeUtil.getRole(getTrade(tradeId)); } diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java index e23651bb207..74cd04ead93 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcTradesService.java @@ -27,9 +27,13 @@ import bisq.proto.grpc.ConfirmPaymentStartedRequest; import bisq.proto.grpc.GetTradeReply; import bisq.proto.grpc.GetTradeRequest; +import bisq.proto.grpc.KeepFundsReply; +import bisq.proto.grpc.KeepFundsRequest; import bisq.proto.grpc.TakeOfferReply; import bisq.proto.grpc.TakeOfferRequest; import bisq.proto.grpc.TradesGrpc; +import bisq.proto.grpc.WithdrawFundsReply; +import bisq.proto.grpc.WithdrawFundsRequest; import io.grpc.Status; import io.grpc.StatusRuntimeException; @@ -119,4 +123,34 @@ public void confirmPaymentReceived(ConfirmPaymentReceivedRequest req, throw ex; } } + + @Override + public void keepFunds(KeepFundsRequest req, + StreamObserver responseObserver) { + try { + coreApi.keepFunds(req.getTradeId()); + var reply = KeepFundsReply.newBuilder().build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } catch (IllegalStateException | IllegalArgumentException cause) { + var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage())); + responseObserver.onError(ex); + throw ex; + } + } + + @Override + public void withdrawFunds(WithdrawFundsRequest req, + StreamObserver responseObserver) { + try { + coreApi.withdrawFunds(req.getTradeId(), req.getAddress()); + var reply = WithdrawFundsReply.newBuilder().build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } catch (IllegalStateException | IllegalArgumentException cause) { + var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage())); + responseObserver.onError(ex); + throw ex; + } + } } diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index 041f1278ea2..006c6a6f111 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -180,6 +180,10 @@ service Trades { } rpc ConfirmPaymentReceived (ConfirmPaymentReceivedRequest) returns (ConfirmPaymentReceivedReply) { } + rpc KeepFunds (KeepFundsRequest) returns (KeepFundsReply) { + } + rpc WithdrawFunds (WithdrawFundsRequest) returns (WithdrawFundsReply) { + } } message TakeOfferRequest { @@ -213,6 +217,21 @@ message GetTradeReply { TradeInfo trade = 1; } +message KeepFundsRequest { + string tradeId = 1; +} + +message KeepFundsReply { +} + +message WithdrawFundsRequest { + string tradeId = 1; + string address = 2; +} + +message WithdrawFundsReply { +} + message TradeInfo { OfferInfo offer = 1; string tradeId = 2; From a3631a022f477cd2e9a00134be978c75a0ce3ec4 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Mon, 26 Oct 2020 20:44:39 -0300 Subject: [PATCH 17/30] Implement api methods 'keepfunds', 'withdrawfunds' The CoreTradesService was refactored to work for newly added api methods: - keepfunds -- close trade, keep funds in bisq wallet - withdrawfunds -- close trade, withdraw funds to external btc wallet A getKey accessor was added to CoreWalletsService (needed by withdrawfunds impl). --- .../java/bisq/core/api/CoreTradesService.java | 120 ++++++++++++++++-- .../bisq/core/api/CoreWalletsService.java | 6 + 2 files changed, 118 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/bisq/core/api/CoreTradesService.java b/core/src/main/java/bisq/core/api/CoreTradesService.java index 88ab0128cd2..1b58334466b 100644 --- a/core/src/main/java/bisq/core/api/CoreTradesService.java +++ b/core/src/main/java/bisq/core/api/CoreTradesService.java @@ -17,36 +17,57 @@ package bisq.core.api; +import bisq.core.btc.model.AddressEntry; +import bisq.core.btc.wallet.BtcWalletService; import bisq.core.offer.Offer; import bisq.core.offer.takeoffer.TakeOfferModel; import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; import bisq.core.trade.TradeUtil; +import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.protocol.BuyerProtocol; import bisq.core.trade.protocol.SellerProtocol; import bisq.core.user.User; +import bisq.core.util.validation.BtcAddressValidator; + +import org.bitcoinj.core.Coin; import javax.inject.Inject; +import java.util.Optional; import java.util.function.Consumer; import lombok.extern.slf4j.Slf4j; +import static bisq.core.btc.model.AddressEntry.Context.TRADE_PAYOUT; import static java.lang.String.format; @Slf4j class CoreTradesService { + // Dependencies on core api services in this package must be kept to an absolute + // minimum, but some trading functions require an unlocked wallet's key, so an + // exception is made in this case. + private final CoreWalletsService coreWalletsService; + + private final BtcWalletService btcWalletService; + private final ClosedTradableManager closedTradableManager; private final TakeOfferModel takeOfferModel; private final TradeManager tradeManager; private final TradeUtil tradeUtil; private final User user; @Inject - public CoreTradesService(TakeOfferModel takeOfferModel, + public CoreTradesService(CoreWalletsService coreWalletsService, + BtcWalletService btcWalletService, + ClosedTradableManager closedTradableManager, + TakeOfferModel takeOfferModel, TradeManager tradeManager, TradeUtil tradeUtil, User user) { + this.coreWalletsService = coreWalletsService; + this.btcWalletService = btcWalletService; + this.closedTradableManager = closedTradableManager; this.takeOfferModel = takeOfferModel; this.tradeManager = tradeManager; this.tradeUtil = tradeUtil; @@ -116,14 +137,50 @@ void confirmPaymentReceived(String tradeId) { } } - @SuppressWarnings("unused") void keepFunds(String tradeId) { - log.info("TODO"); + verifyTradeIsNotClosed(tradeId); + var trade = getOpenTrade(tradeId).orElseThrow(() -> + new IllegalArgumentException(format("trade with id '%s' not found", tradeId))); + log.info("Keeping funds received from trade {}", tradeId); + tradeManager.onTradeCompleted(trade); } - @SuppressWarnings("unused") - void withdrawFunds(String tradeId, String address) { - log.info("TODO"); + void withdrawFunds(String tradeId, String toAddress) { + // An encrypted wallet must be unlocked for this operation. + verifyTradeIsNotClosed(tradeId); + var trade = getOpenTrade(tradeId).orElseThrow(() -> + new IllegalArgumentException(format("trade with id '%s' not found", tradeId))); + + verifyIsValidBTCAddress(toAddress); + + var fromAddressEntry = btcWalletService.getOrCreateAddressEntry(trade.getId(), TRADE_PAYOUT); + verifyFundsNotWithdrawn(fromAddressEntry); + + var amount = trade.getPayoutAmount(); + var fee = getEstimatedTxFee(fromAddressEntry.getAddressString(), toAddress, amount); + var receiverAmount = amount.subtract(fee); + + log.info(format("Withdrawing funds received from trade %s:" + + "%n From %s%n To %s%n Amt %s%n Tx Fee %s%n Receiver Amt %s", + tradeId, + fromAddressEntry.getAddressString(), + toAddress, + amount.toFriendlyString(), + fee.toFriendlyString(), + receiverAmount.toFriendlyString())); + + tradeManager.onWithdrawRequest( + toAddress, + amount, + fee, + coreWalletsService.getKey(), + trade, + () -> { + }, + (errorMessage, throwable) -> { + log.error(errorMessage, throwable); + throw new IllegalStateException(errorMessage, throwable); + }); } String getTradeRole(String tradeId) { @@ -131,11 +188,58 @@ String getTradeRole(String tradeId) { } Trade getTrade(String tradeId) { - return tradeManager.getTradeById(tradeId).orElseThrow(() -> - new IllegalArgumentException(format("trade with id '%s' not found", tradeId))); + return getOpenTrade(tradeId).orElseGet(() -> + getClosedTrade(tradeId).orElseThrow(() -> + new IllegalArgumentException(format("trade with id '%s' not found", tradeId)) + )); + } + + private Optional getOpenTrade(String tradeId) { + return tradeManager.getTradeById(tradeId); + } + + private Optional getClosedTrade(String tradeId) { + return closedTradableManager.getTradableById(tradeId).map(value -> (Trade) value); } private boolean isFollowingBuyerProtocol(Trade trade) { return tradeManager.getTradeProtocol(trade) instanceof BuyerProtocol; } + + private Coin getEstimatedTxFee(String fromAddress, String toAddress, Coin amount) { + // TODO This and identical logic should be refactored into TradeUtil. + try { + return btcWalletService.getFeeEstimationTransaction(fromAddress, + toAddress, + amount, + TRADE_PAYOUT).getFee(); + } catch (Exception ex) { + log.error("", ex); + throw new IllegalStateException(format("could not estimate tx fee: %s", ex.getMessage())); + } + } + + // Throws a RuntimeException trade is already closed. + private void verifyTradeIsNotClosed(String tradeId) { + if (getClosedTrade(tradeId).isPresent()) + throw new IllegalArgumentException(format("trade '%s' is already closed", tradeId)); + } + + // Throws a RuntimeException if address is not valid. + private void verifyIsValidBTCAddress(String address) { + try { + new BtcAddressValidator().validate(address); + } catch (Throwable t) { + log.error("", t); + throw new IllegalArgumentException(format("'%s' is not a valid btc address", address)); + } + } + + // Throws a RuntimeException if address has a zero balance. + private void verifyFundsNotWithdrawn(AddressEntry fromAddressEntry) { + Coin fromAddressBalance = btcWalletService.getBalanceForAddress(fromAddressEntry.getAddress()); + if (fromAddressBalance.isZero()) + throw new IllegalStateException(format("funds already withdrawn from address '%s'", + fromAddressEntry.getAddressString())); + } } diff --git a/core/src/main/java/bisq/core/api/CoreWalletsService.java b/core/src/main/java/bisq/core/api/CoreWalletsService.java index 0f1e1db0844..29394aad8ac 100644 --- a/core/src/main/java/bisq/core/api/CoreWalletsService.java +++ b/core/src/main/java/bisq/core/api/CoreWalletsService.java @@ -71,6 +71,12 @@ public CoreWalletsService(Balances balances, this.btcWalletService = btcWalletService; } + @Nullable + KeyParameter getKey() { + verifyEncryptedWalletIsUnlocked(); + return tempAesKey; + } + long getAvailableBalance() { verifyWalletsAreAvailable(); verifyEncryptedWalletIsUnlocked(); From f1db254073577ac656a0bbd59cdc6246502d4bfa Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Tue, 27 Oct 2020 13:01:33 -0300 Subject: [PATCH 18/30] Make formatSatoshis visible for testing --- cli/src/main/java/bisq/cli/CurrencyFormat.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cli/src/main/java/bisq/cli/CurrencyFormat.java b/cli/src/main/java/bisq/cli/CurrencyFormat.java index 798af169d40..e4d8f89c6c7 100644 --- a/cli/src/main/java/bisq/cli/CurrencyFormat.java +++ b/cli/src/main/java/bisq/cli/CurrencyFormat.java @@ -17,6 +17,8 @@ package bisq.cli; +import com.google.common.annotations.VisibleForTesting; + import java.text.DecimalFormat; import java.text.NumberFormat; @@ -27,15 +29,17 @@ import static java.lang.String.format; -class CurrencyFormat { +@VisibleForTesting +public class CurrencyFormat { private static final NumberFormat NUMBER_FORMAT = NumberFormat.getInstance(Locale.US); static final BigDecimal SATOSHI_DIVISOR = new BigDecimal(100000000); static final DecimalFormat BTC_FORMAT = new DecimalFormat("###,##0.00000000"); + @VisibleForTesting @SuppressWarnings("BigDecimalMethodWithoutRoundingCalled") - static String formatSatoshis(long sats) { + public static String formatSatoshis(long sats) { return BTC_FORMAT.format(BigDecimal.valueOf(sats).divide(SATOSHI_DIVISOR)); } From b8ae566b69578405cc87b8c08c362c98abb3ac99 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Tue, 27 Oct 2020 13:03:48 -0300 Subject: [PATCH 19/30] Add method for printing current jupiter test name --- apitest/src/test/java/bisq/apitest/ApiTestCase.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apitest/src/test/java/bisq/apitest/ApiTestCase.java b/apitest/src/test/java/bisq/apitest/ApiTestCase.java index 854ce4c59bc..7f84772f543 100644 --- a/apitest/src/test/java/bisq/apitest/ApiTestCase.java +++ b/apitest/src/test/java/bisq/apitest/ApiTestCase.java @@ -26,6 +26,8 @@ import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; +import org.junit.jupiter.api.TestInfo; + import static java.util.Arrays.stream; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -117,4 +119,10 @@ protected static void sleep(long ms) { // empty } } + + protected final String testName(TestInfo testInfo) { + return testInfo.getTestMethod().isPresent() + ? testInfo.getTestMethod().get().getName() + : "unknown test name"; + } } From 1e25be5bdc40073921de795191b8f7327f494e5e Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Tue, 27 Oct 2020 13:04:53 -0300 Subject: [PATCH 20/30] Test trade closing api methods 'keepfunds' withdrawfunds' Some refactoring of the api test case hierarchy is included in this commit. --- .../java/bisq/apitest/method/MethodTest.java | 45 ++++++++ .../method/offer/AbstractOfferTest.java | 3 - .../method/trade/AbstractTradeTest.java | 107 +++++++++++++++++- .../method/trade/TakeBuyBTCOfferTest.java | 93 ++++++++------- .../method/trade/TakeSellBTCOfferTest.java | 103 ++++++++++------- 5 files changed, 261 insertions(+), 90 deletions(-) diff --git a/apitest/src/test/java/bisq/apitest/method/MethodTest.java b/apitest/src/test/java/bisq/apitest/method/MethodTest.java index 6a175d118ce..64f6710c7e2 100644 --- a/apitest/src/test/java/bisq/apitest/method/MethodTest.java +++ b/apitest/src/test/java/bisq/apitest/method/MethodTest.java @@ -25,6 +25,7 @@ import bisq.proto.grpc.GetOfferRequest; import bisq.proto.grpc.GetPaymentAccountsRequest; import bisq.proto.grpc.GetTradeRequest; +import bisq.proto.grpc.KeepFundsRequest; import bisq.proto.grpc.LockWalletRequest; import bisq.proto.grpc.MarketPriceRequest; import bisq.proto.grpc.OfferInfo; @@ -34,11 +35,16 @@ import bisq.proto.grpc.TakeOfferRequest; import bisq.proto.grpc.TradeInfo; import bisq.proto.grpc.UnlockWalletRequest; +import bisq.proto.grpc.WithdrawFundsRequest; import protobuf.PaymentAccount; import java.util.stream.Collectors; +import org.junit.jupiter.api.BeforeEach; + +import static bisq.apitest.config.BisqAppConfig.alicedaemon; +import static bisq.apitest.config.BisqAppConfig.bobdaemon; import static bisq.common.app.DevEnv.DEV_PRIVILEGE_PRIV_KEY; import static bisq.core.payment.payload.PaymentMethod.PERFECT_MONEY; import static java.util.Comparator.comparing; @@ -48,6 +54,7 @@ import bisq.apitest.ApiTestCase; import bisq.apitest.config.BisqAppConfig; +import bisq.cli.GrpcStubs; public class MethodTest extends ApiTestCase { @@ -55,6 +62,18 @@ public class MethodTest extends ApiTestCase { protected static final String MEDIATOR = "mediator"; protected static final String REFUND_AGENT = "refundagent"; + protected static GrpcStubs aliceStubs; + protected static GrpcStubs bobStubs; + + protected PaymentAccount alicesDummyAcct; + protected PaymentAccount bobsDummyAcct; + + @BeforeEach + public void initDummyPaymentAccounts() { + alicesDummyAcct = getDefaultPerfectDummyPaymentAccount(alicedaemon); + bobsDummyAcct = getDefaultPerfectDummyPaymentAccount(bobdaemon); + } + // Convenience methods for building gRPC request objects protected final GetBalanceRequest createBalanceRequest() { @@ -109,6 +128,19 @@ protected final ConfirmPaymentReceivedRequest createConfirmPaymentReceivedReques return ConfirmPaymentReceivedRequest.newBuilder().setTradeId(tradeId).build(); } + protected final KeepFundsRequest createKeepFundsRequest(String tradeId) { + return KeepFundsRequest.newBuilder() + .setTradeId(tradeId) + .build(); + } + + protected final WithdrawFundsRequest createWithdrawFundsRequest(String tradeId, String address) { + return WithdrawFundsRequest.newBuilder() + .setTradeId(tradeId) + .setAddress(address) + .build(); + } + // Convenience methods for calling frequently used & thoroughly tested gRPC services. protected final long getBalance(BisqAppConfig bisqAppConfig) { @@ -175,16 +207,29 @@ protected final TradeInfo getTrade(BisqAppConfig bisqAppConfig, String tradeId) return grpcStubs(bisqAppConfig).tradesService.getTrade(req).getTrade(); } + @SuppressWarnings("ResultOfMethodCallIgnored") protected final void confirmPaymentStarted(BisqAppConfig bisqAppConfig, String tradeId) { var req = createConfirmPaymentStartedRequest(tradeId); grpcStubs(bisqAppConfig).tradesService.confirmPaymentStarted(req); } + @SuppressWarnings("ResultOfMethodCallIgnored") protected final void confirmPaymentReceived(BisqAppConfig bisqAppConfig, String tradeId) { var req = createConfirmPaymentReceivedRequest(tradeId); grpcStubs(bisqAppConfig).tradesService.confirmPaymentReceived(req); } + @SuppressWarnings("ResultOfMethodCallIgnored") + protected final void keepFunds(BisqAppConfig bisqAppConfig, String tradeId) { + var req = createKeepFundsRequest(tradeId); + grpcStubs(bisqAppConfig).tradesService.keepFunds(req); + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + protected final void withdrawFunds(BisqAppConfig bisqAppConfig, String tradeId, String address) { + var req = createWithdrawFundsRequest(tradeId, address); + grpcStubs(bisqAppConfig).tradesService.withdrawFunds(req); + } // Static conveniences for test methods and test case fixture setups. protected static RegisterDisputeAgentRequest createRegisterDisputeAgentRequest(String disputeAgentType) { diff --git a/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java index 979d7a33e6a..bc233721d0f 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java @@ -59,9 +59,6 @@ @Slf4j public abstract class AbstractOfferTest extends MethodTest { - protected static GrpcStubs aliceStubs; - protected static GrpcStubs bobStubs; - @BeforeAll public static void setUp() { startSupportingApps(); diff --git a/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java b/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java index 094007abd40..7e4c22e9689 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java @@ -4,6 +4,12 @@ import bisq.proto.grpc.TradeInfo; +import org.slf4j.Logger; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.TestInfo; + +import static bisq.cli.TradeFormat.format; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -13,19 +19,110 @@ public class AbstractTradeTest extends AbstractOfferTest { + // A test fixture encapsulating expected trade protocol status. + // ExpectedProtocolStatus.init should be called before any @Test begins. + protected static final ExpectedProtocolStatus EXPECTED_PROTOCOL_STATUS = new ExpectedProtocolStatus(); + + // A Trade ID cache for use in @Test sequences. + protected static String tradeId; + + @BeforeAll + public static void clearExpectedPaymentStatusFlags() { + EXPECTED_PROTOCOL_STATUS.init(); + } + protected final TradeInfo takeAlicesOffer(String offerId, String paymentAccountId) { return bobStubs.tradesService.takeOffer(createTakeOfferRequest(offerId, paymentAccountId)).getTrade(); } + @SuppressWarnings("unused") protected final TradeInfo takeBobsOffer(String offerId, String paymentAccountId) { return aliceStubs.tradesService.takeOffer(createTakeOfferRequest(offerId, paymentAccountId)).getTrade(); } - protected final void verifyExpectedTradeStateAndPhase(TradeInfo trade, - Trade.State expectedState, - Trade.Phase expectedPhase) { + protected final void verifyExpectedProtocolStatus(TradeInfo trade) { assertNotNull(trade); - assertEquals(expectedState.name(), trade.getState()); - assertEquals(expectedPhase.name(), trade.getPhase()); + assertEquals(EXPECTED_PROTOCOL_STATUS.state.name(), trade.getState()); + assertEquals(EXPECTED_PROTOCOL_STATUS.phase.name(), trade.getPhase()); + assertEquals(EXPECTED_PROTOCOL_STATUS.isDepositPublished, trade.getIsDepositPublished()); + assertEquals(EXPECTED_PROTOCOL_STATUS.isDepositConfirmed, trade.getIsDepositConfirmed()); + assertEquals(EXPECTED_PROTOCOL_STATUS.isFiatSent, trade.getIsFiatSent()); + assertEquals(EXPECTED_PROTOCOL_STATUS.isFiatReceived, trade.getIsFiatReceived()); + assertEquals(EXPECTED_PROTOCOL_STATUS.isPayoutPublished, trade.getIsPayoutPublished()); + assertEquals(EXPECTED_PROTOCOL_STATUS.isWithdrawn, trade.getIsWithdrawn()); + } + + protected final void logTrade(Logger log, + TestInfo testInfo, + String description, + TradeInfo trade) { + log.info(String.format("%s %s%n%s", + testName(testInfo), + description.toUpperCase(), + format(trade))); + } + + @SuppressWarnings("UnusedReturnValue") + static class ExpectedProtocolStatus { + Trade.State state; + Trade.Phase phase; + boolean isDepositPublished; + boolean isDepositConfirmed; + boolean isFiatSent; + boolean isFiatReceived; + boolean isPayoutPublished; + boolean isWithdrawn; + + ExpectedProtocolStatus setState(Trade.State state) { + this.state = state; + return this; + } + + ExpectedProtocolStatus setPhase(Trade.Phase phase) { + this.phase = phase; + return this; + } + + ExpectedProtocolStatus setDepositPublished(boolean depositPublished) { + isDepositPublished = depositPublished; + return this; + } + + ExpectedProtocolStatus setDepositConfirmed(boolean depositConfirmed) { + isDepositConfirmed = depositConfirmed; + return this; + } + + ExpectedProtocolStatus setFiatSent(boolean fiatSent) { + isFiatSent = fiatSent; + return this; + } + + ExpectedProtocolStatus setFiatReceived(boolean fiatReceived) { + isFiatReceived = fiatReceived; + return this; + } + + ExpectedProtocolStatus setPayoutPublished(boolean payoutPublished) { + isPayoutPublished = payoutPublished; + return this; + } + + ExpectedProtocolStatus setWithdrawn(boolean withdrawn) { + isWithdrawn = withdrawn; + return this; + } + + @SuppressWarnings("unused") + void init() { + state = null; + phase = null; + isDepositPublished = false; + isDepositConfirmed = false; + isFiatSent = false; + isFiatReceived = false; + isPayoutPublished = false; + isWithdrawn = false; + } } } diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java index 4aba67fc715..bb7d8ea8239 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeBuyBTCOfferTest.java @@ -17,30 +17,24 @@ package bisq.apitest.method.trade; -import protobuf.PaymentAccount; - import io.grpc.StatusRuntimeException; import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestMethodOrder; import static bisq.apitest.config.BisqAppConfig.alicedaemon; import static bisq.apitest.config.BisqAppConfig.bobdaemon; -import static bisq.cli.TradeFormat.format; +import static bisq.cli.CurrencyFormat.formatSatoshis; import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED; import static bisq.core.trade.Trade.Phase.DEPOSIT_PUBLISHED; import static bisq.core.trade.Trade.Phase.FIAT_SENT; import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED; -import static bisq.core.trade.Trade.State.BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG; -import static bisq.core.trade.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN; -import static bisq.core.trade.Trade.State.SELLER_PUBLISHED_DEPOSIT_TX; -import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG; -import static java.lang.System.out; +import static bisq.core.trade.Trade.State.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; @@ -53,22 +47,14 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest { // Alice is buyer, Bob is seller. - private static String tradeId; - - private PaymentAccount alicesAccount; - private PaymentAccount bobsAccount; - - @BeforeEach - public void init() { - alicesAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); - bobsAccount = getDefaultPerfectDummyPaymentAccount(bobdaemon); - } - @Test @Order(1) - public void testTakeAlicesBuyOffer() { + public void testTakeAlicesBuyOffer(final TestInfo testInfo) { try { - var alicesOffer = createAliceOffer(alicesAccount, "buy", "usd", 12500000); + var alicesOffer = createAliceOffer(alicesDummyAcct, + "buy", + "usd", + 12500000); var offerId = alicesOffer.getId(); // Wait for Alice's AddToOfferBook task. @@ -76,7 +62,7 @@ public void testTakeAlicesBuyOffer() { sleep(3000); assertEquals(1, getOpenOffersCount(aliceStubs, "buy", "usd")); - var trade = takeAlicesOffer(offerId, bobsAccount.getId()); + var trade = takeAlicesOffer(offerId, bobsDummyAcct.getId()); assertNotNull(trade); assertEquals(offerId, trade.getTradeId()); // Cache the trade id for the other tests. @@ -86,13 +72,19 @@ public void testTakeAlicesBuyOffer() { assertEquals(0, getOpenOffersCount(aliceStubs, "buy", "usd")); trade = getTrade(bobdaemon, trade.getTradeId()); - verifyExpectedTradeStateAndPhase(trade, SELLER_PUBLISHED_DEPOSIT_TX, DEPOSIT_PUBLISHED); - out.println(format(trade)); + EXPECTED_PROTOCOL_STATUS.setState(SELLER_PUBLISHED_DEPOSIT_TX) + .setPhase(DEPOSIT_PUBLISHED) + .setDepositPublished(true); + verifyExpectedProtocolStatus(trade); + logTrade(log, testInfo, "Bob's view after taking offer and sending deposit", trade); genBtcBlocksThenWait(1, 2250); trade = getTrade(bobdaemon, trade.getTradeId()); - verifyExpectedTradeStateAndPhase(trade, DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, DEPOSIT_CONFIRMED); - out.println(format(trade)); + EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN) + .setPhase(DEPOSIT_CONFIRMED) + .setDepositConfirmed(true); + verifyExpectedProtocolStatus(trade); + logTrade(log, testInfo, "Bob's view after deposit is confirmed", trade); } catch (StatusRuntimeException e) { fail(e); } @@ -100,18 +92,19 @@ public void testTakeAlicesBuyOffer() { @Test @Order(2) - public void testAlicesConfirmPaymentStarted() { + public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) { try { var trade = getTrade(alicedaemon, tradeId); - assertNotNull(trade); - confirmPaymentStarted(alicedaemon, trade.getTradeId()); sleep(3000); trade = getTrade(alicedaemon, tradeId); assertEquals(OFFER_FEE_PAID.name(), trade.getOffer().getState()); - verifyExpectedTradeStateAndPhase(trade, BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG, FIAT_SENT); - out.println(format(trade)); + EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG) + .setPhase(FIAT_SENT) + .setFiatSent(true); + verifyExpectedProtocolStatus(trade); + logTrade(log, testInfo, "Alice's view after confirming fiat payment sent", trade); } catch (StatusRuntimeException e) { fail(e); } @@ -119,17 +112,41 @@ public void testAlicesConfirmPaymentStarted() { @Test @Order(3) - public void testBobsConfirmPaymentReceived() { + public void testBobsConfirmPaymentReceived(final TestInfo testInfo) { var trade = getTrade(bobdaemon, tradeId); - assertNotNull(trade); - confirmPaymentReceived(bobdaemon, trade.getTradeId()); sleep(3000); trade = getTrade(bobdaemon, tradeId); - // TODO is this a bug? Why is offer.state == available? + // Note: offer.state == available assertEquals(AVAILABLE.name(), trade.getOffer().getState()); - verifyExpectedTradeStateAndPhase(trade, SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG, PAYOUT_PUBLISHED); - out.println(format(trade)); + EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG) + .setPhase(PAYOUT_PUBLISHED) + .setPayoutPublished(true) + .setFiatReceived(true); + verifyExpectedProtocolStatus(trade); + logTrade(log, testInfo, "Bob's view after confirming fiat payment received", trade); + } + + @Test + @Order(4) + public void testAlicesKeepFunds(final TestInfo testInfo) { + genBtcBlocksThenWait(1, 2250); + + var trade = getTrade(alicedaemon, tradeId); + logTrade(log, testInfo, "Alice's view before keeping funds", trade); + + keepFunds(alicedaemon, tradeId); + + genBtcBlocksThenWait(1, 2250); + + trade = getTrade(alicedaemon, tradeId); + EXPECTED_PROTOCOL_STATUS.setState(BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG) + .setPhase(PAYOUT_PUBLISHED); + verifyExpectedProtocolStatus(trade); + logTrade(log, testInfo, "Alice's view after keeping funds", trade); + log.info("{} Alice's current available balance: {} BTC", + testName(testInfo), + formatSatoshis(getBalance(alicedaemon))); } } diff --git a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java index 658b4083231..c1786082ba8 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/TakeSellBTCOfferTest.java @@ -17,30 +17,21 @@ package bisq.apitest.method.trade; -import protobuf.PaymentAccount; - import io.grpc.StatusRuntimeException; import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestMethodOrder; import static bisq.apitest.config.BisqAppConfig.alicedaemon; import static bisq.apitest.config.BisqAppConfig.bobdaemon; -import static bisq.cli.TradeFormat.format; -import static bisq.core.trade.Trade.Phase.DEPOSIT_CONFIRMED; -import static bisq.core.trade.Trade.Phase.DEPOSIT_PUBLISHED; -import static bisq.core.trade.Trade.Phase.FIAT_SENT; -import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED; -import static bisq.core.trade.Trade.State.BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG; -import static bisq.core.trade.Trade.State.BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG; -import static bisq.core.trade.Trade.State.DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN; -import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG; -import static java.lang.System.out; +import static bisq.cli.CurrencyFormat.formatSatoshis; +import static bisq.core.trade.Trade.Phase.*; +import static bisq.core.trade.Trade.State.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; @@ -53,22 +44,14 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest { // Alice is seller, Bob is buyer. - private static String tradeId; - - private PaymentAccount alicesAccount; - private PaymentAccount bobsAccount; - - @BeforeEach - public void init() { - alicesAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); - bobsAccount = getDefaultPerfectDummyPaymentAccount(bobdaemon); - } - @Test @Order(1) - public void testTakeAlicesSellOffer() { + public void testTakeAlicesSellOffer(final TestInfo testInfo) { try { - var alicesOffer = createAliceOffer(alicesAccount, "sell", "usd", 12500000); + var alicesOffer = createAliceOffer(alicesDummyAcct, + "sell", + "usd", + 12500000); var offerId = alicesOffer.getId(); // Wait for Alice's AddToOfferBook task. @@ -77,7 +60,7 @@ public void testTakeAlicesSellOffer() { sleep(3000); assertEquals(1, getOpenOffersCount(bobStubs, "sell", "usd")); - var trade = takeAlicesOffer(offerId, bobsAccount.getId()); + var trade = takeAlicesOffer(offerId, bobsDummyAcct.getId()); assertNotNull(trade); assertEquals(offerId, trade.getTradeId()); // Cache the trade id for the other tests. @@ -87,14 +70,20 @@ public void testTakeAlicesSellOffer() { assertEquals(0, getOpenOffersCount(bobStubs, "sell", "usd")); trade = getTrade(bobdaemon, trade.getTradeId()); - verifyExpectedTradeStateAndPhase(trade, BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG, DEPOSIT_PUBLISHED); - out.println(format(trade)); + EXPECTED_PROTOCOL_STATUS.setState(BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) + .setPhase(DEPOSIT_PUBLISHED) + .setDepositPublished(true); + verifyExpectedProtocolStatus(trade); + + logTrade(log, testInfo, "Bob's view after taking offer and sending deposit", trade); genBtcBlocksThenWait(1, 2250); trade = getTrade(bobdaemon, trade.getTradeId()); - verifyExpectedTradeStateAndPhase(trade, DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, DEPOSIT_CONFIRMED); - out.println(format(trade)); - + EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN) + .setPhase(DEPOSIT_CONFIRMED) + .setDepositConfirmed(true); + verifyExpectedProtocolStatus(trade); + logTrade(log, testInfo, "Bob's view after deposit is confirmed", trade); } catch (StatusRuntimeException e) { fail(e); } @@ -102,20 +91,20 @@ public void testTakeAlicesSellOffer() { @Test @Order(2) - public void testBobsConfirmPaymentStarted() { + public void testBobsConfirmPaymentStarted(final TestInfo testInfo) { try { var trade = getTrade(bobdaemon, tradeId); - assertNotNull(trade); - confirmPaymentStarted(bobdaemon, trade.getTradeId()); sleep(3000); trade = getTrade(bobdaemon, tradeId); - // TODO is this a bug? Why is offer.state == available? + // Note: offer.state == available assertEquals(AVAILABLE.name(), trade.getOffer().getState()); - verifyExpectedTradeStateAndPhase(trade, BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG, FIAT_SENT); - - out.println(format(trade)); + EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG) + .setPhase(FIAT_SENT) + .setFiatSent(true); + verifyExpectedProtocolStatus(trade); + logTrade(log, testInfo, "Bob's view after confirming fiat payment sent", trade); } catch (StatusRuntimeException e) { fail(e); } @@ -123,16 +112,42 @@ public void testBobsConfirmPaymentStarted() { @Test @Order(3) - public void testAlicesConfirmPaymentReceived() { + public void testAlicesConfirmPaymentReceived(final TestInfo testInfo) { var trade = getTrade(alicedaemon, tradeId); - assertNotNull(trade); - confirmPaymentReceived(alicedaemon, trade.getTradeId()); sleep(3000); trade = getTrade(alicedaemon, tradeId); assertEquals(OFFER_FEE_PAID.name(), trade.getOffer().getState()); - verifyExpectedTradeStateAndPhase(trade, SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG, PAYOUT_PUBLISHED); - out.println(format(trade)); + EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG) + .setPhase(PAYOUT_PUBLISHED) + .setPayoutPublished(true) + .setFiatReceived(true); + verifyExpectedProtocolStatus(trade); + logTrade(log, testInfo, "Alice's view after confirming fiat payment received", trade); + } + + @Test + @Order(4) + public void testBobsBtcWithdrawalToExternalAddress(final TestInfo testInfo) { + genBtcBlocksThenWait(1, 2250); + + var trade = getTrade(bobdaemon, tradeId); + logTrade(log, testInfo, "Bob's view before withdrawing funds to external wallet", trade); + + String toAddress = bitcoinCli.getNewBtcAddress(); + withdrawFunds(bobdaemon, tradeId, toAddress); + + genBtcBlocksThenWait(1, 2250); + + trade = getTrade(bobdaemon, tradeId); + EXPECTED_PROTOCOL_STATUS.setState(WITHDRAW_COMPLETED) + .setPhase(WITHDRAWN) + .setWithdrawn(true); + verifyExpectedProtocolStatus(trade); + logTrade(log, testInfo, "Bob's view after withdrawing funds to external wallet", trade); + log.info("{} Bob's current available balance: {} BTC", + testName(testInfo), + formatSatoshis(getBalance(bobdaemon))); } } From 2746b276744a6c282d96170326146ed298f1df97 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Tue, 27 Oct 2020 13:47:23 -0300 Subject: [PATCH 21/30] Fix apitest dummy payment acct init bug This commit fixes non-trade tests broken by the last refactoring. --- .../java/bisq/apitest/method/MethodTest.java | 8 ++--- .../method/RegisterDisputeAgentsTest.java | 12 +++----- .../method/offer/AbstractOfferTest.java | 6 ++++ .../offer/CreateOfferUsingFixedPriceTest.java | 23 +++++++-------- ...CreateOfferUsingMarketPriceMarginTest.java | 29 ++++++++----------- .../method/offer/ValidateCreateOfferTest.java | 4 +-- .../method/trade/AbstractTradeTest.java | 7 +++++ 7 files changed, 44 insertions(+), 45 deletions(-) diff --git a/apitest/src/test/java/bisq/apitest/method/MethodTest.java b/apitest/src/test/java/bisq/apitest/method/MethodTest.java index 64f6710c7e2..cf5b74c750c 100644 --- a/apitest/src/test/java/bisq/apitest/method/MethodTest.java +++ b/apitest/src/test/java/bisq/apitest/method/MethodTest.java @@ -41,8 +41,6 @@ import java.util.stream.Collectors; -import org.junit.jupiter.api.BeforeEach; - import static bisq.apitest.config.BisqAppConfig.alicedaemon; import static bisq.apitest.config.BisqAppConfig.bobdaemon; import static bisq.common.app.DevEnv.DEV_PRIVILEGE_PRIV_KEY; @@ -68,9 +66,11 @@ public class MethodTest extends ApiTestCase { protected PaymentAccount alicesDummyAcct; protected PaymentAccount bobsDummyAcct; - @BeforeEach - public void initDummyPaymentAccounts() { + protected final void initAlicesDummyPaymentAccount() { alicesDummyAcct = getDefaultPerfectDummyPaymentAccount(alicedaemon); + } + + protected final void initBobsDummyPaymentAccount() { bobsDummyAcct = getDefaultPerfectDummyPaymentAccount(bobdaemon); } diff --git a/apitest/src/test/java/bisq/apitest/method/RegisterDisputeAgentsTest.java b/apitest/src/test/java/bisq/apitest/method/RegisterDisputeAgentsTest.java index 9c875012080..379fbc3a004 100644 --- a/apitest/src/test/java/bisq/apitest/method/RegisterDisputeAgentsTest.java +++ b/apitest/src/test/java/bisq/apitest/method/RegisterDisputeAgentsTest.java @@ -56,8 +56,7 @@ public static void setUp() { @Test @Order(1) public void testRegisterArbitratorShouldThrowException() { - var req = - createRegisterDisputeAgentRequest(ARBITRATOR); + 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", @@ -67,8 +66,7 @@ public void testRegisterArbitratorShouldThrowException() { @Test @Order(2) public void testInvalidDisputeAgentTypeArgShouldThrowException() { - var req = - createRegisterDisputeAgentRequest("badagent"); + var req = createRegisterDisputeAgentRequest("badagent"); Throwable exception = assertThrows(StatusRuntimeException.class, () -> grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req)); assertEquals("INVALID_ARGUMENT: unknown dispute agent type 'badagent'", @@ -90,16 +88,14 @@ public void testInvalidRegistrationKeyArgShouldThrowException() { @Test @Order(4) public void testRegisterMediator() { - var req = - createRegisterDisputeAgentRequest(MEDIATOR); + var req = createRegisterDisputeAgentRequest(MEDIATOR); grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req); } @Test @Order(5) public void testRegisterRefundAgent() { - var req = - createRegisterDisputeAgentRequest(REFUND_AGENT); + var req = createRegisterDisputeAgentRequest(REFUND_AGENT); grpcStubs(arbdaemon).disputeAgentsService.registerDisputeAgent(req); } diff --git a/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java index bc233721d0f..a08e08d0bc3 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java @@ -36,6 +36,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind; import static bisq.apitest.config.BisqAppConfig.alicedaemon; @@ -64,6 +65,11 @@ public static void setUp() { startSupportingApps(); } + @BeforeEach + public void initDummyPaymentAccount() { + super.initAlicesDummyPaymentAccount(); + } + static void startSupportingApps() { try { // setUpScaffold(new String[]{"--supportingApps", "bitcoind,seednode,arbdaemon,alicedaemon,bobdaemon", "--enableBisqDebugging", "true"}); diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java index 739faf71e96..10ca6c9e4cb 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingFixedPriceTest.java @@ -28,7 +28,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; -import static bisq.apitest.config.BisqAppConfig.alicedaemon; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -37,12 +36,12 @@ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest { + @Test @Order(1) public void testCreateAUDBTCBuyOfferUsingFixedPrice16000() { - var paymentAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); var req = CreateOfferRequest.newBuilder() - .setPaymentAccountId(paymentAccount.getId()) + .setPaymentAccountId(alicesDummyAcct.getId()) .setDirection("buy") .setCurrencyCode("aud") .setAmount(10000000) @@ -61,7 +60,7 @@ public void testCreateAUDBTCBuyOfferUsingFixedPrice16000() { assertEquals(10000000, newOffer.getAmount()); assertEquals(10000000, newOffer.getMinAmount()); assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); - assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId()); assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("AUD", newOffer.getCounterCurrencyCode()); @@ -73,7 +72,7 @@ public void testCreateAUDBTCBuyOfferUsingFixedPrice16000() { assertEquals(10000000, newOffer.getAmount()); assertEquals(10000000, newOffer.getMinAmount()); assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); - assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId()); assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("AUD", newOffer.getCounterCurrencyCode()); } @@ -81,9 +80,8 @@ public void testCreateAUDBTCBuyOfferUsingFixedPrice16000() { @Test @Order(2) public void testCreateUSDBTCBuyOfferUsingFixedPrice100001234() { - var paymentAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); var req = CreateOfferRequest.newBuilder() - .setPaymentAccountId(paymentAccount.getId()) + .setPaymentAccountId(alicesDummyAcct.getId()) .setDirection("buy") .setCurrencyCode("usd") .setAmount(10000000) @@ -102,7 +100,7 @@ public void testCreateUSDBTCBuyOfferUsingFixedPrice100001234() { assertEquals(10000000, newOffer.getAmount()); assertEquals(10000000, newOffer.getMinAmount()); assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); - assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId()); assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("USD", newOffer.getCounterCurrencyCode()); @@ -114,7 +112,7 @@ public void testCreateUSDBTCBuyOfferUsingFixedPrice100001234() { assertEquals(10000000, newOffer.getAmount()); assertEquals(10000000, newOffer.getMinAmount()); assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); - assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId()); assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("USD", newOffer.getCounterCurrencyCode()); } @@ -122,9 +120,8 @@ public void testCreateUSDBTCBuyOfferUsingFixedPrice100001234() { @Test @Order(3) public void testCreateEURBTCSellOfferUsingFixedPrice95001234() { - var paymentAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); var req = CreateOfferRequest.newBuilder() - .setPaymentAccountId(paymentAccount.getId()) + .setPaymentAccountId(alicesDummyAcct.getId()) .setDirection("sell") .setCurrencyCode("eur") .setAmount(10000000) @@ -143,7 +140,7 @@ public void testCreateEURBTCSellOfferUsingFixedPrice95001234() { assertEquals(10000000, newOffer.getAmount()); assertEquals(10000000, newOffer.getMinAmount()); assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); - assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId()); assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("EUR", newOffer.getCounterCurrencyCode()); @@ -155,7 +152,7 @@ public void testCreateEURBTCSellOfferUsingFixedPrice95001234() { assertEquals(10000000, newOffer.getAmount()); assertEquals(10000000, newOffer.getMinAmount()); assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); - assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId()); assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("EUR", newOffer.getCounterCurrencyCode()); } diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java index f9d379131bb..b0c017baff0 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/CreateOfferUsingMarketPriceMarginTest.java @@ -31,7 +31,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; -import static bisq.apitest.config.BisqAppConfig.alicedaemon; import static bisq.common.util.MathUtils.scaleDownByPowerOf10; import static bisq.common.util.MathUtils.scaleUpByPowerOf10; import static java.lang.Math.abs; @@ -52,10 +51,9 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest { @Test @Order(1) public void testCreateUSDBTCBuyOffer5PctPriceMargin() { - var paymentAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); double priceMarginPctInput = 5.00; var req = CreateOfferRequest.newBuilder() - .setPaymentAccountId(paymentAccount.getId()) + .setPaymentAccountId(alicesDummyAcct.getId()) .setDirection("buy") .setCurrencyCode("usd") .setAmount(10000000) @@ -73,7 +71,7 @@ public void testCreateUSDBTCBuyOffer5PctPriceMargin() { assertEquals(10000000, newOffer.getAmount()); assertEquals(10000000, newOffer.getMinAmount()); assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); - assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId()); assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("USD", newOffer.getCounterCurrencyCode()); @@ -84,7 +82,7 @@ public void testCreateUSDBTCBuyOffer5PctPriceMargin() { assertEquals(10000000, newOffer.getAmount()); assertEquals(10000000, newOffer.getMinAmount()); assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); - assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId()); assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("USD", newOffer.getCounterCurrencyCode()); @@ -94,10 +92,9 @@ public void testCreateUSDBTCBuyOffer5PctPriceMargin() { @Test @Order(2) public void testCreateNZDBTCBuyOfferMinus2PctPriceMargin() { - var paymentAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); double priceMarginPctInput = -2.00; var req = CreateOfferRequest.newBuilder() - .setPaymentAccountId(paymentAccount.getId()) + .setPaymentAccountId(alicesDummyAcct.getId()) .setDirection("buy") .setCurrencyCode("nzd") .setAmount(10000000) @@ -115,7 +112,7 @@ public void testCreateNZDBTCBuyOfferMinus2PctPriceMargin() { assertEquals(10000000, newOffer.getAmount()); assertEquals(10000000, newOffer.getMinAmount()); assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); - assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId()); assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("NZD", newOffer.getCounterCurrencyCode()); @@ -126,7 +123,7 @@ public void testCreateNZDBTCBuyOfferMinus2PctPriceMargin() { assertEquals(10000000, newOffer.getAmount()); assertEquals(10000000, newOffer.getMinAmount()); assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); - assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId()); assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("NZD", newOffer.getCounterCurrencyCode()); @@ -136,10 +133,9 @@ public void testCreateNZDBTCBuyOfferMinus2PctPriceMargin() { @Test @Order(3) public void testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin() { - var paymentAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); double priceMarginPctInput = -1.5; var req = CreateOfferRequest.newBuilder() - .setPaymentAccountId(paymentAccount.getId()) + .setPaymentAccountId(alicesDummyAcct.getId()) .setDirection("sell") .setCurrencyCode("gbp") .setAmount(10000000) @@ -158,7 +154,7 @@ public void testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin() { assertEquals(10000000, newOffer.getAmount()); assertEquals(10000000, newOffer.getMinAmount()); assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); - assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId()); assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("GBP", newOffer.getCounterCurrencyCode()); @@ -169,7 +165,7 @@ public void testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin() { assertEquals(10000000, newOffer.getAmount()); assertEquals(10000000, newOffer.getMinAmount()); assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); - assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId()); assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("GBP", newOffer.getCounterCurrencyCode()); @@ -179,10 +175,9 @@ public void testCreateGBPBTCSellOfferMinus1Point5PctPriceMargin() { @Test @Order(4) public void testCreateBRLBTCSellOffer6Point55PctPriceMargin() { - var paymentAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); double priceMarginPctInput = 6.55; var req = CreateOfferRequest.newBuilder() - .setPaymentAccountId(paymentAccount.getId()) + .setPaymentAccountId(alicesDummyAcct.getId()) .setDirection("sell") .setCurrencyCode("brl") .setAmount(10000000) @@ -201,7 +196,7 @@ public void testCreateBRLBTCSellOffer6Point55PctPriceMargin() { assertEquals(10000000, newOffer.getAmount()); assertEquals(10000000, newOffer.getMinAmount()); assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); - assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId()); assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("BRL", newOffer.getCounterCurrencyCode()); @@ -212,7 +207,7 @@ public void testCreateBRLBTCSellOffer6Point55PctPriceMargin() { assertEquals(10000000, newOffer.getAmount()); assertEquals(10000000, newOffer.getMinAmount()); assertEquals(1500000, newOffer.getBuyerSecurityDeposit()); - assertEquals(paymentAccount.getId(), newOffer.getPaymentAccountId()); + assertEquals(alicesDummyAcct.getId(), newOffer.getPaymentAccountId()); assertEquals("BTC", newOffer.getBaseCurrencyCode()); assertEquals("BRL", newOffer.getCounterCurrencyCode()); diff --git a/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java index 785dc97fdcb..ecf09b1e421 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/ValidateCreateOfferTest.java @@ -30,7 +30,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; -import static bisq.apitest.config.BisqAppConfig.alicedaemon; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -41,9 +40,8 @@ public class ValidateCreateOfferTest extends AbstractOfferTest { @Test @Order(1) public void testAmtTooLargeShouldThrowException() { - var paymentAccount = getDefaultPerfectDummyPaymentAccount(alicedaemon); var req = CreateOfferRequest.newBuilder() - .setPaymentAccountId(paymentAccount.getId()) + .setPaymentAccountId(alicesDummyAcct.getId()) .setDirection("buy") .setCurrencyCode("usd") .setAmount(100000000000L) diff --git a/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java b/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java index 7e4c22e9689..22aee1d70dd 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java @@ -7,6 +7,7 @@ import org.slf4j.Logger; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.TestInfo; import static bisq.cli.TradeFormat.format; @@ -31,6 +32,12 @@ public static void clearExpectedPaymentStatusFlags() { EXPECTED_PROTOCOL_STATUS.init(); } + @BeforeEach + public void initDummyPaymentAccounts() { + super.initAlicesDummyPaymentAccount(); + super.initBobsDummyPaymentAccount(); + } + protected final TradeInfo takeAlicesOffer(String offerId, String paymentAccountId) { return bobStubs.tradesService.takeOffer(createTakeOfferRequest(offerId, paymentAccountId)).getTrade(); } From 027a7d5cd3bfaf4afefbd92612c67356a43d6353 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Tue, 27 Oct 2020 14:54:50 -0300 Subject: [PATCH 22/30] Stub out canceloffer api method The implementation will be added to CoreOffersService in the next PR. --- cli/src/main/java/bisq/cli/CliMain.java | 17 ++++++++++++++++- core/src/main/java/bisq/core/api/CoreApi.java | 4 ++++ .../java/bisq/core/api/CoreOffersService.java | 4 ++++ .../bisq/daemon/grpc/GrpcOffersService.java | 17 +++++++++++++++++ proto/src/main/proto/grpc.proto | 9 +++++++++ 5 files changed, 50 insertions(+), 1 deletion(-) diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java index cecaadf0d56..8639c95602f 100644 --- a/cli/src/main/java/bisq/cli/CliMain.java +++ b/cli/src/main/java/bisq/cli/CliMain.java @@ -17,6 +17,7 @@ package bisq.cli; +import bisq.proto.grpc.CancelOfferRequest; import bisq.proto.grpc.ConfirmPaymentReceivedRequest; import bisq.proto.grpc.ConfirmPaymentStartedRequest; import bisq.proto.grpc.CreateOfferRequest; @@ -74,6 +75,7 @@ public class CliMain { private enum Method { createoffer, + canceloffer, getoffer, getoffers, takeoffer, @@ -238,6 +240,18 @@ public static void run(String[] args) { out.println(formatOfferTable(singletonList(reply.getOffer()), currencyCode)); return; } + case canceloffer: { + if (nonOptionArgs.size() < 2) + throw new IllegalArgumentException("incorrect parameter count, expecting offer id"); + + var offerId = nonOptionArgs.get(1); + var request = CancelOfferRequest.newBuilder() + .setId(offerId) + .build(); + offersService.cancelOffer(request); + out.println("offer canceled and removed from offer book"); + return; + } case getoffer: { if (nonOptionArgs.size() < 2) throw new IllegalArgumentException("incorrect parameter count, expecting offer id"); @@ -246,7 +260,7 @@ public static void run(String[] args) { var request = GetOfferRequest.newBuilder() .setId(offerId) .build(); - var reply = offersService.getOffer(request); + var reply =offersService.getOffer(request); out.println(formatOfferTable(singletonList(reply.getOffer()), reply.getOffer().getCounterCurrencyCode())); return; @@ -475,6 +489,7 @@ private static void printHelp(OptionParser parser, PrintStream stream) { stream.format(rowFormat, "", "amount (btc), min amount, use mkt based price, \\", ""); stream.format(rowFormat, "", "fixed price (btc) | mkt price margin (%), \\", ""); stream.format(rowFormat, "", "security deposit (%)", ""); + stream.format(rowFormat, "canceloffer", "offer id", "Cancel offer with id"); stream.format(rowFormat, "getoffer", "offer id", "Get current offer with id"); stream.format(rowFormat, "getoffers", "buy | sell, currency code", "Get current offers"); stream.format(rowFormat, "takeoffer", "offer id", "Take offer with id"); diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index 001bf4773bc..7cfcc5ce152 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -142,6 +142,10 @@ public Offer editOffer(String offerId, paymentAccount); } + public void cancelOffer(String id) { + coreOffersService.cancelOffer(id); + } + /////////////////////////////////////////////////////////////////////////////////////////// // PaymentAccounts /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/api/CoreOffersService.java b/core/src/main/java/bisq/core/api/CoreOffersService.java index da07677f1b2..1a35af4ea2e 100644 --- a/core/src/main/java/bisq/core/api/CoreOffersService.java +++ b/core/src/main/java/bisq/core/api/CoreOffersService.java @@ -160,6 +160,10 @@ Offer editOffer(String offerId, paymentAccount); } + void cancelOffer(String id) { + log.info("TODO"); + } + private void placeOffer(Offer offer, double buyerSecurityDeposit, boolean useSavingsWallet, diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java index d7785935563..f03155d2f8d 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcOffersService.java @@ -21,6 +21,8 @@ import bisq.core.api.model.OfferInfo; import bisq.core.offer.Offer; +import bisq.proto.grpc.CancelOfferReply; +import bisq.proto.grpc.CancelOfferRequest; import bisq.proto.grpc.CreateOfferReply; import bisq.proto.grpc.CreateOfferRequest; import bisq.proto.grpc.GetOfferReply; @@ -114,4 +116,19 @@ public void createOffer(CreateOfferRequest req, throw ex; } } + + @Override + public void cancelOffer(CancelOfferRequest req, + StreamObserver responseObserver) { + try { + coreApi.cancelOffer(req.getId()); + var reply = CancelOfferReply.newBuilder().build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } catch (IllegalStateException | IllegalArgumentException cause) { + var ex = new StatusRuntimeException(Status.UNKNOWN.withDescription(cause.getMessage())); + responseObserver.onError(ex); + throw ex; + } + } } diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index 006c6a6f111..d9e0a3973d8 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -51,6 +51,8 @@ service Offers { } rpc CreateOffer (CreateOfferRequest) returns (CreateOfferReply) { } + rpc CancelOffer (CancelOfferRequest) returns (CancelOfferReply) { + } } message GetOfferRequest { @@ -85,6 +87,13 @@ message CreateOfferReply { OfferInfo offer = 1; } +message CancelOfferRequest { + string id = 1; +} + +message CancelOfferReply { +} + message OfferInfo { string id = 1; string direction = 2; From 0f1d4f8ac38d0591c2e4c1b10608938b36c8619a Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Tue, 27 Oct 2020 15:03:36 -0300 Subject: [PATCH 23/30] Fix typo --- cli/src/main/java/bisq/cli/CliMain.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main/java/bisq/cli/CliMain.java b/cli/src/main/java/bisq/cli/CliMain.java index 8639c95602f..ec0e5e71bb6 100644 --- a/cli/src/main/java/bisq/cli/CliMain.java +++ b/cli/src/main/java/bisq/cli/CliMain.java @@ -260,7 +260,7 @@ public static void run(String[] args) { var request = GetOfferRequest.newBuilder() .setId(offerId) .build(); - var reply =offersService.getOffer(request); + var reply = offersService.getOffer(request); out.println(formatOfferTable(singletonList(reply.getOffer()), reply.getOffer().getCounterCurrencyCode())); return; From b38507c6e68aca9176da48515633dcc613e80ddc Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Tue, 27 Oct 2020 15:31:57 -0300 Subject: [PATCH 24/30] Implement api method 'canceloffer' --- core/src/main/java/bisq/core/api/CoreOffersService.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/bisq/core/api/CoreOffersService.java b/core/src/main/java/bisq/core/api/CoreOffersService.java index 1a35af4ea2e..6d8641c0579 100644 --- a/core/src/main/java/bisq/core/api/CoreOffersService.java +++ b/core/src/main/java/bisq/core/api/CoreOffersService.java @@ -161,7 +161,13 @@ Offer editOffer(String offerId, } void cancelOffer(String id) { - log.info("TODO"); + Offer offer = getOffer(id); + openOfferManager.removeOffer(offer, + () -> { + }, + errorMessage -> { + throw new IllegalStateException(errorMessage); + }); } private void placeOffer(Offer offer, From 91a2e2ce1ff0e900ac0ad91b92da1de420dc3171 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Tue, 27 Oct 2020 19:06:59 -0300 Subject: [PATCH 25/30] Add canceloffer test --- .../java/bisq/apitest/method/MethodTest.java | 11 +++ .../method/offer/AbstractOfferTest.java | 5 ++ .../apitest/method/offer/CancelOfferTest.java | 81 +++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 apitest/src/test/java/bisq/apitest/method/offer/CancelOfferTest.java diff --git a/apitest/src/test/java/bisq/apitest/method/MethodTest.java b/apitest/src/test/java/bisq/apitest/method/MethodTest.java index cf5b74c750c..dcf438f245e 100644 --- a/apitest/src/test/java/bisq/apitest/method/MethodTest.java +++ b/apitest/src/test/java/bisq/apitest/method/MethodTest.java @@ -17,6 +17,7 @@ package bisq.apitest.method; +import bisq.proto.grpc.CancelOfferRequest; import bisq.proto.grpc.ConfirmPaymentReceivedRequest; import bisq.proto.grpc.ConfirmPaymentStartedRequest; import bisq.proto.grpc.CreatePaymentAccountRequest; @@ -112,6 +113,10 @@ protected final GetOfferRequest createGetOfferRequest(String offerId) { return GetOfferRequest.newBuilder().setId(offerId).build(); } + protected final CancelOfferRequest createCancelOfferRequest(String offerId) { + return CancelOfferRequest.newBuilder().setId(offerId).build(); + } + protected final TakeOfferRequest createTakeOfferRequest(String offerId, String paymentAccountId) { return TakeOfferRequest.newBuilder().setOfferId(offerId).setPaymentAccountId(paymentAccountId).build(); } @@ -202,6 +207,12 @@ protected final OfferInfo getOffer(BisqAppConfig bisqAppConfig, String offerId) return grpcStubs(bisqAppConfig).offersService.getOffer(req).getOffer(); } + @SuppressWarnings("ResultOfMethodCallIgnored") + protected final void cancelOffer(BisqAppConfig bisqAppConfig, String offerId) { + var req = createCancelOfferRequest(offerId); + grpcStubs(bisqAppConfig).offersService.cancelOffer(req); + } + protected final TradeInfo getTrade(BisqAppConfig bisqAppConfig, String tradeId) { var req = createGetTradeRequest(tradeId); return grpcStubs(bisqAppConfig).tradesService.getTrade(req).getTrade(); diff --git a/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java index a08e08d0bc3..b1bae6fbd86 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java @@ -123,6 +123,11 @@ protected final OfferInfo getOffer(String offerId) { return aliceStubs.offersService.getOffer(createGetOfferRequest(offerId)).getOffer(); } + @SuppressWarnings("ResultOfMethodCallIgnored") + protected final void cancelOffer(GrpcStubs grpcStubs, String offerId) { + grpcStubs.offersService.cancelOffer(createCancelOfferRequest(offerId)); + } + protected final OfferInfo getMostRecentOffer(GrpcStubs grpcStubs, String direction, String currencyCode) { List offerInfoList = getOffersSortedByDate(grpcStubs, direction, currencyCode); if (offerInfoList.isEmpty()) diff --git a/apitest/src/test/java/bisq/apitest/method/offer/CancelOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/CancelOfferTest.java new file mode 100644 index 00000000000..6affb9ac38a --- /dev/null +++ b/apitest/src/test/java/bisq/apitest/method/offer/CancelOfferTest.java @@ -0,0 +1,81 @@ +/* + * 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.offer; + +import bisq.core.btc.wallet.Restrictions; + +import bisq.proto.grpc.CreateOfferRequest; +import bisq.proto.grpc.OfferInfo; + +import java.util.List; + +import lombok.extern.slf4j.Slf4j; + +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +@Slf4j +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class CancelOfferTest extends AbstractOfferTest { + + private static final int MAX_OFFERS = 3; + + @Test + @Order(1) + public void testCancelOffer() { + var req = CreateOfferRequest.newBuilder() + .setPaymentAccountId(alicesDummyAcct.getId()) + .setDirection("buy") + .setCurrencyCode("cad") + .setAmount(10000000) + .setMinAmount(10000000) + .setUseMarketBasedPrice(true) + .setMarketPriceMargin(0.00) + .setPrice("0") + .setBuyerSecurityDeposit(Restrictions.getDefaultBuyerSecurityDepositAsPercent()) + .build(); + + // Create some offers. + for (int i = 1; i <= MAX_OFFERS; i++) { + //noinspection ResultOfMethodCallIgnored + aliceStubs.offersService.createOffer(req); + // Wait for Alice's AddToOfferBook task. + // Wait times vary; my logs show >= 2 second delay. + sleep(2500); + } + + List offers = getOffersSortedByDate(aliceStubs, "buy", "cad"); + assertEquals(MAX_OFFERS, offers.size()); + + // Cancel the offers, checking the open offer count after each offer removal. + for (int i = 1; i <= MAX_OFFERS; i++) { + cancelOffer(aliceStubs, offers.remove(0).getId()); + assertEquals(MAX_OFFERS - i, getOpenOffersCount(aliceStubs, "buy", "cad")); + } + + sleep(1000); // wait for offer removal + + offers = getOffersSortedByDate(aliceStubs, "buy", "cad"); + assertEquals(0, offers.size()); + } +} From 8e51e1e495d79470defae2c9877f3a8d43cb095f Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Wed, 28 Oct 2020 17:21:04 -0300 Subject: [PATCH 26/30] Upgrade jupiterVersion = '5.7.0' This upgrades jupiter for all subprojects using this library, but it does not affect released code. --- build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 6007dd14475..57624f26fe5 100644 --- a/build.gradle +++ b/build.gradle @@ -55,7 +55,7 @@ configure(subprojects) { joptVersion = '5.0.4' jsonsimpleVersion = '1.1.1' junitVersion = '4.12' - jupiterVersion = '5.3.2' + jupiterVersion = '5.7.0' kotlinVersion = '1.3.41' knowmXchangeVersion = '4.4.2' langVersion = '3.8' @@ -658,12 +658,12 @@ configure(project(':apitest')) { compileOnly "javax.annotation:javax.annotation-api:$javaxAnnotationVersion" annotationProcessor "org.projectlombok:lombok:$lombokVersion" - testCompile "org.junit.jupiter:junit-jupiter-api:5.6.2" - testCompile "org.junit.jupiter:junit-jupiter-params:5.6.2" + testImplementation "org.junit.jupiter:junit-jupiter-api:$jupiterVersion" + testImplementation "org.junit.jupiter:junit-jupiter-params:$jupiterVersion" + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$jupiterVersion") testAnnotationProcessor "org.projectlombok:lombok:$lombokVersion" testCompileOnly "org.projectlombok:lombok:$lombokVersion" testRuntime "javax.annotation:javax.annotation-api:$javaxAnnotationVersion" - testRuntime("org.junit.jupiter:junit-jupiter-engine:5.6.2") } } From d6524bf46d5e8a9960c1c992a12a106be8877401 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Wed, 28 Oct 2020 17:39:50 -0300 Subject: [PATCH 27/30] Improve apitest cmd line console logging - Show full stack traces in console - Do use previously cached test outputs - Do not show skipped test name in console (too noisy) - Show test run summary, including number of skipped tests - Show note about skipped test info in the html report - Show link to html report on test SUCCESS --- build.gradle | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 57624f26fe5..56ede17a7b1 100644 --- a/build.gradle +++ b/build.gradle @@ -627,8 +627,31 @@ configure(project(':apitest')) { test { useJUnitPlatform() + outputs.upToDateWhen { false } // Don't use previously cached test outputs. testLogging { - events "passed", "skipped", "failed" + showStackTraces = true // Show full stack traces in the console. + exceptionFormat = "full" + // Show passed & failed tests, and anything printed to stderr by the tests in the console. + // Do not show skipped tests in the console; they are shown in the html report. + events "passed", "failed", "standardError" + } + + afterSuite { desc, result -> + if (!desc.parent) { + println("${result.resultType} " + + "[${result.testCount} tests, " + + "${result.successfulTestCount} passed, " + + "${result.failedTestCount} failed, " + + "${result.skippedTestCount} skipped] html report contains skipped test info") + + // Show report link if all tests passed in case you want to see more detail, stdout, skipped, etc. + if(result.resultType == TestResult.ResultType.SUCCESS) { + DirectoryReport htmlReport = getReports().getHtml() + String reportUrl = new org.gradle.internal.logging.ConsoleRenderer() + .asClickableFileUrl(htmlReport.getEntryPoint()) + println("REPORT " + reportUrl) + } + } } } From 2a052035190081c51d561e4e431ffca2b68987ec Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Wed, 28 Oct 2020 17:46:30 -0300 Subject: [PATCH 28/30] Remove dead code --- .../src/main/java/bisq/apitest/linux/AbstractLinuxProcess.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/apitest/src/main/java/bisq/apitest/linux/AbstractLinuxProcess.java b/apitest/src/main/java/bisq/apitest/linux/AbstractLinuxProcess.java index 4687477e956..770df37ede2 100644 --- a/apitest/src/main/java/bisq/apitest/linux/AbstractLinuxProcess.java +++ b/apitest/src/main/java/bisq/apitest/linux/AbstractLinuxProcess.java @@ -70,10 +70,8 @@ public boolean hasShutdownExceptions() { @Override public void logExceptions(List exceptions, org.slf4j.Logger log) { - StringBuilder errorBuilder = new StringBuilder(); for (Throwable t : exceptions) { log.error("", t); - errorBuilder.append(t.getMessage()).append("\n"); } } From f61f148db194f38b16b299027a82289eea6d78ca Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Wed, 28 Oct 2020 17:59:15 -0300 Subject: [PATCH 29/30] Refactor api test fixture setup - Remove dead code from AbstractLinuxProcess. - Make default dummy accts static in MethodTest. - Simplify gRPC stub creation, btc block generation, dispute agent registration, and dummy acct initialization in test case startup. - Make ExpectedProtocolStatus visible to scenario test pkg. --- .../java/bisq/apitest/method/MethodTest.java | 47 ++++++++--- .../method/offer/AbstractOfferTest.java | 30 ++----- .../method/trade/AbstractTradeTest.java | 79 +------------------ .../method/trade/ExpectedProtocolStatus.java | 69 ++++++++++++++++ 4 files changed, 115 insertions(+), 110 deletions(-) create mode 100644 apitest/src/test/java/bisq/apitest/method/trade/ExpectedProtocolStatus.java diff --git a/apitest/src/test/java/bisq/apitest/method/MethodTest.java b/apitest/src/test/java/bisq/apitest/method/MethodTest.java index dcf438f245e..6b21d76fb4a 100644 --- a/apitest/src/test/java/bisq/apitest/method/MethodTest.java +++ b/apitest/src/test/java/bisq/apitest/method/MethodTest.java @@ -43,11 +43,14 @@ import java.util.stream.Collectors; import static bisq.apitest.config.BisqAppConfig.alicedaemon; +import static bisq.apitest.config.BisqAppConfig.arbdaemon; import static bisq.apitest.config.BisqAppConfig.bobdaemon; import static bisq.common.app.DevEnv.DEV_PRIVILEGE_PRIV_KEY; import static bisq.core.payment.payload.PaymentMethod.PERFECT_MONEY; +import static java.util.Arrays.stream; import static java.util.Comparator.comparing; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; @@ -64,15 +67,39 @@ public class MethodTest extends ApiTestCase { protected static GrpcStubs aliceStubs; protected static GrpcStubs bobStubs; - protected PaymentAccount alicesDummyAcct; - protected PaymentAccount bobsDummyAcct; - - protected final void initAlicesDummyPaymentAccount() { - alicesDummyAcct = getDefaultPerfectDummyPaymentAccount(alicedaemon); - } - - protected final void initBobsDummyPaymentAccount() { - bobsDummyAcct = getDefaultPerfectDummyPaymentAccount(bobdaemon); + protected static PaymentAccount alicesDummyAcct; + protected static PaymentAccount bobsDummyAcct; + + public static void startSupportingApps(boolean registerDisputeAgents, + boolean generateBtcBlock, + Enum... supportingApps) { + try { + // To run Bisq apps in debug mode, use the other setUpScaffold method: + // setUpScaffold(new String[]{"--supportingApps", "bitcoind,seednode,arbdaemon,alicedaemon,bobdaemon", + // "--enableBisqDebugging", "true"}); + setUpScaffold(supportingApps); + if (registerDisputeAgents) { + registerDisputeAgents(arbdaemon); + } + + if (stream(supportingApps).map(Enum::name).anyMatch(name -> name.equals(alicedaemon.name()))) { + aliceStubs = grpcStubs(alicedaemon); + alicesDummyAcct = getDefaultPerfectDummyPaymentAccount(alicedaemon); + } + + if (stream(supportingApps).map(Enum::name).anyMatch(name -> name.equals(bobdaemon.name()))) { + bobStubs = grpcStubs(bobdaemon); + bobsDummyAcct = getDefaultPerfectDummyPaymentAccount(bobdaemon); + } + + // Generate 1 regtest block for alice's and/or bob's wallet to + // show 10 BTC balance, and allow time for daemons parse the new block. + if (generateBtcBlock) + genBtcBlocksThenWait(1, 1500); + + } catch (Exception ex) { + fail(ex); + } } // Convenience methods for building gRPC request objects @@ -185,7 +212,7 @@ protected final CreatePaymentAccountRequest createCreatePerfectMoneyPaymentAccou .build(); } - protected final PaymentAccount getDefaultPerfectDummyPaymentAccount(BisqAppConfig bisqAppConfig) { + protected static PaymentAccount getDefaultPerfectDummyPaymentAccount(BisqAppConfig bisqAppConfig) { var req = GetPaymentAccountsRequest.newBuilder().build(); var paymentAccountsService = grpcStubs(bisqAppConfig).paymentAccountsService; PaymentAccount paymentAccount = paymentAccountsService.getPaymentAccounts(req) diff --git a/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java b/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java index b1bae6fbd86..fe9a98aaaae 100644 --- a/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java +++ b/apitest/src/test/java/bisq/apitest/method/offer/AbstractOfferTest.java @@ -36,7 +36,6 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; import static bisq.apitest.Scaffold.BitcoinCoreApp.bitcoind; import static bisq.apitest.config.BisqAppConfig.alicedaemon; @@ -62,28 +61,13 @@ public abstract class AbstractOfferTest extends MethodTest { @BeforeAll public static void setUp() { - startSupportingApps(); - } - - @BeforeEach - public void initDummyPaymentAccount() { - super.initAlicesDummyPaymentAccount(); - } - - static void startSupportingApps() { - try { - // setUpScaffold(new String[]{"--supportingApps", "bitcoind,seednode,arbdaemon,alicedaemon,bobdaemon", "--enableBisqDebugging", "true"}); - setUpScaffold(bitcoind, seednode, arbdaemon, alicedaemon, bobdaemon); - registerDisputeAgents(arbdaemon); - aliceStubs = grpcStubs(alicedaemon); - bobStubs = grpcStubs(bobdaemon); - - // Generate 1 regtest block for alice's wallet to show 10 BTC balance, - // and give alicedaemon time to parse the new block. - genBtcBlocksThenWait(1, 1500); - } catch (Exception ex) { - fail(ex); - } + startSupportingApps(true, + true, + bitcoind, + seednode, + arbdaemon, + alicedaemon, + bobdaemon); } protected final OfferInfo createAliceOffer(PaymentAccount paymentAccount, diff --git a/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java b/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java index 22aee1d70dd..e8537206dbf 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java @@ -1,13 +1,10 @@ package bisq.apitest.method.trade; -import bisq.core.trade.Trade; - import bisq.proto.grpc.TradeInfo; import org.slf4j.Logger; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.TestInfo; import static bisq.cli.TradeFormat.format; @@ -20,24 +17,16 @@ public class AbstractTradeTest extends AbstractOfferTest { - // A test fixture encapsulating expected trade protocol status. - // ExpectedProtocolStatus.init should be called before any @Test begins. - protected static final ExpectedProtocolStatus EXPECTED_PROTOCOL_STATUS = new ExpectedProtocolStatus(); + public static final ExpectedProtocolStatus EXPECTED_PROTOCOL_STATUS = new ExpectedProtocolStatus(); // A Trade ID cache for use in @Test sequences. protected static String tradeId; @BeforeAll - public static void clearExpectedPaymentStatusFlags() { + public static void initStaticFixtures() { EXPECTED_PROTOCOL_STATUS.init(); } - @BeforeEach - public void initDummyPaymentAccounts() { - super.initAlicesDummyPaymentAccount(); - super.initBobsDummyPaymentAccount(); - } - protected final TradeInfo takeAlicesOffer(String offerId, String paymentAccountId) { return bobStubs.tradesService.takeOffer(createTakeOfferRequest(offerId, paymentAccountId)).getTrade(); } @@ -68,68 +57,4 @@ protected final void logTrade(Logger log, description.toUpperCase(), format(trade))); } - - @SuppressWarnings("UnusedReturnValue") - static class ExpectedProtocolStatus { - Trade.State state; - Trade.Phase phase; - boolean isDepositPublished; - boolean isDepositConfirmed; - boolean isFiatSent; - boolean isFiatReceived; - boolean isPayoutPublished; - boolean isWithdrawn; - - ExpectedProtocolStatus setState(Trade.State state) { - this.state = state; - return this; - } - - ExpectedProtocolStatus setPhase(Trade.Phase phase) { - this.phase = phase; - return this; - } - - ExpectedProtocolStatus setDepositPublished(boolean depositPublished) { - isDepositPublished = depositPublished; - return this; - } - - ExpectedProtocolStatus setDepositConfirmed(boolean depositConfirmed) { - isDepositConfirmed = depositConfirmed; - return this; - } - - ExpectedProtocolStatus setFiatSent(boolean fiatSent) { - isFiatSent = fiatSent; - return this; - } - - ExpectedProtocolStatus setFiatReceived(boolean fiatReceived) { - isFiatReceived = fiatReceived; - return this; - } - - ExpectedProtocolStatus setPayoutPublished(boolean payoutPublished) { - isPayoutPublished = payoutPublished; - return this; - } - - ExpectedProtocolStatus setWithdrawn(boolean withdrawn) { - isWithdrawn = withdrawn; - return this; - } - - @SuppressWarnings("unused") - void init() { - state = null; - phase = null; - isDepositPublished = false; - isDepositConfirmed = false; - isFiatSent = false; - isFiatReceived = false; - isPayoutPublished = false; - isWithdrawn = false; - } - } } diff --git a/apitest/src/test/java/bisq/apitest/method/trade/ExpectedProtocolStatus.java b/apitest/src/test/java/bisq/apitest/method/trade/ExpectedProtocolStatus.java new file mode 100644 index 00000000000..63655585947 --- /dev/null +++ b/apitest/src/test/java/bisq/apitest/method/trade/ExpectedProtocolStatus.java @@ -0,0 +1,69 @@ +package bisq.apitest.method.trade; + +import bisq.core.trade.Trade; + +/** + * A test fixture encapsulating expected trade protocol status. + * Status flags should be cleared via init() before starting a new trade protocol. + */ +public class ExpectedProtocolStatus { + Trade.State state; + Trade.Phase phase; + boolean isDepositPublished; + boolean isDepositConfirmed; + boolean isFiatSent; + boolean isFiatReceived; + boolean isPayoutPublished; + boolean isWithdrawn; + + public ExpectedProtocolStatus setState(Trade.State state) { + this.state = state; + return this; + } + + public ExpectedProtocolStatus setPhase(Trade.Phase phase) { + this.phase = phase; + return this; + } + + public ExpectedProtocolStatus setDepositPublished(boolean depositPublished) { + isDepositPublished = depositPublished; + return this; + } + + public ExpectedProtocolStatus setDepositConfirmed(boolean depositConfirmed) { + isDepositConfirmed = depositConfirmed; + return this; + } + + public ExpectedProtocolStatus setFiatSent(boolean fiatSent) { + isFiatSent = fiatSent; + return this; + } + + public ExpectedProtocolStatus setFiatReceived(boolean fiatReceived) { + isFiatReceived = fiatReceived; + return this; + } + + public ExpectedProtocolStatus setPayoutPublished(boolean payoutPublished) { + isPayoutPublished = payoutPublished; + return this; + } + + public ExpectedProtocolStatus setWithdrawn(boolean withdrawn) { + isWithdrawn = withdrawn; + return this; + } + + public void init() { + state = null; + phase = null; + isDepositPublished = false; + isDepositConfirmed = false; + isFiatSent = false; + isFiatReceived = false; + isPayoutPublished = false; + isWithdrawn = false; + } +} From e8e55d2286ec8063361c3ece281883648e6c6ba9 Mon Sep 17 00:00:00 2001 From: ghubstan <36207203+ghubstan@users.noreply.github.com> Date: Wed, 28 Oct 2020 18:01:49 -0300 Subject: [PATCH 30/30] Remove redundant ScenarioTest superclass --- .../scenario/FundWalletScenarioTest.java | 6 +++- .../bisq/apitest/scenario/ScenarioTest.java | 28 ------------------- 2 files changed, 5 insertions(+), 29 deletions(-) delete mode 100644 apitest/src/test/java/bisq/apitest/scenario/ScenarioTest.java diff --git a/apitest/src/test/java/bisq/apitest/scenario/FundWalletScenarioTest.java b/apitest/src/test/java/bisq/apitest/scenario/FundWalletScenarioTest.java index 75977949c33..4b7d40f516c 100644 --- a/apitest/src/test/java/bisq/apitest/scenario/FundWalletScenarioTest.java +++ b/apitest/src/test/java/bisq/apitest/scenario/FundWalletScenarioTest.java @@ -33,9 +33,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; + + +import bisq.apitest.method.MethodTest; + @Slf4j @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -public class FundWalletScenarioTest extends ScenarioTest { +public class FundWalletScenarioTest extends MethodTest { @BeforeAll public static void setUp() { diff --git a/apitest/src/test/java/bisq/apitest/scenario/ScenarioTest.java b/apitest/src/test/java/bisq/apitest/scenario/ScenarioTest.java deleted file mode 100644 index 9750b2ed9d6..00000000000 --- a/apitest/src/test/java/bisq/apitest/scenario/ScenarioTest.java +++ /dev/null @@ -1,28 +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.scenario; - -import lombok.extern.slf4j.Slf4j; - - - -import bisq.apitest.method.MethodTest; - -@Slf4j -public class ScenarioTest extends MethodTest { -}