mirror of
https://github.com/bisq-network/bisq.git
synced 2025-01-18 05:12:42 +01:00
Add API methods 'failtrade', 'unfailtrade'
Prerequisite for next PR: Add API method 'gettrades' The `gettrades` method will show 'open', 'closed', and 'failed' trades. Users already needed to be able to fail and unfail trades for the same reasons they do in the UI. API test cases will need to be able to fail and unfail trades to check correct behavior of 'gettrades' method. Based on branch `rename-keepfunds2closetrade`.
This commit is contained in:
parent
905841b2b2
commit
7690ffd953
@ -8,6 +8,8 @@ import java.util.function.Supplier;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
|
||||
@ -36,6 +38,7 @@ public class AbstractTradeTest extends AbstractOfferTest {
|
||||
public static final ExpectedProtocolStatus EXPECTED_PROTOCOL_STATUS = new ExpectedProtocolStatus();
|
||||
|
||||
// A Trade ID cache for use in @Test sequences.
|
||||
@Getter
|
||||
protected static String tradeId;
|
||||
|
||||
protected final Supplier<Integer> maxTradeStateAndPhaseChecks = () -> isLongRunningTest ? 10 : 2;
|
||||
|
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.apitest.method.trade;
|
||||
|
||||
import io.grpc.StatusRuntimeException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
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 java.lang.String.format;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
|
||||
|
||||
import bisq.apitest.method.offer.AbstractOfferTest;
|
||||
|
||||
@Disabled
|
||||
@Slf4j
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class FailUnfailTradeTest extends AbstractTradeTest {
|
||||
|
||||
@BeforeAll
|
||||
public static void setUp() {
|
||||
AbstractOfferTest.setUp();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
EXPECTED_PROTOCOL_STATUS.init();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void testFailAndUnFailBuyBTCTrade(final TestInfo testInfo) {
|
||||
TakeBuyBTCOfferTest test = new TakeBuyBTCOfferTest();
|
||||
test.testTakeAlicesBuyOffer(testInfo);
|
||||
|
||||
var tradeId = test.getTradeId();
|
||||
aliceClient.failTrade(tradeId);
|
||||
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getTrade(tradeId));
|
||||
String expectedExceptionMessage = format("INVALID_ARGUMENT: trade with id '%s' not found", tradeId);
|
||||
assertEquals(expectedExceptionMessage, exception.getMessage());
|
||||
|
||||
try {
|
||||
aliceClient.unFailTrade(tradeId);
|
||||
aliceClient.getTrade(tradeId); //Throws ex if trade is still failed.
|
||||
} catch (Exception ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void testFailAndUnFailSellBTCTrade(final TestInfo testInfo) {
|
||||
TakeSellBTCOfferTest test = new TakeSellBTCOfferTest();
|
||||
test.testTakeAlicesSellOffer(testInfo);
|
||||
|
||||
var tradeId = test.getTradeId();
|
||||
aliceClient.failTrade(tradeId);
|
||||
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getTrade(tradeId));
|
||||
String expectedExceptionMessage = format("INVALID_ARGUMENT: trade with id '%s' not found", tradeId);
|
||||
assertEquals(expectedExceptionMessage, exception.getMessage());
|
||||
|
||||
try {
|
||||
aliceClient.unFailTrade(tradeId);
|
||||
aliceClient.getTrade(tradeId); //Throws ex if trade is still failed.
|
||||
} catch (Exception ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
public void testFailAndUnFailBuyXmrTrade(final TestInfo testInfo) {
|
||||
TakeBuyXMROfferTest test = new TakeBuyXMROfferTest();
|
||||
test.createXmrPaymentAccounts();
|
||||
test.testTakeAlicesSellBTCForXMROffer(testInfo);
|
||||
|
||||
var tradeId = test.getTradeId();
|
||||
aliceClient.failTrade(tradeId);
|
||||
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getTrade(tradeId));
|
||||
String expectedExceptionMessage = format("INVALID_ARGUMENT: trade with id '%s' not found", tradeId);
|
||||
assertEquals(expectedExceptionMessage, exception.getMessage());
|
||||
|
||||
try {
|
||||
aliceClient.unFailTrade(tradeId);
|
||||
aliceClient.getTrade(tradeId); //Throws ex if trade is still failed.
|
||||
} catch (Exception ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
public void testFailAndUnFailTakeSellXMRTrade(final TestInfo testInfo) {
|
||||
TakeSellXMROfferTest test = new TakeSellXMROfferTest();
|
||||
test.createXmrPaymentAccounts();
|
||||
test.testTakeAlicesBuyBTCForXMROffer(testInfo);
|
||||
|
||||
var tradeId = test.getTradeId();
|
||||
aliceClient.failTrade(tradeId);
|
||||
|
||||
Throwable exception = assertThrows(StatusRuntimeException.class, () -> aliceClient.getTrade(tradeId));
|
||||
String expectedExceptionMessage = format("INVALID_ARGUMENT: trade with id '%s' not found", tradeId);
|
||||
assertEquals(expectedExceptionMessage, exception.getMessage());
|
||||
|
||||
try {
|
||||
aliceClient.unFailTrade(tradeId);
|
||||
aliceClient.getTrade(tradeId); //Throws ex if trade is still failed.
|
||||
} catch (Exception ex) {
|
||||
fail(ex);
|
||||
}
|
||||
}
|
||||
}
|
@ -30,6 +30,7 @@ import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import bisq.apitest.method.trade.AbstractTradeTest;
|
||||
import bisq.apitest.method.trade.BsqSwapTradeTest;
|
||||
import bisq.apitest.method.trade.FailUnfailTradeTest;
|
||||
import bisq.apitest.method.trade.TakeBuyBSQOfferTest;
|
||||
import bisq.apitest.method.trade.TakeBuyBTCOfferTest;
|
||||
import bisq.apitest.method.trade.TakeBuyBTCOfferWithNationalBankAcctTest;
|
||||
@ -130,4 +131,14 @@ public class TradeTest extends AbstractTradeTest {
|
||||
test.testBobTakesBsqSwapOffer();
|
||||
test.testGetBalancesAfterTrade();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(9)
|
||||
public void testFailUnfailTrade(final TestInfo testInfo) {
|
||||
FailUnfailTradeTest test = new FailUnfailTradeTest();
|
||||
test.testFailAndUnFailBuyBTCTrade(testInfo);
|
||||
test.testFailAndUnFailSellBTCTrade(testInfo);
|
||||
test.testFailAndUnFailBuyXmrTrade(testInfo);
|
||||
test.testFailAndUnFailTakeSellXMRTrade(testInfo);
|
||||
}
|
||||
}
|
||||
|
@ -559,6 +559,28 @@ public class CliMain {
|
||||
paymentMethods.forEach(p -> out.println(p.getId()));
|
||||
return;
|
||||
}
|
||||
case failtrade: {
|
||||
var opts = new GetTradeOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var tradeId = opts.getTradeId();
|
||||
client.failTrade(tradeId);
|
||||
out.printf("open trade %s changed to failed trade%n", tradeId);
|
||||
return;
|
||||
}
|
||||
case unfailtrade: {
|
||||
var opts = new GetTradeOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
out.println(client.getMethodHelp(method));
|
||||
return;
|
||||
}
|
||||
var tradeId = opts.getTradeId();
|
||||
client.unFailTrade(tradeId);
|
||||
out.printf("failed trade %s changed to open trade%n", tradeId);
|
||||
return;
|
||||
}
|
||||
case getpaymentacctform: {
|
||||
var opts = new GetPaymentAcctFormOptionParser(args).parse();
|
||||
if (opts.isForHelp()) {
|
||||
@ -870,6 +892,10 @@ public class CliMain {
|
||||
"Withdraw received trade funds to external wallet address");
|
||||
stream.format(rowFormat, "", "[--memo=<\"memo\">]", "");
|
||||
stream.println();
|
||||
stream.format(rowFormat, failtrade.name(), "--trade-id=<trade-id>", "Change open trade to failed trade");
|
||||
stream.println();
|
||||
stream.format(rowFormat, unfailtrade.name(), "--trade-id=<trade-id>", "Change failed trade to open trade");
|
||||
stream.println();
|
||||
stream.format(rowFormat, getpaymentmethods.name(), "", "Get list of supported payment account method ids");
|
||||
stream.println();
|
||||
stream.format(rowFormat, getpaymentacctform.name(), "--payment-method-id=<payment-method-id>", "Get a new payment account form");
|
||||
|
@ -380,6 +380,14 @@ public final class GrpcClient {
|
||||
tradesServiceRequest.withdrawFunds(tradeId, address, memo);
|
||||
}
|
||||
|
||||
public void failTrade(String tradeId) {
|
||||
tradesServiceRequest.failTrade(tradeId);
|
||||
}
|
||||
|
||||
public void unFailTrade(String tradeId) {
|
||||
tradesServiceRequest.unFailTrade(tradeId);
|
||||
}
|
||||
|
||||
public List<PaymentMethod> getPaymentMethods() {
|
||||
return paymentAccountsServiceRequest.getPaymentMethods();
|
||||
}
|
||||
|
@ -42,6 +42,8 @@ public enum Method {
|
||||
getpaymentaccts,
|
||||
getpaymentmethods,
|
||||
gettrade,
|
||||
failtrade,
|
||||
unfailtrade,
|
||||
gettransaction,
|
||||
gettxfeerate,
|
||||
getunusedbsqaddress,
|
||||
|
@ -20,10 +20,12 @@ package bisq.cli.request;
|
||||
import bisq.proto.grpc.CloseTradeRequest;
|
||||
import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
|
||||
import bisq.proto.grpc.ConfirmPaymentStartedRequest;
|
||||
import bisq.proto.grpc.FailTradeRequest;
|
||||
import bisq.proto.grpc.GetTradeRequest;
|
||||
import bisq.proto.grpc.TakeOfferReply;
|
||||
import bisq.proto.grpc.TakeOfferRequest;
|
||||
import bisq.proto.grpc.TradeInfo;
|
||||
import bisq.proto.grpc.UnFailTradeRequest;
|
||||
import bisq.proto.grpc.WithdrawFundsRequest;
|
||||
|
||||
|
||||
@ -103,4 +105,20 @@ public class TradesServiceRequest {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
grpcStubs.tradesService.withdrawFunds(request);
|
||||
}
|
||||
|
||||
public void failTrade(String tradeId) {
|
||||
var request = FailTradeRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.build();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
grpcStubs.tradesService.failTrade(request);
|
||||
}
|
||||
|
||||
public void unFailTrade(String tradeId) {
|
||||
var request = UnFailTradeRequest.newBuilder()
|
||||
.setTradeId(tradeId)
|
||||
.build();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
grpcStubs.tradesService.unFailTrade(request);
|
||||
}
|
||||
}
|
||||
|
@ -335,6 +335,14 @@ public class CoreApi {
|
||||
return coreTradesService.getBsqSwapTradeRole(bsqSwapTrade);
|
||||
}
|
||||
|
||||
public void failTrade(String tradeId) {
|
||||
coreTradesService.failTrade(tradeId);
|
||||
}
|
||||
|
||||
public void unFailTrade(String tradeId) {
|
||||
coreTradesService.unFailTrade(tradeId);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Wallets
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -25,6 +25,7 @@ import bisq.core.offer.bisq_v1.TakeOfferModel;
|
||||
import bisq.core.offer.bsq_swap.BsqSwapTakeOfferModel;
|
||||
import bisq.core.trade.ClosedTradableManager;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.trade.bisq_v1.FailedTradesManager;
|
||||
import bisq.core.trade.bisq_v1.TradeResultHandler;
|
||||
import bisq.core.trade.bisq_v1.TradeUtil;
|
||||
import bisq.core.trade.model.Tradable;
|
||||
@ -63,6 +64,7 @@ class CoreTradesService {
|
||||
private final BtcWalletService btcWalletService;
|
||||
private final OfferUtil offerUtil;
|
||||
private final ClosedTradableManager closedTradableManager;
|
||||
private final FailedTradesManager failedTradesManager;
|
||||
private final TakeOfferModel takeOfferModel;
|
||||
private final BsqSwapTakeOfferModel bsqSwapTakeOfferModel;
|
||||
private final TradeManager tradeManager;
|
||||
@ -75,6 +77,7 @@ class CoreTradesService {
|
||||
BtcWalletService btcWalletService,
|
||||
OfferUtil offerUtil,
|
||||
ClosedTradableManager closedTradableManager,
|
||||
FailedTradesManager failedTradesManager,
|
||||
TakeOfferModel takeOfferModel,
|
||||
BsqSwapTakeOfferModel bsqSwapTakeOfferModel,
|
||||
TradeManager tradeManager,
|
||||
@ -85,6 +88,7 @@ class CoreTradesService {
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.offerUtil = offerUtil;
|
||||
this.closedTradableManager = closedTradableManager;
|
||||
this.failedTradesManager = failedTradesManager;
|
||||
this.takeOfferModel = takeOfferModel;
|
||||
this.bsqSwapTakeOfferModel = bsqSwapTakeOfferModel;
|
||||
this.tradeManager = tradeManager;
|
||||
@ -269,6 +273,37 @@ class CoreTradesService {
|
||||
));
|
||||
}
|
||||
|
||||
void failTrade(String tradeId) {
|
||||
// TODO Recommend that API users should use this method with extra care because
|
||||
// the API lacks methods for diagnosing trade problems, and does not support
|
||||
// interaction with mediators. Users may accidentally fail valid trades,
|
||||
// although they can easily be un-failed with the 'unfailtrade' method.
|
||||
// The 'failtrade' and 'unfailtrade' methods are implemented at this early
|
||||
// stage of API development to help efficiently test a new
|
||||
// 'gettrades --category=<open|closed|failed>'
|
||||
// method currently in development.
|
||||
coreWalletsService.verifyWalletsAreAvailable();
|
||||
coreWalletsService.verifyEncryptedWalletIsUnlocked();
|
||||
|
||||
var trade = getTrade(tradeId);
|
||||
tradeManager.onMoveInvalidTradeToFailedTrades(trade);
|
||||
log.info("Trade {} changed to failed trade.", tradeId);
|
||||
}
|
||||
|
||||
void unFailTrade(String tradeId) {
|
||||
coreWalletsService.verifyWalletsAreAvailable();
|
||||
coreWalletsService.verifyEncryptedWalletIsUnlocked();
|
||||
|
||||
failedTradesManager.getTradeById(tradeId).ifPresentOrElse(failedTrade -> {
|
||||
verifyCanUnfailTrade(failedTrade);
|
||||
failedTradesManager.removeTrade(failedTrade);
|
||||
tradeManager.addFailedTradeToPendingTrades(failedTrade);
|
||||
log.info("Failed trade {} changed to open trade.", tradeId);
|
||||
}, () -> {
|
||||
throw new IllegalArgumentException(format("failed trade '%s' not found", tradeId));
|
||||
});
|
||||
}
|
||||
|
||||
private Optional<Trade> getOpenTrade(String tradeId) {
|
||||
return tradeManager.getTradeById(tradeId);
|
||||
}
|
||||
@ -318,4 +353,30 @@ class CoreTradesService {
|
||||
throw new IllegalStateException(format("funds already withdrawn from address '%s'",
|
||||
fromAddressEntry.getAddressString()));
|
||||
}
|
||||
|
||||
// Throws a RuntimeException if failed trade cannot be changed to OPEN for any reason.
|
||||
private void verifyCanUnfailTrade(Trade failedTrade) {
|
||||
if (tradeUtil.getTradeAddresses(failedTrade) == null)
|
||||
throw new IllegalStateException(
|
||||
format("cannot change failed trade to open because no trade addresses found for '%s'",
|
||||
failedTrade.getId()));
|
||||
|
||||
if (!failedTradesManager.hasDepositTx(failedTrade))
|
||||
throw new IllegalStateException(
|
||||
format("cannot change failed trade to open, no deposit tx found for '%s'",
|
||||
failedTrade.getId()));
|
||||
|
||||
if (!failedTradesManager.hasDelayedPayoutTxBytes(failedTrade))
|
||||
throw new IllegalStateException(
|
||||
format("cannot change failed trade to open, no delayed payout tx found for '%s'",
|
||||
failedTrade.getId()));
|
||||
|
||||
failedTradesManager.getBlockingTradeIds(failedTrade).ifPresent(tradeIds -> {
|
||||
throw new IllegalStateException(
|
||||
format("cannot change failed trade '%s' to open at this time,"
|
||||
+ "%ntry again after completing trade(s):%n\t%s",
|
||||
failedTrade.getId(),
|
||||
String.join(", ", tradeIds)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -24,14 +24,20 @@ import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.trade.model.TradableList;
|
||||
import bisq.core.trade.model.bisq_v1.Trade;
|
||||
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.persistence.PersistenceManager;
|
||||
import bisq.common.proto.persistable.PersistedDataHost;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import javax.inject.Named;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
@ -41,6 +47,8 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import lombok.Setter;
|
||||
|
||||
import static bisq.core.btc.model.AddressEntry.Context.AVAILABLE;
|
||||
|
||||
public class FailedTradesManager implements PersistedDataHost {
|
||||
private static final Logger log = LoggerFactory.getLogger(FailedTradesManager.class);
|
||||
private final TradableList<Trade> failedTrades = new TradableList<>();
|
||||
@ -51,6 +59,8 @@ public class FailedTradesManager implements PersistedDataHost {
|
||||
private final PersistenceManager<TradableList<Trade>> persistenceManager;
|
||||
private final TradeUtil tradeUtil;
|
||||
private final DumpDelayedPayoutTx dumpDelayedPayoutTx;
|
||||
private final boolean allowFaultyDelayedTxs;
|
||||
|
||||
@Setter
|
||||
private Predicate<Trade> unFailTradeCallback;
|
||||
|
||||
@ -61,7 +71,8 @@ public class FailedTradesManager implements PersistedDataHost {
|
||||
PersistenceManager<TradableList<Trade>> persistenceManager,
|
||||
TradeUtil tradeUtil,
|
||||
CleanupMailboxMessagesService cleanupMailboxMessagesService,
|
||||
DumpDelayedPayoutTx dumpDelayedPayoutTx) {
|
||||
DumpDelayedPayoutTx dumpDelayedPayoutTx,
|
||||
@Named(Config.ALLOW_FAULTY_DELAYED_TXS) boolean allowFaultyDelayedTxs) {
|
||||
this.keyRing = keyRing;
|
||||
this.priceFeedService = priceFeedService;
|
||||
this.btcWalletService = btcWalletService;
|
||||
@ -69,6 +80,7 @@ public class FailedTradesManager implements PersistedDataHost {
|
||||
this.dumpDelayedPayoutTx = dumpDelayedPayoutTx;
|
||||
this.persistenceManager = persistenceManager;
|
||||
this.tradeUtil = tradeUtil;
|
||||
this.allowFaultyDelayedTxs = allowFaultyDelayedTxs;
|
||||
|
||||
this.persistenceManager.initialize(failedTrades, "FailedTrades", PersistenceManager.Source.PRIVATE);
|
||||
}
|
||||
@ -136,17 +148,59 @@ public class FailedTradesManager implements PersistedDataHost {
|
||||
if (addresses == null) {
|
||||
return "Addresses not found";
|
||||
}
|
||||
StringBuilder blockingTrades = new StringBuilder();
|
||||
for (var entry : btcWalletService.getAddressEntryListAsImmutableList()) {
|
||||
if (entry.getContext() == AddressEntry.Context.AVAILABLE)
|
||||
continue;
|
||||
if (entry.getAddressString() != null &&
|
||||
(entry.getAddressString().equals(addresses.first) ||
|
||||
entry.getAddressString().equals(addresses.second))) {
|
||||
blockingTrades.append(entry.getOfferId()).append(", ");
|
||||
Optional<List<String>> blockingTradeIds = getBlockingTradeIds(trade);
|
||||
return blockingTradeIds.map(strings -> String.join(",", strings)).orElse("");
|
||||
}
|
||||
|
||||
public Optional<List<String>> getBlockingTradeIds(Trade trade) {
|
||||
var tradeAddresses = tradeUtil.getTradeAddresses(trade);
|
||||
if (tradeAddresses == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Predicate<AddressEntry> isBeingUsedForOtherTrade = (addressEntry) -> {
|
||||
if (addressEntry.getContext() == AVAILABLE) {
|
||||
return false;
|
||||
}
|
||||
String address = addressEntry.getAddressString();
|
||||
return address != null
|
||||
&& (address.equals(tradeAddresses.first) || address.equals(tradeAddresses.second));
|
||||
};
|
||||
|
||||
List<String> blockingTradeIds = new ArrayList<>();
|
||||
for (var addressEntry : btcWalletService.getAddressEntryListAsImmutableList()) {
|
||||
if (isBeingUsedForOtherTrade.test(addressEntry)) {
|
||||
var offerId = addressEntry.getOfferId();
|
||||
// TODO Be certain 'List<String> blockingTrades' should NOT be populated
|
||||
// with the trade parameter's tradeId. The 'var addressEntry' will
|
||||
// always be found in the 'var tradeAddresses' tuple, so check
|
||||
// offerId != trade.getId() to avoid the bug being fixed by the next if
|
||||
// statement (if it was a bug).
|
||||
if (!Objects.equals(offerId, trade.getId()) && !blockingTradeIds.contains(offerId))
|
||||
blockingTradeIds.add(offerId);
|
||||
}
|
||||
}
|
||||
return blockingTrades.toString();
|
||||
return blockingTradeIds.isEmpty()
|
||||
? Optional.empty()
|
||||
: Optional.of(blockingTradeIds);
|
||||
}
|
||||
|
||||
public boolean hasDepositTx(Trade failedTrade) {
|
||||
if (failedTrade.getDepositTx() == null) {
|
||||
log.warn("Failed trade {} has no deposit tx.", failedTrade.getId());
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasDelayedPayoutTxBytes(Trade failedTrade) {
|
||||
if (failedTrade.getDelayedPayoutTxBytes() != null) {
|
||||
return true;
|
||||
} else {
|
||||
log.warn("Failed trade {} has no delayedPayoutTxBytes.", failedTrade.getId());
|
||||
return allowFaultyDelayedTxs;
|
||||
}
|
||||
}
|
||||
|
||||
private void requestPersistence() {
|
||||
|
28
core/src/main/resources/help/failtrade-help.txt
Normal file
28
core/src/main/resources/help/failtrade-help.txt
Normal file
@ -0,0 +1,28 @@
|
||||
failtrade
|
||||
|
||||
NAME
|
||||
----
|
||||
failtrade - change an open trade to a failed trade
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
failtrade
|
||||
--trade-id=<trade-id>
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
If there are problems with an existing trade, and it cannot be completed, it can be moved
|
||||
from the open trades list to the failed trades list.
|
||||
|
||||
You cannot open mediation or arbitration for a failed trade, but you can change a failed
|
||||
trade back to an open trade with the 'unfailtrade' command.
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
--trade-id
|
||||
The ID of the trade (the full offer-id).
|
||||
|
||||
EXAMPLES
|
||||
--------
|
||||
Change the status of an active, open trade to failed:
|
||||
$ ./bisq-cli --password=xyz --port=9998 failtrade --trade-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea
|
32
core/src/main/resources/help/unfailtrade-help.txt
Normal file
32
core/src/main/resources/help/unfailtrade-help.txt
Normal file
@ -0,0 +1,32 @@
|
||||
unfailtrade
|
||||
|
||||
NAME
|
||||
----
|
||||
unfailtrade - change a failed trade to an open trade
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
unfailtrade
|
||||
--trade-id=<trade-id>
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
This is a possible way to unlock funds stuck in a failed trade.
|
||||
|
||||
The operation could fail for any of the following reasons:
|
||||
|
||||
The trade's deposit transaction is missing (null).
|
||||
The trade's delayed payout transaction is missing (null).
|
||||
The trade is using wallet addresses also being used one or more other trades.
|
||||
|
||||
Before proceeding, make sure you have a backup of your data directory.
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
--trade-id
|
||||
The ID of the trade (the full offer-id).
|
||||
|
||||
EXAMPLES
|
||||
--------
|
||||
Change the status of failed trade back to an open trade:
|
||||
$ ./bisq-cli --password=xyz --port=9998 unfailtrade --trade-id=83e8b2e2-51b6-4f39-a748-3ebd29c22aea
|
@ -29,10 +29,14 @@ import bisq.proto.grpc.ConfirmPaymentReceivedReply;
|
||||
import bisq.proto.grpc.ConfirmPaymentReceivedRequest;
|
||||
import bisq.proto.grpc.ConfirmPaymentStartedReply;
|
||||
import bisq.proto.grpc.ConfirmPaymentStartedRequest;
|
||||
import bisq.proto.grpc.FailTradeReply;
|
||||
import bisq.proto.grpc.FailTradeRequest;
|
||||
import bisq.proto.grpc.GetTradeReply;
|
||||
import bisq.proto.grpc.GetTradeRequest;
|
||||
import bisq.proto.grpc.TakeOfferReply;
|
||||
import bisq.proto.grpc.TakeOfferRequest;
|
||||
import bisq.proto.grpc.UnFailTradeReply;
|
||||
import bisq.proto.grpc.UnFailTradeRequest;
|
||||
import bisq.proto.grpc.WithdrawFundsReply;
|
||||
import bisq.proto.grpc.WithdrawFundsRequest;
|
||||
|
||||
@ -177,6 +181,32 @@ class GrpcTradesService extends TradesImplBase {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failTrade(FailTradeRequest req,
|
||||
StreamObserver<FailTradeReply> responseObserver) {
|
||||
try {
|
||||
coreApi.failTrade(req.getTradeId());
|
||||
var reply = FailTradeReply.newBuilder().build();
|
||||
responseObserver.onNext(reply);
|
||||
responseObserver.onCompleted();
|
||||
} catch (Throwable cause) {
|
||||
exceptionHandler.handleException(log, cause, responseObserver);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unFailTrade(UnFailTradeRequest req,
|
||||
StreamObserver<UnFailTradeReply> responseObserver) {
|
||||
try {
|
||||
coreApi.unFailTrade(req.getTradeId());
|
||||
var reply = UnFailTradeReply.newBuilder().build();
|
||||
responseObserver.onNext(reply);
|
||||
responseObserver.onCompleted();
|
||||
} catch (Throwable cause) {
|
||||
exceptionHandler.handleException(log, cause, responseObserver);
|
||||
}
|
||||
}
|
||||
|
||||
final ServerInterceptor[] interceptors() {
|
||||
Optional<ServerInterceptor> rateMeteringInterceptor = rateMeteringInterceptor();
|
||||
return rateMeteringInterceptor.map(serverInterceptor ->
|
||||
|
@ -396,6 +396,10 @@ service Trades {
|
||||
}
|
||||
rpc CloseTrade (CloseTradeRequest) returns (CloseTradeReply) {
|
||||
}
|
||||
rpc FailTrade (FailTradeRequest) returns (FailTradeReply) {
|
||||
}
|
||||
rpc UnFailTrade (UnFailTradeRequest) returns (UnFailTradeReply) {
|
||||
}
|
||||
rpc WithdrawFunds (WithdrawFundsRequest) returns (WithdrawFundsReply) {
|
||||
}
|
||||
}
|
||||
@ -440,6 +444,20 @@ message CloseTradeRequest {
|
||||
message CloseTradeReply {
|
||||
}
|
||||
|
||||
message FailTradeRequest {
|
||||
string tradeId = 1;
|
||||
}
|
||||
|
||||
message FailTradeReply {
|
||||
}
|
||||
|
||||
message UnFailTradeRequest {
|
||||
string tradeId = 1;
|
||||
}
|
||||
|
||||
message UnFailTradeReply {
|
||||
}
|
||||
|
||||
message WithdrawFundsRequest {
|
||||
string tradeId = 1;
|
||||
string address = 2;
|
||||
|
Loading…
Reference in New Issue
Block a user