Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement api methods 'keepfunds', 'withdrawfunds' #4711

Merged
merged 17 commits into from
Oct 31, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 112 additions & 8 deletions core/src/main/java/bisq/core/api/CoreTradesService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -116,26 +137,109 @@ 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) {
return tradeUtil.getRole(getTrade(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<Trade> getOpenTrade(String tradeId) {
return tradeManager.getTradeById(tradeId);
}

private Optional<Trade> getClosedTrade(String tradeId) {
return closedTradableManager.getTradableById(tradeId).map(value -> (Trade) value);
ghubstan marked this conversation as resolved.
Show resolved Hide resolved
}

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()));
}
}
6 changes: 6 additions & 0 deletions core/src/main/java/bisq/core/api/CoreWalletsService.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ public CoreWalletsService(Balances balances,
this.btcWalletService = btcWalletService;
}

@Nullable
KeyParameter getKey() {
verifyEncryptedWalletIsUnlocked();
return tempAesKey;
}

long getAvailableBalance() {
verifyWalletsAreAvailable();
verifyEncryptedWalletIsUnlocked();
Expand Down