diff --git a/core/src/main/java/haveno/core/offer/OpenOfferManager.java b/core/src/main/java/haveno/core/offer/OpenOfferManager.java index 93a490b9..8181b9b6 100644 --- a/core/src/main/java/haveno/core/offer/OpenOfferManager.java +++ b/core/src/main/java/haveno/core/offer/OpenOfferManager.java @@ -667,7 +667,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe getOpenOfferById(offer.getId()).ifPresent(openOffer -> { removeOpenOffer(openOffer); openOffer.setState(OpenOffer.State.CLOSED); - xmrWalletService.resetOfferFundingForOpenOffer(offer.getId()); + xmrWalletService.resetAddressEntriesForOpenOffer(offer.getId()); offerBookService.removeOffer(openOffer.getOffer().getOfferPayload(), () -> log.info("Successfully removed offer {}", offer.getId()), log::error); @@ -764,6 +764,10 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe } } + public boolean hasAvailableOutput(BigInteger amount) { + return findSplitOutputFundingTx(getOpenOffers(), null, amount, null) != null; + } + /////////////////////////////////////////////////////////////////////////////////////////// // Place offer helpers /////////////////////////////////////////////////////////////////////////////////////////// @@ -811,7 +815,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe // find tx with exact input amount MoneroTxWallet splitOutputTx = findSplitOutputFundingTx(openOffers, openOffer); - if (openOffer.getScheduledTxHashes() == null && splitOutputTx != null) { + if (splitOutputTx != null && openOffer.getScheduledTxHashes() == null) { openOffer.setScheduledTxHashes(Arrays.asList(splitOutputTx.getHash())); openOffer.setSplitOutputTxHash(splitOutputTx.getHash()); openOffer.setScheduledAmount(offerReserveAmount.toString()); @@ -829,7 +833,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe // on error, create new tx to split output if offer subaddress does not have exact output int offerSubaddress = xmrWalletService.getOrCreateAddressEntry(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING).getSubaddressIndex(); if (!splitOutputTx.getOutgoingTransfer().getSubaddressIndices().equals(Arrays.asList(offerSubaddress))) { - log.warn("Splitting new output because spending existing output(s) failed for offer {}", openOffer.getId()); + log.warn("Splitting new output because spending existing output(s) failed for offer {}. Split output tx subaddresses={}. Offer funding subadress={}", openOffer.getId(), splitOutputTx.getOutgoingTransfer().getSubaddressIndices(), offerSubaddress); splitOrSchedule(openOffers, openOffer, offerReserveAmount); resultHandler.handleResult(null); } else { @@ -846,7 +850,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe signAndPostOffer(openOffer, true, resultHandler, errorMessageHandler); return; } else if (openOffer.getScheduledTxHashes() == null) { - scheduleOfferWithEarliestTxs(openOffers, openOffer); + scheduleWithEarliestTxs(openOffers, openOffer); } } @@ -860,29 +864,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe }).start(); } - private void splitOrSchedule(List openOffers, OpenOffer openOffer, BigInteger offerReserveAmount) { - - // handle sufficient available balance to split output - boolean sufficientAvailableBalance = xmrWalletService.getWallet().getUnlockedBalance(0).compareTo(offerReserveAmount) >= 0; - if (sufficientAvailableBalance) { - - // create and relay tx to split output - MoneroTxWallet splitOutputTx = createAndRelaySplitOutputTx(openOffer); - - // schedule txs - openOffer.setScheduledTxHashes(Arrays.asList(splitOutputTx.getHash())); - openOffer.setSplitOutputTxHash(splitOutputTx.getHash()); - openOffer.setScheduledAmount(offerReserveAmount.toString()); - openOffer.setState(OpenOffer.State.SCHEDULED); - } else if (openOffer.getScheduledTxHashes() == null) { - scheduleOfferWithEarliestTxs(openOffers, openOffer); - } - } - - public boolean hasAvailableOutput(BigInteger amount) { - return findSplitOutputFundingTx(getOpenOffers(), null, amount, null) != null; - } - private MoneroTxWallet findSplitOutputFundingTx(List openOffers, OpenOffer openOffer) { XmrAddressEntry addressEntry = xmrWalletService.getOrCreateAddressEntry(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING); return findSplitOutputFundingTx(openOffers, openOffer, openOffer.getOffer().getReserveAmount(), addressEntry.getSubaddressIndex()); @@ -968,7 +949,38 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe return earliestUnscheduledTx; } - private void scheduleOfferWithEarliestTxs(List openOffers, OpenOffer openOffer) { + private void splitOrSchedule(List openOffers, OpenOffer openOffer, BigInteger offerReserveAmount) { + + // handle sufficient available balance to split output + boolean sufficientAvailableBalance = xmrWalletService.getWallet().getUnlockedBalance(0).compareTo(offerReserveAmount) >= 0; + if (sufficientAvailableBalance) { + splitAndSchedule(openOffer); + } else if (openOffer.getScheduledTxHashes() == null) { + scheduleWithEarliestTxs(openOffers, openOffer); + } + } + + private MoneroTxWallet splitAndSchedule(OpenOffer openOffer) { + BigInteger reserveAmount = openOffer.getOffer().getReserveAmount(); + xmrWalletService.swapAddressEntryToAvailable(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING); // change funding subaddress in case funded with unsuitable output(s) + XmrAddressEntry entry = xmrWalletService.getOrCreateAddressEntry(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING); + log.info("Creating split output tx to fund offer {} at subaddress {}", openOffer.getId(), entry.getSubaddressIndex()); + MoneroTxWallet splitOutputTx = xmrWalletService.getWallet().createTx(new MoneroTxConfig() + .setAccountIndex(0) + .setAddress(entry.getAddressString()) + .setAmount(reserveAmount) + .setRelay(true)); + log.info("Done creating split output tx to fund offer {}", openOffer.getId()); + + // schedule txs + openOffer.setScheduledTxHashes(Arrays.asList(splitOutputTx.getHash())); + openOffer.setSplitOutputTxHash(splitOutputTx.getHash()); + openOffer.setScheduledAmount(openOffer.getOffer().getReserveAmount().toString()); + openOffer.setState(OpenOffer.State.SCHEDULED); + return splitOutputTx; + } + + private void scheduleWithEarliestTxs(List openOffers, OpenOffer openOffer) { // check for sufficient balance - scheduled offers amount BigInteger offerReserveAmount = openOffer.getOffer().getReserveAmount(); @@ -999,20 +1011,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe openOffer.setState(OpenOffer.State.SCHEDULED); } - private MoneroTxWallet createAndRelaySplitOutputTx(OpenOffer openOffer) { - BigInteger reserveAmount = openOffer.getOffer().getReserveAmount(); - xmrWalletService.swapAddressEntryToAvailable(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING); // change funding subaddress in case funded with unsuitable output(s) - String fundingSubaddress = xmrWalletService.getOrCreateAddressEntry(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING).getAddressString(); - log.info("Creating split output tx to fund offer {}", openOffer.getId()); - MoneroTxWallet splitOutputTx = xmrWalletService.getWallet().createTx(new MoneroTxConfig() - .setAccountIndex(0) - .setAddress(fundingSubaddress) - .setAmount(reserveAmount) - .setRelay(true)); - log.info("Done creating split output tx to fund offer {}", openOffer.getId()); - return splitOutputTx; - } - private BigInteger getScheduledAmount(List openOffers) { BigInteger scheduledAmount = BigInteger.valueOf(0); for (OpenOffer openOffer : openOffers) { diff --git a/core/src/main/java/haveno/core/trade/TradeManager.java b/core/src/main/java/haveno/core/trade/TradeManager.java index ef2da5dc..8fe4014f 100644 --- a/core/src/main/java/haveno/core/trade/TradeManager.java +++ b/core/src/main/java/haveno/core/trade/TradeManager.java @@ -691,26 +691,26 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi } private void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress peer) { - log.info("Received InitMultisigRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid()); + log.info("Received {} for trade {} from {} with uid {}", request.getClass().getSimpleName(), request.getTradeId(), peer, request.getUid()); - try { - Validator.nonEmptyStringOf(request.getTradeId()); - } catch (Throwable t) { - log.warn("Invalid InitMultisigRequest " + request.toString()); - return; - } + try { + Validator.nonEmptyStringOf(request.getTradeId()); + } catch (Throwable t) { + log.warn("Invalid InitMultisigRequest " + request.toString()); + return; + } - Optional tradeOptional = getOpenTrade(request.getTradeId()); - if (!tradeOptional.isPresent()) { - log.warn("No trade with id " + request.getTradeId() + " at node " + P2PService.getMyNodeAddress()); - return; - } - Trade trade = tradeOptional.get(); - getTradeProtocol(trade).handleInitMultisigRequest(request, peer); + Optional tradeOptional = getOpenTrade(request.getTradeId()); + if (!tradeOptional.isPresent()) { + log.warn("No trade with id " + request.getTradeId() + " at node " + P2PService.getMyNodeAddress()); + return; + } + Trade trade = tradeOptional.get(); + getTradeProtocol(trade).handleInitMultisigRequest(request, peer); } private void handleSignContractRequest(SignContractRequest request, NodeAddress peer) { - log.info("Received SignContractRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid()); + log.info("Received {} for trade {} from {} with uid {}", request.getClass().getSimpleName(), request.getTradeId(), peer, request.getUid()); try { Validator.nonEmptyStringOf(request.getTradeId()); @@ -729,7 +729,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi } private void handleSignContractResponse(SignContractResponse request, NodeAddress peer) { - log.info("Received SignContractResponse from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid()); + log.info("Received {} for trade {} from {} with uid {}", request.getClass().getSimpleName(), request.getTradeId(), peer, request.getUid()); try { Validator.nonEmptyStringOf(request.getTradeId()); @@ -748,7 +748,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi } private void handleDepositRequest(DepositRequest request, NodeAddress peer) { - log.info("Received DepositRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid()); + log.info("Received {} for trade {} from {} with uid {}", request.getClass().getSimpleName(), request.getTradeId(), peer, request.getUid()); try { Validator.nonEmptyStringOf(request.getTradeId()); @@ -767,7 +767,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi } private void handleDepositResponse(DepositResponse response, NodeAddress peer) { - log.info("Received DepositResponse from {} with tradeId {} and uid {}", peer, response.getTradeId(), response.getUid()); + log.info("Received {} for trade {} from {} with uid {}", response.getClass().getSimpleName(), response.getTradeId(), peer, response.getUid()); try { Validator.nonEmptyStringOf(response.getTradeId()); diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/RemoveOffer.java b/core/src/main/java/haveno/core/trade/protocol/tasks/RemoveOffer.java index 18dcf0c8..c5b8a3f5 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/RemoveOffer.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/RemoveOffer.java @@ -38,7 +38,7 @@ public class RemoveOffer extends TradeTask { if (trade instanceof MakerTrade) { processModel.getOpenOfferManager().closeOpenOffer(checkNotNull(trade.getOffer())); } else { - trade.getXmrWalletService().resetOfferFundingForOpenOffer(trade.getId()); + trade.getXmrWalletService().resetAddressEntriesForOpenOffer(trade.getId()); } complete(); diff --git a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java index ad2e2ee3..90767048 100644 --- a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java +++ b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java @@ -687,7 +687,7 @@ public class XmrWalletService { wallet.startSyncing(connectionsService.getRefreshPeriodMs()); if (getMoneroNetworkType() != MoneroNetworkType.MAINNET) log.info("Monero wallet balance={}, unlocked balance={}", wallet.getBalance(0), wallet.getUnlockedBalance(0)); - // TODO: using this to signify both daemon and wallet synced, use separate sync handlers + // TODO: using this to signify both daemon and wallet synced, use separate sync handlers? connectionsService.doneDownload(); // notify setup that main wallet is initialized @@ -978,12 +978,11 @@ public class XmrWalletService { public synchronized void resetAddressEntriesForOpenOffer(String offerId) { log.info("resetAddressEntriesForOpenOffer offerId={}", offerId); swapAddressEntryToAvailable(offerId, XmrAddressEntry.Context.OFFER_FUNDING); - swapAddressEntryToAvailable(offerId, XmrAddressEntry.Context.TRADE_PAYOUT); - } - public synchronized void resetOfferFundingForOpenOffer(String offerId) { - log.info("resetOfferFundingForOpenOffer offerId={}", offerId); - swapAddressEntryToAvailable(offerId, XmrAddressEntry.Context.OFFER_FUNDING); + // swap trade payout to available if applicable + if (tradeManager == null) return; + Trade trade = tradeManager.getTrade(offerId); + if (trade == null || trade.isPayoutUnlocked()) swapAddressEntryToAvailable(offerId, XmrAddressEntry.Context.TRADE_PAYOUT); } public synchronized void resetAddressEntriesForTrade(String offerId) { @@ -1136,9 +1135,10 @@ public class XmrWalletService { } public Stream getAddressEntriesForAvailableBalanceStream() { - Stream availableAndPayout = Stream.concat(getAddressEntries(XmrAddressEntry.Context.TRADE_PAYOUT).stream(), getFundedAvailableAddressEntries().stream()); - Stream available = Stream.concat(availableAndPayout, getAddressEntries(XmrAddressEntry.Context.ARBITRATOR).stream()); + Stream available = getFundedAvailableAddressEntries().stream(); + available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.ARBITRATOR).stream()); available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.OFFER_FUNDING).stream().filter(entry -> !tradeManager.getOpenOfferManager().getOpenOfferById(entry.getOfferId()).isPresent())); + available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.TRADE_PAYOUT).stream().filter(entry -> tradeManager.getTrade(entry.getOfferId()) == null || tradeManager.getTrade(entry.getOfferId()).isPayoutUnlocked())); return available.filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).compareTo(BigInteger.valueOf(0)) > 0); } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index a91db437..61e4bf84 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1017,8 +1017,8 @@ funds.tab.transactions=Transactions funds.deposit.unused=Unused funds.deposit.usedInTx=Used in {0} transaction(s) funds.deposit.baseAddress=Base address -funds.deposit.offerFunding=Reserved for offer funding -funds.deposit.tradePayout=Reserved for trade payout +funds.deposit.offerFunding=Reserved for offer funding ({0}) +funds.deposit.tradePayout=Reserved for trade payout ({0}) funds.deposit.fundHavenoWallet=Fund Haveno wallet funds.deposit.noAddresses=No deposit addresses have been generated yet funds.deposit.fundWallet=Fund your wallet diff --git a/desktop/src/main/java/haveno/desktop/main/funds/deposit/DepositListItem.java b/desktop/src/main/java/haveno/desktop/main/funds/deposit/DepositListItem.java index d0f302c2..f9725d7c 100644 --- a/desktop/src/main/java/haveno/desktop/main/funds/deposit/DepositListItem.java +++ b/desktop/src/main/java/haveno/desktop/main/funds/deposit/DepositListItem.java @@ -104,10 +104,10 @@ class DepositListItem { usage = numTxsWithOutputs == 0 ? Res.get("funds.deposit.unused") : Res.get("funds.deposit.usedInTx", numTxsWithOutputs); break; case OFFER_FUNDING: - usage = Res.get("funds.deposit.offerFunding"); + usage = Res.get("funds.deposit.offerFunding", addressEntry.getShortOfferId()); break; case TRADE_PAYOUT: - usage = Res.get("funds.deposit.tradePayout"); + usage = Res.get("funds.deposit.tradePayout", addressEntry.getShortOfferId()); break; default: usage = addressEntry.getContext().toString();