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

fix scheduling offers by computing spendable amount from txs #1480

Merged
merged 2 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ public Offer createAndGetOffer(String offerId,
isPrivateOffer,
buyerAsTakerWithoutDeposit);


// verify buyer as taker security deposit
boolean isBuyerMaker = offerUtil.isBuyOffer(direction);
if (!isBuyerMaker && !isPrivateOffer && buyerAsTakerWithoutDeposit) {
Expand Down
66 changes: 38 additions & 28 deletions core/src/main/java/haveno/core/offer/OpenOfferManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -948,7 +948,7 @@ private void doProcessPendingOffer(List<OpenOffer> openOffers, OpenOffer openOff
if (openOffer.getScheduledTxHashes() != null) {
boolean scheduledTxsAvailable = true;
for (MoneroTxWallet tx : xmrWalletService.getTxs(openOffer.getScheduledTxHashes())) {
if (!tx.isLocked() && !isOutputsAvailable(tx)) {
if (!tx.isLocked() && !hasSpendableAmount(tx)) {
scheduledTxsAvailable = false;
break;
}
Expand Down Expand Up @@ -1165,31 +1165,21 @@ private void scheduleWithEarliestTxs(List<OpenOffer> openOffers, OpenOffer openO
throw new RuntimeException("Not enough money in Haveno wallet");
}

// get earliest available or pending txs with sufficient incoming amount
// get earliest available or pending txs with sufficient spendable amount
BigInteger scheduledAmount = BigInteger.ZERO;
Set<MoneroTxWallet> scheduledTxs = new HashSet<MoneroTxWallet>();
for (MoneroTxWallet tx : xmrWalletService.getTxs()) {

// skip if no funds available
BigInteger sentToSelfAmount = xmrWalletService.getAmountSentToSelf(tx); // amount sent to self always shows 0, so compute from destinations manually
if (sentToSelfAmount.equals(BigInteger.ZERO) && (tx.getIncomingTransfers() == null || tx.getIncomingTransfers().isEmpty())) continue;
if (!isOutputsAvailable(tx)) continue;
// get spendable amount
BigInteger spendableAmount = getSpendableAmount(tx);

// skip if no spendable amount or already scheduled
if (spendableAmount.equals(BigInteger.ZERO)) continue;
if (isTxScheduledByOtherOffer(openOffers, openOffer, tx.getHash())) continue;

// schedule transaction if funds sent to self, because they are not included in incoming transfers // TODO: fix in libraries?
if (sentToSelfAmount.compareTo(BigInteger.ZERO) > 0) {
scheduledAmount = scheduledAmount.add(sentToSelfAmount);
scheduledTxs.add(tx);
} else if (tx.getIncomingTransfers() != null) {

// schedule transaction if incoming tranfers to account 0
for (MoneroIncomingTransfer transfer : tx.getIncomingTransfers()) {
if (transfer.getAccountIndex() == 0) {
scheduledAmount = scheduledAmount.add(transfer.getAmount());
scheduledTxs.add(tx);
}
}
}
// schedule tx
scheduledAmount = scheduledAmount.add(spendableAmount);
scheduledTxs.add(tx);

// break if sufficient funds
if (scheduledAmount.compareTo(offerReserveAmount) >= 0) break;
Expand All @@ -1202,6 +1192,34 @@ private void scheduleWithEarliestTxs(List<OpenOffer> openOffers, OpenOffer openO
openOffer.setState(OpenOffer.State.PENDING);
}

private BigInteger getSpendableAmount(MoneroTxWallet tx) {

// compute spendable amount from outputs if confirmed
if (tx.isConfirmed()) {
BigInteger spendableAmount = BigInteger.ZERO;
if (tx.getOutputsWallet() != null) {
for (MoneroOutputWallet output : tx.getOutputsWallet()) {
if (!output.isSpent() && !output.isFrozen() && output.getAccountIndex() == 0) {
spendableAmount = spendableAmount.add(output.getAmount());
}
}
}
return spendableAmount;
}

// funds sent to self always show 0 incoming amount, so compute from destinations manually
// TODO: this excludes change output, so change is missing from spendable amount until confirmed
BigInteger sentToSelfAmount = xmrWalletService.getAmountSentToSelf(tx);
if (sentToSelfAmount.compareTo(BigInteger.ZERO) > 0) return sentToSelfAmount;

// if not confirmed and not sent to self, return incoming amount
return tx.getIncomingAmount() == null ? BigInteger.ZERO : tx.getIncomingAmount();
}

private boolean hasSpendableAmount(MoneroTxWallet tx) {
return getSpendableAmount(tx).compareTo(BigInteger.ZERO) > 0;
}

private BigInteger getScheduledAmount(List<OpenOffer> openOffers) {
BigInteger scheduledAmount = BigInteger.ZERO;
for (OpenOffer openOffer : openOffers) {
Expand Down Expand Up @@ -1233,14 +1251,6 @@ private boolean isTxScheduledByOtherOffer(List<OpenOffer> openOffers, OpenOffer
return false;
}

private boolean isOutputsAvailable(MoneroTxWallet tx) {
if (tx.getOutputsWallet() == null) return false;
for (MoneroOutputWallet output : tx.getOutputsWallet()) {
if (output.isSpent() || output.isFrozen()) return false;
}
return true;
}

private void signAndPostOffer(OpenOffer openOffer,
boolean useSavingsWallet, // TODO: remove this?
TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public String getDirectionLabel() {
? offer.getDirection()
: offer.getMirroredDirection();
String currencyCode = tradable.getOffer().getCurrencyCode();
return DisplayUtils.getDirectionWithCode(direction, currencyCode);
return DisplayUtils.getDirectionWithCode(direction, currencyCode, offer.isPrivateOffer());
}

public Date getDate() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ String getVolume(FailedTradesListItem item) {
}

String getDirectionLabel(FailedTradesListItem item) {
return (item != null) ? DisplayUtils.getDirectionWithCode(dataModel.getDirection(item.getTrade().getOffer()), item.getTrade().getOffer().getCurrencyCode()) : "";
return (item != null) ? DisplayUtils.getDirectionWithCode(dataModel.getDirection(item.getTrade().getOffer()), item.getTrade().getOffer().getCurrencyCode(), item.getTrade().getOffer().isPrivateOffer()) : "";
}

String getMarketLabel(FailedTradesListItem item) {
Expand Down
4 changes: 0 additions & 4 deletions desktop/src/main/java/haveno/desktop/util/DisplayUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,6 @@ public static String booleanToYesNo(boolean value) {
// Offer direction
///////////////////////////////////////////////////////////////////////////////////////////

public static String getDirectionWithCode(OfferDirection direction, String currencyCode) {
return getDirectionWithCode(direction, currencyCode, false);
}

public static String getDirectionWithCode(OfferDirection direction, String currencyCode, boolean isPrivate) {
if (CurrencyUtil.isTraditionalCurrency(currencyCode))
return (direction == OfferDirection.BUY) ? Res.get(isPrivate ? "shared.buyCurrencyLocked" : "shared.buyCurrency", Res.getBaseCurrencyCode()) : Res.get(isPrivate ? "shared.sellCurrencyLocked" : "shared.sellCurrency", Res.getBaseCurrencyCode());
Expand Down
Loading