mirror of
https://github.com/haveno-dex/haveno.git
synced 2024-12-25 23:39:26 -05:00
refactoring based on congestion testing
retry creating and processing trade txs on failure do not use connection manager polling to reduce requests use global daemon lock for wallet sync operations sync wallets on poll if behind use local util to get payment uri to avoid blocking all peers share multisig hex on deposits confirmed import multisig hex when needed
This commit is contained in:
parent
f519ac12a5
commit
e63141279c
@ -199,15 +199,15 @@ public class CoreApi {
|
|||||||
// Monero Connections
|
// Monero Connections
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void addMoneroConnection(MoneroRpcConnection connection) {
|
public void addXmrConnection(MoneroRpcConnection connection) {
|
||||||
xmrConnectionService.addConnection(connection);
|
xmrConnectionService.addConnection(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeMoneroConnection(String connectionUri) {
|
public void removeXmrConnection(String connectionUri) {
|
||||||
xmrConnectionService.removeConnection(connectionUri);
|
xmrConnectionService.removeConnection(connectionUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MoneroRpcConnection getMoneroConnection() {
|
public MoneroRpcConnection getXmrConnection() {
|
||||||
return xmrConnectionService.getConnection();
|
return xmrConnectionService.getConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,15 +215,15 @@ public class CoreApi {
|
|||||||
return xmrConnectionService.getConnections();
|
return xmrConnectionService.getConnections();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMoneroConnection(String connectionUri) {
|
public void setXmrConnection(String connectionUri) {
|
||||||
xmrConnectionService.setConnection(connectionUri);
|
xmrConnectionService.setConnection(connectionUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMoneroConnection(MoneroRpcConnection connection) {
|
public void setXmrConnection(MoneroRpcConnection connection) {
|
||||||
xmrConnectionService.setConnection(connection);
|
xmrConnectionService.setConnection(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MoneroRpcConnection checkMoneroConnection() {
|
public MoneroRpcConnection checkXmrConnection() {
|
||||||
return xmrConnectionService.checkConnection();
|
return xmrConnectionService.checkConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,19 +231,19 @@ public class CoreApi {
|
|||||||
return xmrConnectionService.checkConnections();
|
return xmrConnectionService.checkConnections();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startCheckingMoneroConnection(Long refreshPeriod) {
|
public void startCheckingXmrConnection(Long refreshPeriod) {
|
||||||
xmrConnectionService.startCheckingConnection(refreshPeriod);
|
xmrConnectionService.startCheckingConnection(refreshPeriod);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stopCheckingMoneroConnection() {
|
public void stopCheckingXmrConnection() {
|
||||||
xmrConnectionService.stopCheckingConnection();
|
xmrConnectionService.stopCheckingConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MoneroRpcConnection getBestAvailableMoneroConnection() {
|
public MoneroRpcConnection getBestAvailableXmrConnection() {
|
||||||
return xmrConnectionService.getBestAvailableConnection();
|
return xmrConnectionService.getBestAvailableConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMoneroConnectionAutoSwitch(boolean autoSwitch) {
|
public void setXmrConnectionAutoSwitch(boolean autoSwitch) {
|
||||||
xmrConnectionService.setAutoSwitch(autoSwitch);
|
xmrConnectionService.setAutoSwitch(autoSwitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,6 +90,7 @@ public final class XmrConnectionService {
|
|||||||
private boolean isInitialized;
|
private boolean isInitialized;
|
||||||
private boolean pollInProgress;
|
private boolean pollInProgress;
|
||||||
private MoneroDaemonRpc daemon;
|
private MoneroDaemonRpc daemon;
|
||||||
|
private Boolean isConnected = false;
|
||||||
@Getter
|
@Getter
|
||||||
private MoneroDaemonInfo lastInfo;
|
private MoneroDaemonInfo lastInfo;
|
||||||
private Long syncStartHeight = null;
|
private Long syncStartHeight = null;
|
||||||
@ -148,7 +149,6 @@ public final class XmrConnectionService {
|
|||||||
isInitialized = false;
|
isInitialized = false;
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
if (daemonPollLooper != null) daemonPollLooper.stop();
|
if (daemonPollLooper != null) daemonPollLooper.stop();
|
||||||
connectionManager.stopPolling();
|
|
||||||
daemon = null;
|
daemon = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -171,7 +171,7 @@ public final class XmrConnectionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Boolean isConnected() {
|
public Boolean isConnected() {
|
||||||
return connectionManager.isConnected();
|
return isConnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addConnection(MoneroRpcConnection connection) {
|
public void addConnection(MoneroRpcConnection connection) {
|
||||||
@ -196,6 +196,12 @@ public final class XmrConnectionService {
|
|||||||
return connectionManager.getConnections();
|
return connectionManager.getConnections();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void switchToBestConnection() {
|
||||||
|
if (isFixedConnection() || !connectionManager.getAutoSwitch()) return;
|
||||||
|
MoneroRpcConnection bestConnection = getBestAvailableConnection();
|
||||||
|
if (bestConnection != null) setConnection(bestConnection);
|
||||||
|
}
|
||||||
|
|
||||||
public void setConnection(String connectionUri) {
|
public void setConnection(String connectionUri) {
|
||||||
accountService.checkAccountOpen();
|
accountService.checkAccountOpen();
|
||||||
connectionManager.setConnection(connectionUri); // listener will update connection list
|
connectionManager.setConnection(connectionUri); // listener will update connection list
|
||||||
@ -226,8 +232,8 @@ public final class XmrConnectionService {
|
|||||||
|
|
||||||
public void stopCheckingConnection() {
|
public void stopCheckingConnection() {
|
||||||
accountService.checkAccountOpen();
|
accountService.checkAccountOpen();
|
||||||
connectionManager.stopPolling();
|
|
||||||
connectionList.setRefreshPeriod(-1L);
|
connectionList.setRefreshPeriod(-1L);
|
||||||
|
updatePolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MoneroRpcConnection getBestAvailableConnection() {
|
public MoneroRpcConnection getBestAvailableConnection() {
|
||||||
@ -472,8 +478,6 @@ public final class XmrConnectionService {
|
|||||||
if (!isFixedConnection() && (connectionManager.getConnection() == null || connectionManager.getAutoSwitch())) {
|
if (!isFixedConnection() && (connectionManager.getConnection() == null || connectionManager.getAutoSwitch())) {
|
||||||
MoneroRpcConnection bestConnection = getBestAvailableConnection();
|
MoneroRpcConnection bestConnection = getBestAvailableConnection();
|
||||||
if (bestConnection != null) setConnection(bestConnection);
|
if (bestConnection != null) setConnection(bestConnection);
|
||||||
} else {
|
|
||||||
checkConnection();
|
|
||||||
}
|
}
|
||||||
} else if (!isInitialized) {
|
} else if (!isInitialized) {
|
||||||
|
|
||||||
@ -485,19 +489,11 @@ public final class XmrConnectionService {
|
|||||||
|
|
||||||
// start local node if applicable
|
// start local node if applicable
|
||||||
maybeStartLocalNode();
|
maybeStartLocalNode();
|
||||||
|
|
||||||
// update connection
|
|
||||||
checkConnection();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// register connection listener
|
// register connection listener
|
||||||
connectionManager.addListener(this::onConnectionChanged);
|
connectionManager.addListener(this::onConnectionChanged);
|
||||||
|
|
||||||
// start polling after delay
|
|
||||||
UserThread.runAfter(() -> {
|
|
||||||
if (!isShutDownStarted) connectionManager.startPolling(getRefreshPeriodMs() * 2);
|
|
||||||
}, getDefaultRefreshPeriodMs() * 2 / 1000);
|
|
||||||
|
|
||||||
isInitialized = true;
|
isInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -524,7 +520,6 @@ public final class XmrConnectionService {
|
|||||||
|
|
||||||
private void onConnectionChanged(MoneroRpcConnection currentConnection) {
|
private void onConnectionChanged(MoneroRpcConnection currentConnection) {
|
||||||
if (isShutDownStarted) return;
|
if (isShutDownStarted) return;
|
||||||
log.info("XmrConnectionService.onConnectionChanged() uri={}, connected={}", currentConnection == null ? null : currentConnection.getUri(), currentConnection == null ? "false" : currentConnection.isConnected());
|
|
||||||
if (currentConnection == null) {
|
if (currentConnection == null) {
|
||||||
log.warn("Setting daemon connection to null");
|
log.warn("Setting daemon connection to null");
|
||||||
Thread.dumpStack();
|
Thread.dumpStack();
|
||||||
@ -532,9 +527,11 @@ public final class XmrConnectionService {
|
|||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
if (currentConnection == null) {
|
if (currentConnection == null) {
|
||||||
daemon = null;
|
daemon = null;
|
||||||
|
isConnected = false;
|
||||||
connectionList.setCurrentConnectionUri(null);
|
connectionList.setCurrentConnectionUri(null);
|
||||||
} else {
|
} else {
|
||||||
daemon = new MoneroDaemonRpc(currentConnection);
|
daemon = new MoneroDaemonRpc(currentConnection);
|
||||||
|
isConnected = currentConnection.isConnected();
|
||||||
connectionList.removeConnection(currentConnection.getUri());
|
connectionList.removeConnection(currentConnection.getUri());
|
||||||
connectionList.addConnection(currentConnection);
|
connectionList.addConnection(currentConnection);
|
||||||
connectionList.setCurrentConnectionUri(currentConnection.getUri());
|
connectionList.setCurrentConnectionUri(currentConnection.getUri());
|
||||||
@ -546,9 +543,13 @@ public final class XmrConnectionService {
|
|||||||
numUpdates.set(numUpdates.get() + 1);
|
numUpdates.set(numUpdates.get() + 1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
updatePolling();
|
|
||||||
|
// update polling
|
||||||
|
doPollDaemon();
|
||||||
|
UserThread.runAfter(() -> updatePolling(), getRefreshPeriodMs() / 1000);
|
||||||
|
|
||||||
// notify listeners in parallel
|
// notify listeners in parallel
|
||||||
|
log.info("XmrConnectionService.onConnectionChanged() uri={}, connected={}", currentConnection == null ? null : currentConnection.getUri(), currentConnection == null ? "false" : isConnected);
|
||||||
synchronized (listenerLock) {
|
synchronized (listenerLock) {
|
||||||
for (MoneroConnectionManagerListener listener : listeners) {
|
for (MoneroConnectionManagerListener listener : listeners) {
|
||||||
ThreadUtils.submitToPool(() -> listener.onConnectionChanged(currentConnection));
|
ThreadUtils.submitToPool(() -> listener.onConnectionChanged(currentConnection));
|
||||||
@ -557,18 +558,14 @@ public final class XmrConnectionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updatePolling() {
|
private void updatePolling() {
|
||||||
new Thread(() -> {
|
|
||||||
synchronized (lock) {
|
|
||||||
stopPolling();
|
stopPolling();
|
||||||
if (connectionList.getRefreshPeriod() >= 0) startPolling(); // 0 means default refresh poll
|
if (connectionList.getRefreshPeriod() >= 0) startPolling(); // 0 means default refresh poll
|
||||||
}
|
}
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startPolling() {
|
private void startPolling() {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
if (daemonPollLooper != null) daemonPollLooper.stop();
|
if (daemonPollLooper != null) daemonPollLooper.stop();
|
||||||
daemonPollLooper = new TaskLooper(() -> pollDaemonInfo());
|
daemonPollLooper = new TaskLooper(() -> pollDaemon());
|
||||||
daemonPollLooper.start(getRefreshPeriodMs());
|
daemonPollLooper.start(getRefreshPeriodMs());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -582,17 +579,34 @@ public final class XmrConnectionService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pollDaemonInfo() {
|
private void pollDaemon() {
|
||||||
if (pollInProgress) return;
|
if (pollInProgress) return;
|
||||||
|
doPollDaemon();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doPollDaemon() {
|
||||||
synchronized (pollLock) {
|
synchronized (pollLock) {
|
||||||
pollInProgress = true;
|
pollInProgress = true;
|
||||||
if (isShutDownStarted) return;
|
if (isShutDownStarted) return;
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// poll daemon
|
// poll daemon
|
||||||
log.debug("Polling daemon info");
|
if (daemon == null) switchToBestConnection();
|
||||||
if (daemon == null) throw new RuntimeException("No daemon connection");
|
if (daemon == null) throw new RuntimeException("No connection to Monero daemon");
|
||||||
|
try {
|
||||||
lastInfo = daemon.getInfo();
|
lastInfo = daemon.getInfo();
|
||||||
|
} catch (Exception e) {
|
||||||
|
try {
|
||||||
|
log.warn("Failed to fetch daemon info, trying to switch to best connection: " + e.getMessage());
|
||||||
|
switchToBestConnection();
|
||||||
|
lastInfo = daemon.getInfo();
|
||||||
|
} catch (Exception e2) {
|
||||||
|
throw e2; // caught internally
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// connected to daemon
|
||||||
|
isConnected = true;
|
||||||
|
|
||||||
// update properties on user thread
|
// update properties on user thread
|
||||||
UserThread.execute(() -> {
|
UserThread.execute(() -> {
|
||||||
@ -632,19 +646,15 @@ public final class XmrConnectionService {
|
|||||||
lastErrorTimestamp = null;
|
lastErrorTimestamp = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update and notify connected state
|
|
||||||
if (!Boolean.TRUE.equals(connectionManager.isConnected())) {
|
|
||||||
connectionManager.checkConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear error message
|
// clear error message
|
||||||
if (Boolean.TRUE.equals(connectionManager.isConnected()) && HavenoUtils.havenoSetup != null) {
|
if (HavenoUtils.havenoSetup != null) HavenoUtils.havenoSetup.getWalletServiceErrorMsg().set(null);
|
||||||
HavenoUtils.havenoSetup.getWalletServiceErrorMsg().set(null);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
||||||
// skip if shut down or connected
|
// not connected to daemon
|
||||||
if (isShutDownStarted || Boolean.TRUE.equals(isConnected())) return;
|
isConnected = false;
|
||||||
|
|
||||||
|
// skip if shut down
|
||||||
|
if (isShutDownStarted) return;
|
||||||
|
|
||||||
// log error message periodically
|
// log error message periodically
|
||||||
if ((lastErrorTimestamp == null || System.currentTimeMillis() - lastErrorTimestamp > MIN_ERROR_LOG_PERIOD_MS)) {
|
if ((lastErrorTimestamp == null || System.currentTimeMillis() - lastErrorTimestamp > MIN_ERROR_LOG_PERIOD_MS)) {
|
||||||
@ -653,20 +663,8 @@ public final class XmrConnectionService {
|
|||||||
if (DevEnv.isDevMode()) e.printStackTrace();
|
if (DevEnv.isDevMode()) e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
new Thread(() -> {
|
|
||||||
if (isShutDownStarted) return;
|
|
||||||
if (!isFixedConnection() && connectionManager.getAutoSwitch()) {
|
|
||||||
MoneroRpcConnection bestConnection = getBestAvailableConnection();
|
|
||||||
if (bestConnection != null) connectionManager.setConnection(bestConnection);
|
|
||||||
} else {
|
|
||||||
connectionManager.checkConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
// set error message
|
// set error message
|
||||||
if (!Boolean.TRUE.equals(connectionManager.isConnected()) && HavenoUtils.havenoSetup != null) {
|
if (HavenoUtils.havenoSetup != null) HavenoUtils.havenoSetup.getWalletServiceErrorMsg().set(e.getMessage());
|
||||||
HavenoUtils.havenoSetup.getWalletServiceErrorMsg().set(e.getMessage());
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
} finally {
|
} finally {
|
||||||
pollInProgress = false;
|
pollInProgress = false;
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,6 @@ package haveno.core.offer;
|
|||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.name.Named;
|
import com.google.inject.name.Named;
|
||||||
import common.utils.GenUtils;
|
|
||||||
import haveno.common.UserThread;
|
import haveno.common.UserThread;
|
||||||
import haveno.common.config.Config;
|
import haveno.common.config.Config;
|
||||||
import haveno.common.file.JsonFileManager;
|
import haveno.common.file.JsonFileManager;
|
||||||
@ -46,6 +45,7 @@ import haveno.core.api.XmrConnectionService;
|
|||||||
import haveno.core.filter.FilterManager;
|
import haveno.core.filter.FilterManager;
|
||||||
import haveno.core.locale.Res;
|
import haveno.core.locale.Res;
|
||||||
import haveno.core.provider.price.PriceFeedService;
|
import haveno.core.provider.price.PriceFeedService;
|
||||||
|
import haveno.core.trade.HavenoUtils;
|
||||||
import haveno.core.util.JsonUtil;
|
import haveno.core.util.JsonUtil;
|
||||||
import haveno.core.xmr.wallet.XmrKeyImageListener;
|
import haveno.core.xmr.wallet.XmrKeyImageListener;
|
||||||
import haveno.core.xmr.wallet.XmrKeyImagePoller;
|
import haveno.core.xmr.wallet.XmrKeyImagePoller;
|
||||||
@ -287,7 +287,7 @@ public class OfferBookService {
|
|||||||
// first poll after 20s
|
// first poll after 20s
|
||||||
// TODO: remove?
|
// TODO: remove?
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
GenUtils.waitFor(20000);
|
HavenoUtils.waitFor(20000);
|
||||||
keyImagePoller.poll();
|
keyImagePoller.poll();
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,6 @@ package haveno.core.offer;
|
|||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import common.utils.GenUtils;
|
|
||||||
import haveno.common.ThreadUtils;
|
import haveno.common.ThreadUtils;
|
||||||
import haveno.common.Timer;
|
import haveno.common.Timer;
|
||||||
import haveno.common.UserThread;
|
import haveno.common.UserThread;
|
||||||
@ -71,6 +70,7 @@ import haveno.core.trade.ClosedTradableManager;
|
|||||||
import haveno.core.trade.HavenoUtils;
|
import haveno.core.trade.HavenoUtils;
|
||||||
import haveno.core.trade.TradableList;
|
import haveno.core.trade.TradableList;
|
||||||
import haveno.core.trade.handlers.TransactionResultHandler;
|
import haveno.core.trade.handlers.TransactionResultHandler;
|
||||||
|
import haveno.core.trade.protocol.TradeProtocol;
|
||||||
import haveno.core.trade.statistics.TradeStatisticsManager;
|
import haveno.core.trade.statistics.TradeStatisticsManager;
|
||||||
import haveno.core.user.Preferences;
|
import haveno.core.user.Preferences;
|
||||||
import haveno.core.user.User;
|
import haveno.core.user.User;
|
||||||
@ -278,7 +278,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
// first poll in 5s
|
// first poll in 5s
|
||||||
// TODO: remove?
|
// TODO: remove?
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
GenUtils.waitFor(5000);
|
HavenoUtils.waitFor(5000);
|
||||||
signedOfferKeyImagePoller.poll();
|
signedOfferKeyImagePoller.poll();
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
@ -349,7 +349,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
// For typical number of offers we are tolerant with delay to give enough time to broadcast.
|
// For typical number of offers we are tolerant with delay to give enough time to broadcast.
|
||||||
// If number of offers is very high we limit to 3 sec. to not delay other shutdown routines.
|
// If number of offers is very high we limit to 3 sec. to not delay other shutdown routines.
|
||||||
long delayMs = Math.min(3000, size * 200 + 500);
|
long delayMs = Math.min(3000, size * 200 + 500);
|
||||||
GenUtils.waitFor(delayMs);
|
HavenoUtils.waitFor(delayMs);
|
||||||
}, THREAD_ID);
|
}, THREAD_ID);
|
||||||
} else {
|
} else {
|
||||||
broadcaster.flush();
|
broadcaster.flush();
|
||||||
@ -705,9 +705,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
// remove open offer which thaws its key images
|
// remove open offer which thaws its key images
|
||||||
private void onCancelled(@NotNull OpenOffer openOffer) {
|
private void onCancelled(@NotNull OpenOffer openOffer) {
|
||||||
Offer offer = openOffer.getOffer();
|
Offer offer = openOffer.getOffer();
|
||||||
if (offer.getOfferPayload().getReserveTxKeyImages() != null) {
|
|
||||||
xmrWalletService.thawOutputs(offer.getOfferPayload().getReserveTxKeyImages());
|
xmrWalletService.thawOutputs(offer.getOfferPayload().getReserveTxKeyImages());
|
||||||
}
|
|
||||||
offer.setState(Offer.State.REMOVED);
|
offer.setState(Offer.State.REMOVED);
|
||||||
openOffer.setState(OpenOffer.State.CANCELED);
|
openOffer.setState(OpenOffer.State.CANCELED);
|
||||||
removeOpenOffer(openOffer);
|
removeOpenOffer(openOffer);
|
||||||
@ -1029,6 +1027,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
// handle sufficient available balance to split output
|
// handle sufficient available balance to split output
|
||||||
boolean sufficientAvailableBalance = xmrWalletService.getWallet().getUnlockedBalance(0).compareTo(offerReserveAmount) >= 0;
|
boolean sufficientAvailableBalance = xmrWalletService.getWallet().getUnlockedBalance(0).compareTo(offerReserveAmount) >= 0;
|
||||||
if (sufficientAvailableBalance) {
|
if (sufficientAvailableBalance) {
|
||||||
|
log.info("Splitting and scheduling outputs for offer {} at subaddress {}", openOffer.getShortId());
|
||||||
splitAndSchedule(openOffer);
|
splitAndSchedule(openOffer);
|
||||||
} else if (openOffer.getScheduledTxHashes() == null) {
|
} else if (openOffer.getScheduledTxHashes() == null) {
|
||||||
scheduleWithEarliestTxs(openOffers, openOffer);
|
scheduleWithEarliestTxs(openOffers, openOffer);
|
||||||
@ -1038,16 +1037,28 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||||||
private MoneroTxWallet splitAndSchedule(OpenOffer openOffer) {
|
private MoneroTxWallet splitAndSchedule(OpenOffer openOffer) {
|
||||||
BigInteger reserveAmount = openOffer.getOffer().getReserveAmount();
|
BigInteger reserveAmount = openOffer.getOffer().getReserveAmount();
|
||||||
xmrWalletService.swapAddressEntryToAvailable(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING); // change funding subaddress in case funded with unsuitable output(s)
|
xmrWalletService.swapAddressEntryToAvailable(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING); // change funding subaddress in case funded with unsuitable output(s)
|
||||||
|
MoneroTxWallet splitOutputTx = null;
|
||||||
|
synchronized (XmrWalletService.WALLET_LOCK) {
|
||||||
XmrAddressEntry entry = xmrWalletService.getOrCreateAddressEntry(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING);
|
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());
|
log.info("Creating split output tx to fund offer {} at subaddress {}", openOffer.getShortId(), entry.getSubaddressIndex());
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
MoneroTxWallet splitOutputTx = xmrWalletService.getWallet().createTx(new MoneroTxConfig()
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
|
try {
|
||||||
|
splitOutputTx = xmrWalletService.createTx(new MoneroTxConfig()
|
||||||
.setAccountIndex(0)
|
.setAccountIndex(0)
|
||||||
.setAddress(entry.getAddressString())
|
.setAddress(entry.getAddressString())
|
||||||
.setAmount(reserveAmount)
|
.setAmount(reserveAmount)
|
||||||
.setRelay(true)
|
.setRelay(true)
|
||||||
.setPriority(XmrWalletService.PROTOCOL_FEE_PRIORITY));
|
.setPriority(XmrWalletService.PROTOCOL_FEE_PRIORITY));
|
||||||
|
break;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Error creating split output tx to fund offer {} at subaddress {}, attempt={}/{}, error={}", openOffer.getShortId(), entry.getSubaddressIndex(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
||||||
|
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||||
|
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||||
|
}
|
||||||
|
}
|
||||||
log.info("Done creating split output tx to fund offer {} in {} ms", openOffer.getId(), System.currentTimeMillis() - startTime);
|
log.info("Done creating split output tx to fund offer {} in {} ms", openOffer.getId(), System.currentTimeMillis() - startTime);
|
||||||
|
}
|
||||||
|
|
||||||
// schedule txs
|
// schedule txs
|
||||||
openOffer.setSplitOutputTxHash(splitOutputTx.getHash());
|
openOffer.setSplitOutputTxHash(splitOutputTx.getHash());
|
||||||
|
@ -133,7 +133,7 @@ public class PlaceOfferProtocol {
|
|||||||
stopTimeoutTimer();
|
stopTimeoutTimer();
|
||||||
timeoutTimer = UserThread.runAfter(() -> {
|
timeoutTimer = UserThread.runAfter(() -> {
|
||||||
handleError(Res.get("createOffer.timeoutAtPublishing"));
|
handleError(Res.get("createOffer.timeoutAtPublishing"));
|
||||||
}, TradeProtocol.TRADE_TIMEOUT_SECONDS);
|
}, TradeProtocol.TRADE_STEP_TIMEOUT_SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopTimeoutTimer() {
|
private void stopTimeoutTimer() {
|
||||||
|
@ -17,12 +17,22 @@
|
|||||||
|
|
||||||
package haveno.core.offer.placeoffer.tasks;
|
package haveno.core.offer.placeoffer.tasks;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import haveno.common.taskrunner.Task;
|
import haveno.common.taskrunner.Task;
|
||||||
import haveno.common.taskrunner.TaskRunner;
|
import haveno.common.taskrunner.TaskRunner;
|
||||||
import haveno.core.offer.Offer;
|
import haveno.core.offer.Offer;
|
||||||
|
import haveno.core.offer.OfferDirection;
|
||||||
|
import haveno.core.offer.OpenOffer;
|
||||||
import haveno.core.offer.placeoffer.PlaceOfferModel;
|
import haveno.core.offer.placeoffer.PlaceOfferModel;
|
||||||
|
import haveno.core.trade.HavenoUtils;
|
||||||
|
import haveno.core.trade.protocol.TradeProtocol;
|
||||||
import haveno.core.xmr.model.XmrAddressEntry;
|
import haveno.core.xmr.model.XmrAddressEntry;
|
||||||
|
import haveno.core.xmr.wallet.XmrWalletService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import monero.daemon.model.MoneroOutput;
|
||||||
import monero.wallet.model.MoneroTxWallet;
|
import monero.wallet.model.MoneroTxWallet;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ -35,7 +45,8 @@ public class MakerReserveOfferFunds extends Task<PlaceOfferModel> {
|
|||||||
@Override
|
@Override
|
||||||
protected void run() {
|
protected void run() {
|
||||||
|
|
||||||
Offer offer = model.getOpenOffer().getOffer();
|
OpenOffer openOffer = model.getOpenOffer();
|
||||||
|
Offer offer = openOffer.getOffer();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
runInterceptHook();
|
runInterceptHook();
|
||||||
@ -44,16 +55,51 @@ public class MakerReserveOfferFunds extends Task<PlaceOfferModel> {
|
|||||||
model.getXmrWalletService().getConnectionService().verifyConnection();
|
model.getXmrWalletService().getConnectionService().verifyConnection();
|
||||||
|
|
||||||
// create reserve tx
|
// create reserve tx
|
||||||
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(model.getOpenOffer());
|
MoneroTxWallet reserveTx = null;
|
||||||
model.setReserveTx(reserveTx);
|
synchronized (XmrWalletService.WALLET_LOCK) {
|
||||||
|
|
||||||
|
// collect relevant info
|
||||||
|
BigInteger penaltyFee = HavenoUtils.multiply(offer.getAmount(), offer.getPenaltyFeePct());
|
||||||
|
BigInteger makerFee = offer.getMaxMakerFee();
|
||||||
|
BigInteger sendAmount = offer.getDirection() == OfferDirection.BUY ? BigInteger.ZERO : offer.getAmount();
|
||||||
|
BigInteger securityDeposit = offer.getDirection() == OfferDirection.BUY ? offer.getMaxBuyerSecurityDeposit() : offer.getMaxSellerSecurityDeposit();
|
||||||
|
String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
||||||
|
XmrAddressEntry fundingEntry = model.getXmrWalletService().getAddressEntry(offer.getId(), XmrAddressEntry.Context.OFFER_FUNDING).orElse(null);
|
||||||
|
Integer preferredSubaddressIndex = fundingEntry == null ? null : fundingEntry.getSubaddressIndex();
|
||||||
|
|
||||||
|
// attempt creating reserve tx
|
||||||
|
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||||
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
|
try {
|
||||||
|
reserveTx = model.getXmrWalletService().createReserveTx(penaltyFee, makerFee, sendAmount, securityDeposit, returnAddress, openOffer.isReserveExactAmount(), preferredSubaddressIndex);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Error creating reserve tx, attempt={}/{}, offerId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, openOffer.getShortId(), e.getMessage());
|
||||||
|
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||||
|
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||||
|
}
|
||||||
|
|
||||||
// check for error in case creating reserve tx exceeded timeout // TODO: better way?
|
// check for error in case creating reserve tx exceeded timeout // TODO: better way?
|
||||||
if (!model.getXmrWalletService().getAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).isPresent()) {
|
if (!model.getXmrWalletService().getAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).isPresent()) {
|
||||||
throw new RuntimeException("An error has occurred posting offer " + offer.getId() + " causing its subaddress entry to be deleted");
|
throw new RuntimeException("An error has occurred posting offer " + offer.getId() + " causing its subaddress entry to be deleted");
|
||||||
}
|
}
|
||||||
|
if (reserveTx != null) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect reserved key images
|
||||||
|
List<String> reservedKeyImages = new ArrayList<String>();
|
||||||
|
for (MoneroOutput input : reserveTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex());
|
||||||
|
|
||||||
|
// update offer state
|
||||||
|
openOffer.setReserveTxHash(reserveTx.getHash());
|
||||||
|
openOffer.setReserveTxHex(reserveTx.getFullHex());
|
||||||
|
openOffer.setReserveTxKey(reserveTx.getKey());
|
||||||
|
offer.getOfferPayload().setReserveTxKeyImages(reservedKeyImages);
|
||||||
|
}
|
||||||
|
|
||||||
// reset protocol timeout
|
// reset protocol timeout
|
||||||
model.getProtocol().startTimeoutTimer();
|
model.getProtocol().startTimeoutTimer();
|
||||||
|
model.setReserveTx(reserveTx);
|
||||||
complete();
|
complete();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
offer.setErrorMessage("An error occurred.\n" +
|
offer.setErrorMessage("An error occurred.\n" +
|
||||||
|
@ -524,7 +524,6 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||||||
|
|
||||||
// update multisig hex
|
// update multisig hex
|
||||||
if (message.getUpdatedMultisigHex() != null) sender.setUpdatedMultisigHex(message.getUpdatedMultisigHex());
|
if (message.getUpdatedMultisigHex() != null) sender.setUpdatedMultisigHex(message.getUpdatedMultisigHex());
|
||||||
if (trade.walletExists()) trade.importMultisigHex();
|
|
||||||
|
|
||||||
// add chat message with price info
|
// add chat message with price info
|
||||||
if (trade instanceof ArbitratorTrade) addPriceInfoMessage(dispute, 0);
|
if (trade instanceof ArbitratorTrade) addPriceInfoMessage(dispute, 0);
|
||||||
@ -896,17 +895,12 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||||||
}
|
}
|
||||||
|
|
||||||
// create dispute payout tx
|
// create dispute payout tx
|
||||||
MoneroTxWallet payoutTx = null;
|
MoneroTxWallet payoutTx = trade.createDisputePayoutTx(txConfig);
|
||||||
try {
|
|
||||||
payoutTx = trade.getWallet().createTx(txConfig);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
throw new RuntimeException("Loser payout is too small to cover the mining fee");
|
|
||||||
}
|
|
||||||
|
|
||||||
// update trade state
|
// update trade state
|
||||||
if (updateState) {
|
if (updateState) {
|
||||||
trade.getProcessModel().setUnsignedPayoutTx(payoutTx);
|
trade.getProcessModel().setUnsignedPayoutTx(payoutTx);
|
||||||
|
trade.getSelf().setUpdatedMultisigHex(trade.getWallet().exportMultisigHex());
|
||||||
trade.setPayoutTx(payoutTx);
|
trade.setPayoutTx(payoutTx);
|
||||||
trade.setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
|
trade.setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
|
||||||
if (trade.getBuyer().getUpdatedMultisigHex() != null && trade.getBuyer().getUnsignedPayoutTxHex() == null) trade.getBuyer().setUnsignedPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
|
if (trade.getBuyer().getUpdatedMultisigHex() != null && trade.getBuyer().getUnsignedPayoutTxHex() == null) trade.getBuyer().setUnsignedPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
|
||||||
|
@ -36,7 +36,6 @@ package haveno.core.support.dispute.arbitration;
|
|||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import common.utils.GenUtils;
|
|
||||||
import haveno.common.ThreadUtils;
|
import haveno.common.ThreadUtils;
|
||||||
import haveno.common.Timer;
|
import haveno.common.Timer;
|
||||||
import haveno.common.UserThread;
|
import haveno.common.UserThread;
|
||||||
@ -68,6 +67,7 @@ import haveno.core.trade.Contract;
|
|||||||
import haveno.core.trade.HavenoUtils;
|
import haveno.core.trade.HavenoUtils;
|
||||||
import haveno.core.trade.Trade;
|
import haveno.core.trade.Trade;
|
||||||
import haveno.core.trade.TradeManager;
|
import haveno.core.trade.TradeManager;
|
||||||
|
import haveno.core.trade.protocol.TradeProtocol;
|
||||||
import haveno.core.xmr.wallet.TradeWalletService;
|
import haveno.core.xmr.wallet.TradeWalletService;
|
||||||
import haveno.core.xmr.wallet.XmrWalletService;
|
import haveno.core.xmr.wallet.XmrWalletService;
|
||||||
import haveno.network.p2p.AckMessageSourceType;
|
import haveno.network.p2p.AckMessageSourceType;
|
||||||
@ -290,7 +290,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||||||
log.info("Deferring signing and publishing dispute payout tx for {} {}", trade.getClass().getSimpleName(), trade.getId());
|
log.info("Deferring signing and publishing dispute payout tx for {} {}", trade.getClass().getSimpleName(), trade.getId());
|
||||||
for (int i = 0; i < 5; i++) {
|
for (int i = 0; i < 5; i++) {
|
||||||
if (trade.isPayoutPublished()) break;
|
if (trade.isPayoutPublished()) break;
|
||||||
GenUtils.waitFor(Trade.DEFER_PUBLISH_MS / 5);
|
HavenoUtils.waitFor(Trade.DEFER_PUBLISH_MS / 5);
|
||||||
}
|
}
|
||||||
if (!trade.isPayoutPublished()) trade.syncAndPollWallet();
|
if (!trade.isPayoutPublished()) trade.syncAndPollWallet();
|
||||||
}
|
}
|
||||||
@ -310,7 +310,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||||||
log.info("Dispute payout tx already published for {} {}", trade.getClass().getSimpleName(), trade.getId());
|
log.info("Dispute payout tx already published for {} {}", trade.getClass().getSimpleName(), trade.getId());
|
||||||
} else {
|
} else {
|
||||||
if (e instanceof IllegalArgumentException) throw e;
|
if (e instanceof IllegalArgumentException) throw e;
|
||||||
else throw new RuntimeException("Failed to sign and publish dispute payout tx from arbitrator: " + e.getMessage() + ". TradeId = " + tradeId, e);
|
else throw new RuntimeException("Failed to sign and publish dispute payout tx from arbitrator for " + trade.getClass().getSimpleName() + " " + tradeId + ": " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -430,8 +430,8 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||||||
if (!expectedBuyerAmount.equals(actualBuyerAmount)) throw new IllegalArgumentException("Unexpected buyer payout: " + expectedBuyerAmount + " vs " + actualBuyerAmount);
|
if (!expectedBuyerAmount.equals(actualBuyerAmount)) throw new IllegalArgumentException("Unexpected buyer payout: " + expectedBuyerAmount + " vs " + actualBuyerAmount);
|
||||||
if (!expectedSellerAmount.equals(actualSellerAmount)) throw new IllegalArgumentException("Unexpected seller payout: " + expectedSellerAmount + " vs " + actualSellerAmount);
|
if (!expectedSellerAmount.equals(actualSellerAmount)) throw new IllegalArgumentException("Unexpected seller payout: " + expectedSellerAmount + " vs " + actualSellerAmount);
|
||||||
|
|
||||||
// check wallet's daemon connection
|
// check daemon connection
|
||||||
trade.checkAndVerifyDaemonConnection();
|
trade.verifyDaemonConnection();
|
||||||
|
|
||||||
// determine if we already signed dispute payout tx
|
// determine if we already signed dispute payout tx
|
||||||
// TODO: better way, such as by saving signed dispute payout tx hex in designated field instead of shared payoutTxHex field?
|
// TODO: better way, such as by saving signed dispute payout tx hex in designated field instead of shared payoutTxHex field?
|
||||||
@ -471,8 +471,17 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||||||
}
|
}
|
||||||
|
|
||||||
// submit fully signed payout tx to the network
|
// submit fully signed payout tx to the network
|
||||||
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
|
try {
|
||||||
List<String> txHashes = multisigWallet.submitMultisigTxHex(disputeTxSet.getMultisigTxHex());
|
List<String> txHashes = multisigWallet.submitMultisigTxHex(disputeTxSet.getMultisigTxHex());
|
||||||
disputeTxSet.getTxs().get(0).setHash(txHashes.get(0)); // manually update hash which is known after signed
|
disputeTxSet.getTxs().get(0).setHash(txHashes.get(0)); // manually update hash which is known after signed
|
||||||
|
break;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Failed to submit dispute payout tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, trade.getShortId(), e.getMessage());
|
||||||
|
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||||
|
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// update state
|
// update state
|
||||||
trade.setPayoutTx(disputeTxSet.getTxs().get(0)); // TODO (woodser): is trade.payoutTx() mutually exclusive from dispute payout tx?
|
trade.setPayoutTx(disputeTxSet.getTxs().get(0)); // TODO (woodser): is trade.payoutTx() mutually exclusive from dispute payout tx?
|
||||||
|
@ -19,6 +19,8 @@ package haveno.core.trade;
|
|||||||
|
|
||||||
import com.google.common.base.CaseFormat;
|
import com.google.common.base.CaseFormat;
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
|
|
||||||
|
import common.utils.GenUtils;
|
||||||
import haveno.common.config.Config;
|
import haveno.common.config.Config;
|
||||||
import haveno.common.crypto.CryptoException;
|
import haveno.common.crypto.CryptoException;
|
||||||
import haveno.common.crypto.Hash;
|
import haveno.common.crypto.Hash;
|
||||||
@ -70,6 +72,17 @@ public class HavenoUtils {
|
|||||||
public static final double TAKER_FEE_PCT = 0.0075; // 0.75%
|
public static final double TAKER_FEE_PCT = 0.0075; // 0.75%
|
||||||
public static final double PENALTY_FEE_PCT = 0.02; // 2%
|
public static final double PENALTY_FEE_PCT = 0.02; // 2%
|
||||||
|
|
||||||
|
// synchronize requests to the daemon
|
||||||
|
private static boolean SYNC_DAEMON_REQUESTS = true; // sync long requests to daemon (e.g. refresh, update pool)
|
||||||
|
private static boolean SYNC_WALLET_REQUESTS = false; // additionally sync wallet functions to daemon (e.g. create tx, import multisig hex)
|
||||||
|
private static Object DAEMON_LOCK = new Object();
|
||||||
|
public static Object getDaemonLock() {
|
||||||
|
return SYNC_DAEMON_REQUESTS ? DAEMON_LOCK : new Object();
|
||||||
|
}
|
||||||
|
public static Object getWalletFunctionLock() {
|
||||||
|
return SYNC_WALLET_REQUESTS ? getDaemonLock() : new Object();
|
||||||
|
}
|
||||||
|
|
||||||
// non-configurable
|
// non-configurable
|
||||||
public static final DecimalFormatSymbols DECIMAL_FORMAT_SYMBOLS = DecimalFormatSymbols.getInstance(Locale.US); // use the US locale as a base for all DecimalFormats (commas should be omitted from number strings)
|
public static final DecimalFormatSymbols DECIMAL_FORMAT_SYMBOLS = DecimalFormatSymbols.getInstance(Locale.US); // use the US locale as a base for all DecimalFormats (commas should be omitted from number strings)
|
||||||
public static int XMR_SMALLEST_UNIT_EXPONENT = 12;
|
public static int XMR_SMALLEST_UNIT_EXPONENT = 12;
|
||||||
@ -108,6 +121,10 @@ public class HavenoUtils {
|
|||||||
return new Date().before(releaseDatePlusDays);
|
return new Date().before(releaseDatePlusDays);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void waitFor(long waitMs) {
|
||||||
|
GenUtils.waitFor(waitMs);
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------- CONVERSION UTILS -------------------------------
|
// ----------------------- CONVERSION UTILS -------------------------------
|
||||||
|
|
||||||
public static BigInteger coinToAtomicUnits(Coin coin) {
|
public static BigInteger coinToAtomicUnits(Coin coin) {
|
||||||
|
@ -37,7 +37,6 @@ package haveno.core.trade;
|
|||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import com.google.protobuf.Message;
|
import com.google.protobuf.Message;
|
||||||
import common.utils.GenUtils;
|
|
||||||
import haveno.common.ThreadUtils;
|
import haveno.common.ThreadUtils;
|
||||||
import haveno.common.UserThread;
|
import haveno.common.UserThread;
|
||||||
import haveno.common.crypto.Encryption;
|
import haveno.common.crypto.Encryption;
|
||||||
@ -120,7 +119,6 @@ import java.util.Arrays;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -479,7 +477,6 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
private long payoutTxFee;
|
private long payoutTxFee;
|
||||||
private Long payoutHeight;
|
private Long payoutHeight;
|
||||||
private IdlePayoutSyncer idlePayoutSyncer;
|
private IdlePayoutSyncer idlePayoutSyncer;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
private boolean isCompleted;
|
private boolean isCompleted;
|
||||||
@ -638,18 +635,14 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
// handle trade state events
|
// handle trade state events
|
||||||
tradeStateSubscription = EasyBind.subscribe(stateProperty, newValue -> {
|
tradeStateSubscription = EasyBind.subscribe(stateProperty, newValue -> {
|
||||||
if (!isInitialized || isShutDownStarted) return;
|
if (!isInitialized || isShutDownStarted) return;
|
||||||
ThreadUtils.execute(() -> {
|
// no processing
|
||||||
if (newValue == Trade.State.MULTISIG_COMPLETED) {
|
|
||||||
updatePollPeriod();
|
|
||||||
startPolling();
|
|
||||||
}
|
|
||||||
}, getId());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// handle trade phase events
|
// handle trade phase events
|
||||||
tradePhaseSubscription = EasyBind.subscribe(phaseProperty, newValue -> {
|
tradePhaseSubscription = EasyBind.subscribe(phaseProperty, newValue -> {
|
||||||
if (!isInitialized || isShutDownStarted) return;
|
if (!isInitialized || isShutDownStarted) return;
|
||||||
ThreadUtils.execute(() -> {
|
ThreadUtils.execute(() -> {
|
||||||
|
if (newValue == Trade.Phase.DEPOSIT_REQUESTED) startPolling();
|
||||||
if (isDepositsPublished() && !isPayoutUnlocked()) updatePollPeriod();
|
if (isDepositsPublished() && !isPayoutUnlocked()) updatePollPeriod();
|
||||||
if (isPaymentReceived()) {
|
if (isPaymentReceived()) {
|
||||||
UserThread.execute(() -> {
|
UserThread.execute(() -> {
|
||||||
@ -674,9 +667,9 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
|
|
||||||
// sync main wallet to update pending balance
|
// sync main wallet to update pending balance
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
GenUtils.waitFor(1000);
|
HavenoUtils.waitFor(1000);
|
||||||
if (isShutDownStarted) return;
|
if (isShutDownStarted) return;
|
||||||
if (Boolean.TRUE.equals(xmrConnectionService.isConnected())) xmrWalletService.syncWallet(xmrWalletService.getWallet());
|
if (xmrConnectionService.isConnected()) xmrWalletService.syncWallet();
|
||||||
}).start();
|
}).start();
|
||||||
|
|
||||||
// complete disputed trade
|
// complete disputed trade
|
||||||
@ -731,16 +724,17 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
setPayoutStateUnlocked();
|
setPayoutStateUnlocked();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("Missing trade wallet for " + getClass().getSimpleName() + " " + getId());
|
log.warn("Missing trade wallet for {} {}, state={}, marked completed={}", getClass().getSimpleName(), getShortId(), getState(), isCompleted());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize syncing and polling
|
// start polling if deposit requested
|
||||||
tryInitPolling();
|
if (isDepositRequested()) tryInitPolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void requestPersistence() {
|
public void requestPersistence() {
|
||||||
processModel.getTradeManager().requestPersistence();
|
if (processModel.getTradeManager() != null) processModel.getTradeManager().requestPersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TradeProtocol getProtocol() {
|
public TradeProtocol getProtocol() {
|
||||||
@ -793,21 +787,8 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
return MONERO_TRADE_WALLET_PREFIX + getShortId() + "_" + getShortUid();
|
return MONERO_TRADE_WALLET_PREFIX + getShortId() + "_" + getShortUid();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void checkAndVerifyDaemonConnection() {
|
public void verifyDaemonConnection() {
|
||||||
|
if (!Boolean.TRUE.equals(xmrConnectionService.isConnected())) throw new RuntimeException("Connection service is not connected to a Monero node");
|
||||||
// check connection which might update
|
|
||||||
xmrConnectionService.checkConnection();
|
|
||||||
xmrConnectionService.verifyConnection();
|
|
||||||
|
|
||||||
// check wallet connection on same thread as connection change
|
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
ThreadUtils.submitToPool((() -> {
|
|
||||||
ThreadUtils.execute(() -> {
|
|
||||||
if (!isWalletConnectedToDaemon()) throw new RuntimeException("Trade wallet is not connected to a Monero node"); // wallet connection is updated on trade thread
|
|
||||||
latch.countDown();
|
|
||||||
}, getConnectionChangedThreadId());
|
|
||||||
}));
|
|
||||||
HavenoUtils.awaitLatch(latch); // TODO: better way?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isWalletConnectedToDaemon() {
|
public boolean isWalletConnectedToDaemon() {
|
||||||
@ -848,7 +829,7 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
|
|
||||||
// reset wallet poll period after duration
|
// reset wallet poll period after duration
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
GenUtils.waitFor(pollNormalDuration);
|
HavenoUtils.waitFor(pollNormalDuration);
|
||||||
Long pollNormalStartTimeMsCopy = pollNormalStartTimeMs; // copy to avoid race condition
|
Long pollNormalStartTimeMsCopy = pollNormalStartTimeMs; // copy to avoid race condition
|
||||||
if (pollNormalStartTimeMsCopy == null) return;
|
if (pollNormalStartTimeMsCopy == null) return;
|
||||||
if (!isShutDown && System.currentTimeMillis() >= pollNormalStartTimeMsCopy + pollNormalDuration) {
|
if (!isShutDown && System.currentTimeMillis() >= pollNormalStartTimeMsCopy + pollNormalDuration) {
|
||||||
@ -860,21 +841,38 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
|
|
||||||
public void importMultisigHex() {
|
public void importMultisigHex() {
|
||||||
synchronized (walletLock) {
|
synchronized (walletLock) {
|
||||||
|
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||||
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
|
try {
|
||||||
|
doImportMultisigHex();
|
||||||
|
break;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Failed to import multisig hex, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, getShortId(), e.getMessage());
|
||||||
|
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||||
|
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doImportMultisigHex() {
|
||||||
|
|
||||||
// ensure wallet sees deposits confirmed
|
// ensure wallet sees deposits confirmed
|
||||||
if (!isDepositsConfirmed()) syncAndPollWallet();
|
if (!isDepositsConfirmed()) syncAndPollWallet();
|
||||||
|
|
||||||
// import multisig hexes
|
// collect multisig hex from peers
|
||||||
List<String> multisigHexes = new ArrayList<String>();
|
List<String> multisigHexes = new ArrayList<String>();
|
||||||
for (TradePeer node : getAllTradeParties()) if (node.getUpdatedMultisigHex() != null) multisigHexes.add(node.getUpdatedMultisigHex());
|
for (TradePeer peer : getOtherPeers()) if (peer.getUpdatedMultisigHex() != null) multisigHexes.add(peer.getUpdatedMultisigHex());
|
||||||
if (!multisigHexes.isEmpty()) {
|
|
||||||
log.info("Importing multisig hex for {} {}", getClass().getSimpleName(), getId());
|
// import multisig hex
|
||||||
|
log.info("Importing multisig hexes for {} {}, count={}", getClass().getSimpleName(), getShortId(), multisigHexes.size());
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
getWallet().importMultisigHex(multisigHexes.toArray(new String[0]));
|
if (!multisigHexes.isEmpty()) {
|
||||||
log.info("Done importing multisig hex for {} {} in {} ms", getClass().getSimpleName(), getId(), System.currentTimeMillis() - startTime);
|
wallet.importMultisigHex(multisigHexes.toArray(new String[0]));
|
||||||
}
|
|
||||||
requestSaveWallet();
|
requestSaveWallet();
|
||||||
}
|
}
|
||||||
|
log.info("Done importing multisig hexes for {} {} in {} ms, count={}", getClass().getSimpleName(), getShortId(), System.currentTimeMillis() - startTime, multisigHexes.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void changeWalletPassword(String oldPassword, String newPassword) {
|
public void changeWalletPassword(String oldPassword, String newPassword) {
|
||||||
@ -891,10 +889,10 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
public void saveWallet() {
|
public void saveWallet() {
|
||||||
synchronized (walletLock) {
|
synchronized (walletLock) {
|
||||||
if (!walletExists()) {
|
if (!walletExists()) {
|
||||||
log.warn("Cannot save wallet for {} {} because it does not exist", getClass().getSimpleName(), getId());
|
log.warn("Cannot save wallet for {} {} because it does not exist", getClass().getSimpleName(), getShortId());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (wallet == null) throw new RuntimeException("Trade wallet is not open for trade " + getId());
|
if (wallet == null) throw new RuntimeException("Trade wallet is not open for trade " + getShortId());
|
||||||
xmrWalletService.saveWallet(wallet);
|
xmrWalletService.saveWallet(wallet);
|
||||||
maybeBackupWallet();
|
maybeBackupWallet();
|
||||||
}
|
}
|
||||||
@ -953,7 +951,13 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
|
|
||||||
// check for balance
|
// check for balance
|
||||||
if (wallet.getBalance().compareTo(BigInteger.ZERO) > 0) {
|
if (wallet.getBalance().compareTo(BigInteger.ZERO) > 0) {
|
||||||
throw new IllegalStateException("Refusing to delete wallet for " + getClass().getSimpleName() + " " + getId() + " because it has a balance");
|
synchronized (HavenoUtils.getDaemonLock()) {
|
||||||
|
log.warn("Rescanning spent outputs for {} {}", getClass().getSimpleName(), getId());
|
||||||
|
wallet.rescanSpent();
|
||||||
|
if (wallet.getBalance().compareTo(BigInteger.ZERO) > 0) {
|
||||||
|
throw new IllegalStateException("Refusing to delete wallet for " + getClass().getSimpleName() + " " + getId() + " because it has a balance of " + wallet.getBalance());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// force close wallet without warning
|
// force close wallet without warning
|
||||||
@ -1021,17 +1025,44 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
return contract;
|
return contract;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MoneroTxWallet createTx(MoneroTxConfig txConfig) {
|
||||||
|
synchronized (walletLock) {
|
||||||
|
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||||
|
return wallet.createTx(txConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the payout tx.
|
* Create the payout tx.
|
||||||
*
|
*
|
||||||
* @return MoneroTxWallet the payout tx when the trade is successfully completed
|
* @return the payout tx when the trade is successfully completed
|
||||||
*/
|
*/
|
||||||
public MoneroTxWallet createPayoutTx() {
|
public MoneroTxWallet createPayoutTx() {
|
||||||
|
|
||||||
// check connection to monero daemon
|
// check connection to monero daemon
|
||||||
checkAndVerifyDaemonConnection();
|
verifyDaemonConnection();
|
||||||
|
|
||||||
// check multisig import
|
// create payout tx
|
||||||
|
synchronized (walletLock) {
|
||||||
|
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||||
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
|
try {
|
||||||
|
return doCreatePayoutTx();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Failed to create payout tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, getShortId(), e.getMessage());
|
||||||
|
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||||
|
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new RuntimeException("Failed to create payout tx for " + getClass().getSimpleName() + " " + getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MoneroTxWallet doCreatePayoutTx() {
|
||||||
|
|
||||||
|
// check if multisig import needed
|
||||||
MoneroWallet multisigWallet = getWallet();
|
MoneroWallet multisigWallet = getWallet();
|
||||||
if (multisigWallet.isMultisigImportNeeded()) throw new RuntimeException("Cannot create payout tx because multisig import is needed");
|
if (multisigWallet.isMultisigImportNeeded()) throw new RuntimeException("Cannot create payout tx because multisig import is needed");
|
||||||
|
|
||||||
@ -1047,7 +1078,7 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
BigInteger sellerPayoutAmount = sellerDepositAmount.subtract(tradeAmount);
|
BigInteger sellerPayoutAmount = sellerDepositAmount.subtract(tradeAmount);
|
||||||
|
|
||||||
// create payout tx
|
// create payout tx
|
||||||
MoneroTxWallet payoutTx = multisigWallet.createTx(new MoneroTxConfig()
|
MoneroTxWallet payoutTx = createTx(new MoneroTxConfig()
|
||||||
.setAccountIndex(0)
|
.setAccountIndex(0)
|
||||||
.addDestination(buyerPayoutAddress, buyerPayoutAmount)
|
.addDestination(buyerPayoutAddress, buyerPayoutAmount)
|
||||||
.addDestination(sellerPayoutAddress, sellerPayoutAmount)
|
.addDestination(sellerPayoutAddress, sellerPayoutAmount)
|
||||||
@ -1066,6 +1097,24 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
return payoutTx;
|
return payoutTx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MoneroTxWallet createDisputePayoutTx(MoneroTxConfig txConfig) {
|
||||||
|
synchronized (walletLock) {
|
||||||
|
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||||
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
|
try {
|
||||||
|
return createTx(txConfig);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (e.getMessage().contains("not possible")) throw new RuntimeException("Loser payout is too small to cover the mining fee");
|
||||||
|
log.warn("Failed to create payout tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, getShortId(), e.getMessage());
|
||||||
|
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||||
|
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new RuntimeException("Failed to create payout tx for " + getClass().getSimpleName() + " " + getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process a payout tx.
|
* Process a payout tx.
|
||||||
*
|
*
|
||||||
@ -1074,6 +1123,7 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
* @param publish publishes the signed payout tx if true
|
* @param publish publishes the signed payout tx if true
|
||||||
*/
|
*/
|
||||||
public void processPayoutTx(String payoutTxHex, boolean sign, boolean publish) {
|
public void processPayoutTx(String payoutTxHex, boolean sign, boolean publish) {
|
||||||
|
synchronized (walletLock) {
|
||||||
log.info("Processing payout tx for {} {}", getClass().getSimpleName(), getId());
|
log.info("Processing payout tx for {} {}", getClass().getSimpleName(), getId());
|
||||||
|
|
||||||
// gather relevant info
|
// gather relevant info
|
||||||
@ -1117,8 +1167,8 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
BigInteger expectedSellerPayout = sellerDepositAmount.subtract(tradeAmount).subtract(txCostSplit);
|
BigInteger expectedSellerPayout = sellerDepositAmount.subtract(tradeAmount).subtract(txCostSplit);
|
||||||
if (!sellerPayoutDestination.getAmount().equals(expectedSellerPayout)) throw new IllegalArgumentException("Seller destination amount is not deposit amount - trade amount - 1/2 tx costs, " + sellerPayoutDestination.getAmount() + " vs " + expectedSellerPayout);
|
if (!sellerPayoutDestination.getAmount().equals(expectedSellerPayout)) throw new IllegalArgumentException("Seller destination amount is not deposit amount - trade amount - 1/2 tx costs, " + sellerPayoutDestination.getAmount() + " vs " + expectedSellerPayout);
|
||||||
|
|
||||||
// check wallet connection
|
// check connection
|
||||||
if (sign || publish) checkAndVerifyDaemonConnection();
|
if (sign || publish) verifyDaemonConnection();
|
||||||
|
|
||||||
// handle tx signing
|
// handle tx signing
|
||||||
if (sign) {
|
if (sign) {
|
||||||
@ -1147,10 +1197,20 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
|
|
||||||
// submit payout tx
|
// submit payout tx
|
||||||
if (publish) {
|
if (publish) {
|
||||||
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
|
try {
|
||||||
wallet.submitMultisigTxHex(payoutTxHex);
|
wallet.submitMultisigTxHex(payoutTxHex);
|
||||||
pollWallet();
|
break;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Failed to submit payout tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, getShortId(), e.getMessage());
|
||||||
|
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||||
|
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pollWallet();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt the peer's payment account payload using the given key.
|
* Decrypt the peer's payment account payload using the given key.
|
||||||
@ -1173,6 +1233,7 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
|
|
||||||
// set payment account payload
|
// set payment account payload
|
||||||
getTradePeer().setPaymentAccountPayload(paymentAccountPayload);
|
getTradePeer().setPaymentAccountPayload(paymentAccountPayload);
|
||||||
|
processModel.getPaymentAccountDecryptedProperty().set(true);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@ -1220,6 +1281,7 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
public void clearAndShutDown() {
|
public void clearAndShutDown() {
|
||||||
ThreadUtils.execute(() -> {
|
ThreadUtils.execute(() -> {
|
||||||
clearProcessData();
|
clearProcessData();
|
||||||
|
onShutDownStarted();
|
||||||
ThreadUtils.submitToPool(() -> shutDown()); // run off trade thread
|
ThreadUtils.submitToPool(() -> shutDown()); // run off trade thread
|
||||||
}, getId());
|
}, getId());
|
||||||
}
|
}
|
||||||
@ -1237,7 +1299,7 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
|
|
||||||
// TODO: clear other process data
|
// TODO: clear other process data
|
||||||
setPayoutTxHex(null);
|
setPayoutTxHex(null);
|
||||||
for (TradePeer peer : getAllTradeParties()) {
|
for (TradePeer peer : getAllPeers()) {
|
||||||
peer.setUnsignedPayoutTxHex(null);
|
peer.setUnsignedPayoutTxHex(null);
|
||||||
peer.setUpdatedMultisigHex(null);
|
peer.setUpdatedMultisigHex(null);
|
||||||
peer.setDisputeClosedMessage(null);
|
peer.setDisputeClosedMessage(null);
|
||||||
@ -1294,7 +1356,7 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
// repeatedly acquire lock to clear tasks
|
// repeatedly acquire lock to clear tasks
|
||||||
for (int i = 0; i < 20; i++) {
|
for (int i = 0; i < 20; i++) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
GenUtils.waitFor(10);
|
HavenoUtils.waitFor(10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1390,6 +1452,7 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.state = state;
|
this.state = state;
|
||||||
|
requestPersistence();
|
||||||
UserThread.await(() -> {
|
UserThread.await(() -> {
|
||||||
stateProperty.set(state);
|
stateProperty.set(state);
|
||||||
phaseProperty.set(state.getPhase());
|
phaseProperty.set(state.getPhase());
|
||||||
@ -1421,6 +1484,7 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.payoutState = payoutState;
|
this.payoutState = payoutState;
|
||||||
|
requestPersistence();
|
||||||
UserThread.await(() -> payoutStateProperty.set(payoutState));
|
UserThread.await(() -> payoutStateProperty.set(payoutState));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1572,13 +1636,13 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
throw new RuntimeException("Trade is not maker, taker, or arbitrator");
|
throw new RuntimeException("Trade is not maker, taker, or arbitrator");
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<TradePeer> getPeers() {
|
private List<TradePeer> getOtherPeers() {
|
||||||
List<TradePeer> peers = getAllTradeParties();
|
List<TradePeer> peers = getAllPeers();
|
||||||
if (!peers.remove(getSelf())) throw new IllegalStateException("Failed to remove self from list of peers");
|
if (!peers.remove(getSelf())) throw new IllegalStateException("Failed to remove self from list of peers");
|
||||||
return peers;
|
return peers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<TradePeer> getAllTradeParties() {
|
private List<TradePeer> getAllPeers() {
|
||||||
List<TradePeer> peers = new ArrayList<TradePeer>();
|
List<TradePeer> peers = new ArrayList<TradePeer>();
|
||||||
peers.add(getMaker());
|
peers.add(getMaker());
|
||||||
peers.add(getTaker());
|
peers.add(getTaker());
|
||||||
@ -1765,7 +1829,7 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
if (this instanceof BuyerTrade) {
|
if (this instanceof BuyerTrade) {
|
||||||
return getArbitrator().isDepositsConfirmedMessageAcked();
|
return getArbitrator().isDepositsConfirmedMessageAcked();
|
||||||
} else {
|
} else {
|
||||||
for (TradePeer peer : getPeers()) if (!peer.isDepositsConfirmedMessageAcked()) return false;
|
for (TradePeer peer : getOtherPeers()) if (!peer.isDepositsConfirmedMessageAcked()) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1982,13 +2046,19 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sync and reprocess messages on new thread
|
// sync and reprocess messages on new thread
|
||||||
if (isInitialized && connection != null && !Boolean.FALSE.equals(connection.isConnected())) {
|
if (isInitialized && connection != null && !Boolean.FALSE.equals(xmrConnectionService.isConnected())) {
|
||||||
ThreadUtils.execute(() -> tryInitPolling(), getId());
|
ThreadUtils.execute(() -> tryInitPolling(), getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void tryInitPolling() {
|
private void tryInitPolling() {
|
||||||
if (isShutDownStarted) return;
|
if (isShutDownStarted) return;
|
||||||
|
|
||||||
|
// set known deposit txs
|
||||||
|
List<MoneroTxWallet> depositTxs = wallet.getTxs(new MoneroTxQuery().setIncludeOutputs(true).setInTxPool(false));
|
||||||
|
setDepositTxs(depositTxs);
|
||||||
|
|
||||||
|
// start polling
|
||||||
if (!isIdling()) {
|
if (!isIdling()) {
|
||||||
tryInitPollingAux();
|
tryInitPollingAux();
|
||||||
} else {
|
} else {
|
||||||
@ -2023,9 +2093,12 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
private void syncWallet(boolean pollWallet) {
|
private void syncWallet(boolean pollWallet) {
|
||||||
if (getWallet() == null) throw new RuntimeException("Cannot sync trade wallet because it doesn't exist for " + getClass().getSimpleName() + ", " + getId());
|
if (getWallet() == null) throw new RuntimeException("Cannot sync trade wallet because it doesn't exist for " + getClass().getSimpleName() + ", " + getId());
|
||||||
if (getWallet().getDaemonConnection() == null) throw new RuntimeException("Cannot sync trade wallet because it's not connected to a Monero daemon for " + getClass().getSimpleName() + ", " + getId());
|
if (getWallet().getDaemonConnection() == null) throw new RuntimeException("Cannot sync trade wallet because it's not connected to a Monero daemon for " + getClass().getSimpleName() + ", " + getId());
|
||||||
log.info("Syncing wallet for {} {}", getClass().getSimpleName(), getId());
|
if (isWalletBehind()) {
|
||||||
xmrWalletService.syncWallet(getWallet());
|
log.info("Syncing wallet for {} {}", getClass().getSimpleName(), getShortId());
|
||||||
log.info("Done syncing wallet for {} {}", getClass().getSimpleName(), getId());
|
long startTime = System.currentTimeMillis();
|
||||||
|
syncWalletIfBehind();
|
||||||
|
log.info("Done syncing wallet for {} {} in {} ms", getClass().getSimpleName(), getShortId(), System.currentTimeMillis() - startTime);
|
||||||
|
}
|
||||||
|
|
||||||
// apply tor after wallet synced depending on configuration
|
// apply tor after wallet synced depending on configuration
|
||||||
if (!wasWalletSynced) {
|
if (!wasWalletSynced) {
|
||||||
@ -2063,6 +2136,7 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
private void startPolling() {
|
private void startPolling() {
|
||||||
synchronized (walletLock) {
|
synchronized (walletLock) {
|
||||||
if (isShutDownStarted || isPollInProgress()) return;
|
if (isShutDownStarted || isPollInProgress()) return;
|
||||||
|
updatePollPeriod();
|
||||||
log.info("Starting to poll wallet for {} {}", getClass().getSimpleName(), getId());
|
log.info("Starting to poll wallet for {} {}", getClass().getSimpleName(), getId());
|
||||||
pollLooper = new TaskLooper(() -> pollWallet());
|
pollLooper = new TaskLooper(() -> pollWallet());
|
||||||
pollLooper.start(pollPeriodMs);
|
pollLooper.start(pollPeriodMs);
|
||||||
@ -2110,7 +2184,15 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
MoneroTxQuery query = new MoneroTxQuery().setIncludeOutputs(true);
|
MoneroTxQuery query = new MoneroTxQuery().setIncludeOutputs(true);
|
||||||
Boolean updatePool = !isDepositsConfirmed() && (getMaker().getDepositTx() == null || getTaker().getDepositTx() == null);
|
Boolean updatePool = !isDepositsConfirmed() && (getMaker().getDepositTx() == null || getTaker().getDepositTx() == null);
|
||||||
if (!updatePool) query.setInTxPool(false); // avoid updating from pool if possible
|
if (!updatePool) query.setInTxPool(false); // avoid updating from pool if possible
|
||||||
List<MoneroTxWallet> txs = wallet.getTxs(query);
|
List<MoneroTxWallet> txs;
|
||||||
|
if (!updatePool) txs = wallet.getTxs(query);
|
||||||
|
else {
|
||||||
|
synchronized (walletLock) {
|
||||||
|
synchronized (HavenoUtils.getDaemonLock()) {
|
||||||
|
txs = wallet.getTxs(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
setDepositTxs(txs);
|
setDepositTxs(txs);
|
||||||
if (getMaker().getDepositTx() == null || getTaker().getDepositTx() == null) return; // skip if either deposit tx not seen
|
if (getMaker().getDepositTx() == null || getTaker().getDepositTx() == null) return; // skip if either deposit tx not seen
|
||||||
setStateDepositsSeen();
|
setStateDepositsSeen();
|
||||||
@ -2142,7 +2224,7 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
if (isPayoutExpected || isPayoutPublished()) syncWalletIfBehind();
|
if (isPayoutExpected || isPayoutPublished()) syncWalletIfBehind();
|
||||||
|
|
||||||
// rescan spent outputs to detect unconfirmed payout tx
|
// rescan spent outputs to detect unconfirmed payout tx
|
||||||
if (isPayoutExpected && !isPayoutPublished()) {
|
if (isPayoutExpected && wallet.getBalance().compareTo(BigInteger.ZERO) > 0) {
|
||||||
try {
|
try {
|
||||||
wallet.rescanSpent();
|
wallet.rescanSpent();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -2154,7 +2236,15 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
MoneroTxQuery query = new MoneroTxQuery().setIncludeOutputs(true);
|
MoneroTxQuery query = new MoneroTxQuery().setIncludeOutputs(true);
|
||||||
boolean updatePool = isPayoutExpected && !isPayoutConfirmed();
|
boolean updatePool = isPayoutExpected && !isPayoutConfirmed();
|
||||||
if (!updatePool) query.setInTxPool(false); // avoid updating from pool if possible
|
if (!updatePool) query.setInTxPool(false); // avoid updating from pool if possible
|
||||||
List<MoneroTxWallet> txs = wallet.getTxs(query);
|
List<MoneroTxWallet> txs = null;
|
||||||
|
if (!updatePool) txs = wallet.getTxs(query);
|
||||||
|
else {
|
||||||
|
synchronized (walletLock) {
|
||||||
|
synchronized (HavenoUtils.getDaemonLock()) {
|
||||||
|
txs = wallet.getTxs(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
setDepositTxs(txs);
|
setDepositTxs(txs);
|
||||||
|
|
||||||
// check if any outputs spent (observed on payout published)
|
// check if any outputs spent (observed on payout published)
|
||||||
@ -2191,7 +2281,15 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void syncWalletIfBehind() {
|
private void syncWalletIfBehind() {
|
||||||
if (wallet.getHeight() < xmrConnectionService.getTargetHeight()) syncWallet(false);
|
if (isWalletBehind()) {
|
||||||
|
synchronized (walletLock) {
|
||||||
|
xmrWalletService.syncWallet(wallet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isWalletBehind() {
|
||||||
|
return wallet.getHeight() < xmrConnectionService.getTargetHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDepositTxs(List<? extends MoneroTx> txs) {
|
private void setDepositTxs(List<? extends MoneroTx> txs) {
|
||||||
@ -2278,9 +2376,8 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
processing = false;
|
processing = false;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
processing = false;
|
processing = false;
|
||||||
boolean isWalletConnected = isWalletConnectedToDaemon();
|
if (!isInitialized || isShutDownStarted) return;
|
||||||
if (!isWalletConnected) xmrConnectionService.checkConnection(); // check connection if wallet is not connected
|
if (isWalletConnectedToDaemon()) {
|
||||||
if (isInitialized &&!isShutDownStarted && isWalletConnected) {
|
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
log.warn("Error polling idle trade for {} {}: {}. Monerod={}", getClass().getSimpleName(), getId(), e.getMessage(), getXmrWalletService().getConnectionService().getConnection());
|
log.warn("Error polling idle trade for {} {}: {}. Monerod={}", getClass().getSimpleName(), getId(), e.getMessage(), getXmrWalletService().getConnectionService().getConnection());
|
||||||
};
|
};
|
||||||
|
@ -38,7 +38,6 @@ import static com.google.common.base.Preconditions.checkArgument;
|
|||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import common.utils.GenUtils;
|
|
||||||
import haveno.common.ClockWatcher;
|
import haveno.common.ClockWatcher;
|
||||||
import haveno.common.ThreadUtils;
|
import haveno.common.ThreadUtils;
|
||||||
import haveno.common.UserThread;
|
import haveno.common.UserThread;
|
||||||
@ -512,7 +511,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
}).start();
|
}).start();
|
||||||
|
|
||||||
// allow execution to start
|
// allow execution to start
|
||||||
GenUtils.waitFor(100);
|
HavenoUtils.waitFor(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initPersistedTrade(Trade trade) {
|
private void initPersistedTrade(Trade trade) {
|
||||||
@ -1249,27 +1248,20 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void addTrade(Trade trade) {
|
private void addTrade(Trade trade) {
|
||||||
UserThread.execute(() -> {
|
|
||||||
synchronized (tradableList) {
|
synchronized (tradableList) {
|
||||||
if (tradableList.add(trade)) {
|
if (tradableList.add(trade)) {
|
||||||
requestPersistence();
|
requestPersistence();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeTrade(Trade trade) {
|
private void removeTrade(Trade trade) {
|
||||||
log.info("TradeManager.removeTrade() " + trade.getId());
|
log.info("TradeManager.removeTrade() " + trade.getId());
|
||||||
synchronized (tradableList) {
|
|
||||||
if (!tradableList.contains(trade)) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove trade
|
// remove trade
|
||||||
UserThread.execute(() -> {
|
|
||||||
synchronized (tradableList) {
|
synchronized (tradableList) {
|
||||||
tradableList.remove(trade);
|
if (!tradableList.remove(trade)) return;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// unregister and persist
|
// unregister and persist
|
||||||
p2PService.removeDecryptedDirectMessageListener(getTradeProtocol(trade));
|
p2PService.removeDecryptedDirectMessageListener(getTradeProtocol(trade));
|
||||||
@ -1277,21 +1269,18 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void maybeRemoveTradeOnError(Trade trade) {
|
private void maybeRemoveTradeOnError(Trade trade) {
|
||||||
synchronized (tradableList) {
|
|
||||||
if (trade.isDepositRequested() && !trade.isDepositFailed()) {
|
if (trade.isDepositRequested() && !trade.isDepositFailed()) {
|
||||||
listenForCleanup(trade);
|
listenForCleanup(trade);
|
||||||
} else {
|
} else {
|
||||||
removeTradeOnError(trade);
|
removeTradeOnError(trade);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void removeTradeOnError(Trade trade) {
|
private void removeTradeOnError(Trade trade) {
|
||||||
log.warn("TradeManager.removeTradeOnError() trade={}, tradeId={}, state={}", trade.getClass().getSimpleName(), trade.getShortId(), trade.getState());
|
log.warn("TradeManager.removeTradeOnError() trade={}, tradeId={}, state={}", trade.getClass().getSimpleName(), trade.getShortId(), trade.getState());
|
||||||
synchronized (tradableList) {
|
|
||||||
|
|
||||||
// unreserve taker key images
|
// unreserve taker key images
|
||||||
if (trade instanceof TakerTrade && trade.getSelf().getReserveTxKeyImages() != null) {
|
if (trade instanceof TakerTrade) {
|
||||||
xmrWalletService.thawOutputs(trade.getSelf().getReserveTxKeyImages());
|
xmrWalletService.thawOutputs(trade.getSelf().getReserveTxKeyImages());
|
||||||
trade.getSelf().setReserveTxKeyImages(null);
|
trade.getSelf().setReserveTxKeyImages(null);
|
||||||
}
|
}
|
||||||
@ -1301,7 +1290,6 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
if (trade instanceof MakerTrade && openOffer.isPresent()) {
|
if (trade instanceof MakerTrade && openOffer.isPresent()) {
|
||||||
openOfferManager.unreserveOpenOffer(openOffer.get());
|
openOfferManager.unreserveOpenOffer(openOffer.get());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// clear and shut down trade
|
// clear and shut down trade
|
||||||
trade.clearAndShutDown();
|
trade.clearAndShutDown();
|
||||||
@ -1358,7 +1346,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
|||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
|
|
||||||
// wait minimum time
|
// wait minimum time
|
||||||
GenUtils.waitFor(Math.max(0, REMOVE_AFTER_MS - (System.currentTimeMillis() - startTime)));
|
HavenoUtils.waitFor(Math.max(0, REMOVE_AFTER_MS - (System.currentTimeMillis() - startTime)));
|
||||||
|
|
||||||
// get trade's deposit txs from daemon
|
// get trade's deposit txs from daemon
|
||||||
MoneroTx makerDepositTx = trade.getMaker().getDepositTxHash() == null ? null : xmrWalletService.getDaemon().getTx(trade.getMaker().getDepositTxHash());
|
MoneroTx makerDepositTx = trade.getMaker().getDepositTxHash() == null ? null : xmrWalletService.getDaemon().getTx(trade.getMaker().getDepositTxHash());
|
||||||
|
@ -59,13 +59,13 @@ public class ArbitratorProtocol extends DisputeProtocol {
|
|||||||
ArbitratorSendInitTradeOrMultisigRequests.class)
|
ArbitratorSendInitTradeOrMultisigRequests.class)
|
||||||
.using(new TradeTaskRunner(trade,
|
.using(new TradeTaskRunner(trade,
|
||||||
() -> {
|
() -> {
|
||||||
startTimeout(TRADE_TIMEOUT_SECONDS);
|
startTimeout(TRADE_STEP_TIMEOUT_SECONDS);
|
||||||
handleTaskRunnerSuccess(peer, message);
|
handleTaskRunnerSuccess(peer, message);
|
||||||
},
|
},
|
||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
handleTaskRunnerFault(peer, message, errorMessage);
|
handleTaskRunnerFault(peer, message, errorMessage);
|
||||||
}))
|
}))
|
||||||
.withTimeout(TRADE_TIMEOUT_SECONDS))
|
.withTimeout(TRADE_STEP_TIMEOUT_SECONDS))
|
||||||
.executeTasks(true);
|
.executeTasks(true);
|
||||||
awaitTradeLatch();
|
awaitTradeLatch();
|
||||||
}
|
}
|
||||||
@ -100,7 +100,7 @@ public class ArbitratorProtocol extends DisputeProtocol {
|
|||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
handleTaskRunnerFault(sender, request, errorMessage);
|
handleTaskRunnerFault(sender, request, errorMessage);
|
||||||
}))
|
}))
|
||||||
.withTimeout(TRADE_TIMEOUT_SECONDS))
|
.withTimeout(TRADE_STEP_TIMEOUT_SECONDS))
|
||||||
.executeTasks(true);
|
.executeTasks(true);
|
||||||
awaitTradeLatch();
|
awaitTradeLatch();
|
||||||
}
|
}
|
||||||
|
@ -74,13 +74,13 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
|
|||||||
MakerSendInitTradeRequest.class)
|
MakerSendInitTradeRequest.class)
|
||||||
.using(new TradeTaskRunner(trade,
|
.using(new TradeTaskRunner(trade,
|
||||||
() -> {
|
() -> {
|
||||||
startTimeout(TRADE_TIMEOUT_SECONDS);
|
startTimeout(TRADE_STEP_TIMEOUT_SECONDS);
|
||||||
handleTaskRunnerSuccess(peer, message);
|
handleTaskRunnerSuccess(peer, message);
|
||||||
},
|
},
|
||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
handleTaskRunnerFault(peer, message, errorMessage);
|
handleTaskRunnerFault(peer, message, errorMessage);
|
||||||
}))
|
}))
|
||||||
.withTimeout(TRADE_TIMEOUT_SECONDS))
|
.withTimeout(TRADE_STEP_TIMEOUT_SECONDS))
|
||||||
.executeTasks(true);
|
.executeTasks(true);
|
||||||
awaitTradeLatch();
|
awaitTradeLatch();
|
||||||
}
|
}
|
||||||
|
@ -79,13 +79,13 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
|
|||||||
TakerSendInitTradeRequestToArbitrator.class)
|
TakerSendInitTradeRequestToArbitrator.class)
|
||||||
.using(new TradeTaskRunner(trade,
|
.using(new TradeTaskRunner(trade,
|
||||||
() -> {
|
() -> {
|
||||||
startTimeout(TRADE_TIMEOUT_SECONDS);
|
startTimeout(TRADE_STEP_TIMEOUT_SECONDS);
|
||||||
unlatchTrade();
|
unlatchTrade();
|
||||||
},
|
},
|
||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
handleError(errorMessage);
|
handleError(errorMessage);
|
||||||
}))
|
}))
|
||||||
.withTimeout(TRADE_TIMEOUT_SECONDS))
|
.withTimeout(TRADE_STEP_TIMEOUT_SECONDS))
|
||||||
.executeTasks(true);
|
.executeTasks(true);
|
||||||
awaitTradeLatch();
|
awaitTradeLatch();
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,7 @@ import haveno.core.trade.protocol.tasks.BuyerPreparePaymentSentMessage;
|
|||||||
import haveno.core.trade.protocol.tasks.BuyerSendPaymentSentMessageToArbitrator;
|
import haveno.core.trade.protocol.tasks.BuyerSendPaymentSentMessageToArbitrator;
|
||||||
import haveno.core.trade.protocol.tasks.BuyerSendPaymentSentMessageToSeller;
|
import haveno.core.trade.protocol.tasks.BuyerSendPaymentSentMessageToSeller;
|
||||||
import haveno.core.trade.protocol.tasks.SendDepositsConfirmedMessageToArbitrator;
|
import haveno.core.trade.protocol.tasks.SendDepositsConfirmedMessageToArbitrator;
|
||||||
|
import haveno.core.trade.protocol.tasks.SendDepositsConfirmedMessageToSeller;
|
||||||
import haveno.core.trade.protocol.tasks.TradeTask;
|
import haveno.core.trade.protocol.tasks.TradeTask;
|
||||||
import haveno.network.p2p.NodeAddress;
|
import haveno.network.p2p.NodeAddress;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -158,6 +159,6 @@ public class BuyerProtocol extends DisputeProtocol {
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
public Class<? extends TradeTask>[] getDepositsConfirmedTasks() {
|
public Class<? extends TradeTask>[] getDepositsConfirmedTasks() {
|
||||||
return new Class[] { SendDepositsConfirmedMessageToArbitrator.class };
|
return new Class[] { SendDepositsConfirmedMessageToSeller.class, SendDepositsConfirmedMessageToArbitrator.class };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,6 +163,7 @@ public class ProcessModel implements Model, PersistablePayload {
|
|||||||
private ObjectProperty<MessageState> paymentSentMessageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
private ObjectProperty<MessageState> paymentSentMessageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
||||||
@Setter
|
@Setter
|
||||||
private ObjectProperty<MessageState> paymentSentMessageStatePropertyArbitrator = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
private ObjectProperty<MessageState> paymentSentMessageStatePropertyArbitrator = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
||||||
|
private ObjectProperty<Boolean> paymentAccountDecryptedProperty = new SimpleObjectProperty<>(false);
|
||||||
|
|
||||||
public ProcessModel(String offerId, String accountId, PubKeyRing pubKeyRing) {
|
public ProcessModel(String offerId, String accountId, PubKeyRing pubKeyRing) {
|
||||||
this(offerId, accountId, pubKeyRing, new TradePeer(), new TradePeer(), new TradePeer());
|
this(offerId, accountId, pubKeyRing, new TradePeer(), new TradePeer(), new TradePeer());
|
||||||
|
@ -79,13 +79,13 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
|
|||||||
MakerSendInitTradeRequest.class)
|
MakerSendInitTradeRequest.class)
|
||||||
.using(new TradeTaskRunner(trade,
|
.using(new TradeTaskRunner(trade,
|
||||||
() -> {
|
() -> {
|
||||||
startTimeout(TRADE_TIMEOUT_SECONDS);
|
startTimeout(TRADE_STEP_TIMEOUT_SECONDS);
|
||||||
handleTaskRunnerSuccess(peer, message);
|
handleTaskRunnerSuccess(peer, message);
|
||||||
},
|
},
|
||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
handleTaskRunnerFault(peer, message, errorMessage);
|
handleTaskRunnerFault(peer, message, errorMessage);
|
||||||
}))
|
}))
|
||||||
.withTimeout(TRADE_TIMEOUT_SECONDS))
|
.withTimeout(TRADE_STEP_TIMEOUT_SECONDS))
|
||||||
.executeTasks(true);
|
.executeTasks(true);
|
||||||
awaitTradeLatch();
|
awaitTradeLatch();
|
||||||
}
|
}
|
||||||
|
@ -80,13 +80,13 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
|
|||||||
TakerSendInitTradeRequestToArbitrator.class)
|
TakerSendInitTradeRequestToArbitrator.class)
|
||||||
.using(new TradeTaskRunner(trade,
|
.using(new TradeTaskRunner(trade,
|
||||||
() -> {
|
() -> {
|
||||||
startTimeout(TRADE_TIMEOUT_SECONDS);
|
startTimeout(TRADE_STEP_TIMEOUT_SECONDS);
|
||||||
unlatchTrade();
|
unlatchTrade();
|
||||||
},
|
},
|
||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
handleError(errorMessage);
|
handleError(errorMessage);
|
||||||
}))
|
}))
|
||||||
.withTimeout(TRADE_TIMEOUT_SECONDS))
|
.withTimeout(TRADE_STEP_TIMEOUT_SECONDS))
|
||||||
.executeTasks(true);
|
.executeTasks(true);
|
||||||
awaitTradeLatch();
|
awaitTradeLatch();
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ package haveno.core.trade.protocol;
|
|||||||
import haveno.common.ThreadUtils;
|
import haveno.common.ThreadUtils;
|
||||||
import haveno.common.Timer;
|
import haveno.common.Timer;
|
||||||
import haveno.common.UserThread;
|
import haveno.common.UserThread;
|
||||||
|
import haveno.common.config.Config;
|
||||||
import haveno.common.crypto.PubKeyRing;
|
import haveno.common.crypto.PubKeyRing;
|
||||||
import haveno.common.handlers.ErrorMessageHandler;
|
import haveno.common.handlers.ErrorMessageHandler;
|
||||||
import haveno.common.proto.network.NetworkEnvelope;
|
import haveno.common.proto.network.NetworkEnvelope;
|
||||||
@ -67,7 +68,7 @@ import haveno.core.trade.protocol.tasks.ProcessInitMultisigRequest;
|
|||||||
import haveno.core.trade.protocol.tasks.ProcessPaymentReceivedMessage;
|
import haveno.core.trade.protocol.tasks.ProcessPaymentReceivedMessage;
|
||||||
import haveno.core.trade.protocol.tasks.ProcessPaymentSentMessage;
|
import haveno.core.trade.protocol.tasks.ProcessPaymentSentMessage;
|
||||||
import haveno.core.trade.protocol.tasks.ProcessSignContractRequest;
|
import haveno.core.trade.protocol.tasks.ProcessSignContractRequest;
|
||||||
import haveno.core.trade.protocol.tasks.ProcessSignContractResponse;
|
import haveno.core.trade.protocol.tasks.SendDepositRequest;
|
||||||
import haveno.core.trade.protocol.tasks.RemoveOffer;
|
import haveno.core.trade.protocol.tasks.RemoveOffer;
|
||||||
import haveno.core.trade.protocol.tasks.SellerPublishTradeStatistics;
|
import haveno.core.trade.protocol.tasks.SellerPublishTradeStatistics;
|
||||||
import haveno.core.trade.protocol.tasks.MaybeResendDisputeClosedMessageWithPayout;
|
import haveno.core.trade.protocol.tasks.MaybeResendDisputeClosedMessageWithPayout;
|
||||||
@ -93,8 +94,10 @@ import java.util.concurrent.CountDownLatch;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public abstract class TradeProtocol implements DecryptedDirectMessageListener, DecryptedMailboxListener {
|
public abstract class TradeProtocol implements DecryptedDirectMessageListener, DecryptedMailboxListener {
|
||||||
|
|
||||||
public static final int TRADE_TIMEOUT_SECONDS = 120;
|
public static final int TRADE_STEP_TIMEOUT_SECONDS = Config.baseCurrencyNetwork().isTestnet() ? 45 : 180;
|
||||||
private static final String TIMEOUT_REACHED = "Timeout reached.";
|
private static final String TIMEOUT_REACHED = "Timeout reached.";
|
||||||
|
public static final int MAX_ATTEMPTS = 3;
|
||||||
|
public static final long REPROCESS_DELAY_MS = 5000;
|
||||||
|
|
||||||
protected final ProcessModel processModel;
|
protected final ProcessModel processModel;
|
||||||
protected final Trade trade;
|
protected final Trade trade;
|
||||||
@ -104,6 +107,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||||||
protected TradeResultHandler tradeResultHandler;
|
protected TradeResultHandler tradeResultHandler;
|
||||||
protected ErrorMessageHandler errorMessageHandler;
|
protected ErrorMessageHandler errorMessageHandler;
|
||||||
|
|
||||||
|
private boolean depositsConfirmedTasksCalled;
|
||||||
private int reprocessPaymentReceivedMessageCount;
|
private int reprocessPaymentReceivedMessageCount;
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -251,14 +255,14 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||||||
}
|
}
|
||||||
|
|
||||||
// send deposits confirmed message if applicable
|
// send deposits confirmed message if applicable
|
||||||
maybeSendDepositsConfirmedMessages();
|
|
||||||
EasyBind.subscribe(trade.stateProperty(), state -> maybeSendDepositsConfirmedMessages());
|
EasyBind.subscribe(trade.stateProperty(), state -> maybeSendDepositsConfirmedMessages());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void maybeSendDepositsConfirmedMessages() {
|
public void maybeSendDepositsConfirmedMessages() {
|
||||||
if (!trade.isInitialized() || trade.isShutDownStarted()) return;
|
if (!trade.isInitialized() || trade.isShutDownStarted()) return;
|
||||||
ThreadUtils.execute(() -> {
|
ThreadUtils.execute(() -> {
|
||||||
if (!trade.isDepositsConfirmed() || trade.isDepositsConfirmedAcked() || trade.isPayoutPublished()) return;
|
if (!trade.isDepositsConfirmed() || trade.isDepositsConfirmedAcked() || trade.isPayoutPublished() || depositsConfirmedTasksCalled) return;
|
||||||
|
depositsConfirmedTasksCalled = true;
|
||||||
synchronized (trade) {
|
synchronized (trade) {
|
||||||
if (!trade.isInitialized() || trade.isShutDownStarted()) return; // skip if shutting down
|
if (!trade.isInitialized() || trade.isShutDownStarted()) return; // skip if shutting down
|
||||||
latchTrade();
|
latchTrade();
|
||||||
@ -316,13 +320,13 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||||||
MaybeSendSignContractRequest.class)
|
MaybeSendSignContractRequest.class)
|
||||||
.using(new TradeTaskRunner(trade,
|
.using(new TradeTaskRunner(trade,
|
||||||
() -> {
|
() -> {
|
||||||
startTimeout(TRADE_TIMEOUT_SECONDS);
|
startTimeout(TRADE_STEP_TIMEOUT_SECONDS);
|
||||||
handleTaskRunnerSuccess(sender, request);
|
handleTaskRunnerSuccess(sender, request);
|
||||||
},
|
},
|
||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
handleTaskRunnerFault(sender, request, errorMessage);
|
handleTaskRunnerFault(sender, request, errorMessage);
|
||||||
}))
|
}))
|
||||||
.withTimeout(TRADE_TIMEOUT_SECONDS))
|
.withTimeout(TRADE_STEP_TIMEOUT_SECONDS))
|
||||||
.executeTasks(true);
|
.executeTasks(true);
|
||||||
awaitTradeLatch();
|
awaitTradeLatch();
|
||||||
}
|
}
|
||||||
@ -354,13 +358,13 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||||||
ProcessSignContractRequest.class)
|
ProcessSignContractRequest.class)
|
||||||
.using(new TradeTaskRunner(trade,
|
.using(new TradeTaskRunner(trade,
|
||||||
() -> {
|
() -> {
|
||||||
startTimeout(TRADE_TIMEOUT_SECONDS);
|
startTimeout(TRADE_STEP_TIMEOUT_SECONDS);
|
||||||
handleTaskRunnerSuccess(sender, message);
|
handleTaskRunnerSuccess(sender, message);
|
||||||
},
|
},
|
||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
handleTaskRunnerFault(sender, message, errorMessage);
|
handleTaskRunnerFault(sender, message, errorMessage);
|
||||||
}))
|
}))
|
||||||
.withTimeout(TRADE_TIMEOUT_SECONDS)) // extend timeout
|
.withTimeout(TRADE_STEP_TIMEOUT_SECONDS)) // extend timeout
|
||||||
.executeTasks(true);
|
.executeTasks(true);
|
||||||
awaitTradeLatch();
|
awaitTradeLatch();
|
||||||
} else {
|
} else {
|
||||||
@ -396,16 +400,16 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||||||
.from(sender))
|
.from(sender))
|
||||||
.setup(tasks(
|
.setup(tasks(
|
||||||
// TODO (woodser): validate request
|
// TODO (woodser): validate request
|
||||||
ProcessSignContractResponse.class)
|
SendDepositRequest.class)
|
||||||
.using(new TradeTaskRunner(trade,
|
.using(new TradeTaskRunner(trade,
|
||||||
() -> {
|
() -> {
|
||||||
startTimeout(TRADE_TIMEOUT_SECONDS);
|
startTimeout(TRADE_STEP_TIMEOUT_SECONDS);
|
||||||
handleTaskRunnerSuccess(sender, message);
|
handleTaskRunnerSuccess(sender, message);
|
||||||
},
|
},
|
||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
handleTaskRunnerFault(sender, message, errorMessage);
|
handleTaskRunnerFault(sender, message, errorMessage);
|
||||||
}))
|
}))
|
||||||
.withTimeout(TRADE_TIMEOUT_SECONDS)) // extend timeout
|
.withTimeout(TRADE_STEP_TIMEOUT_SECONDS)) // extend timeout
|
||||||
.executeTasks(true);
|
.executeTasks(true);
|
||||||
awaitTradeLatch();
|
awaitTradeLatch();
|
||||||
} else {
|
} else {
|
||||||
@ -451,7 +455,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
|||||||
errorMessage -> {
|
errorMessage -> {
|
||||||
handleTaskRunnerFault(sender, response, errorMessage);
|
handleTaskRunnerFault(sender, response, errorMessage);
|
||||||
}))
|
}))
|
||||||
.withTimeout(TRADE_TIMEOUT_SECONDS))
|
.withTimeout(TRADE_STEP_TIMEOUT_SECONDS))
|
||||||
.executeTasks(true);
|
.executeTasks(true);
|
||||||
awaitTradeLatch();
|
awaitTradeLatch();
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ public class BuyerPreparePaymentSentMessage extends TradeTask {
|
|||||||
|
|
||||||
// skip if payout tx already created
|
// skip if payout tx already created
|
||||||
if (trade.getPayoutTxHex() != null) {
|
if (trade.getPayoutTxHex() != null) {
|
||||||
log.warn("Skipping preparation of payment sent message because payout tx is already created for {} {}", trade.getClass().getSimpleName(), trade.getId());
|
log.warn("Skipping preparation of payment sent message because payout tx is already created for {} {}", trade.getClass().getSimpleName(), trade.getShortId());
|
||||||
complete();
|
complete();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -83,7 +83,7 @@ public class BuyerPreparePaymentSentMessage extends TradeTask {
|
|||||||
trade.importMultisigHex();
|
trade.importMultisigHex();
|
||||||
|
|
||||||
// create payout tx
|
// create payout tx
|
||||||
log.info("Buyer creating unsigned payout tx");
|
log.info("Buyer creating unsigned payout tx for {} {} ", trade.getClass().getSimpleName(), trade.getShortId());
|
||||||
MoneroTxWallet payoutTx = trade.createPayoutTx();
|
MoneroTxWallet payoutTx = trade.createPayoutTx();
|
||||||
trade.setPayoutTx(payoutTx);
|
trade.setPayoutTx(payoutTx);
|
||||||
trade.setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
|
trade.setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
|
||||||
|
@ -28,6 +28,7 @@ import haveno.core.trade.Trade.State;
|
|||||||
import haveno.core.trade.messages.SignContractRequest;
|
import haveno.core.trade.messages.SignContractRequest;
|
||||||
import haveno.core.trade.protocol.TradeProtocol;
|
import haveno.core.trade.protocol.TradeProtocol;
|
||||||
import haveno.core.xmr.model.XmrAddressEntry;
|
import haveno.core.xmr.model.XmrAddressEntry;
|
||||||
|
import haveno.core.xmr.wallet.XmrWalletService;
|
||||||
import haveno.network.p2p.SendDirectMessageListener;
|
import haveno.network.p2p.SendDirectMessageListener;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import monero.daemon.model.MoneroOutput;
|
import monero.daemon.model.MoneroOutput;
|
||||||
@ -78,37 +79,70 @@ public class MaybeSendSignContractRequest extends TradeTask {
|
|||||||
trade.addInitProgressStep();
|
trade.addInitProgressStep();
|
||||||
|
|
||||||
// create deposit tx and freeze inputs
|
// create deposit tx and freeze inputs
|
||||||
|
MoneroTxWallet depositTx = null;
|
||||||
|
synchronized (XmrWalletService.WALLET_LOCK) {
|
||||||
|
|
||||||
|
// check for timeout
|
||||||
|
if (isTimedOut()) throw new RuntimeException("Trade protocol has timed out while creating deposit tx, tradeId=" + trade.getShortId());
|
||||||
|
|
||||||
|
// collect relevant info
|
||||||
Integer subaddressIndex = null;
|
Integer subaddressIndex = null;
|
||||||
boolean reserveExactAmount = false;
|
boolean reserveExactAmount = false;
|
||||||
if (trade instanceof MakerTrade) {
|
if (trade instanceof MakerTrade) {
|
||||||
reserveExactAmount = processModel.getOpenOfferManager().getOpenOfferById(trade.getId()).get().isReserveExactAmount();
|
reserveExactAmount = processModel.getOpenOfferManager().getOpenOfferById(trade.getId()).get().isReserveExactAmount();
|
||||||
if (reserveExactAmount) subaddressIndex = model.getXmrWalletService().getAddressEntry(trade.getId(), XmrAddressEntry.Context.OFFER_FUNDING).get().getSubaddressIndex();
|
if (reserveExactAmount) subaddressIndex = model.getXmrWalletService().getAddressEntry(trade.getId(), XmrAddressEntry.Context.OFFER_FUNDING).get().getSubaddressIndex();
|
||||||
}
|
}
|
||||||
MoneroTxWallet depositTx = trade.getXmrWalletService().createDepositTx(trade, reserveExactAmount, subaddressIndex);
|
|
||||||
|
|
||||||
// check if trade still exists
|
// thaw reserved outputs
|
||||||
if (!processModel.getTradeManager().hasOpenTrade(trade)) {
|
if (trade.getSelf().getReserveTxKeyImages() != null) {
|
||||||
throw new RuntimeException("Trade protocol has timed out while creating reserve tx, tradeId=" + trade.getId());
|
trade.getXmrWalletService().thawOutputs(trade.getSelf().getReserveTxKeyImages());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// attempt creating deposit tx
|
||||||
|
try {
|
||||||
|
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||||
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
|
try {
|
||||||
|
depositTx = trade.getXmrWalletService().createDepositTx(trade, reserveExactAmount, subaddressIndex);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Error creating deposit tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, trade.getShortId(), e.getMessage());
|
||||||
|
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||||
|
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for timeout
|
||||||
|
if (isTimedOut()) throw new RuntimeException("Trade protocol has timed out while creating deposit tx, tradeId=" + trade.getShortId());
|
||||||
|
if (depositTx != null) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
|
||||||
|
// re-freeze reserved outputs
|
||||||
|
if (trade.getSelf().getReserveTxKeyImages() != null) {
|
||||||
|
trade.getXmrWalletService().freezeOutputs(trade.getSelf().getReserveTxKeyImages());
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// reset protocol timeout
|
// reset protocol timeout
|
||||||
trade.getProtocol().startTimeout(TradeProtocol.TRADE_TIMEOUT_SECONDS);
|
trade.getProtocol().startTimeout(TradeProtocol.TRADE_STEP_TIMEOUT_SECONDS);
|
||||||
|
|
||||||
// collect reserved key images
|
// collect reserved key images
|
||||||
List<String> reservedKeyImages = new ArrayList<String>();
|
List<String> reservedKeyImages = new ArrayList<String>();
|
||||||
for (MoneroOutput input : depositTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex());
|
for (MoneroOutput input : depositTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex());
|
||||||
|
|
||||||
// save process state
|
// update trade state
|
||||||
|
BigInteger securityDeposit = trade instanceof BuyerTrade ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee();
|
||||||
|
trade.getSelf().setSecurityDeposit(securityDeposit.subtract(depositTx.getFee()));
|
||||||
trade.getSelf().setDepositTx(depositTx);
|
trade.getSelf().setDepositTx(depositTx);
|
||||||
trade.getSelf().setDepositTxHash(depositTx.getHash());
|
trade.getSelf().setDepositTxHash(depositTx.getHash());
|
||||||
trade.getSelf().setDepositTxFee(depositTx.getFee());
|
trade.getSelf().setDepositTxFee(depositTx.getFee());
|
||||||
trade.getSelf().setReserveTxKeyImages(reservedKeyImages);
|
trade.getSelf().setReserveTxKeyImages(reservedKeyImages);
|
||||||
trade.getSelf().setPayoutAddressString(trade.getXmrWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString()); // TODO (woodser): allow custom payout address?
|
trade.getSelf().setPayoutAddressString(trade.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString()); // TODO (woodser): allow custom payout address?
|
||||||
trade.getSelf().setPaymentAccountPayload(trade.getProcessModel().getPaymentAccountPayload(trade.getSelf().getPaymentAccountId()));
|
trade.getSelf().setPaymentAccountPayload(trade.getProcessModel().getPaymentAccountPayload(trade.getSelf().getPaymentAccountId()));
|
||||||
|
}
|
||||||
// TODO: security deposit should be based on trade amount, not max offer amount
|
|
||||||
BigInteger securityDeposit = trade instanceof BuyerTrade ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee();
|
|
||||||
trade.getSelf().setSecurityDeposit(securityDeposit.subtract(depositTx.getFee()));
|
|
||||||
|
|
||||||
// maker signs deposit hash nonce to avoid challenge protocol
|
// maker signs deposit hash nonce to avoid challenge protocol
|
||||||
byte[] sig = null;
|
byte[] sig = null;
|
||||||
@ -170,4 +204,8 @@ public class MaybeSendSignContractRequest extends TradeTask {
|
|||||||
processModel.getTradeManager().requestPersistence();
|
processModel.getTradeManager().requestPersistence();
|
||||||
complete();
|
complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isTimedOut() {
|
||||||
|
return !processModel.getTradeManager().hasOpenTrade(trade);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
package haveno.core.trade.protocol.tasks;
|
package haveno.core.trade.protocol.tasks;
|
||||||
|
|
||||||
|
|
||||||
|
import haveno.common.ThreadUtils;
|
||||||
import haveno.common.taskrunner.TaskRunner;
|
import haveno.common.taskrunner.TaskRunner;
|
||||||
import haveno.core.trade.Trade;
|
import haveno.core.trade.Trade;
|
||||||
import haveno.core.trade.messages.DepositsConfirmedMessage;
|
import haveno.core.trade.messages.DepositsConfirmedMessage;
|
||||||
@ -53,25 +54,26 @@ public class ProcessDepositsConfirmedMessage extends TradeTask {
|
|||||||
if (sender.getNodeAddress().equals(trade.getSeller().getNodeAddress()) && sender != trade.getSeller()) trade.getSeller().setNodeAddress(null);
|
if (sender.getNodeAddress().equals(trade.getSeller().getNodeAddress()) && sender != trade.getSeller()) trade.getSeller().setNodeAddress(null);
|
||||||
if (sender.getNodeAddress().equals(trade.getArbitrator().getNodeAddress()) && sender != trade.getArbitrator()) trade.getArbitrator().setNodeAddress(null);
|
if (sender.getNodeAddress().equals(trade.getArbitrator().getNodeAddress()) && sender != trade.getArbitrator()) trade.getArbitrator().setNodeAddress(null);
|
||||||
|
|
||||||
// update multisig hex
|
|
||||||
sender.setUpdatedMultisigHex(request.getUpdatedMultisigHex());
|
|
||||||
|
|
||||||
// decrypt seller payment account payload if key given
|
// decrypt seller payment account payload if key given
|
||||||
if (request.getSellerPaymentAccountKey() != null && trade.getTradePeer().getPaymentAccountPayload() == null) {
|
if (request.getSellerPaymentAccountKey() != null && trade.getTradePeer().getPaymentAccountPayload() == null) {
|
||||||
log.info(trade.getClass().getSimpleName() + " decrypting using seller payment account key");
|
log.info(trade.getClass().getSimpleName() + " decrypting using seller payment account key");
|
||||||
trade.decryptPeerPaymentAccountPayload(request.getSellerPaymentAccountKey());
|
trade.decryptPeerPaymentAccountPayload(request.getSellerPaymentAccountKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
// persist
|
// update multisig hex
|
||||||
processModel.getTradeManager().requestPersistence();
|
sender.setUpdatedMultisigHex(request.getUpdatedMultisigHex());
|
||||||
|
|
||||||
// try to import multisig hex (retry later)
|
// try to import multisig hex (retry later)
|
||||||
|
ThreadUtils.submitToPool(() -> {
|
||||||
try {
|
try {
|
||||||
trade.importMultisigHex();
|
trade.importMultisigHex();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// persist
|
||||||
|
processModel.getTradeManager().requestPersistence();
|
||||||
complete();
|
complete();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
failed(t);
|
failed(t);
|
||||||
|
@ -34,7 +34,6 @@
|
|||||||
|
|
||||||
package haveno.core.trade.protocol.tasks;
|
package haveno.core.trade.protocol.tasks;
|
||||||
|
|
||||||
import common.utils.GenUtils;
|
|
||||||
import haveno.common.taskrunner.TaskRunner;
|
import haveno.common.taskrunner.TaskRunner;
|
||||||
import haveno.core.account.sign.SignedWitness;
|
import haveno.core.account.sign.SignedWitness;
|
||||||
import haveno.core.support.dispute.Dispute;
|
import haveno.core.support.dispute.Dispute;
|
||||||
@ -145,7 +144,7 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
|
|||||||
log.info("Deferring signing and publishing payout tx for {} {}", trade.getClass().getSimpleName(), trade.getId());
|
log.info("Deferring signing and publishing payout tx for {} {}", trade.getClass().getSimpleName(), trade.getId());
|
||||||
for (int i = 0; i < 5; i++) {
|
for (int i = 0; i < 5; i++) {
|
||||||
if (trade.isPayoutPublished()) break;
|
if (trade.isPayoutPublished()) break;
|
||||||
GenUtils.waitFor(Trade.DEFER_PUBLISH_MS / 5);
|
HavenoUtils.waitFor(Trade.DEFER_PUBLISH_MS / 5);
|
||||||
}
|
}
|
||||||
if (!trade.isPayoutPublished()) trade.syncAndPollWallet();
|
if (!trade.isPayoutPublished()) trade.syncAndPollWallet();
|
||||||
}
|
}
|
||||||
|
@ -61,15 +61,6 @@ public class ProcessPaymentSentMessage extends TradeTask {
|
|||||||
if (trade.isSeller()) trade.decryptPeerPaymentAccountPayload(message.getPaymentAccountKey());
|
if (trade.isSeller()) trade.decryptPeerPaymentAccountPayload(message.getPaymentAccountKey());
|
||||||
trade.requestPersistence();
|
trade.requestPersistence();
|
||||||
|
|
||||||
// try to import multisig hex off main thread (retry later)
|
|
||||||
new Thread(() -> {
|
|
||||||
try {
|
|
||||||
trade.importMultisigHex();
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
|
|
||||||
// update state
|
// update state
|
||||||
trade.advanceState(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
trade.advanceState(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
||||||
trade.requestPersistence();
|
trade.requestPersistence();
|
||||||
|
@ -37,7 +37,7 @@ public class SellerPreparePaymentReceivedMessage extends TradeTask {
|
|||||||
runInterceptHook();
|
runInterceptHook();
|
||||||
|
|
||||||
// check connection
|
// check connection
|
||||||
trade.checkAndVerifyDaemonConnection();
|
trade.verifyDaemonConnection();
|
||||||
|
|
||||||
// handle first time preparation
|
// handle first time preparation
|
||||||
if (trade.getArbitrator().getPaymentReceivedMessage() == null) {
|
if (trade.getArbitrator().getPaymentReceivedMessage() == null) {
|
||||||
|
@ -29,14 +29,16 @@ import haveno.core.trade.protocol.TradePeer;
|
|||||||
import haveno.network.p2p.SendDirectMessageListener;
|
import haveno.network.p2p.SendDirectMessageListener;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ProcessSignContractResponse extends TradeTask {
|
public class SendDepositRequest extends TradeTask {
|
||||||
|
|
||||||
@SuppressWarnings({"unused"})
|
@SuppressWarnings({"unused"})
|
||||||
public ProcessSignContractResponse(TaskRunner taskHandler, Trade trade) {
|
public SendDepositRequest(TaskRunner taskHandler, Trade trade) {
|
||||||
super(taskHandler, trade);
|
super(taskHandler, trade);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +109,11 @@ public class ProcessSignContractResponse extends TradeTask {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.info("Waiting for another contract signature to send deposit request");
|
List<String> awaitingSignaturesFrom = new ArrayList<>();
|
||||||
|
if (processModel.getArbitrator().getContractSignature() == null) awaitingSignaturesFrom.add("arbitrator");
|
||||||
|
if (processModel.getMaker().getContractSignature() == null) awaitingSignaturesFrom.add("maker");
|
||||||
|
if (processModel.getTaker().getContractSignature() == null) awaitingSignaturesFrom.add("taker");
|
||||||
|
log.info("Waiting for contract signature from {} to send deposit request", awaitingSignaturesFrom);
|
||||||
complete(); // does not yet have needed signatures
|
complete(); // does not yet have needed signatures
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
@ -23,6 +23,8 @@ import haveno.core.trade.HavenoUtils;
|
|||||||
import haveno.core.trade.Trade;
|
import haveno.core.trade.Trade;
|
||||||
import haveno.core.trade.protocol.TradeProtocol;
|
import haveno.core.trade.protocol.TradeProtocol;
|
||||||
import haveno.core.xmr.model.XmrAddressEntry;
|
import haveno.core.xmr.model.XmrAddressEntry;
|
||||||
|
import haveno.core.xmr.wallet.XmrWalletService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import monero.daemon.model.MoneroOutput;
|
import monero.daemon.model.MoneroOutput;
|
||||||
import monero.wallet.model.MoneroTxWallet;
|
import monero.wallet.model.MoneroTxWallet;
|
||||||
|
|
||||||
@ -30,6 +32,7 @@ import java.math.BigInteger;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class TakerReserveTradeFunds extends TradeTask {
|
public class TakerReserveTradeFunds extends TradeTask {
|
||||||
|
|
||||||
public TakerReserveTradeFunds(TaskRunner taskHandler, Trade trade) {
|
public TakerReserveTradeFunds(TaskRunner taskHandler, Trade trade) {
|
||||||
@ -42,28 +45,49 @@ public class TakerReserveTradeFunds extends TradeTask {
|
|||||||
runInterceptHook();
|
runInterceptHook();
|
||||||
|
|
||||||
// create reserve tx
|
// create reserve tx
|
||||||
|
MoneroTxWallet reserveTx = null;
|
||||||
|
synchronized (XmrWalletService.WALLET_LOCK) {
|
||||||
|
|
||||||
|
// check for timeout
|
||||||
|
if (isTimedOut()) throw new RuntimeException("Trade protocol has timed out while creating reserve tx, tradeId=" + trade.getShortId());
|
||||||
|
|
||||||
|
// collect relevant info
|
||||||
BigInteger penaltyFee = HavenoUtils.multiply(trade.getAmount(), trade.getOffer().getPenaltyFeePct());
|
BigInteger penaltyFee = HavenoUtils.multiply(trade.getAmount(), trade.getOffer().getPenaltyFeePct());
|
||||||
BigInteger takerFee = trade.getTakerFee();
|
BigInteger takerFee = trade.getTakerFee();
|
||||||
BigInteger sendAmount = trade.getOffer().getDirection() == OfferDirection.BUY ? trade.getAmount() : BigInteger.ZERO;
|
BigInteger sendAmount = trade.getOffer().getDirection() == OfferDirection.BUY ? trade.getAmount() : BigInteger.ZERO;
|
||||||
BigInteger securityDeposit = trade.getOffer().getDirection() == OfferDirection.BUY ? trade.getSellerSecurityDepositBeforeMiningFee() : trade.getBuyerSecurityDepositBeforeMiningFee();
|
BigInteger securityDeposit = trade.getOffer().getDirection() == OfferDirection.BUY ? trade.getSellerSecurityDepositBeforeMiningFee() : trade.getBuyerSecurityDepositBeforeMiningFee();
|
||||||
String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
String returnAddress = trade.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
||||||
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(penaltyFee, takerFee, sendAmount, securityDeposit, returnAddress, false, null);
|
|
||||||
|
|
||||||
// check if trade still exists
|
// attempt creating reserve tx
|
||||||
if (!processModel.getTradeManager().hasOpenTrade(trade)) {
|
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||||
throw new RuntimeException("Trade protocol has timed out while creating reserve tx, tradeId=" + trade.getId());
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
|
try {
|
||||||
|
reserveTx = model.getXmrWalletService().createReserveTx(penaltyFee, takerFee, sendAmount, securityDeposit, returnAddress, false, null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Error creating reserve tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, trade.getShortId(), e.getMessage());
|
||||||
|
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||||
|
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check for timeout
|
||||||
|
if (isTimedOut()) throw new RuntimeException("Trade protocol has timed out while creating reserve tx, tradeId=" + trade.getShortId());
|
||||||
|
if (reserveTx != null) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset protocol timeout
|
||||||
|
trade.getProtocol().startTimeout(TradeProtocol.TRADE_STEP_TIMEOUT_SECONDS);
|
||||||
|
|
||||||
// collect reserved key images
|
// collect reserved key images
|
||||||
List<String> reservedKeyImages = new ArrayList<String>();
|
List<String> reservedKeyImages = new ArrayList<String>();
|
||||||
for (MoneroOutput input : reserveTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex());
|
for (MoneroOutput input : reserveTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex());
|
||||||
|
|
||||||
// reset protocol timeout
|
// update trade state
|
||||||
trade.getProtocol().startTimeout(TradeProtocol.TRADE_TIMEOUT_SECONDS);
|
trade.getTaker().setReserveTxKeyImages(reservedKeyImages);
|
||||||
|
}
|
||||||
|
|
||||||
// save process state
|
// save process state
|
||||||
processModel.setReserveTx(reserveTx);
|
processModel.setReserveTx(reserveTx);
|
||||||
processModel.getTaker().setReserveTxKeyImages(reservedKeyImages);
|
|
||||||
processModel.getTradeManager().requestPersistence();
|
processModel.getTradeManager().requestPersistence();
|
||||||
trade.addInitProgressStep();
|
trade.addInitProgressStep();
|
||||||
complete();
|
complete();
|
||||||
@ -74,4 +98,8 @@ public class TakerReserveTradeFunds extends TradeTask {
|
|||||||
failed(t);
|
failed(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isTimedOut() {
|
||||||
|
return !processModel.getTradeManager().hasOpenTrade(trade);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@ import com.google.common.util.concurrent.Service.State;
|
|||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.name.Named;
|
import com.google.inject.name.Named;
|
||||||
|
|
||||||
import common.utils.GenUtils;
|
|
||||||
import common.utils.JsonUtils;
|
import common.utils.JsonUtils;
|
||||||
import haveno.common.ThreadUtils;
|
import haveno.common.ThreadUtils;
|
||||||
import haveno.common.UserThread;
|
import haveno.common.UserThread;
|
||||||
@ -33,8 +32,6 @@ import haveno.common.util.Utilities;
|
|||||||
import haveno.core.api.AccountServiceListener;
|
import haveno.core.api.AccountServiceListener;
|
||||||
import haveno.core.api.CoreAccountService;
|
import haveno.core.api.CoreAccountService;
|
||||||
import haveno.core.api.XmrConnectionService;
|
import haveno.core.api.XmrConnectionService;
|
||||||
import haveno.core.offer.Offer;
|
|
||||||
import haveno.core.offer.OfferDirection;
|
|
||||||
import haveno.core.offer.OpenOffer;
|
import haveno.core.offer.OpenOffer;
|
||||||
import haveno.core.trade.BuyerTrade;
|
import haveno.core.trade.BuyerTrade;
|
||||||
import haveno.core.trade.HavenoUtils;
|
import haveno.core.trade.HavenoUtils;
|
||||||
@ -131,10 +128,10 @@ public class XmrWalletService {
|
|||||||
private static final int NUM_MAX_WALLET_BACKUPS = 1;
|
private static final int NUM_MAX_WALLET_BACKUPS = 1;
|
||||||
private static final int MONERO_LOG_LEVEL = -1; // monero library log level, -1 to disable
|
private static final int MONERO_LOG_LEVEL = -1; // monero library log level, -1 to disable
|
||||||
private static final int MAX_SYNC_ATTEMPTS = 3;
|
private static final int MAX_SYNC_ATTEMPTS = 3;
|
||||||
private static final boolean PRINT_STACK_TRACE = false;
|
private static final boolean PRINT_RPC_STACK_TRACE = false;
|
||||||
private static final String THREAD_ID = XmrWalletService.class.getSimpleName();
|
private static final String THREAD_ID = XmrWalletService.class.getSimpleName();
|
||||||
private static final long SHUTDOWN_TIMEOUT_MS = 60000;
|
private static final long SHUTDOWN_TIMEOUT_MS = 60000;
|
||||||
private static final long NUM_BLOCKS_BEHIND_WARNING = 10;
|
private static final long NUM_BLOCKS_BEHIND_WARNING = 5;
|
||||||
|
|
||||||
private final User user;
|
private final User user;
|
||||||
private final Preferences preferences;
|
private final Preferences preferences;
|
||||||
@ -155,7 +152,7 @@ public class XmrWalletService {
|
|||||||
private ChangeListener<? super Number> walletInitListener;
|
private ChangeListener<? super Number> walletInitListener;
|
||||||
private TradeManager tradeManager;
|
private TradeManager tradeManager;
|
||||||
private MoneroWallet wallet;
|
private MoneroWallet wallet;
|
||||||
private Object walletLock = new Object();
|
public static final Object WALLET_LOCK = new Object();
|
||||||
private boolean wasWalletSynced = false;
|
private boolean wasWalletSynced = false;
|
||||||
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 isClosingWallet = false;
|
private boolean isClosingWallet = false;
|
||||||
@ -374,11 +371,21 @@ public class XmrWalletService {
|
|||||||
return useNativeXmrWallet && MoneroUtils.isNativeLibraryLoaded();
|
return useNativeXmrWallet && MoneroUtils.isNativeLibraryLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MoneroSyncResult syncWallet() {
|
||||||
|
MoneroSyncResult result = syncWallet(wallet);
|
||||||
|
walletHeight.set(wallet.getHeight());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync the given wallet in a thread pool with other wallets.
|
* Sync the given wallet in a thread pool with other wallets.
|
||||||
*/
|
*/
|
||||||
public MoneroSyncResult syncWallet(MoneroWallet wallet) {
|
public MoneroSyncResult syncWallet(MoneroWallet wallet) {
|
||||||
Callable<MoneroSyncResult> task = () -> wallet.sync();
|
Callable<MoneroSyncResult> task = () -> {
|
||||||
|
synchronized (HavenoUtils.getDaemonLock()) {
|
||||||
|
return wallet.sync();
|
||||||
|
}
|
||||||
|
};
|
||||||
Future<MoneroSyncResult> future = syncWalletThreadPool.submit(task);
|
Future<MoneroSyncResult> future = syncWalletThreadPool.submit(task);
|
||||||
try {
|
try {
|
||||||
return future.get();
|
return future.get();
|
||||||
@ -448,24 +455,26 @@ public class XmrWalletService {
|
|||||||
if (name.contains(File.separator)) throw new IllegalArgumentException("Path not expected: " + name);
|
if (name.contains(File.separator)) throw new IllegalArgumentException("Path not expected: " + name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MoneroTxWallet createTx(MoneroTxConfig txConfig) {
|
||||||
|
synchronized (WALLET_LOCK) {
|
||||||
|
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||||
|
return wallet.createTx(txConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public MoneroTxWallet createTx(List<MoneroDestination> destinations) {
|
public MoneroTxWallet createTx(List<MoneroDestination> destinations) {
|
||||||
synchronized (walletLock) {
|
MoneroTxWallet tx = createTx(new MoneroTxConfig().setAccountIndex(0).setDestinations(destinations).setRelay(false).setCanSplit(false));;
|
||||||
try {
|
|
||||||
MoneroTxWallet tx = wallet.createTx(new MoneroTxConfig().setAccountIndex(0).setDestinations(destinations).setRelay(false).setCanSplit(false));
|
|
||||||
//printTxs("XmrWalletService.createTx", tx);
|
//printTxs("XmrWalletService.createTx", tx);
|
||||||
requestSaveMainWallet();
|
requestSaveMainWallet();
|
||||||
return tx;
|
return tx;
|
||||||
} catch (Exception e) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thaw all outputs not reserved for a trade.
|
* Thaw all outputs not reserved for a trade.
|
||||||
*/
|
*/
|
||||||
public void thawUnreservedOutputs() {
|
public void thawUnreservedOutputs() {
|
||||||
synchronized (walletLock) {
|
synchronized (WALLET_LOCK) {
|
||||||
|
|
||||||
// collect reserved outputs
|
// collect reserved outputs
|
||||||
Set<String> reservedKeyImages = new HashSet<String>();
|
Set<String> reservedKeyImages = new HashSet<String>();
|
||||||
@ -505,26 +514,25 @@ public class XmrWalletService {
|
|||||||
* @param keyImages the key images to freeze
|
* @param keyImages the key images to freeze
|
||||||
*/
|
*/
|
||||||
public void freezeOutputs(Collection<String> keyImages) {
|
public void freezeOutputs(Collection<String> keyImages) {
|
||||||
synchronized (walletLock) {
|
synchronized (WALLET_LOCK) {
|
||||||
for (String keyImage : keyImages) wallet.freezeOutput(keyImage);
|
for (String keyImage : keyImages) wallet.freezeOutput(keyImage);
|
||||||
|
cacheWalletInfo();
|
||||||
requestSaveMainWallet();
|
requestSaveMainWallet();
|
||||||
doPollWallet(false);
|
|
||||||
}
|
}
|
||||||
updateBalanceListeners(); // TODO (monero-java): balance listeners not notified on freeze/thaw output
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thaw the given outputs with a lock on the wallet.
|
* Thaw the given outputs with a lock on the wallet.
|
||||||
*
|
*
|
||||||
* @param keyImages the key images to thaw
|
* @param keyImages the key images to thaw (ignored if null or empty)
|
||||||
*/
|
*/
|
||||||
public void thawOutputs(Collection<String> keyImages) {
|
public void thawOutputs(Collection<String> keyImages) {
|
||||||
synchronized (walletLock) {
|
if (keyImages == null || keyImages.isEmpty()) return;
|
||||||
|
synchronized (WALLET_LOCK) {
|
||||||
for (String keyImage : keyImages) wallet.thawOutput(keyImage);
|
for (String keyImage : keyImages) wallet.thawOutput(keyImage);
|
||||||
|
cacheWalletInfo();
|
||||||
requestSaveMainWallet();
|
requestSaveMainWallet();
|
||||||
doPollWallet(false);
|
|
||||||
}
|
}
|
||||||
updateBalanceListeners(); // TODO (monero-java): balance listeners not notified on freeze/thaw output
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Integer> getSubaddressesWithExactInput(BigInteger amount) {
|
private List<Integer> getSubaddressesWithExactInput(BigInteger amount) {
|
||||||
@ -542,40 +550,6 @@ public class XmrWalletService {
|
|||||||
return new ArrayList<Integer>(subaddressIndices);
|
return new ArrayList<Integer>(subaddressIndices);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a reserve tx for an open offer and freeze its inputs.
|
|
||||||
*
|
|
||||||
* @param openOffer is the open offer to create a reserve tx for
|
|
||||||
*/
|
|
||||||
public MoneroTxWallet createReserveTx(OpenOffer openOffer) {
|
|
||||||
synchronized (walletLock) {
|
|
||||||
|
|
||||||
// collect offer data
|
|
||||||
Offer offer = openOffer.getOffer();
|
|
||||||
BigInteger penaltyFee = HavenoUtils.multiply(offer.getAmount(), offer.getPenaltyFeePct());
|
|
||||||
BigInteger makerFee = offer.getMaxMakerFee();
|
|
||||||
BigInteger sendAmount = offer.getDirection() == OfferDirection.BUY ? BigInteger.ZERO : offer.getAmount();
|
|
||||||
BigInteger securityDeposit = offer.getDirection() == OfferDirection.BUY ? offer.getMaxBuyerSecurityDeposit() : offer.getMaxSellerSecurityDeposit();
|
|
||||||
String returnAddress = getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
|
||||||
XmrAddressEntry fundingEntry = getAddressEntry(offer.getId(), XmrAddressEntry.Context.OFFER_FUNDING).orElse(null);
|
|
||||||
Integer preferredSubaddressIndex = fundingEntry == null ? null : fundingEntry.getSubaddressIndex();
|
|
||||||
|
|
||||||
// create reserve tx
|
|
||||||
MoneroTxWallet reserveTx = createReserveTx(penaltyFee, makerFee, sendAmount, securityDeposit, returnAddress, openOffer.isReserveExactAmount(), preferredSubaddressIndex);
|
|
||||||
|
|
||||||
// collect reserved key images
|
|
||||||
List<String> reservedKeyImages = new ArrayList<String>();
|
|
||||||
for (MoneroOutput input : reserveTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex());
|
|
||||||
|
|
||||||
// save offer state
|
|
||||||
openOffer.setReserveTxHash(reserveTx.getHash());
|
|
||||||
openOffer.setReserveTxHex(reserveTx.getFullHex());
|
|
||||||
openOffer.setReserveTxKey(reserveTx.getKey());
|
|
||||||
offer.getOfferPayload().setReserveTxKeyImages(reservedKeyImages);
|
|
||||||
return reserveTx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the reserve tx and freeze its inputs. The full amount is returned
|
* Create the reserve tx and freeze its inputs. The full amount is returned
|
||||||
* to the sender's payout address less the penalty and mining fees.
|
* to the sender's payout address less the penalty and mining fees.
|
||||||
@ -587,15 +561,19 @@ public class XmrWalletService {
|
|||||||
* @param returnAddress return address for reserved funds
|
* @param returnAddress return address for reserved funds
|
||||||
* @param reserveExactAmount specifies to reserve the exact input amount
|
* @param reserveExactAmount specifies to reserve the exact input amount
|
||||||
* @param preferredSubaddressIndex preferred source subaddress to spend from (optional)
|
* @param preferredSubaddressIndex preferred source subaddress to spend from (optional)
|
||||||
* @return a transaction to reserve a trade
|
* @return the reserve tx
|
||||||
*/
|
*/
|
||||||
public MoneroTxWallet createReserveTx(BigInteger penaltyFee, BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String returnAddress, boolean reserveExactAmount, Integer preferredSubaddressIndex) {
|
public MoneroTxWallet createReserveTx(BigInteger penaltyFee, BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String returnAddress, boolean reserveExactAmount, Integer preferredSubaddressIndex) {
|
||||||
|
synchronized (WALLET_LOCK) {
|
||||||
|
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||||
log.info("Creating reserve tx with preferred subaddress index={}, return address={}", preferredSubaddressIndex, returnAddress);
|
log.info("Creating reserve tx with preferred subaddress index={}, return address={}", preferredSubaddressIndex, returnAddress);
|
||||||
long time = System.currentTimeMillis();
|
long time = System.currentTimeMillis();
|
||||||
MoneroTxWallet reserveTx = createTradeTx(penaltyFee, tradeFee, sendAmount, securityDeposit, returnAddress, reserveExactAmount, preferredSubaddressIndex);
|
MoneroTxWallet reserveTx = createTradeTx(penaltyFee, tradeFee, sendAmount, securityDeposit, returnAddress, reserveExactAmount, preferredSubaddressIndex);
|
||||||
log.info("Done creating reserve tx in {} ms", System.currentTimeMillis() - time);
|
log.info("Done creating reserve tx in {} ms", System.currentTimeMillis() - time);
|
||||||
return reserveTx;
|
return reserveTx;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the multisig deposit tx and freeze its inputs.
|
* Create the multisig deposit tx and freeze its inputs.
|
||||||
@ -606,28 +584,23 @@ 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) {
|
||||||
synchronized (walletLock) {
|
synchronized (WALLET_LOCK) {
|
||||||
|
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||||
// thaw reserved outputs
|
|
||||||
if (trade.getSelf().getReserveTxKeyImages() != null) {
|
|
||||||
thawOutputs(trade.getSelf().getReserveTxKeyImages());
|
|
||||||
}
|
|
||||||
|
|
||||||
// create deposit tx
|
|
||||||
String multisigAddress = trade.getProcessModel().getMultisigAddress();
|
String multisigAddress = trade.getProcessModel().getMultisigAddress();
|
||||||
BigInteger tradeFee = trade instanceof MakerTrade ? trade.getMakerFee() : trade.getTakerFee();
|
BigInteger tradeFee = trade instanceof MakerTrade ? trade.getMakerFee() : trade.getTakerFee();
|
||||||
BigInteger sendAmount = trade instanceof BuyerTrade ? BigInteger.ZERO : trade.getAmount();
|
BigInteger sendAmount = trade instanceof BuyerTrade ? BigInteger.ZERO : trade.getAmount();
|
||||||
BigInteger securityDeposit = trade instanceof BuyerTrade ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee();
|
BigInteger securityDeposit = trade instanceof BuyerTrade ? trade.getBuyerSecurityDepositBeforeMiningFee() : trade.getSellerSecurityDepositBeforeMiningFee();
|
||||||
long time = System.currentTimeMillis();
|
long time = System.currentTimeMillis();
|
||||||
log.info("Creating deposit tx with multisig address={}", multisigAddress);
|
log.info("Creating deposit tx for trade {} {} with multisig address={}", trade.getClass().getSimpleName(), trade.getShortId(), multisigAddress);
|
||||||
MoneroTxWallet depositTx = createTradeTx(null, tradeFee, sendAmount, securityDeposit, multisigAddress, reserveExactAmount, preferredSubaddressIndex);
|
MoneroTxWallet depositTx = createTradeTx(null, tradeFee, sendAmount, securityDeposit, multisigAddress, reserveExactAmount, preferredSubaddressIndex);
|
||||||
log.info("Done creating deposit tx for trade {} {} in {} ms", trade.getClass().getSimpleName(), trade.getId(), System.currentTimeMillis() - time);
|
log.info("Done creating deposit tx for trade {} {} in {} ms", trade.getClass().getSimpleName(), trade.getShortId(), System.currentTimeMillis() - time);
|
||||||
return depositTx;
|
return depositTx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private MoneroTxWallet createTradeTx(BigInteger penaltyFee, BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String address, boolean reserveExactAmount, Integer preferredSubaddressIndex) {
|
private MoneroTxWallet createTradeTx(BigInteger penaltyFee, BigInteger tradeFee, BigInteger sendAmount, BigInteger securityDeposit, String address, boolean reserveExactAmount, Integer preferredSubaddressIndex) {
|
||||||
synchronized (walletLock) {
|
synchronized (WALLET_LOCK) {
|
||||||
MoneroWallet wallet = getWallet();
|
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
|
||||||
@ -675,7 +648,7 @@ public class XmrWalletService {
|
|||||||
.setSubtractFeeFrom(0) // pay fee from transfer amount
|
.setSubtractFeeFrom(0) // pay fee from transfer amount
|
||||||
.setPriority(XmrWalletService.PROTOCOL_FEE_PRIORITY);
|
.setPriority(XmrWalletService.PROTOCOL_FEE_PRIORITY);
|
||||||
if (!BigInteger.valueOf(0).equals(feeAmount)) txConfig.addDestination(HavenoUtils.getTradeFeeAddress(), feeAmount);
|
if (!BigInteger.valueOf(0).equals(feeAmount)) txConfig.addDestination(HavenoUtils.getTradeFeeAddress(), feeAmount);
|
||||||
MoneroTxWallet tradeTx = wallet.createTx(txConfig);
|
MoneroTxWallet tradeTx = createTx(txConfig);
|
||||||
|
|
||||||
// freeze inputs
|
// freeze inputs
|
||||||
List<String> keyImages = new ArrayList<String>();
|
List<String> keyImages = new ArrayList<String>();
|
||||||
@ -872,7 +845,7 @@ public class XmrWalletService {
|
|||||||
Runnable shutDownTask = () -> {
|
Runnable shutDownTask = () -> {
|
||||||
|
|
||||||
// remove listeners
|
// remove listeners
|
||||||
synchronized (walletLock) {
|
synchronized (WALLET_LOCK) {
|
||||||
if (wallet != null) {
|
if (wallet != null) {
|
||||||
for (MoneroWalletListenerI listener : new HashSet<>(wallet.getListeners())) {
|
for (MoneroWalletListenerI listener : new HashSet<>(wallet.getListeners())) {
|
||||||
wallet.removeListener(listener);
|
wallet.removeListener(listener);
|
||||||
@ -1174,7 +1147,7 @@ public class XmrWalletService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<MoneroTxWallet> getTxs() {
|
public List<MoneroTxWallet> getTxs() {
|
||||||
return getTxs(new MoneroTxQuery());
|
return getTxs(new MoneroTxQuery().setIncludeOutputs(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<MoneroTxWallet> getTxs(MoneroTxQuery query) {
|
public List<MoneroTxWallet> getTxs(MoneroTxQuery query) {
|
||||||
@ -1242,7 +1215,7 @@ public class XmrWalletService {
|
|||||||
|
|
||||||
// force restart main wallet if connection changed before synced
|
// force restart main wallet if connection changed before synced
|
||||||
if (!wasWalletSynced) {
|
if (!wasWalletSynced) {
|
||||||
if (!Boolean.TRUE.equals(connection.isConnected())) return;
|
if (!Boolean.TRUE.equals(xmrConnectionService.isConnected())) return;
|
||||||
ThreadUtils.submitToPool(() -> {
|
ThreadUtils.submitToPool(() -> {
|
||||||
log.warn("Force restarting main wallet because connection changed before inital sync");
|
log.warn("Force restarting main wallet because connection changed before inital sync");
|
||||||
forceRestartMainWallet();
|
forceRestartMainWallet();
|
||||||
@ -1275,7 +1248,7 @@ public class XmrWalletService {
|
|||||||
|
|
||||||
private void initMainWalletIfConnected() {
|
private void initMainWalletIfConnected() {
|
||||||
ThreadUtils.execute(() -> {
|
ThreadUtils.execute(() -> {
|
||||||
synchronized (walletLock) {
|
synchronized (WALLET_LOCK) {
|
||||||
if (wallet == null && xmrConnectionService.downloadPercentageProperty().get() == 1 && !isShutDownStarted) {
|
if (wallet == null && xmrConnectionService.downloadPercentageProperty().get() == 1 && !isShutDownStarted) {
|
||||||
maybeInitMainWallet(true);
|
maybeInitMainWallet(true);
|
||||||
if (walletInitListener != null) xmrConnectionService.downloadPercentageProperty().removeListener(walletInitListener);
|
if (walletInitListener != null) xmrConnectionService.downloadPercentageProperty().removeListener(walletInitListener);
|
||||||
@ -1295,7 +1268,7 @@ public class XmrWalletService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void maybeInitMainWallet(boolean sync, int numAttempts) {
|
private void maybeInitMainWallet(boolean sync, int numAttempts) {
|
||||||
synchronized (walletLock) {
|
synchronized (WALLET_LOCK) {
|
||||||
if (isShutDownStarted) return;
|
if (isShutDownStarted) return;
|
||||||
|
|
||||||
// open or create wallet main wallet
|
// open or create wallet main wallet
|
||||||
@ -1304,7 +1277,7 @@ public class XmrWalletService {
|
|||||||
log.info("Initializing main wallet with monerod=" + (daemon == null ? "null" : daemon.getRpcConnection().getUri()));
|
log.info("Initializing main wallet with monerod=" + (daemon == null ? "null" : daemon.getRpcConnection().getUri()));
|
||||||
if (MoneroUtils.walletExists(xmrWalletFile.getPath())) {
|
if (MoneroUtils.walletExists(xmrWalletFile.getPath())) {
|
||||||
wallet = openWallet(MONERO_WALLET_NAME, rpcBindPort, isProxyApplied(wasWalletSynced));
|
wallet = openWallet(MONERO_WALLET_NAME, rpcBindPort, isProxyApplied(wasWalletSynced));
|
||||||
} else if (xmrConnectionService.getConnection() != null && Boolean.TRUE.equals(xmrConnectionService.getConnection().isConnected())) {
|
} else if (Boolean.TRUE.equals(xmrConnectionService.isConnected())) {
|
||||||
wallet = createWallet(MONERO_WALLET_NAME, rpcBindPort);
|
wallet = createWallet(MONERO_WALLET_NAME, rpcBindPort);
|
||||||
|
|
||||||
// set wallet creation date to yesterday to guarantee complete restore
|
// set wallet creation date to yesterday to guarantee complete restore
|
||||||
@ -1393,7 +1366,7 @@ public class XmrWalletService {
|
|||||||
|
|
||||||
// get sync notifications from native wallet
|
// get sync notifications from native wallet
|
||||||
if (wallet instanceof MoneroWalletFull) {
|
if (wallet instanceof MoneroWalletFull) {
|
||||||
if (runReconnectTestOnStartup) GenUtils.waitFor(1000); // delay sync to test
|
if (runReconnectTestOnStartup) HavenoUtils.waitFor(1000); // delay sync to test
|
||||||
wallet.sync(new MoneroWalletListener() {
|
wallet.sync(new MoneroWalletListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) {
|
public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) {
|
||||||
@ -1419,11 +1392,6 @@ public class XmrWalletService {
|
|||||||
if (height < xmrConnectionService.getTargetHeight()) updateSyncProgress(height);
|
if (height < xmrConnectionService.getTargetHeight()) updateSyncProgress(height);
|
||||||
else {
|
else {
|
||||||
syncWithProgressLooper.stop();
|
syncWithProgressLooper.stop();
|
||||||
try {
|
|
||||||
doPollWallet(true);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
wasWalletSynced = true;
|
wasWalletSynced = true;
|
||||||
updateSyncProgress(height);
|
updateSyncProgress(height);
|
||||||
syncWithProgressLatch.countDown();
|
syncWithProgressLatch.countDown();
|
||||||
@ -1465,19 +1433,19 @@ public class XmrWalletService {
|
|||||||
private MoneroWalletFull createWalletFull(MoneroWalletConfig config) {
|
private MoneroWalletFull createWalletFull(MoneroWalletConfig config) {
|
||||||
|
|
||||||
// must be connected to daemon
|
// must be connected to daemon
|
||||||
MoneroRpcConnection connection = xmrConnectionService.getConnection();
|
if (!Boolean.TRUE.equals(xmrConnectionService.isConnected())) throw new RuntimeException("Must be connected to daemon before creating wallet");
|
||||||
if (connection == null || !Boolean.TRUE.equals(connection.isConnected())) throw new RuntimeException("Must be connected to daemon before creating wallet");
|
|
||||||
|
|
||||||
// create wallet
|
// create wallet
|
||||||
MoneroWalletFull walletFull = null;
|
MoneroWalletFull walletFull = null;
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// create wallet
|
// create wallet
|
||||||
|
MoneroRpcConnection connection = xmrConnectionService.getConnection();
|
||||||
log.info("Creating full wallet " + config.getPath() + " connected to monerod=" + connection.getUri());
|
log.info("Creating full wallet " + config.getPath() + " connected to monerod=" + connection.getUri());
|
||||||
long time = System.currentTimeMillis();
|
long time = System.currentTimeMillis();
|
||||||
config.setServer(connection);
|
config.setServer(connection);
|
||||||
walletFull = MoneroWalletFull.createWallet(config);
|
walletFull = MoneroWalletFull.createWallet(config);
|
||||||
walletFull.getDaemonConnection().setPrintStackTrace(PRINT_STACK_TRACE);
|
walletFull.getDaemonConnection().setPrintStackTrace(PRINT_RPC_STACK_TRACE);
|
||||||
log.info("Done creating full wallet " + config.getPath() + " in " + (System.currentTimeMillis() - time) + " ms");
|
log.info("Done creating full wallet " + config.getPath() + " in " + (System.currentTimeMillis() - time) + " ms");
|
||||||
return walletFull;
|
return walletFull;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -1499,7 +1467,7 @@ public class XmrWalletService {
|
|||||||
config.setNetworkType(getMoneroNetworkType());
|
config.setNetworkType(getMoneroNetworkType());
|
||||||
config.setServer(connection);
|
config.setServer(connection);
|
||||||
walletFull = MoneroWalletFull.openWallet(config);
|
walletFull = MoneroWalletFull.openWallet(config);
|
||||||
if (walletFull.getDaemonConnection() != null) walletFull.getDaemonConnection().setPrintStackTrace(PRINT_STACK_TRACE);
|
if (walletFull.getDaemonConnection() != null) walletFull.getDaemonConnection().setPrintStackTrace(PRINT_RPC_STACK_TRACE);
|
||||||
log.info("Done opening full wallet " + config.getPath());
|
log.info("Done opening full wallet " + config.getPath());
|
||||||
return walletFull;
|
return walletFull;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -1512,8 +1480,7 @@ public class XmrWalletService {
|
|||||||
private MoneroWalletRpc createWalletRpc(MoneroWalletConfig config, Integer port) {
|
private MoneroWalletRpc createWalletRpc(MoneroWalletConfig config, Integer port) {
|
||||||
|
|
||||||
// must be connected to daemon
|
// must be connected to daemon
|
||||||
MoneroRpcConnection connection = xmrConnectionService.getConnection();
|
if (!Boolean.TRUE.equals(xmrConnectionService.isConnected())) throw new RuntimeException("Must be connected to daemon before creating wallet");
|
||||||
if (connection == null || !Boolean.TRUE.equals(connection.isConnected())) throw new RuntimeException("Must be connected to daemon before creating wallet");
|
|
||||||
|
|
||||||
// create wallet
|
// create wallet
|
||||||
MoneroWalletRpc walletRpc = null;
|
MoneroWalletRpc walletRpc = null;
|
||||||
@ -1521,17 +1488,18 @@ public class XmrWalletService {
|
|||||||
|
|
||||||
// start monero-wallet-rpc instance
|
// start monero-wallet-rpc instance
|
||||||
walletRpc = startWalletRpcInstance(port, isProxyApplied(false));
|
walletRpc = startWalletRpcInstance(port, isProxyApplied(false));
|
||||||
walletRpc.getRpcConnection().setPrintStackTrace(PRINT_STACK_TRACE);
|
walletRpc.getRpcConnection().setPrintStackTrace(PRINT_RPC_STACK_TRACE);
|
||||||
|
|
||||||
// prevent wallet rpc from syncing
|
// prevent wallet rpc from syncing
|
||||||
walletRpc.stopSyncing();
|
walletRpc.stopSyncing();
|
||||||
|
|
||||||
// create wallet
|
// create wallet
|
||||||
|
MoneroRpcConnection connection = xmrConnectionService.getConnection();
|
||||||
log.info("Creating RPC wallet " + config.getPath() + " connected to monerod=" + connection.getUri());
|
log.info("Creating RPC wallet " + config.getPath() + " connected to monerod=" + connection.getUri());
|
||||||
long time = System.currentTimeMillis();
|
long time = System.currentTimeMillis();
|
||||||
config.setServer(connection);
|
config.setServer(connection);
|
||||||
walletRpc.createWallet(config);
|
walletRpc.createWallet(config);
|
||||||
walletRpc.getDaemonConnection().setPrintStackTrace(PRINT_STACK_TRACE);
|
walletRpc.getDaemonConnection().setPrintStackTrace(PRINT_RPC_STACK_TRACE);
|
||||||
log.info("Done creating RPC wallet " + config.getPath() + " in " + (System.currentTimeMillis() - time) + " ms");
|
log.info("Done creating RPC wallet " + config.getPath() + " in " + (System.currentTimeMillis() - time) + " ms");
|
||||||
return walletRpc;
|
return walletRpc;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -1547,7 +1515,7 @@ public class XmrWalletService {
|
|||||||
|
|
||||||
// start monero-wallet-rpc instance
|
// start monero-wallet-rpc instance
|
||||||
walletRpc = startWalletRpcInstance(port, applyProxyUri);
|
walletRpc = startWalletRpcInstance(port, applyProxyUri);
|
||||||
walletRpc.getRpcConnection().setPrintStackTrace(PRINT_STACK_TRACE);
|
walletRpc.getRpcConnection().setPrintStackTrace(PRINT_RPC_STACK_TRACE);
|
||||||
|
|
||||||
// prevent wallet rpc from syncing
|
// prevent wallet rpc from syncing
|
||||||
walletRpc.stopSyncing();
|
walletRpc.stopSyncing();
|
||||||
@ -1560,7 +1528,7 @@ public class XmrWalletService {
|
|||||||
log.info("Opening RPC wallet " + config.getPath() + " connected to daemon " + connection.getUri());
|
log.info("Opening RPC wallet " + config.getPath() + " connected to daemon " + connection.getUri());
|
||||||
config.setServer(connection);
|
config.setServer(connection);
|
||||||
walletRpc.openWallet(config);
|
walletRpc.openWallet(config);
|
||||||
if (walletRpc.getDaemonConnection() != null) walletRpc.getDaemonConnection().setPrintStackTrace(PRINT_STACK_TRACE);
|
if (walletRpc.getDaemonConnection() != null) walletRpc.getDaemonConnection().setPrintStackTrace(PRINT_RPC_STACK_TRACE);
|
||||||
log.info("Done opening RPC wallet " + config.getPath());
|
log.info("Done opening RPC wallet " + config.getPath());
|
||||||
return walletRpc;
|
return walletRpc;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -1613,7 +1581,7 @@ public class XmrWalletService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onConnectionChanged(MoneroRpcConnection connection) {
|
private void onConnectionChanged(MoneroRpcConnection connection) {
|
||||||
synchronized (walletLock) {
|
synchronized (WALLET_LOCK) {
|
||||||
if (wallet == null || isShutDownStarted) return;
|
if (wallet == null || isShutDownStarted) return;
|
||||||
if (HavenoUtils.connectionConfigsEqual(connection, wallet.getDaemonConnection())) return;
|
if (HavenoUtils.connectionConfigsEqual(connection, wallet.getDaemonConnection())) return;
|
||||||
String oldProxyUri = wallet == null || wallet.getDaemonConnection() == null ? null : wallet.getDaemonConnection().getProxyUri();
|
String oldProxyUri = wallet == null || wallet.getDaemonConnection() == null ? null : wallet.getDaemonConnection().getProxyUri();
|
||||||
@ -1634,7 +1602,7 @@ public class XmrWalletService {
|
|||||||
|
|
||||||
// sync wallet on new thread
|
// sync wallet on new thread
|
||||||
if (connection != null && !isShutDownStarted) {
|
if (connection != null && !isShutDownStarted) {
|
||||||
wallet.getDaemonConnection().setPrintStackTrace(PRINT_STACK_TRACE);
|
wallet.getDaemonConnection().setPrintStackTrace(PRINT_RPC_STACK_TRACE);
|
||||||
updatePollPeriod();
|
updatePollPeriod();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1673,7 +1641,7 @@ public class XmrWalletService {
|
|||||||
|
|
||||||
private void closeMainWallet(boolean save) {
|
private void closeMainWallet(boolean save) {
|
||||||
stopPolling();
|
stopPolling();
|
||||||
synchronized (walletLock) {
|
synchronized (WALLET_LOCK) {
|
||||||
try {
|
try {
|
||||||
if (wallet != null) {
|
if (wallet != null) {
|
||||||
isClosingWallet = true;
|
isClosingWallet = true;
|
||||||
@ -1697,13 +1665,13 @@ public class XmrWalletService {
|
|||||||
private void forceRestartMainWallet() {
|
private void forceRestartMainWallet() {
|
||||||
log.warn("Force restarting main wallet");
|
log.warn("Force restarting main wallet");
|
||||||
forceCloseMainWallet();
|
forceCloseMainWallet();
|
||||||
synchronized (walletLock) {
|
synchronized (WALLET_LOCK) {
|
||||||
maybeInitMainWallet(true);
|
maybeInitMainWallet(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startPolling() {
|
private void startPolling() {
|
||||||
synchronized (walletLock) {
|
synchronized (WALLET_LOCK) {
|
||||||
if (isShutDownStarted || isPollInProgress()) return;
|
if (isShutDownStarted || isPollInProgress()) return;
|
||||||
log.info("Starting to poll main wallet");
|
log.info("Starting to poll main wallet");
|
||||||
updatePollPeriod();
|
updatePollPeriod();
|
||||||
@ -1733,7 +1701,7 @@ public class XmrWalletService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setPollPeriod(long pollPeriodMs) {
|
private void setPollPeriod(long pollPeriodMs) {
|
||||||
synchronized (walletLock) {
|
synchronized (WALLET_LOCK) {
|
||||||
if (this.isShutDownStarted) return;
|
if (this.isShutDownStarted) return;
|
||||||
if (this.pollPeriodMs != null && this.pollPeriodMs == pollPeriodMs) return;
|
if (this.pollPeriodMs != null && this.pollPeriodMs == pollPeriodMs) return;
|
||||||
this.pollPeriodMs = pollPeriodMs;
|
this.pollPeriodMs = pollPeriodMs;
|
||||||
@ -1751,72 +1719,52 @@ public class XmrWalletService {
|
|||||||
|
|
||||||
private void doPollWallet(boolean updateTxs) {
|
private void doPollWallet(boolean updateTxs) {
|
||||||
synchronized (pollLock) {
|
synchronized (pollLock) {
|
||||||
|
if (isShutDownStarted) return;
|
||||||
pollInProgress = true;
|
pollInProgress = true;
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// log warning if wallet is too far behind daemon
|
// switch to best connection if daemon is too far behind
|
||||||
MoneroDaemonInfo lastInfo = xmrConnectionService.getLastInfo();
|
MoneroDaemonInfo lastInfo = xmrConnectionService.getLastInfo();
|
||||||
if (lastInfo == null) {
|
if (lastInfo == null) {
|
||||||
log.warn("Last daemon info is null");
|
log.warn("Last daemon info is null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long walletHeight = wallet.getHeight();
|
if (wasWalletSynced && walletHeight.get() < xmrConnectionService.getTargetHeight() - NUM_BLOCKS_BEHIND_WARNING && !Config.baseCurrencyNetwork().isTestnet()) {
|
||||||
if (wasWalletSynced && walletHeight < xmrConnectionService.getTargetHeight() - NUM_BLOCKS_BEHIND_WARNING && !Config.baseCurrencyNetwork().isTestnet()) {
|
log.warn("Updating connection because main wallet is {} blocks behind monerod, wallet height={}, monerod height={}", xmrConnectionService.getTargetHeight() - walletHeight.get(), walletHeight.get(), lastInfo.getHeight());
|
||||||
log.warn("Main wallet is {} blocks behind monerod, wallet height={}, monerod height={},", xmrConnectionService.getTargetHeight() - walletHeight, walletHeight, lastInfo.getHeight());
|
xmrConnectionService.switchToBestConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
// sync wallet if behind daemon
|
// sync wallet if behind daemon
|
||||||
if (wallet.getHeight() < xmrConnectionService.getTargetHeight()) wallet.sync();
|
if (walletHeight.get() < xmrConnectionService.getTargetHeight()) {
|
||||||
|
synchronized (WALLET_LOCK) { // avoid long sync from blocking other operations
|
||||||
|
syncWallet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// fetch transactions from pool and store to cache
|
// fetch transactions from pool and store to cache
|
||||||
// TODO: ideally wallet should sync every poll and then avoid updating from pool on fetching txs?
|
// TODO: ideally wallet should sync every poll and then avoid updating from pool on fetching txs?
|
||||||
if (updateTxs) {
|
if (updateTxs) {
|
||||||
|
synchronized (WALLET_LOCK) { // avoid long fetch from blocking other operations
|
||||||
|
synchronized (HavenoUtils.getDaemonLock()) {
|
||||||
try {
|
try {
|
||||||
cachedTxs = wallet.getTxs(new MoneroTxQuery().setIncludeOutputs(true));
|
cachedTxs = wallet.getTxs(new MoneroTxQuery().setIncludeOutputs(true));
|
||||||
} catch (Exception e) { // fetch from pool can fail
|
} catch (Exception e) { // fetch from pool can fail
|
||||||
log.warn("Error polling main wallet's transactions from the pool: {}", e.getMessage());
|
if (!isShutDownStarted) log.warn("Error polling main wallet's transactions from the pool: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get basic wallet info
|
// cache wallet info
|
||||||
long height = wallet.getHeight();
|
cacheWalletInfo();
|
||||||
BigInteger balance = wallet.getBalance();
|
|
||||||
BigInteger unlockedBalance = wallet.getUnlockedBalance();
|
|
||||||
cachedSubaddresses = wallet.getSubaddresses(0);
|
|
||||||
cachedOutputs = wallet.getOutputs();
|
|
||||||
|
|
||||||
// cache and notify changes
|
|
||||||
if (cachedHeight == null) {
|
|
||||||
cachedHeight = height;
|
|
||||||
cachedBalance = balance;
|
|
||||||
cachedAvailableBalance = unlockedBalance;
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// notify listeners of new block
|
|
||||||
if (height != cachedHeight) {
|
|
||||||
cachedHeight = height;
|
|
||||||
onNewBlock(height);
|
|
||||||
}
|
|
||||||
|
|
||||||
// notify listeners of balance change
|
|
||||||
if (!balance.equals(cachedBalance) || !unlockedBalance.equals(cachedAvailableBalance)) {
|
|
||||||
cachedBalance = balance;
|
|
||||||
cachedAvailableBalance = unlockedBalance;
|
|
||||||
onBalancesChanged(balance, unlockedBalance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (isShutDownStarted) return;
|
if (wallet == null || isShutDownStarted) return;
|
||||||
boolean isConnectionRefused = e.getMessage() != null && e.getMessage().contains("Connection refused");
|
boolean isConnectionRefused = e.getMessage() != null && e.getMessage().contains("Connection refused");
|
||||||
if (isConnectionRefused && wallet != null) forceRestartMainWallet();
|
if (isConnectionRefused) forceRestartMainWallet();
|
||||||
else {
|
else if (isWalletConnectedToDaemon()) {
|
||||||
boolean isWalletConnected = isWalletConnectedToDaemon();
|
|
||||||
if (!isWalletConnected) xmrConnectionService.checkConnection(); // check connection if wallet is not connected
|
|
||||||
if (wallet != null && isWalletConnected) {
|
|
||||||
log.warn("Error polling main wallet, errorMessage={}. Monerod={}", e.getMessage(), getConnectionService().getConnection());
|
log.warn("Error polling main wallet, errorMessage={}. Monerod={}", e.getMessage(), getConnectionService().getConnection());
|
||||||
//e.printStackTrace();
|
//e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
pollInProgress = false;
|
pollInProgress = false;
|
||||||
}
|
}
|
||||||
@ -1824,7 +1772,7 @@ public class XmrWalletService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isWalletConnectedToDaemon() {
|
public boolean isWalletConnectedToDaemon() {
|
||||||
synchronized (walletLock) {
|
synchronized (WALLET_LOCK) {
|
||||||
try {
|
try {
|
||||||
if (wallet == null) return false;
|
if (wallet == null) return false;
|
||||||
return wallet.isConnectedToDaemon();
|
return wallet.isConnectedToDaemon();
|
||||||
@ -1841,6 +1789,33 @@ public class XmrWalletService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void cacheWalletInfo() {
|
||||||
|
|
||||||
|
// get basic wallet info
|
||||||
|
long height = wallet.getHeight();
|
||||||
|
BigInteger balance = wallet.getBalance();
|
||||||
|
BigInteger unlockedBalance = wallet.getUnlockedBalance();
|
||||||
|
cachedSubaddresses = wallet.getSubaddresses(0);
|
||||||
|
cachedOutputs = wallet.getOutputs();
|
||||||
|
|
||||||
|
// cache and notify changes
|
||||||
|
if (cachedHeight == null) {
|
||||||
|
cachedHeight = height;
|
||||||
|
cachedBalance = balance;
|
||||||
|
cachedAvailableBalance = unlockedBalance;
|
||||||
|
onNewBlock(height);
|
||||||
|
onBalancesChanged(balance, unlockedBalance);
|
||||||
|
} else {
|
||||||
|
boolean heightChanged = height != cachedHeight;
|
||||||
|
boolean balancesChanged = !balance.equals(cachedBalance) || !unlockedBalance.equals(cachedAvailableBalance);
|
||||||
|
cachedHeight = height;
|
||||||
|
cachedBalance = balance;
|
||||||
|
cachedAvailableBalance = unlockedBalance;
|
||||||
|
if (heightChanged) onNewBlock(height);
|
||||||
|
if (balancesChanged) onBalancesChanged(balance, unlockedBalance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
|
private void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
|
||||||
updateBalanceListeners();
|
updateBalanceListeners();
|
||||||
for (MoneroWalletListenerI listener : walletListeners) ThreadUtils.submitToPool(() -> listener.onBalancesChanged(newBalance, newUnlockedBalance));
|
for (MoneroWalletListenerI listener : walletListeners) ThreadUtils.submitToPool(() -> listener.onBalancesChanged(newBalance, newUnlockedBalance));
|
||||||
|
@ -57,10 +57,10 @@ import haveno.proto.grpc.SetAutoSwitchReply;
|
|||||||
import haveno.proto.grpc.SetAutoSwitchRequest;
|
import haveno.proto.grpc.SetAutoSwitchRequest;
|
||||||
import haveno.proto.grpc.SetConnectionReply;
|
import haveno.proto.grpc.SetConnectionReply;
|
||||||
import haveno.proto.grpc.SetConnectionRequest;
|
import haveno.proto.grpc.SetConnectionRequest;
|
||||||
import haveno.proto.grpc.StartCheckingConnectionsReply;
|
import haveno.proto.grpc.StartCheckingConnectionReply;
|
||||||
import haveno.proto.grpc.StartCheckingConnectionsRequest;
|
import haveno.proto.grpc.StartCheckingConnectionRequest;
|
||||||
import haveno.proto.grpc.StopCheckingConnectionsReply;
|
import haveno.proto.grpc.StopCheckingConnectionReply;
|
||||||
import haveno.proto.grpc.StopCheckingConnectionsRequest;
|
import haveno.proto.grpc.StopCheckingConnectionRequest;
|
||||||
import haveno.proto.grpc.UrlConnection;
|
import haveno.proto.grpc.UrlConnection;
|
||||||
import static haveno.proto.grpc.XmrConnectionsGrpc.XmrConnectionsImplBase;
|
import static haveno.proto.grpc.XmrConnectionsGrpc.XmrConnectionsImplBase;
|
||||||
import static haveno.proto.grpc.XmrConnectionsGrpc.getAddConnectionMethod;
|
import static haveno.proto.grpc.XmrConnectionsGrpc.getAddConnectionMethod;
|
||||||
@ -72,8 +72,8 @@ import static haveno.proto.grpc.XmrConnectionsGrpc.getGetConnectionsMethod;
|
|||||||
import static haveno.proto.grpc.XmrConnectionsGrpc.getRemoveConnectionMethod;
|
import static haveno.proto.grpc.XmrConnectionsGrpc.getRemoveConnectionMethod;
|
||||||
import static haveno.proto.grpc.XmrConnectionsGrpc.getSetAutoSwitchMethod;
|
import static haveno.proto.grpc.XmrConnectionsGrpc.getSetAutoSwitchMethod;
|
||||||
import static haveno.proto.grpc.XmrConnectionsGrpc.getSetConnectionMethod;
|
import static haveno.proto.grpc.XmrConnectionsGrpc.getSetConnectionMethod;
|
||||||
import static haveno.proto.grpc.XmrConnectionsGrpc.getStartCheckingConnectionsMethod;
|
import static haveno.proto.grpc.XmrConnectionsGrpc.getStartCheckingConnectionMethod;
|
||||||
import static haveno.proto.grpc.XmrConnectionsGrpc.getStopCheckingConnectionsMethod;
|
import static haveno.proto.grpc.XmrConnectionsGrpc.getStopCheckingConnectionMethod;
|
||||||
import io.grpc.ServerInterceptor;
|
import io.grpc.ServerInterceptor;
|
||||||
import io.grpc.stub.StreamObserver;
|
import io.grpc.stub.StreamObserver;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
@ -102,7 +102,7 @@ class GrpcXmrConnectionService extends XmrConnectionsImplBase {
|
|||||||
public void addConnection(AddConnectionRequest request,
|
public void addConnection(AddConnectionRequest request,
|
||||||
StreamObserver<AddConnectionReply> responseObserver) {
|
StreamObserver<AddConnectionReply> responseObserver) {
|
||||||
handleRequest(responseObserver, () -> {
|
handleRequest(responseObserver, () -> {
|
||||||
coreApi.addMoneroConnection(toMoneroRpcConnection(request.getConnection()));
|
coreApi.addXmrConnection(toMoneroRpcConnection(request.getConnection()));
|
||||||
return AddConnectionReply.newBuilder().build();
|
return AddConnectionReply.newBuilder().build();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -111,7 +111,7 @@ class GrpcXmrConnectionService extends XmrConnectionsImplBase {
|
|||||||
public void removeConnection(RemoveConnectionRequest request,
|
public void removeConnection(RemoveConnectionRequest request,
|
||||||
StreamObserver<RemoveConnectionReply> responseObserver) {
|
StreamObserver<RemoveConnectionReply> responseObserver) {
|
||||||
handleRequest(responseObserver, () -> {
|
handleRequest(responseObserver, () -> {
|
||||||
coreApi.removeMoneroConnection(validateUri(request.getUrl()));
|
coreApi.removeXmrConnection(validateUri(request.getUrl()));
|
||||||
return RemoveConnectionReply.newBuilder().build();
|
return RemoveConnectionReply.newBuilder().build();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -120,7 +120,7 @@ class GrpcXmrConnectionService extends XmrConnectionsImplBase {
|
|||||||
public void getConnection(GetConnectionRequest request,
|
public void getConnection(GetConnectionRequest request,
|
||||||
StreamObserver<GetConnectionReply> responseObserver) {
|
StreamObserver<GetConnectionReply> responseObserver) {
|
||||||
handleRequest(responseObserver, () -> {
|
handleRequest(responseObserver, () -> {
|
||||||
UrlConnection replyConnection = toUrlConnection(coreApi.getMoneroConnection());
|
UrlConnection replyConnection = toUrlConnection(coreApi.getXmrConnection());
|
||||||
GetConnectionReply.Builder builder = GetConnectionReply.newBuilder();
|
GetConnectionReply.Builder builder = GetConnectionReply.newBuilder();
|
||||||
if (replyConnection != null) {
|
if (replyConnection != null) {
|
||||||
builder.setConnection(replyConnection);
|
builder.setConnection(replyConnection);
|
||||||
@ -145,10 +145,10 @@ class GrpcXmrConnectionService extends XmrConnectionsImplBase {
|
|||||||
StreamObserver<SetConnectionReply> responseObserver) {
|
StreamObserver<SetConnectionReply> responseObserver) {
|
||||||
handleRequest(responseObserver, () -> {
|
handleRequest(responseObserver, () -> {
|
||||||
if (request.getUrl() != null && !request.getUrl().isEmpty())
|
if (request.getUrl() != null && !request.getUrl().isEmpty())
|
||||||
coreApi.setMoneroConnection(validateUri(request.getUrl()));
|
coreApi.setXmrConnection(validateUri(request.getUrl()));
|
||||||
else if (request.hasConnection())
|
else if (request.hasConnection())
|
||||||
coreApi.setMoneroConnection(toMoneroRpcConnection(request.getConnection()));
|
coreApi.setXmrConnection(toMoneroRpcConnection(request.getConnection()));
|
||||||
else coreApi.setMoneroConnection((MoneroRpcConnection) null); // disconnect from client
|
else coreApi.setXmrConnection((MoneroRpcConnection) null); // disconnect from client
|
||||||
return SetConnectionReply.newBuilder().build();
|
return SetConnectionReply.newBuilder().build();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -157,7 +157,7 @@ class GrpcXmrConnectionService extends XmrConnectionsImplBase {
|
|||||||
public void checkConnection(CheckConnectionRequest request,
|
public void checkConnection(CheckConnectionRequest request,
|
||||||
StreamObserver<CheckConnectionReply> responseObserver) {
|
StreamObserver<CheckConnectionReply> responseObserver) {
|
||||||
handleRequest(responseObserver, () -> {
|
handleRequest(responseObserver, () -> {
|
||||||
MoneroRpcConnection connection = coreApi.checkMoneroConnection();
|
MoneroRpcConnection connection = coreApi.checkXmrConnection();
|
||||||
UrlConnection replyConnection = toUrlConnection(connection);
|
UrlConnection replyConnection = toUrlConnection(connection);
|
||||||
CheckConnectionReply.Builder builder = CheckConnectionReply.newBuilder();
|
CheckConnectionReply.Builder builder = CheckConnectionReply.newBuilder();
|
||||||
if (replyConnection != null) {
|
if (replyConnection != null) {
|
||||||
@ -179,22 +179,22 @@ class GrpcXmrConnectionService extends XmrConnectionsImplBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startCheckingConnections(StartCheckingConnectionsRequest request,
|
public void startCheckingConnection(StartCheckingConnectionRequest request,
|
||||||
StreamObserver<StartCheckingConnectionsReply> responseObserver) {
|
StreamObserver<StartCheckingConnectionReply> responseObserver) {
|
||||||
handleRequest(responseObserver, () -> {
|
handleRequest(responseObserver, () -> {
|
||||||
int refreshMillis = request.getRefreshPeriod();
|
int refreshMillis = request.getRefreshPeriod();
|
||||||
Long refreshPeriod = refreshMillis == 0 ? null : (long) refreshMillis;
|
Long refreshPeriod = refreshMillis == 0 ? null : (long) refreshMillis;
|
||||||
coreApi.startCheckingMoneroConnection(refreshPeriod);
|
coreApi.startCheckingXmrConnection(refreshPeriod);
|
||||||
return StartCheckingConnectionsReply.newBuilder().build();
|
return StartCheckingConnectionReply.newBuilder().build();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stopCheckingConnections(StopCheckingConnectionsRequest request,
|
public void stopCheckingConnection(StopCheckingConnectionRequest request,
|
||||||
StreamObserver<StopCheckingConnectionsReply> responseObserver) {
|
StreamObserver<StopCheckingConnectionReply> responseObserver) {
|
||||||
handleRequest(responseObserver, () -> {
|
handleRequest(responseObserver, () -> {
|
||||||
coreApi.stopCheckingMoneroConnection();
|
coreApi.stopCheckingXmrConnection();
|
||||||
return StopCheckingConnectionsReply.newBuilder().build();
|
return StopCheckingConnectionReply.newBuilder().build();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,7 +202,7 @@ class GrpcXmrConnectionService extends XmrConnectionsImplBase {
|
|||||||
public void getBestAvailableConnection(GetBestAvailableConnectionRequest request,
|
public void getBestAvailableConnection(GetBestAvailableConnectionRequest request,
|
||||||
StreamObserver<GetBestAvailableConnectionReply> responseObserver) {
|
StreamObserver<GetBestAvailableConnectionReply> responseObserver) {
|
||||||
handleRequest(responseObserver, () -> {
|
handleRequest(responseObserver, () -> {
|
||||||
MoneroRpcConnection connection = coreApi.getBestAvailableMoneroConnection();
|
MoneroRpcConnection connection = coreApi.getBestAvailableXmrConnection();
|
||||||
UrlConnection replyConnection = toUrlConnection(connection);
|
UrlConnection replyConnection = toUrlConnection(connection);
|
||||||
GetBestAvailableConnectionReply.Builder builder = GetBestAvailableConnectionReply.newBuilder();
|
GetBestAvailableConnectionReply.Builder builder = GetBestAvailableConnectionReply.newBuilder();
|
||||||
if (replyConnection != null) {
|
if (replyConnection != null) {
|
||||||
@ -216,7 +216,7 @@ class GrpcXmrConnectionService extends XmrConnectionsImplBase {
|
|||||||
public void setAutoSwitch(SetAutoSwitchRequest request,
|
public void setAutoSwitch(SetAutoSwitchRequest request,
|
||||||
StreamObserver<SetAutoSwitchReply> responseObserver) {
|
StreamObserver<SetAutoSwitchReply> responseObserver) {
|
||||||
handleRequest(responseObserver, () -> {
|
handleRequest(responseObserver, () -> {
|
||||||
coreApi.setMoneroConnectionAutoSwitch(request.getAutoSwitch());
|
coreApi.setXmrConnectionAutoSwitch(request.getAutoSwitch());
|
||||||
return SetAutoSwitchReply.newBuilder().build();
|
return SetAutoSwitchReply.newBuilder().build();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -300,8 +300,8 @@ class GrpcXmrConnectionService extends XmrConnectionsImplBase {
|
|||||||
put(getSetConnectionMethod().getFullMethodName(), new GrpcCallRateMeter(allowedCallsPerTimeWindow, SECONDS));
|
put(getSetConnectionMethod().getFullMethodName(), new GrpcCallRateMeter(allowedCallsPerTimeWindow, SECONDS));
|
||||||
put(getCheckConnectionMethod().getFullMethodName(), new GrpcCallRateMeter(allowedCallsPerTimeWindow, SECONDS));
|
put(getCheckConnectionMethod().getFullMethodName(), new GrpcCallRateMeter(allowedCallsPerTimeWindow, SECONDS));
|
||||||
put(getCheckConnectionsMethod().getFullMethodName(), new GrpcCallRateMeter(allowedCallsPerTimeWindow, SECONDS));
|
put(getCheckConnectionsMethod().getFullMethodName(), new GrpcCallRateMeter(allowedCallsPerTimeWindow, SECONDS));
|
||||||
put(getStartCheckingConnectionsMethod().getFullMethodName(), new GrpcCallRateMeter(allowedCallsPerTimeWindow, SECONDS));
|
put(getStartCheckingConnectionMethod().getFullMethodName(), new GrpcCallRateMeter(allowedCallsPerTimeWindow, SECONDS));
|
||||||
put(getStopCheckingConnectionsMethod().getFullMethodName(), new GrpcCallRateMeter(allowedCallsPerTimeWindow, SECONDS));
|
put(getStopCheckingConnectionMethod().getFullMethodName(), new GrpcCallRateMeter(allowedCallsPerTimeWindow, SECONDS));
|
||||||
put(getGetBestAvailableConnectionMethod().getFullMethodName(), new GrpcCallRateMeter(allowedCallsPerTimeWindow, SECONDS));
|
put(getGetBestAvailableConnectionMethod().getFullMethodName(), new GrpcCallRateMeter(allowedCallsPerTimeWindow, SECONDS));
|
||||||
put(getSetAutoSwitchMethod().getFullMethodName(), new GrpcCallRateMeter(allowedCallsPerTimeWindow, SECONDS));
|
put(getSetAutoSwitchMethod().getFullMethodName(), new GrpcCallRateMeter(allowedCallsPerTimeWindow, SECONDS));
|
||||||
}}
|
}}
|
||||||
|
@ -91,6 +91,7 @@ import javafx.scene.layout.GridPane;
|
|||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.util.Callback;
|
import javafx.util.Callback;
|
||||||
|
import monero.common.MoneroUtils;
|
||||||
import monero.wallet.model.MoneroTxConfig;
|
import monero.wallet.model.MoneroTxConfig;
|
||||||
import monero.wallet.model.MoneroWalletListener;
|
import monero.wallet.model.MoneroWalletListener;
|
||||||
import net.glxn.qrgen.QRCode;
|
import net.glxn.qrgen.QRCode;
|
||||||
@ -365,7 +366,7 @@ public class DepositView extends ActivatableView<VBox, Void> {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private String getPaymentUri() {
|
private String getPaymentUri() {
|
||||||
return xmrWalletService.getWallet().getPaymentUri(new MoneroTxConfig()
|
return MoneroUtils.getPaymentUri(new MoneroTxConfig()
|
||||||
.setAddress(addressTextField.getAddress())
|
.setAddress(addressTextField.getAddress())
|
||||||
.setAmount(HavenoUtils.coinToAtomicUnits(getAmount()))
|
.setAmount(HavenoUtils.coinToAtomicUnits(getAmount()))
|
||||||
.setNote(paymentLabelString));
|
.setNote(paymentLabelString));
|
||||||
|
@ -261,7 +261,7 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
|||||||
|
|
||||||
// create tx
|
// create tx
|
||||||
if (amount.compareTo(BigInteger.ZERO) <= 0) throw new RuntimeException(Res.get("portfolio.pending.step5_buyer.amountTooLow"));
|
if (amount.compareTo(BigInteger.ZERO) <= 0) throw new RuntimeException(Res.get("portfolio.pending.step5_buyer.amountTooLow"));
|
||||||
MoneroTxWallet tx = xmrWalletService.getWallet().createTx(new MoneroTxConfig()
|
MoneroTxWallet tx = xmrWalletService.createTx(new MoneroTxConfig()
|
||||||
.setAccountIndex(0)
|
.setAccountIndex(0)
|
||||||
.setAmount(amount)
|
.setAmount(amount)
|
||||||
.setAddress(withdrawToAddress)
|
.setAddress(withdrawToAddress)
|
||||||
|
@ -613,10 +613,12 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
|||||||
if (offer.getState() == Offer.State.OFFER_FEE_RESERVED) errorMessage.set(errMessage + Res.get("createOffer.errorInfo"));
|
if (offer.getState() == Offer.State.OFFER_FEE_RESERVED) errorMessage.set(errMessage + Res.get("createOffer.errorInfo"));
|
||||||
else errorMessage.set(errMessage);
|
else errorMessage.set(errMessage);
|
||||||
|
|
||||||
|
UserThread.execute(() -> {
|
||||||
updateButtonDisableState();
|
updateButtonDisableState();
|
||||||
updateSpinnerInfo();
|
updateSpinnerInfo();
|
||||||
|
|
||||||
resultHandler.run();
|
resultHandler.run();
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
updateButtonDisableState();
|
updateButtonDisableState();
|
||||||
|
@ -21,6 +21,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.name.Named;
|
import com.google.inject.name.Named;
|
||||||
import haveno.common.ClockWatcher;
|
import haveno.common.ClockWatcher;
|
||||||
|
import haveno.common.UserThread;
|
||||||
import haveno.common.app.DevEnv;
|
import haveno.common.app.DevEnv;
|
||||||
import haveno.core.account.witness.AccountAgeWitnessService;
|
import haveno.core.account.witness.AccountAgeWitnessService;
|
||||||
import haveno.core.network.MessageState;
|
import haveno.core.network.MessageState;
|
||||||
@ -101,6 +102,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
|
|||||||
@Getter
|
@Getter
|
||||||
private final ObjectProperty<MessageState> messageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
private final ObjectProperty<MessageState> messageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
||||||
private Subscription tradeStateSubscription;
|
private Subscription tradeStateSubscription;
|
||||||
|
private Subscription paymentAccountDecryptedSubscription;
|
||||||
private Subscription payoutStateSubscription;
|
private Subscription payoutStateSubscription;
|
||||||
private Subscription messageStateSubscription;
|
private Subscription messageStateSubscription;
|
||||||
@Getter
|
@Getter
|
||||||
@ -146,6 +148,11 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
|
|||||||
tradeStateSubscription = null;
|
tradeStateSubscription = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (paymentAccountDecryptedSubscription != null) {
|
||||||
|
paymentAccountDecryptedSubscription.unsubscribe();
|
||||||
|
paymentAccountDecryptedSubscription = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (payoutStateSubscription != null) {
|
if (payoutStateSubscription != null) {
|
||||||
payoutStateSubscription.unsubscribe();
|
payoutStateSubscription.unsubscribe();
|
||||||
payoutStateSubscription = null;
|
payoutStateSubscription = null;
|
||||||
@ -167,6 +174,10 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
|
|||||||
buyerState.set(BuyerState.UNDEFINED);
|
buyerState.set(BuyerState.UNDEFINED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (paymentAccountDecryptedSubscription != null) {
|
||||||
|
paymentAccountDecryptedSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
if (payoutStateSubscription != null) {
|
if (payoutStateSubscription != null) {
|
||||||
payoutStateSubscription.unsubscribe();
|
payoutStateSubscription.unsubscribe();
|
||||||
sellerState.set(SellerState.UNDEFINED);
|
sellerState.set(SellerState.UNDEFINED);
|
||||||
@ -183,6 +194,9 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
|
|||||||
tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), state -> {
|
tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), state -> {
|
||||||
onTradeStateChanged(state);
|
onTradeStateChanged(state);
|
||||||
});
|
});
|
||||||
|
paymentAccountDecryptedSubscription = EasyBind.subscribe(trade.getProcessModel().getPaymentAccountDecryptedProperty(), decrypted -> {
|
||||||
|
refresh();
|
||||||
|
});
|
||||||
payoutStateSubscription = EasyBind.subscribe(trade.payoutStateProperty(), state -> {
|
payoutStateSubscription = EasyBind.subscribe(trade.payoutStateProperty(), state -> {
|
||||||
onPayoutStateChanged(state);
|
onPayoutStateChanged(state);
|
||||||
});
|
});
|
||||||
@ -191,6 +205,14 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void refresh() {
|
||||||
|
UserThread.execute(() -> {
|
||||||
|
sellerState.set(UNDEFINED);
|
||||||
|
buyerState.set(BuyerState.UNDEFINED);
|
||||||
|
onTradeStateChanged(trade.getState());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void onMessageStateChanged(MessageState messageState) {
|
private void onMessageStateChanged(MessageState messageState) {
|
||||||
messageStateProperty.set(messageState);
|
messageStateProperty.set(messageState);
|
||||||
}
|
}
|
||||||
|
@ -221,7 +221,7 @@ public class BuyerStep2View extends TradeStepView {
|
|||||||
|
|
||||||
|
|
||||||
PaymentAccountPayload paymentAccountPayload = model.dataModel.getSellersPaymentAccountPayload();
|
PaymentAccountPayload paymentAccountPayload = model.dataModel.getSellersPaymentAccountPayload();
|
||||||
String paymentMethodId = paymentAccountPayload != null ? paymentAccountPayload.getPaymentMethodId() : "<missing payment account payload>";
|
String paymentMethodId = paymentAccountPayload != null ? paymentAccountPayload.getPaymentMethodId() : "<pending>";
|
||||||
TitledGroupBg accountTitledGroupBg = addTitledGroupBg(gridPane, ++gridRow, 4,
|
TitledGroupBg accountTitledGroupBg = addTitledGroupBg(gridPane, ++gridRow, 4,
|
||||||
Res.get("portfolio.pending.step2_buyer.startPaymentUsing", Res.get(paymentMethodId)),
|
Res.get("portfolio.pending.step2_buyer.startPaymentUsing", Res.get(paymentMethodId)),
|
||||||
Layout.COMPACT_GROUP_DISTANCE);
|
Layout.COMPACT_GROUP_DISTANCE);
|
||||||
|
@ -93,6 +93,7 @@ import javafx.stage.StageStyle;
|
|||||||
import javafx.util.Callback;
|
import javafx.util.Callback;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import monero.common.MoneroUtils;
|
||||||
import monero.daemon.model.MoneroTx;
|
import monero.daemon.model.MoneroTx;
|
||||||
import monero.wallet.MoneroWallet;
|
import monero.wallet.MoneroWallet;
|
||||||
import monero.wallet.model.MoneroTxConfig;
|
import monero.wallet.model.MoneroTxConfig;
|
||||||
@ -686,7 +687,7 @@ public class GUIUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String getMoneroURI(String address, BigInteger amount, String label, MoneroWallet wallet) {
|
public static String getMoneroURI(String address, BigInteger amount, String label, MoneroWallet wallet) {
|
||||||
return wallet.getPaymentUri(new MoneroTxConfig()
|
return MoneroUtils.getPaymentUri(new MoneroTxConfig()
|
||||||
.setAddress(address)
|
.setAddress(address)
|
||||||
.setAmount(amount)
|
.setAmount(amount)
|
||||||
.setNote(label));
|
.setNote(label));
|
||||||
|
@ -315,9 +315,9 @@ service XmrConnections {
|
|||||||
}
|
}
|
||||||
rpc CheckConnections(CheckConnectionsRequest) returns (CheckConnectionsReply) {
|
rpc CheckConnections(CheckConnectionsRequest) returns (CheckConnectionsReply) {
|
||||||
}
|
}
|
||||||
rpc StartCheckingConnections(StartCheckingConnectionsRequest) returns (StartCheckingConnectionsReply) {
|
rpc StartCheckingConnection(StartCheckingConnectionRequest) returns (StartCheckingConnectionReply) {
|
||||||
}
|
}
|
||||||
rpc StopCheckingConnections(StopCheckingConnectionsRequest) returns (StopCheckingConnectionsReply) {
|
rpc StopCheckingConnection(StopCheckingConnectionRequest) returns (StopCheckingConnectionReply) {
|
||||||
}
|
}
|
||||||
rpc GetBestAvailableConnection(GetBestAvailableConnectionRequest) returns (GetBestAvailableConnectionReply) {
|
rpc GetBestAvailableConnection(GetBestAvailableConnectionRequest) returns (GetBestAvailableConnectionReply) {
|
||||||
}
|
}
|
||||||
@ -388,15 +388,15 @@ message CheckConnectionsReply {
|
|||||||
repeated UrlConnection connections = 1;
|
repeated UrlConnection connections = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message StartCheckingConnectionsRequest {
|
message StartCheckingConnectionRequest {
|
||||||
int32 refresh_period = 1; // milliseconds
|
int32 refresh_period = 1; // milliseconds
|
||||||
}
|
}
|
||||||
|
|
||||||
message StartCheckingConnectionsReply {}
|
message StartCheckingConnectionReply {}
|
||||||
|
|
||||||
message StopCheckingConnectionsRequest {}
|
message StopCheckingConnectionRequest {}
|
||||||
|
|
||||||
message StopCheckingConnectionsReply {}
|
message StopCheckingConnectionReply {}
|
||||||
|
|
||||||
message GetBestAvailableConnectionRequest {}
|
message GetBestAvailableConnectionRequest {}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user