mirror of
https://github.com/haveno-dex/haveno.git
synced 2024-10-01 01:35:48 -04:00
fix 'not enough money' error on reserve exact offer amount #1089
This commit is contained in:
parent
86e4f7b3f2
commit
5d39eecd4f
@ -42,7 +42,6 @@ import javafx.beans.property.SimpleObjectProperty;
|
|||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -51,7 +50,6 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
@Slf4j
|
|
||||||
public final class OpenOffer implements Tradable {
|
public final class OpenOffer implements Tradable {
|
||||||
|
|
||||||
public enum State {
|
public enum State {
|
||||||
|
@ -96,6 +96,7 @@ import java.math.BigInteger;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
@ -113,6 +114,7 @@ import monero.daemon.model.MoneroKeyImageSpentStatus;
|
|||||||
import monero.daemon.model.MoneroTx;
|
import monero.daemon.model.MoneroTx;
|
||||||
import monero.wallet.model.MoneroIncomingTransfer;
|
import monero.wallet.model.MoneroIncomingTransfer;
|
||||||
import monero.wallet.model.MoneroOutputQuery;
|
import monero.wallet.model.MoneroOutputQuery;
|
||||||
|
import monero.wallet.model.MoneroOutputWallet;
|
||||||
import monero.wallet.model.MoneroTransferQuery;
|
import monero.wallet.model.MoneroTransferQuery;
|
||||||
import monero.wallet.model.MoneroTxConfig;
|
import monero.wallet.model.MoneroTxConfig;
|
||||||
import monero.wallet.model.MoneroTxQuery;
|
import monero.wallet.model.MoneroTxQuery;
|
||||||
@ -852,10 +854,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasAvailableOutput(BigInteger amount) {
|
|
||||||
return findSplitOutputFundingTx(getOpenOffers(), null, amount, null) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Place offer helpers
|
// Place offer helpers
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -929,7 +927,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
if (openOffer.isReserveExactAmount()) {
|
if (openOffer.isReserveExactAmount()) {
|
||||||
|
|
||||||
// find tx with exact input amount
|
// find tx with exact input amount
|
||||||
MoneroTxWallet splitOutputTx = findSplitOutputFundingTx(openOffers, openOffer);
|
MoneroTxWallet splitOutputTx = getSplitOutputFundingTx(openOffers, openOffer);
|
||||||
if (splitOutputTx != null && openOffer.getSplitOutputTxHash() == null) {
|
if (splitOutputTx != null && openOffer.getSplitOutputTxHash() == null) {
|
||||||
setSplitOutputTx(openOffer, splitOutputTx);
|
setSplitOutputTx(openOffer, splitOutputTx);
|
||||||
}
|
}
|
||||||
@ -965,89 +963,62 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private MoneroTxWallet findSplitOutputFundingTx(List<OpenOffer> openOffers, OpenOffer openOffer) {
|
private MoneroTxWallet getSplitOutputFundingTx(List<OpenOffer> openOffers, OpenOffer openOffer) {
|
||||||
XmrAddressEntry addressEntry = xmrWalletService.getOrCreateAddressEntry(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING);
|
XmrAddressEntry addressEntry = xmrWalletService.getOrCreateAddressEntry(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING);
|
||||||
return findSplitOutputFundingTx(openOffers, openOffer, openOffer.getOffer().getAmountNeeded(), addressEntry.getSubaddressIndex());
|
return getSplitOutputFundingTx(openOffers, openOffer, openOffer.getOffer().getAmountNeeded(), addressEntry.getSubaddressIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
private MoneroTxWallet findSplitOutputFundingTx(List<OpenOffer> openOffers, OpenOffer openOffer, BigInteger reserveAmount, Integer preferredSubaddressIndex) {
|
private MoneroTxWallet getSplitOutputFundingTx(List<OpenOffer> openOffers, OpenOffer openOffer, BigInteger reserveAmount, Integer preferredSubaddressIndex) {
|
||||||
List<MoneroTxWallet> fundingTxs = new ArrayList<>();
|
|
||||||
MoneroTxWallet earliestUnscheduledTx = null;
|
|
||||||
|
|
||||||
// return split output tx if already assigned
|
// return split output tx if already assigned
|
||||||
if (openOffer != null && openOffer.getSplitOutputTxHash() != null) {
|
if (openOffer != null && openOffer.getSplitOutputTxHash() != null) {
|
||||||
return xmrWalletService.getTx(openOffer.getSplitOutputTxHash());
|
return xmrWalletService.getTx(openOffer.getSplitOutputTxHash());
|
||||||
}
|
}
|
||||||
|
|
||||||
// return earliest tx with exact amount to offer's subaddress if available
|
// get split output tx to offer's preferred subaddress
|
||||||
if (preferredSubaddressIndex != null) {
|
if (preferredSubaddressIndex != null) {
|
||||||
|
List<MoneroTxWallet> fundingTxs = getSplitOutputFundingTxs(reserveAmount, preferredSubaddressIndex);
|
||||||
|
MoneroTxWallet earliestUnscheduledTx = getEarliestUnscheduledTx(openOffers, openOffer, fundingTxs);
|
||||||
|
if (earliestUnscheduledTx != null) return earliestUnscheduledTx;
|
||||||
|
}
|
||||||
|
|
||||||
// get txs with exact output amount
|
// get split output tx to any subaddress
|
||||||
fundingTxs = xmrWalletService.getTxs(new MoneroTxQuery()
|
List<MoneroTxWallet> fundingTxs = getSplitOutputFundingTxs(reserveAmount, null);
|
||||||
.setIsConfirmed(true)
|
return getEarliestUnscheduledTx(openOffers, openOffer, fundingTxs);
|
||||||
.setOutputQuery(new MoneroOutputQuery()
|
}
|
||||||
|
|
||||||
|
private List<MoneroTxWallet> getSplitOutputFundingTxs(BigInteger reserveAmount, Integer preferredSubaddressIndex) {
|
||||||
|
List<MoneroTxWallet> splitOutputTxs = xmrWalletService.getTxs(new MoneroTxQuery().setIsIncoming(true).setIsFailed(false));
|
||||||
|
Set<MoneroTxWallet> removeTxs = new HashSet<MoneroTxWallet>();
|
||||||
|
for (MoneroTxWallet tx : splitOutputTxs) {
|
||||||
|
if (tx.getOutputs() != null) { // outputs not available until first confirmation
|
||||||
|
for (MoneroOutputWallet output : tx.getOutputsWallet()) {
|
||||||
|
if (output.isSpent() || output.isFrozen()) removeTxs.add(tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasExactAmount(tx, reserveAmount, preferredSubaddressIndex)) removeTxs.add(tx);
|
||||||
|
}
|
||||||
|
splitOutputTxs.removeAll(removeTxs);
|
||||||
|
return splitOutputTxs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasExactAmount(MoneroTxWallet tx, BigInteger amount, Integer preferredSubaddressIndex) {
|
||||||
|
boolean hasExactOutput = (tx.getOutputsWallet(new MoneroOutputQuery()
|
||||||
.setAccountIndex(0)
|
.setAccountIndex(0)
|
||||||
.setSubaddressIndex(preferredSubaddressIndex)
|
.setSubaddressIndex(preferredSubaddressIndex)
|
||||||
.setAmount(reserveAmount)
|
.setAmount(amount)).size() > 0);
|
||||||
.setIsSpent(false)
|
if (hasExactOutput) return true;
|
||||||
.setIsFrozen(false)));
|
boolean hasExactTransfer = (tx.getTransfers(new MoneroTransferQuery()
|
||||||
|
|
||||||
// return earliest tx if available
|
|
||||||
earliestUnscheduledTx = getEarliestUnscheduledTx(openOffers, fundingTxs);
|
|
||||||
if (earliestUnscheduledTx != null) return earliestUnscheduledTx;
|
|
||||||
}
|
|
||||||
|
|
||||||
// return if awaiting scheduled tx
|
|
||||||
if (openOffer.getScheduledTxHashes() != null) return null;
|
|
||||||
|
|
||||||
// get all transactions including from pool
|
|
||||||
List<MoneroTxWallet> allTxs = xmrWalletService.getTxs(false);
|
|
||||||
|
|
||||||
if (preferredSubaddressIndex != null) {
|
|
||||||
|
|
||||||
// return earliest tx with exact incoming transfer to fund offer's subaddress if available (since outputs are not available until confirmed)
|
|
||||||
fundingTxs.clear();
|
|
||||||
for (MoneroTxWallet tx : allTxs) {
|
|
||||||
boolean hasExactTransfer = tx.getTransfers(new MoneroTransferQuery()
|
|
||||||
.setIsIncoming(true)
|
|
||||||
.setAccountIndex(0)
|
.setAccountIndex(0)
|
||||||
.setSubaddressIndex(preferredSubaddressIndex)
|
.setSubaddressIndex(preferredSubaddressIndex)
|
||||||
.setAmount(reserveAmount)).size() > 0;
|
.setAmount(amount)).size() > 0);
|
||||||
if (hasExactTransfer) fundingTxs.add(tx);
|
return hasExactTransfer;
|
||||||
}
|
|
||||||
earliestUnscheduledTx = getEarliestUnscheduledTx(openOffers, fundingTxs);
|
|
||||||
if (earliestUnscheduledTx != null) return earliestUnscheduledTx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// return earliest tx with exact confirmed output to any subaddress if available
|
private MoneroTxWallet getEarliestUnscheduledTx(List<OpenOffer> openOffers, OpenOffer excludeOpenOffer, List<MoneroTxWallet> txs) {
|
||||||
fundingTxs.clear();
|
|
||||||
for (MoneroTxWallet tx : allTxs) {
|
|
||||||
boolean hasExactOutput = tx.getOutputsWallet(new MoneroOutputQuery()
|
|
||||||
.setAccountIndex(0)
|
|
||||||
.setAmount(reserveAmount)
|
|
||||||
.setIsSpent(false)
|
|
||||||
.setIsFrozen(false)).size() > 0;
|
|
||||||
if (hasExactOutput) fundingTxs.add(tx);
|
|
||||||
}
|
|
||||||
earliestUnscheduledTx = getEarliestUnscheduledTx(openOffers, fundingTxs);
|
|
||||||
if (earliestUnscheduledTx != null) return earliestUnscheduledTx;
|
|
||||||
|
|
||||||
// return earliest tx with exact incoming transfer to any subaddress if available (since outputs are not available until confirmed)
|
|
||||||
fundingTxs.clear();
|
|
||||||
for (MoneroTxWallet tx : allTxs) {
|
|
||||||
boolean hasExactTransfer = tx.getTransfers(new MoneroTransferQuery()
|
|
||||||
.setIsIncoming(true)
|
|
||||||
.setAccountIndex(0)
|
|
||||||
.setAmount(reserveAmount)).size() > 0;
|
|
||||||
if (hasExactTransfer) fundingTxs.add(tx);
|
|
||||||
}
|
|
||||||
return getEarliestUnscheduledTx(openOffers, fundingTxs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private MoneroTxWallet getEarliestUnscheduledTx(List<OpenOffer> openOffers, List<MoneroTxWallet> txs) {
|
|
||||||
MoneroTxWallet earliestUnscheduledTx = null;
|
MoneroTxWallet earliestUnscheduledTx = null;
|
||||||
for (MoneroTxWallet tx : txs) {
|
for (MoneroTxWallet tx : txs) {
|
||||||
if (isTxScheduled(openOffers, tx.getHash())) continue;
|
if (isTxScheduledByOtherOffer(openOffers, excludeOpenOffer, tx.getHash())) continue;
|
||||||
if (earliestUnscheduledTx == null || (earliestUnscheduledTx.getNumConfirmations() < tx.getNumConfirmations())) earliestUnscheduledTx = tx;
|
if (earliestUnscheduledTx == null || (earliestUnscheduledTx.getNumConfirmations() < tx.getNumConfirmations())) earliestUnscheduledTx = tx;
|
||||||
}
|
}
|
||||||
return earliestUnscheduledTx;
|
return earliestUnscheduledTx;
|
||||||
@ -1121,7 +1092,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
List<String> scheduledTxHashes = new ArrayList<String>();
|
List<String> scheduledTxHashes = new ArrayList<String>();
|
||||||
BigInteger scheduledAmount = BigInteger.ZERO;
|
BigInteger scheduledAmount = BigInteger.ZERO;
|
||||||
for (MoneroTxWallet lockedTx : lockedTxs) {
|
for (MoneroTxWallet lockedTx : lockedTxs) {
|
||||||
if (isTxScheduled(openOffers, lockedTx.getHash())) continue;
|
if (isTxScheduledByOtherOffer(openOffers, openOffer, lockedTx.getHash())) continue;
|
||||||
if (lockedTx.getIncomingTransfers() == null || lockedTx.getIncomingTransfers().isEmpty()) continue;
|
if (lockedTx.getIncomingTransfers() == null || lockedTx.getIncomingTransfers().isEmpty()) continue;
|
||||||
scheduledTxHashes.add(lockedTx.getHash());
|
scheduledTxHashes.add(lockedTx.getHash());
|
||||||
for (MoneroIncomingTransfer transfer : lockedTx.getIncomingTransfers()) {
|
for (MoneroIncomingTransfer transfer : lockedTx.getIncomingTransfers()) {
|
||||||
@ -1154,11 +1125,12 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
return scheduledAmount;
|
return scheduledAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isTxScheduled(List<OpenOffer> openOffers, String txHash) {
|
private boolean isTxScheduledByOtherOffer(List<OpenOffer> openOffers, OpenOffer openOffer, String txHash) {
|
||||||
for (OpenOffer openOffer : openOffers) {
|
for (OpenOffer otherOffer : openOffers) {
|
||||||
if (openOffer.getState() != OpenOffer.State.SCHEDULED) continue;
|
if (otherOffer == openOffer) continue;
|
||||||
if (openOffer.getScheduledTxHashes() == null) continue;
|
if (otherOffer.getState() != OpenOffer.State.SCHEDULED) continue;
|
||||||
for (String scheduledTxHash : openOffer.getScheduledTxHashes()) {
|
if (otherOffer.getScheduledTxHashes() == null) continue;
|
||||||
|
for (String scheduledTxHash : otherOffer.getScheduledTxHashes()) {
|
||||||
if (txHash.equals(scheduledTxHash)) return true;
|
if (txHash.equals(scheduledTxHash)) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -466,11 +466,6 @@ public abstract class MutableOfferDataModel extends OfferDataModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasAvailableSplitOutput() {
|
|
||||||
BigInteger reserveAmount = totalToPay.get();
|
|
||||||
return openOfferManager.hasAvailableOutput(reserveAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Utils
|
// Utils
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
Loading…
Reference in New Issue
Block a user