synchronize on wallet lock in XmrWalletService

This commit is contained in:
woodser 2023-07-25 11:15:16 -04:00
parent 426d25f78c
commit c39c5cf387

View file

@ -111,6 +111,7 @@ public class XmrWalletService {
private TradeManager tradeManager; private TradeManager tradeManager;
private MoneroWalletRpc wallet; private MoneroWalletRpc wallet;
private Object walletLock = new Object();
private final Map<String, Optional<MoneroTx>> txCache = new HashMap<String, Optional<MoneroTx>>(); private final Map<String, Optional<MoneroTx>> txCache = new HashMap<String, Optional<MoneroTx>>();
private boolean isShutDownStarted = false; private boolean isShutDownStarted = false;
private ExecutorService syncWalletThreadPool = Executors.newFixedThreadPool(10); // TODO: adjust based on connection type private ExecutorService syncWalletThreadPool = Executors.newFixedThreadPool(10); // TODO: adjust based on connection type
@ -299,14 +300,14 @@ public class XmrWalletService {
} }
public MoneroTxWallet createTx(List<MoneroDestination> destinations) { public MoneroTxWallet createTx(List<MoneroDestination> destinations) {
try { synchronized (walletLock) {
synchronized (wallet) { try {
MoneroTxWallet tx = wallet.createTx(new MoneroTxConfig().setAccountIndex(0).setDestinations(destinations).setRelay(false).setCanSplit(false)); MoneroTxWallet tx = wallet.createTx(new MoneroTxConfig().setAccountIndex(0).setDestinations(destinations).setRelay(false).setCanSplit(false));
//printTxs("XmrWalletService.createTx", tx); //printTxs("XmrWalletService.createTx", tx);
return tx; return tx;
} catch (Exception e) {
throw e;
} }
} catch (Exception e) {
throw e;
} }
} }
@ -316,7 +317,7 @@ public class XmrWalletService {
* @param keyImages the key images to thaw * @param keyImages the key images to thaw
*/ */
public void thawOutputs(Collection<String> keyImages) { public void thawOutputs(Collection<String> keyImages) {
synchronized (getWallet()) { synchronized (walletLock) {
for (String keyImage : keyImages) wallet.thawOutput(keyImage); for (String keyImage : keyImages) wallet.thawOutput(keyImage);
} }
} }
@ -365,13 +366,7 @@ public class XmrWalletService {
* @return MoneroTxWallet the multisig deposit tx * @return MoneroTxWallet the multisig deposit tx
*/ */
public MoneroTxWallet createDepositTx(Trade trade, boolean reserveExactAmount, Integer preferredSubaddressIndex) { public MoneroTxWallet createDepositTx(Trade trade, boolean reserveExactAmount, Integer preferredSubaddressIndex) {
Offer offer = trade.getProcessModel().getOffer(); synchronized (walletLock) {
String multisigAddress = trade.getProcessModel().getMultisigAddress();
BigInteger tradeFee = trade instanceof MakerTrade ? trade.getOffer().getMakerFee() : trade.getTakerFee();
BigInteger sendAmount = trade instanceof BuyerTrade ? BigInteger.valueOf(0) : offer.getAmount();
BigInteger securityDeposit = trade instanceof BuyerTrade ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit();
MoneroWallet wallet = getWallet();
synchronized (wallet) {
// thaw reserved outputs // thaw reserved outputs
if (trade.getSelf().getReserveTxKeyImages() != null) { if (trade.getSelf().getReserveTxKeyImages() != null) {
@ -379,6 +374,11 @@ public class XmrWalletService {
} }
// create deposit tx // create deposit tx
Offer offer = trade.getProcessModel().getOffer();
String multisigAddress = trade.getProcessModel().getMultisigAddress();
BigInteger tradeFee = trade instanceof MakerTrade ? trade.getOffer().getMakerFee() : trade.getTakerFee();
BigInteger sendAmount = trade instanceof BuyerTrade ? BigInteger.valueOf(0) : offer.getAmount();
BigInteger securityDeposit = trade instanceof BuyerTrade ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit();
long time = System.currentTimeMillis(); long time = System.currentTimeMillis();
log.info("Creating deposit tx with multisig address={}", multisigAddress); log.info("Creating deposit tx with multisig address={}", multisigAddress);
MoneroTxWallet depositTx = createTradeTx(tradeFee, sendAmount, securityDeposit, multisigAddress, false, reserveExactAmount, preferredSubaddressIndex); MoneroTxWallet depositTx = createTradeTx(tradeFee, sendAmount, securityDeposit, multisigAddress, false, reserveExactAmount, preferredSubaddressIndex);
@ -388,8 +388,8 @@ public class XmrWalletService {
} }
private MoneroTxWallet createTradeTx(BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String address, boolean isReserveTx, boolean reserveExactAmount, Integer preferredSubaddressIndex) { private MoneroTxWallet createTradeTx(BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String address, boolean isReserveTx, boolean reserveExactAmount, Integer preferredSubaddressIndex) {
MoneroWallet wallet = getWallet(); synchronized (walletLock) {
synchronized (wallet) { MoneroWallet wallet = getWallet();
// create a list of subaddresses to attempt spending from in preferred order // create a list of subaddresses to attempt spending from in preferred order
List<Integer> subaddressIndices = new ArrayList<Integer>(); List<Integer> subaddressIndices = new ArrayList<Integer>();
@ -425,29 +425,29 @@ public class XmrWalletService {
private MoneroTxWallet createTradeTxFromSubaddress(BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String address, boolean isReserveTx, boolean reserveExactAmount, Integer subaddressIndex) { private MoneroTxWallet createTradeTxFromSubaddress(BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String address, boolean isReserveTx, boolean reserveExactAmount, Integer subaddressIndex) {
// create tx // create tx
MoneroTxWallet tradeTx = wallet.createTx(new MoneroTxConfig() MoneroTxWallet tradeTx = wallet.createTx(new MoneroTxConfig()
.setAccountIndex(0) .setAccountIndex(0)
.setSubaddressIndices(subaddressIndex) .setSubaddressIndices(subaddressIndex)
.addDestination(HavenoUtils.getTradeFeeAddress(), isReserveTx ? securityDeposit : tradeFee) // reserve tx charges security deposit if published .addDestination(HavenoUtils.getTradeFeeAddress(), isReserveTx ? securityDeposit : tradeFee) // reserve tx charges security deposit if published
.addDestination(address, sendAmount.add(isReserveTx ? tradeFee : securityDeposit)) .addDestination(address, sendAmount.add(isReserveTx ? tradeFee : securityDeposit))
.setSubtractFeeFrom(isReserveTx ? 0 : 1)); // pay fee from same destination as security deposit .setSubtractFeeFrom(isReserveTx ? 0 : 1)); // pay fee from same destination as security deposit
// check if tx uses exact input, since wallet2 can prefer to spend 2 outputs // check if tx uses exact input, since wallet2 can prefer to spend 2 outputs
if (reserveExactAmount) { if (reserveExactAmount) {
BigInteger exactInputAmount = tradeFee.add(sendAmount).add(securityDeposit); BigInteger exactInputAmount = tradeFee.add(sendAmount).add(securityDeposit);
BigInteger inputSum = BigInteger.valueOf(0); BigInteger inputSum = BigInteger.valueOf(0);
for (MoneroOutputWallet txInput : tradeTx.getInputsWallet()) { for (MoneroOutputWallet txInput : tradeTx.getInputsWallet()) {
MoneroOutputWallet input = wallet.getOutputs(new MoneroOutputQuery().setKeyImage(txInput.getKeyImage())).get(0); MoneroOutputWallet input = wallet.getOutputs(new MoneroOutputQuery().setKeyImage(txInput.getKeyImage())).get(0);
inputSum = inputSum.add(input.getAmount()); inputSum = inputSum.add(input.getAmount());
}
if (inputSum.compareTo(exactInputAmount) > 0) throw new RuntimeException("Cannot create transaction with exact input amount");
} }
if (inputSum.compareTo(exactInputAmount) > 0) throw new RuntimeException("Cannot create transaction with exact input amount");
}
// freeze inputs // freeze inputs
for (MoneroOutput input : tradeTx.getInputs()) wallet.freezeOutput(input.getKeyImage().getHex()); for (MoneroOutput input : tradeTx.getInputs()) wallet.freezeOutput(input.getKeyImage().getHex());
saveMainWallet(); saveMainWallet();
return tradeTx; return tradeTx;
} }
/** /**
@ -814,35 +814,35 @@ public class XmrWalletService {
} }
private void onConnectionChanged(MoneroRpcConnection connection) { private void onConnectionChanged(MoneroRpcConnection connection) {
if (isShutDownStarted) return; synchronized (walletLock) {
if (wallet != null && HavenoUtils.connectionConfigsEqual(connection, wallet.getDaemonConnection())) return; if (isShutDownStarted) return;
if (wallet != null && HavenoUtils.connectionConfigsEqual(connection, wallet.getDaemonConnection())) return;
log.info("Setting main wallet daemon connection: " + (connection == null ? null : connection.getUri())); log.info("Setting main wallet daemon connection: " + (connection == null ? null : connection.getUri()));
String oldProxyUri = wallet == null || wallet.getDaemonConnection() == null ? null : wallet.getDaemonConnection().getProxyUri(); String oldProxyUri = wallet == null || wallet.getDaemonConnection() == null ? null : wallet.getDaemonConnection().getProxyUri();
String newProxyUri = connection == null ? null : connection.getProxyUri(); String newProxyUri = connection == null ? null : connection.getProxyUri();
if (wallet == null) maybeInitMainWallet(); if (wallet == null) maybeInitMainWallet();
else if (wallet instanceof MoneroWalletRpc && !StringUtils.equals(oldProxyUri, newProxyUri)) { else if (wallet instanceof MoneroWalletRpc && !StringUtils.equals(oldProxyUri, newProxyUri)) {
log.info("Restarting main wallet since proxy URI has changed"); log.info("Restarting main wallet since proxy URI has changed");
synchronized (wallet) {
closeMainWallet(true); closeMainWallet(true);
maybeInitMainWallet(); maybeInitMainWallet();
} else {
wallet.setDaemonConnection(connection);
if (connection != null) {
wallet.getDaemonConnection().setPrintStackTrace(PRINT_STACK_TRACE);
new Thread(() -> {
try {
wallet.startSyncing(connectionsService.getRefreshPeriodMs());
if (!Boolean.FALSE.equals(connection.isConnected())) wallet.sync();
} catch (Exception e) {
log.warn("Failed to sync main wallet after setting daemon connection: " + e.getMessage());
}
}).start();
}
} }
} else {
wallet.setDaemonConnection(connection);
if (connection != null) {
wallet.getDaemonConnection().setPrintStackTrace(PRINT_STACK_TRACE);
new Thread(() -> {
try {
wallet.startSyncing(connectionsService.getRefreshPeriodMs());
if (!Boolean.FALSE.equals(connection.isConnected())) wallet.sync();
} catch (Exception e) {
log.warn("Failed to sync main wallet after setting daemon connection: " + e.getMessage());
}
}).start();
}
}
log.info("Done setting main wallet daemon connection: " + (connection == null ? null : connection.getUri())); log.info("Done setting main wallet daemon connection: " + (connection == null ? null : connection.getUri()));
}
} }
private void notifyBalanceListeners() { private void notifyBalanceListeners() {
@ -1110,27 +1110,27 @@ public class XmrWalletService {
} }
public BigInteger getBalanceForSubaddress(int subaddressIndex) { public BigInteger getBalanceForSubaddress(int subaddressIndex) {
synchronized (wallet) { synchronized (walletLock) {
return wallet.getBalance(0, subaddressIndex); return wallet.getBalance(0, subaddressIndex);
} }
} }
public BigInteger getAvailableBalanceForSubaddress(int subaddressIndex) { public BigInteger getAvailableBalanceForSubaddress(int subaddressIndex) {
synchronized (wallet) { synchronized (walletLock) {
return wallet.getUnlockedBalance(0, subaddressIndex); return wallet.getUnlockedBalance(0, subaddressIndex);
} }
} }
public BigInteger getBalance() { public BigInteger getBalance() {
if (wallet == null) return BigInteger.valueOf(0); if (wallet == null) return BigInteger.valueOf(0);
synchronized (wallet) { synchronized (walletLock) {
return wallet.getBalance(0); return wallet.getBalance(0);
} }
} }
public BigInteger getAvailableBalance() { public BigInteger getAvailableBalance() {
if (wallet == null) return BigInteger.valueOf(0); if (wallet == null) return BigInteger.valueOf(0);
synchronized (wallet) { synchronized (walletLock) {
return wallet.getUnlockedBalance(0); return wallet.getUnlockedBalance(0);
} }
} }