fix bugs resetting trade payout address entries

This commit is contained in:
woodser 2023-07-27 08:02:03 -04:00
parent aa36518f69
commit 900d3a91e1
6 changed files with 71 additions and 73 deletions

View file

@ -667,7 +667,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
getOpenOfferById(offer.getId()).ifPresent(openOffer -> { getOpenOfferById(offer.getId()).ifPresent(openOffer -> {
removeOpenOffer(openOffer); removeOpenOffer(openOffer);
openOffer.setState(OpenOffer.State.CLOSED); openOffer.setState(OpenOffer.State.CLOSED);
xmrWalletService.resetOfferFundingForOpenOffer(offer.getId()); xmrWalletService.resetAddressEntriesForOpenOffer(offer.getId());
offerBookService.removeOffer(openOffer.getOffer().getOfferPayload(), offerBookService.removeOffer(openOffer.getOffer().getOfferPayload(),
() -> log.info("Successfully removed offer {}", offer.getId()), () -> log.info("Successfully removed offer {}", offer.getId()),
log::error); 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 // Place offer helpers
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -811,7 +815,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// find tx with exact input amount // find tx with exact input amount
MoneroTxWallet splitOutputTx = findSplitOutputFundingTx(openOffers, openOffer); MoneroTxWallet splitOutputTx = findSplitOutputFundingTx(openOffers, openOffer);
if (openOffer.getScheduledTxHashes() == null && splitOutputTx != null) { if (splitOutputTx != null && openOffer.getScheduledTxHashes() == null) {
openOffer.setScheduledTxHashes(Arrays.asList(splitOutputTx.getHash())); openOffer.setScheduledTxHashes(Arrays.asList(splitOutputTx.getHash()));
openOffer.setSplitOutputTxHash(splitOutputTx.getHash()); openOffer.setSplitOutputTxHash(splitOutputTx.getHash());
openOffer.setScheduledAmount(offerReserveAmount.toString()); 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 // 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(); int offerSubaddress = xmrWalletService.getOrCreateAddressEntry(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING).getSubaddressIndex();
if (!splitOutputTx.getOutgoingTransfer().getSubaddressIndices().equals(Arrays.asList(offerSubaddress))) { 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); splitOrSchedule(openOffers, openOffer, offerReserveAmount);
resultHandler.handleResult(null); resultHandler.handleResult(null);
} else { } else {
@ -846,7 +850,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
signAndPostOffer(openOffer, true, resultHandler, errorMessageHandler); signAndPostOffer(openOffer, true, resultHandler, errorMessageHandler);
return; return;
} else if (openOffer.getScheduledTxHashes() == null) { } else if (openOffer.getScheduledTxHashes() == null) {
scheduleOfferWithEarliestTxs(openOffers, openOffer); scheduleWithEarliestTxs(openOffers, openOffer);
} }
} }
@ -860,29 +864,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
}).start(); }).start();
} }
private void splitOrSchedule(List<OpenOffer> 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<OpenOffer> openOffers, OpenOffer openOffer) { private MoneroTxWallet findSplitOutputFundingTx(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().getReserveAmount(), addressEntry.getSubaddressIndex()); return findSplitOutputFundingTx(openOffers, openOffer, openOffer.getOffer().getReserveAmount(), addressEntry.getSubaddressIndex());
@ -968,7 +949,38 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
return earliestUnscheduledTx; return earliestUnscheduledTx;
} }
private void scheduleOfferWithEarliestTxs(List<OpenOffer> openOffers, OpenOffer openOffer) { private void splitOrSchedule(List<OpenOffer> 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<OpenOffer> openOffers, OpenOffer openOffer) {
// check for sufficient balance - scheduled offers amount // check for sufficient balance - scheduled offers amount
BigInteger offerReserveAmount = openOffer.getOffer().getReserveAmount(); BigInteger offerReserveAmount = openOffer.getOffer().getReserveAmount();
@ -999,20 +1011,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
openOffer.setState(OpenOffer.State.SCHEDULED); 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<OpenOffer> openOffers) { private BigInteger getScheduledAmount(List<OpenOffer> openOffers) {
BigInteger scheduledAmount = BigInteger.valueOf(0); BigInteger scheduledAmount = BigInteger.valueOf(0);
for (OpenOffer openOffer : openOffers) { for (OpenOffer openOffer : openOffers) {

View file

@ -691,7 +691,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
} }
private void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress peer) { 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 { try {
Validator.nonEmptyStringOf(request.getTradeId()); Validator.nonEmptyStringOf(request.getTradeId());
@ -710,7 +710,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
} }
private void handleSignContractRequest(SignContractRequest request, NodeAddress 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 { try {
Validator.nonEmptyStringOf(request.getTradeId()); Validator.nonEmptyStringOf(request.getTradeId());
@ -729,7 +729,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
} }
private void handleSignContractResponse(SignContractResponse request, NodeAddress peer) { 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 { try {
Validator.nonEmptyStringOf(request.getTradeId()); Validator.nonEmptyStringOf(request.getTradeId());
@ -748,7 +748,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
} }
private void handleDepositRequest(DepositRequest request, NodeAddress peer) { 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 { try {
Validator.nonEmptyStringOf(request.getTradeId()); Validator.nonEmptyStringOf(request.getTradeId());
@ -767,7 +767,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
} }
private void handleDepositResponse(DepositResponse response, NodeAddress peer) { 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 { try {
Validator.nonEmptyStringOf(response.getTradeId()); Validator.nonEmptyStringOf(response.getTradeId());

View file

@ -38,7 +38,7 @@ public class RemoveOffer extends TradeTask {
if (trade instanceof MakerTrade) { if (trade instanceof MakerTrade) {
processModel.getOpenOfferManager().closeOpenOffer(checkNotNull(trade.getOffer())); processModel.getOpenOfferManager().closeOpenOffer(checkNotNull(trade.getOffer()));
} else { } else {
trade.getXmrWalletService().resetOfferFundingForOpenOffer(trade.getId()); trade.getXmrWalletService().resetAddressEntriesForOpenOffer(trade.getId());
} }
complete(); complete();

View file

@ -687,7 +687,7 @@ public class XmrWalletService {
wallet.startSyncing(connectionsService.getRefreshPeriodMs()); wallet.startSyncing(connectionsService.getRefreshPeriodMs());
if (getMoneroNetworkType() != MoneroNetworkType.MAINNET) log.info("Monero wallet balance={}, unlocked balance={}", wallet.getBalance(0), wallet.getUnlockedBalance(0)); 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(); connectionsService.doneDownload();
// notify setup that main wallet is initialized // notify setup that main wallet is initialized
@ -978,12 +978,11 @@ public class XmrWalletService {
public synchronized void resetAddressEntriesForOpenOffer(String offerId) { public synchronized void resetAddressEntriesForOpenOffer(String offerId) {
log.info("resetAddressEntriesForOpenOffer offerId={}", offerId); log.info("resetAddressEntriesForOpenOffer offerId={}", offerId);
swapAddressEntryToAvailable(offerId, XmrAddressEntry.Context.OFFER_FUNDING); swapAddressEntryToAvailable(offerId, XmrAddressEntry.Context.OFFER_FUNDING);
swapAddressEntryToAvailable(offerId, XmrAddressEntry.Context.TRADE_PAYOUT);
}
public synchronized void resetOfferFundingForOpenOffer(String offerId) { // swap trade payout to available if applicable
log.info("resetOfferFundingForOpenOffer offerId={}", offerId); if (tradeManager == null) return;
swapAddressEntryToAvailable(offerId, XmrAddressEntry.Context.OFFER_FUNDING); Trade trade = tradeManager.getTrade(offerId);
if (trade == null || trade.isPayoutUnlocked()) swapAddressEntryToAvailable(offerId, XmrAddressEntry.Context.TRADE_PAYOUT);
} }
public synchronized void resetAddressEntriesForTrade(String offerId) { public synchronized void resetAddressEntriesForTrade(String offerId) {
@ -1136,9 +1135,10 @@ public class XmrWalletService {
} }
public Stream<XmrAddressEntry> getAddressEntriesForAvailableBalanceStream() { public Stream<XmrAddressEntry> getAddressEntriesForAvailableBalanceStream() {
Stream<XmrAddressEntry> availableAndPayout = Stream.concat(getAddressEntries(XmrAddressEntry.Context.TRADE_PAYOUT).stream(), getFundedAvailableAddressEntries().stream()); Stream<XmrAddressEntry> available = getFundedAvailableAddressEntries().stream();
Stream<XmrAddressEntry> available = Stream.concat(availableAndPayout, getAddressEntries(XmrAddressEntry.Context.ARBITRATOR).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.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); return available.filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).compareTo(BigInteger.valueOf(0)) > 0);
} }

View file

@ -1017,8 +1017,8 @@ funds.tab.transactions=Transactions
funds.deposit.unused=Unused funds.deposit.unused=Unused
funds.deposit.usedInTx=Used in {0} transaction(s) funds.deposit.usedInTx=Used in {0} transaction(s)
funds.deposit.baseAddress=Base address funds.deposit.baseAddress=Base address
funds.deposit.offerFunding=Reserved for offer funding funds.deposit.offerFunding=Reserved for offer funding ({0})
funds.deposit.tradePayout=Reserved for trade payout funds.deposit.tradePayout=Reserved for trade payout ({0})
funds.deposit.fundHavenoWallet=Fund Haveno wallet funds.deposit.fundHavenoWallet=Fund Haveno wallet
funds.deposit.noAddresses=No deposit addresses have been generated yet funds.deposit.noAddresses=No deposit addresses have been generated yet
funds.deposit.fundWallet=Fund your wallet funds.deposit.fundWallet=Fund your wallet

View file

@ -104,10 +104,10 @@ class DepositListItem {
usage = numTxsWithOutputs == 0 ? Res.get("funds.deposit.unused") : Res.get("funds.deposit.usedInTx", numTxsWithOutputs); usage = numTxsWithOutputs == 0 ? Res.get("funds.deposit.unused") : Res.get("funds.deposit.usedInTx", numTxsWithOutputs);
break; break;
case OFFER_FUNDING: case OFFER_FUNDING:
usage = Res.get("funds.deposit.offerFunding"); usage = Res.get("funds.deposit.offerFunding", addressEntry.getShortOfferId());
break; break;
case TRADE_PAYOUT: case TRADE_PAYOUT:
usage = Res.get("funds.deposit.tradePayout"); usage = Res.get("funds.deposit.tradePayout", addressEntry.getShortOfferId());
break; break;
default: default:
usage = addressEntry.getContext().toString(); usage = addressEntry.getContext().toString();