mirror of
https://github.com/haveno-dex/haveno.git
synced 2024-11-17 00:07:49 +00:00
fix potential double spend by locking wallet when thawing outputs
This commit is contained in:
parent
3a50397a61
commit
e5cf2f8429
4 changed files with 41 additions and 25 deletions
|
@ -30,6 +30,7 @@ import java.math.BigDecimal;
|
|||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -287,6 +288,17 @@ public class XmrWalletService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thaw the given outputs with a lock on the wallet.
|
||||
*
|
||||
* @param keyImages the key images to thaw
|
||||
*/
|
||||
public void thawOutputs(Collection<String> keyImages) {
|
||||
synchronized (getWallet()) {
|
||||
for (String keyImage : keyImages) wallet.thawOutput(keyImage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the reserve tx and freeze its inputs. The full amount is returned
|
||||
* to the sender's payout address less the trade fee.
|
||||
|
@ -314,8 +326,19 @@ public class XmrWalletService {
|
|||
BigInteger tradeFee = HavenoUtils.coinToAtomicUnits(trade instanceof MakerTrade ? trade.getOffer().getMakerFee() : trade.getTakerFee());
|
||||
BigInteger peerAmount = HavenoUtils.coinToAtomicUnits(trade instanceof BuyerTrade ? Coin.ZERO : offer.getAmount());
|
||||
BigInteger securityDeposit = HavenoUtils.coinToAtomicUnits(trade instanceof BuyerTrade ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit());
|
||||
log.info("Creating deposit tx with fee={}, peerAmount={}, securityDeposit={}", tradeFee, peerAmount, securityDeposit);
|
||||
return createTradeTx(tradeFee, peerAmount, securityDeposit, multisigAddress);
|
||||
|
||||
// thaw reserved outputs then create deposit tx
|
||||
MoneroWallet wallet = getWallet();
|
||||
synchronized (wallet) {
|
||||
|
||||
// thaw reserved outputs
|
||||
if (trade.getSelf().getReserveTxKeyImages() != null) {
|
||||
thawOutputs(trade.getSelf().getReserveTxKeyImages());
|
||||
}
|
||||
|
||||
log.info("Creating deposit tx with fee={}, peerAmount={}, securityDeposit={}", tradeFee, peerAmount, securityDeposit);
|
||||
return createTradeTx(tradeFee, peerAmount, securityDeposit, multisigAddress);
|
||||
}
|
||||
}
|
||||
|
||||
private MoneroTxWallet createTradeTx(BigInteger tradeFee, BigInteger peerAmount, BigInteger securityDeposit, String address) {
|
||||
|
|
|
@ -587,7 +587,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
private void onRemoved(@NotNull OpenOffer openOffer) {
|
||||
Offer offer = openOffer.getOffer();
|
||||
if (offer.getOfferPayload().getReserveTxKeyImages() != null) {
|
||||
for (String frozenKeyImage : offer.getOfferPayload().getReserveTxKeyImages()) xmrWalletService.getWallet().thawOutput(frozenKeyImage);
|
||||
xmrWalletService.thawOutputs(offer.getOfferPayload().getReserveTxKeyImages());
|
||||
xmrWalletService.saveMainWallet();
|
||||
}
|
||||
offer.setState(Offer.State.REMOVED);
|
||||
|
|
|
@ -306,18 +306,18 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
}
|
||||
|
||||
// thaw unreserved outputs
|
||||
Set<String> frozenKeyImages = xmrWalletService.getWallet().getOutputs(new MoneroOutputQuery()
|
||||
Set<String> unreservedFrozenKeyImages = xmrWalletService.getWallet().getOutputs(new MoneroOutputQuery()
|
||||
.setIsFrozen(true)
|
||||
.setIsSpent(false))
|
||||
.stream()
|
||||
.map(output -> output.getKeyImage().getHex())
|
||||
.collect(Collectors.toSet());
|
||||
frozenKeyImages.removeAll(reservedKeyImages);
|
||||
for (String unreservedFrozenKeyImage : frozenKeyImages) {
|
||||
log.info("Thawing output which is not reserved for offer or trade: " + unreservedFrozenKeyImage);
|
||||
xmrWalletService.getWallet().thawOutput(unreservedFrozenKeyImage);
|
||||
unreservedFrozenKeyImages.removeAll(reservedKeyImages);
|
||||
if (!unreservedFrozenKeyImages.isEmpty()) {
|
||||
log.info("Thawing outputs which are not reserved for offer or trade: " + unreservedFrozenKeyImages);
|
||||
xmrWalletService.thawOutputs(unreservedFrozenKeyImages);
|
||||
xmrWalletService.saveMainWallet();
|
||||
}
|
||||
if (!frozenKeyImages.isEmpty()) xmrWalletService.saveMainWallet();
|
||||
}
|
||||
|
||||
public TradeProtocol getTradeProtocol(Trade trade) {
|
||||
|
@ -1059,21 +1059,22 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||
synchronized(tradableList) {
|
||||
if (!tradableList.contains(trade)) return;
|
||||
|
||||
// skip if trade wallet possibly funded
|
||||
// unreserve key images
|
||||
if (trade instanceof TakerTrade && trade.getSelf().getReserveTxKeyImages() != null) {
|
||||
xmrWalletService.thawOutputs(trade.getSelf().getReserveTxKeyImages());
|
||||
xmrWalletService.saveMainWallet();
|
||||
trade.getSelf().setReserveTxKeyImages(null);
|
||||
}
|
||||
|
||||
// stop if trade wallet possibly funded
|
||||
if (xmrWalletService.multisigWalletExists(trade.getId()) && trade.isDepositRequested()) {
|
||||
log.warn("Not removing trade {} because trade wallet could be funded", trade.getId());
|
||||
log.warn("Refusing to delete {} {} because trade wallet could be funded", trade.getClass().getSimpleName(), trade.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// delete trade wallet if exists
|
||||
if (xmrWalletService.multisigWalletExists(trade.getId())) trade.deleteWallet();
|
||||
|
||||
// unreserve key images
|
||||
if (trade instanceof TakerTrade && trade.getSelf().getReserveTxKeyImages() != null) {
|
||||
for (String keyImage : trade.getSelf().getReserveTxKeyImages()) xmrWalletService.getWallet().thawOutput(keyImage);
|
||||
xmrWalletService.saveMainWallet();
|
||||
}
|
||||
|
||||
// remove trade
|
||||
removeTrade(trade);
|
||||
}
|
||||
|
|
|
@ -66,17 +66,9 @@ public class MaybeSendSignContractRequest extends TradeTask {
|
|||
return;
|
||||
}
|
||||
|
||||
// thaw reserved outputs
|
||||
MoneroWallet wallet = trade.getXmrWalletService().getWallet();
|
||||
for (String reserveTxKeyImage : trade.getSelf().getReserveTxKeyImages()) {
|
||||
wallet.thawOutput(reserveTxKeyImage);
|
||||
}
|
||||
|
||||
// create deposit tx and freeze inputs
|
||||
MoneroTxWallet depositTx = trade.getXmrWalletService().createDepositTx(trade);
|
||||
|
||||
// TODO (woodser): save frozen key images and unfreeze if trade fails before deposited to multisig
|
||||
|
||||
// save process state
|
||||
processModel.setDepositTxXmr(depositTx); // TODO: trade.getSelf().setDepositTx()
|
||||
trade.getSelf().setDepositTxHash(depositTx.getHash());
|
||||
|
|
Loading…
Reference in a new issue