start main wallet when daemon synced with improved UserThread

This commit is contained in:
woodser 2023-12-31 10:38:02 -05:00
parent 5466689857
commit 5d88936600
3 changed files with 69 additions and 49 deletions

View File

@ -45,7 +45,7 @@ public class UserThread {
@Getter @Getter
@Setter @Setter
private static Executor executor; private static Executor executor;
private static final String USER_THREAD_NAME = "UserThread"; private static Thread USER_THREAD;
public static void setTimerClass(Class<? extends Timer> timerClass) { public static void setTimerClass(Class<? extends Timer> timerClass) {
UserThread.timerClass = timerClass; UserThread.timerClass = timerClass;
@ -59,8 +59,10 @@ public class UserThread {
public static void execute(Runnable command) { public static void execute(Runnable command) {
executor.execute(() -> { executor.execute(() -> {
Thread.currentThread().setName(USER_THREAD_NAME); synchronized (executor) {
command.run(); USER_THREAD = Thread.currentThread();
command.run();
}
}); });
} }
@ -79,9 +81,9 @@ public class UserThread {
} }
} }
// TODO: better way to determine if on UserThread, since this is not reliable public static boolean isUserThread(Thread thread) {
private static boolean isUserThread(Thread thread) { return thread == USER_THREAD;
return USER_THREAD_NAME.equals(thread.getName());
} }
// Prefer FxTimer if a delay is needed in a JavaFx class (gui module) // Prefer FxTimer if a delay is needed in a JavaFx class (gui module)
@ -99,7 +101,7 @@ public class UserThread {
} }
public static Timer runAfter(Runnable runnable, long delay, TimeUnit timeUnit) { public static Timer runAfter(Runnable runnable, long delay, TimeUnit timeUnit) {
return getTimer().runLater(Duration.ofMillis(timeUnit.toMillis(delay)), runnable); return getTimer().runLater(Duration.ofMillis(timeUnit.toMillis(delay)), () -> execute(runnable));
} }
public static Timer runPeriodically(Runnable runnable, long intervalInSec) { public static Timer runPeriodically(Runnable runnable, long intervalInSec) {
@ -107,7 +109,7 @@ public class UserThread {
} }
public static Timer runPeriodically(Runnable runnable, long interval, TimeUnit timeUnit) { public static Timer runPeriodically(Runnable runnable, long interval, TimeUnit timeUnit) {
return getTimer().runPeriodically(Duration.ofMillis(timeUnit.toMillis(interval)), runnable); return getTimer().runPeriodically(Duration.ofMillis(timeUnit.toMillis(interval)), () -> execute(runnable));
} }
private static Timer getTimer() { private static Timer getTimer() {

View File

@ -87,7 +87,6 @@ import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import lombok.Getter; import lombok.Getter;
import monero.daemon.model.MoneroTx; import monero.daemon.model.MoneroTx;
import monero.wallet.model.MoneroOutputQuery;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.KeyParameter;
import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.EasyBind;
@ -353,35 +352,6 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
} }
} }
private void thawUnreservedOutputs() {
if (xmrWalletService.getWallet() == null) return;
// collect reserved outputs
Set<String> reservedKeyImages = new HashSet<String>();
for (Trade trade : getObservableList()) {
if (trade.getSelf().getReserveTxKeyImages() == null) continue;
reservedKeyImages.addAll(trade.getSelf().getReserveTxKeyImages());
}
for (OpenOffer openOffer : openOfferManager.getObservableList()) {
if (openOffer.getOffer().getOfferPayload().getReserveTxKeyImages() == null) continue;
reservedKeyImages.addAll(openOffer.getOffer().getOfferPayload().getReserveTxKeyImages());
}
// thaw unreserved outputs
Set<String> unreservedFrozenKeyImages = xmrWalletService.getWallet().getOutputs(new MoneroOutputQuery()
.setIsFrozen(true)
.setIsSpent(false))
.stream()
.map(output -> output.getKeyImage().getHex())
.collect(Collectors.toSet());
unreservedFrozenKeyImages.removeAll(reservedKeyImages);
if (!unreservedFrozenKeyImages.isEmpty()) {
log.warn("Thawing outputs which are not reserved for offer or trade: " + unreservedFrozenKeyImages);
xmrWalletService.thawOutputs(unreservedFrozenKeyImages);
xmrWalletService.saveMainWallet();
}
}
public TradeProtocol getTradeProtocol(Trade trade) { public TradeProtocol getTradeProtocol(Trade trade) {
synchronized (tradeProtocolByTradeId) { synchronized (tradeProtocolByTradeId) {
return tradeProtocolByTradeId.get(trade.getUid()); return tradeProtocolByTradeId.get(trade.getUid());
@ -464,9 +434,10 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
} }
// thaw unreserved outputs // thaw unreserved outputs
thawUnreservedOutputs(); xmrWalletService.thawUnreservedOutputs();
// reset any available funded address entries // reset any available funded address entries
if (isShutDownStarted) return;
xmrWalletService.getAddressEntriesForAvailableBalanceStream() xmrWalletService.getAddressEntriesForAvailableBalanceStream()
.filter(addressEntry -> addressEntry.getOfferId() != null) .filter(addressEntry -> addressEntry.getOfferId() != null)
.forEach(addressEntry -> { .forEach(addressEntry -> {
@ -476,6 +447,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
} }
// notify that persisted trades initialized // notify that persisted trades initialized
if (isShutDownStarted) return;
persistedTradesInitialized.set(true); persistedTradesInitialized.set(true);
getObservableList().addListener((ListChangeListener<Trade>) change -> onTradesChanged()); getObservableList().addListener((ListChangeListener<Trade>) change -> onTradesChanged());
onTradesChanged(); onTradesChanged();

View File

@ -12,6 +12,7 @@ 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.OpenOffer;
import haveno.core.trade.BuyerTrade; import haveno.core.trade.BuyerTrade;
import haveno.core.trade.HavenoUtils; import haveno.core.trade.HavenoUtils;
import haveno.core.trade.MakerTrade; import haveno.core.trade.MakerTrade;
@ -80,6 +81,7 @@ import java.util.stream.Stream;
import javafx.beans.property.LongProperty; import javafx.beans.property.LongProperty;
import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.SimpleLongProperty; import javafx.beans.property.SimpleLongProperty;
import javafx.beans.value.ChangeListener;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
@ -120,6 +122,7 @@ public class XmrWalletService {
protected final CopyOnWriteArraySet<XmrBalanceListener> balanceListeners = new CopyOnWriteArraySet<>(); protected final CopyOnWriteArraySet<XmrBalanceListener> balanceListeners = new CopyOnWriteArraySet<>();
protected final CopyOnWriteArraySet<MoneroWalletListenerI> walletListeners = new CopyOnWriteArraySet<>(); protected final CopyOnWriteArraySet<MoneroWalletListenerI> walletListeners = new CopyOnWriteArraySet<>();
private ChangeListener<? super Number> walletInitListener;
private TradeManager tradeManager; private TradeManager tradeManager;
private MoneroWalletRpc wallet; private MoneroWalletRpc wallet;
private Object walletLock = new Object(); private Object walletLock = new Object();
@ -364,6 +367,45 @@ public class XmrWalletService {
} }
} }
/**
* Thaw all outputs not reserved for a trade.
*/
public void thawUnreservedOutputs() {
synchronized (walletLock) {
// collect reserved outputs
Set<String> reservedKeyImages = new HashSet<String>();
for (Trade trade : tradeManager.getObservableList()) {
if (trade.getSelf().getReserveTxKeyImages() == null) continue;
reservedKeyImages.addAll(trade.getSelf().getReserveTxKeyImages());
}
for (OpenOffer openOffer : tradeManager.getOpenOfferManager().getObservableList()) {
if (openOffer.getOffer().getOfferPayload().getReserveTxKeyImages() == null) continue;
reservedKeyImages.addAll(openOffer.getOffer().getOfferPayload().getReserveTxKeyImages());
}
// ensure wallet is open
if (wallet == null) {
log.warn("Cannot thaw unreserved outputs because wallet not open");
return;
}
// thaw unreserved outputs
Set<String> unreservedFrozenKeyImages = wallet.getOutputs(new MoneroOutputQuery()
.setIsFrozen(true)
.setIsSpent(false))
.stream()
.map(output -> output.getKeyImage().getHex())
.collect(Collectors.toSet());
unreservedFrozenKeyImages.removeAll(reservedKeyImages);
if (!unreservedFrozenKeyImages.isEmpty()) {
log.warn("Thawing outputs which are not reserved for offer or trade: " + unreservedFrozenKeyImages);
thawOutputs(unreservedFrozenKeyImages);
saveMainWallet();
}
}
}
/** /**
* Thaw the given outputs with a lock on the wallet. * Thaw the given outputs with a lock on the wallet.
* *
@ -695,17 +737,21 @@ public class XmrWalletService {
HavenoUtils.submitToThread(() -> onConnectionChanged(connection), THREAD_ID); HavenoUtils.submitToThread(() -> onConnectionChanged(connection), THREAD_ID);
}); });
// wait for monerod to sync // initialize main wallet when daemon synced
if (xmrConnectionService.downloadPercentageProperty().get() != 1) { walletInitListener = (obs, oldVal, newVal) -> initMainWalletIfConnected();
CountDownLatch latch = new CountDownLatch(1); xmrConnectionService.downloadPercentageProperty().addListener(walletInitListener);
xmrConnectionService.downloadPercentageProperty().addListener((obs, oldVal, newVal) -> { initMainWalletIfConnected();
if (xmrConnectionService.downloadPercentageProperty().get() == 1) latch.countDown(); }
});
HavenoUtils.awaitLatch(latch);
}
// initialize main wallet private void initMainWalletIfConnected() {
maybeInitMainWallet(true); HavenoUtils.submitToThread(() -> {
synchronized (walletLock) {
if (xmrConnectionService.downloadPercentageProperty().get() == 1 && wallet == null && !isShutDownStarted) {
maybeInitMainWallet(true);
if (walletInitListener != null) xmrConnectionService.downloadPercentageProperty().removeListener(walletInitListener);
}
}
}, THREAD_ID);
} }
private void maybeInitMainWallet(boolean sync) { private void maybeInitMainWallet(boolean sync) {