show daemon sync progress on startup then sync wallet

This commit is contained in:
woodser 2023-11-24 13:33:55 -05:00
parent 846b278b5d
commit d094997666
17 changed files with 119 additions and 86 deletions

View file

@ -2,6 +2,7 @@ package haveno.core.api;
import haveno.common.UserThread;
import haveno.common.app.DevEnv;
import haveno.common.config.BaseCurrencyNetwork;
import haveno.common.config.Config;
import haveno.core.trade.HavenoUtils;
import haveno.core.user.Preferences;
@ -69,6 +70,7 @@ public final class CoreMoneroConnectionsService {
private MoneroDaemonRpc daemon;
@Getter
private MoneroDaemonInfo lastInfo;
private Long syncStartHeight = null;
private TaskLooper daemonPollLooper;
private boolean isShutDownStarted;
private List<MoneroConnectionManagerListener> listeners = new ArrayList<>();
@ -299,17 +301,12 @@ public final class CoreMoneroConnectionsService {
return downloadPercentageProperty().get() == 1d;
}
/**
* Signals that both the daemon and wallet have synced.
*
* TODO: separate daemon and wallet download/done listeners
*/
public void doneDownload() {
// ------------------------------- HELPERS --------------------------------
private void doneDownload() {
downloadListener.doneDownload();
}
// ------------------------------- HELPERS --------------------------------
private boolean isConnectionLocal(MoneroRpcConnection connection) {
return connection != null && HavenoUtils.isLocalHost(connection.getUri());
}
@ -555,11 +552,26 @@ public final class CoreMoneroConnectionsService {
synchronized (lock) {
if (isShutDownStarted) return;
try {
// poll daemon
log.debug("Polling daemon info");
if (daemon == null) throw new RuntimeException("No daemon connection");
lastInfo = daemon.getInfo();
// set chain height
chainHeight.set(lastInfo.getHeight());
// update sync progress
boolean isTestnet = Config.baseCurrencyNetwork() == BaseCurrencyNetwork.XMR_LOCAL;
if (lastInfo.isSynchronized() || isTestnet) doneDownload(); // TODO: skipping synchronized check for testnet because tests cannot sync 3rd local node, see "Can manage Monero daemon connections"
else if (lastInfo.isBusySyncing()) {
long targetHeight = lastInfo.getTargetHeight();
long blocksLeft = targetHeight - lastInfo.getHeight();
if (syncStartHeight == null) syncStartHeight = lastInfo.getHeight();
double percent = ((double) Math.max(1, lastInfo.getHeight() - syncStartHeight) / (double) (targetHeight - syncStartHeight)) * 100d; // grant at least 1 block to show progress
downloadListener.progress(percent, blocksLeft, null);
}
// set peer connections
// TODO: peers often uknown due to restricted RPC call, skipping call to get peer connections
// try {

View file

@ -19,6 +19,7 @@ package haveno.core.app;
import haveno.core.api.CoreMoneroConnectionsService;
import haveno.core.api.CoreNotificationService;
import haveno.core.xmr.wallet.XmrWalletService;
import haveno.network.p2p.BootstrapListener;
import haveno.network.p2p.P2PService;
import javafx.beans.property.BooleanProperty;
@ -47,11 +48,13 @@ public class AppStartupState {
private final BooleanProperty applicationFullyInitialized = new SimpleBooleanProperty();
private final BooleanProperty updatedDataReceived = new SimpleBooleanProperty();
private final BooleanProperty isBlockDownloadComplete = new SimpleBooleanProperty();
private final BooleanProperty isWalletSynced = new SimpleBooleanProperty();
private final BooleanProperty hasSufficientPeersForBroadcast = new SimpleBooleanProperty();
@Inject
public AppStartupState(CoreNotificationService notificationService,
CoreMoneroConnectionsService connectionsService,
XmrWalletService xmrWalletService,
P2PService p2PService) {
p2PService.addP2PServiceListener(new BootstrapListener() {
@ -66,6 +69,11 @@ public class AppStartupState {
isBlockDownloadComplete.set(true);
});
xmrWalletService.downloadPercentageProperty().addListener((observable, oldValue, newValue) -> {
if (xmrWalletService.isWalletSynced())
isWalletSynced.set(true);
});
connectionsService.numPeersProperty().addListener((observable, oldValue, newValue) -> {
if (connectionsService.hasSufficientPeersForBroadcast())
hasSufficientPeersForBroadcast.set(true);
@ -73,14 +81,15 @@ public class AppStartupState {
p2pNetworkAndWalletInitialized = EasyBind.combine(updatedDataReceived,
isBlockDownloadComplete,
isWalletSynced,
hasSufficientPeersForBroadcast, // TODO: consider sufficient number of peers?
allDomainServicesInitialized,
(a, b, c, d) -> {
log.info("Combined initialized state = {} = updatedDataReceived={} && isBlockDownloadComplete={} && hasSufficientPeersForBroadcast={} && allDomainServicesInitialized={}", (a && b && c && d), updatedDataReceived.get(), isBlockDownloadComplete.get(), hasSufficientPeersForBroadcast.get(), allDomainServicesInitialized.get());
if (a && b) {
(a, b, c, d, e) -> {
log.info("Combined initialized state = {} = updatedDataReceived={} && isBlockDownloadComplete={} && isWalletSynced={} && hasSufficientPeersForBroadcast={} && allDomainServicesInitialized={}", (a && b && c && d && e), updatedDataReceived.get(), isBlockDownloadComplete.get(), isWalletSynced.get(), hasSufficientPeersForBroadcast.get(), allDomainServicesInitialized.get());
if (a && b && c) {
walletAndNetworkReady.set(true);
}
return a && d; // app fully initialized before daemon connection and wallet by default // TODO: rename variable
return a && e; // app fully initialized before daemon connection and wallet by default
});
p2pNetworkAndWalletInitialized.subscribe((observable, oldValue, newValue) -> {
if (newValue) {
@ -136,6 +145,10 @@ public class AppStartupState {
return isBlockDownloadComplete.get();
}
public boolean isWalletSynced() {
return isWalletSynced.get();
}
public ReadOnlyBooleanProperty isBlockDownloadCompleteProperty() {
return isBlockDownloadComplete;
}

View file

@ -438,7 +438,7 @@ public class HavenoSetup {
revolutAccountsUpdateHandler,
amazonGiftCardAccountsUpdateHandler);
if (walletsSetup.downloadPercentageProperty().get() == 1) { // TODO: update for XMR
if (xmrWalletService.downloadPercentageProperty().get() == 1) {
checkForLockedUpFunds();
}

View file

@ -31,6 +31,7 @@ import haveno.core.xmr.exceptions.InvalidHostException;
import haveno.core.xmr.exceptions.RejectedTxException;
import haveno.core.xmr.setup.WalletsSetup;
import haveno.core.xmr.wallet.WalletsManager;
import haveno.core.xmr.wallet.XmrWalletService;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
@ -39,9 +40,8 @@ import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import monero.common.MoneroUtils;
import org.bitcoinj.core.RejectMessage;
import org.bitcoinj.core.VersionMessage;
import org.bitcoinj.store.BlockStoreException;
import org.bitcoinj.store.ChainFileLockedException;
import org.fxmisc.easybind.EasyBind;
@ -61,6 +61,7 @@ public class WalletAppSetup {
private final WalletsManager walletsManager;
private final WalletsSetup walletsSetup;
private final CoreMoneroConnectionsService connectionService;
private final XmrWalletService xmrWalletService;
private final Config config;
private final Preferences preferences;
@ -85,12 +86,14 @@ public class WalletAppSetup {
WalletsManager walletsManager,
WalletsSetup walletsSetup,
CoreMoneroConnectionsService connectionService,
XmrWalletService xmrWalletService,
Config config,
Preferences preferences) {
this.coreContext = coreContext;
this.walletsManager = walletsManager;
this.walletsSetup = walletsSetup;
this.connectionService = connectionService;
this.xmrWalletService = xmrWalletService;
this.config = config;
this.preferences = preferences;
this.useTorForXmr.set(preferences.getUseTorForXmr());
@ -101,18 +104,21 @@ public class WalletAppSetup {
@Nullable Runnable showPopupIfInvalidBtcConfigHandler,
Runnable downloadCompleteHandler,
Runnable walletInitializedHandler) {
log.info("Initialize WalletAppSetup with BitcoinJ version {} and hash of BitcoinJ commit {}",
VersionMessage.BITCOINJ_VERSION, "2a80db4");
log.info("Initialize WalletAppSetup with monero-java version {}", MoneroUtils.getVersion());
ObjectProperty<Throwable> walletServiceException = new SimpleObjectProperty<>();
xmrInfoBinding = EasyBind.combine(connectionService.downloadPercentageProperty(), // TODO (woodser): update to XMR
xmrInfoBinding = EasyBind.combine(connectionService.downloadPercentageProperty(),
connectionService.chainHeightProperty(),
xmrWalletService.downloadPercentageProperty(),
xmrWalletService.walletHeightProperty(),
walletServiceException,
getWalletServiceErrorMsg(),
(downloadPercentage, chainHeight, exception, errorMsg) -> {
(chainDownloadPercentage, chainHeight, walletDownloadPercentage, walletHeight, exception, errorMsg) -> {
String result;
if (exception == null && errorMsg == null) {
double percentage = (double) downloadPercentage;
// TODO: update for daemon and wallet sync progress
double percentage = (double) chainDownloadPercentage;
xmrSyncProgress.set(percentage);
Long bestChainHeight = chainHeight == null ? null : (Long) chainHeight;
String chainHeightAsString = bestChainHeight != null && bestChainHeight > 0 ?

View file

@ -120,8 +120,10 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
private final KeyRing keyRing;
private final User user;
private final P2PService p2PService;
@Getter
private final CoreMoneroConnectionsService connectionsService;
private final BtcWalletService btcWalletService;
@Getter
private final XmrWalletService xmrWalletService;
private final TradeWalletService tradeWalletService;
private final OfferBookService offerBookService;

View file

@ -31,6 +31,7 @@ import haveno.core.trade.Trade;
import haveno.core.trade.TradeManager;
import haveno.core.trade.protocol.TradeProtocol;
import haveno.core.trade.protocol.TradeProtocol.MailboxMessageComparator;
import haveno.core.xmr.wallet.XmrWalletService;
import haveno.network.p2p.AckMessage;
import haveno.network.p2p.AckMessageSourceType;
import haveno.network.p2p.DecryptedMessageWithPubKey;
@ -53,6 +54,7 @@ public abstract class SupportManager {
protected final P2PService p2PService;
protected final TradeManager tradeManager;
protected final CoreMoneroConnectionsService connectionService;
protected final XmrWalletService xmrWalletService;
protected final CoreNotificationService notificationService;
protected final Map<String, Timer> delayMsgMap = new HashMap<>();
private final Object lock = new Object();
@ -68,10 +70,12 @@ public abstract class SupportManager {
public SupportManager(P2PService p2PService,
CoreMoneroConnectionsService connectionService,
XmrWalletService xmrWalletService,
CoreNotificationService notificationService,
TradeManager tradeManager) {
this.p2PService = p2PService;
this.connectionService = connectionService;
this.xmrWalletService = xmrWalletService;
this.mailboxMessageService = p2PService.getMailboxMessageService();
this.notificationService = notificationService;
this.tradeManager = tradeManager;
@ -333,7 +337,8 @@ public abstract class SupportManager {
return allServicesInitialized &&
p2PService.isBootstrapped() &&
connectionService.isDownloadComplete() &&
connectionService.hasSufficientPeersForBroadcast();
connectionService.hasSufficientPeersForBroadcast() &&
xmrWalletService.isWalletSynced();
}

View file

@ -114,7 +114,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
DisputeListService<T> disputeListService,
Config config,
PriceFeedService priceFeedService) {
super(p2PService, connectionService, notificationService, tradeManager);
super(p2PService, connectionService, xmrWalletService, notificationService, tradeManager);
this.tradeWalletService = tradeWalletService;
this.xmrWalletService = xmrWalletService;
@ -257,6 +257,11 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
tryApplyMessages();
});
xmrWalletService.downloadPercentageProperty().addListener((observable, oldValue, newValue) -> {
if (xmrWalletService.isWalletSynced())
tryApplyMessages();
});
connectionService.numPeersProperty().addListener((observable, oldValue, newValue) -> {
if (connectionService.hasSufficientPeersForBroadcast())
tryApplyMessages();

View file

@ -28,6 +28,7 @@ import haveno.core.support.messages.ChatMessage;
import haveno.core.support.messages.SupportMessage;
import haveno.core.trade.Trade;
import haveno.core.trade.TradeManager;
import haveno.core.xmr.wallet.XmrWalletService;
import haveno.network.p2p.AckMessageSourceType;
import haveno.network.p2p.NodeAddress;
import haveno.network.p2p.P2PService;
@ -53,10 +54,11 @@ public class TraderChatManager extends SupportManager {
@Inject
public TraderChatManager(P2PService p2PService,
CoreMoneroConnectionsService connectionService,
XmrWalletService xmrWalletService,
CoreNotificationService notificationService,
TradeManager tradeManager,
PubKeyRingProvider pubKeyRingProvider) {
super(p2PService, connectionService, notificationService, tradeManager);
super(p2PService, connectionService, xmrWalletService, notificationService, tradeManager);
this.pubKeyRingProvider = pubKeyRingProvider;
}

View file

@ -86,7 +86,7 @@ public class Balances {
}
private void updateBalances() {
if (!xmrWalletService.isWalletReady()) return;
if (!xmrWalletService.isWalletAvailable()) return;
try {
updateAvailableBalance();
updatePendingBalance();
@ -94,7 +94,7 @@ public class Balances {
updateReservedTradeBalance();
updateReservedBalance();
} catch (Exception e) {
if (xmrWalletService.isWalletReady()) throw e; // ignore exception if wallet isn't ready
if (xmrWalletService.isWalletAvailable()) throw e; // ignore exception if wallet isn't ready
}
}

View file

@ -10,7 +10,7 @@ import java.util.Date;
public class DownloadListener {
private final DoubleProperty percentage = new SimpleDoubleProperty(-1);
public void progress(double percentage, int blocksLeft, Date date) {
public void progress(double percentage, long blocksLeft, Date date) {
UserThread.execute(() -> this.percentage.set(percentage / 100d));
}

View file

@ -32,7 +32,6 @@ import org.bitcoinj.core.Context;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.PeerAddress;
import org.bitcoinj.core.PeerGroup;
import org.bitcoinj.core.listeners.DownloadProgressTracker;
import org.bitcoinj.crypto.KeyCrypter;
import org.bitcoinj.net.discovery.PeerDiscovery;
import org.bitcoinj.script.Script;
@ -92,7 +91,6 @@ public class WalletConfig extends AbstractIdleService {
protected volatile File vBtcWalletFile;
protected PeerAddress[] peerAddresses;
protected DownloadListener downloadListener;
protected InputStream checkpoints;
protected String userAgent, version;
@Nullable
@ -169,15 +167,6 @@ public class WalletConfig extends AbstractIdleService {
return setPeerNodes(new PeerAddress(params, localHost, params.getPort()));
}
/**
* If you want to learn about the sync process, you can provide a listener here. For instance, a
* {@link DownloadProgressTracker} is a good choice.
*/
public WalletConfig setDownloadListener(DownloadListener listener) {
this.downloadListener = listener;
return this;
}
/**
* If set, the file is expected to contain a checkpoints file calculated with BuildCheckpoints. It makes initial
* block sync faster for new users - please refer to the documentation on the bitcoinj website

View file

@ -42,13 +42,7 @@ import haveno.core.xmr.nodes.XmrNodesSetupPreferences;
import haveno.network.Socks5MultiDiscovery;
import haveno.network.Socks5ProxyProvider;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleLongProperty;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@ -107,9 +101,6 @@ public class WalletsSetup {
private final NetworkParameters params;
private final File walletDir;
private final int socks5DiscoverMode;
private final IntegerProperty numPeers = new SimpleIntegerProperty(0);
private final LongProperty chainHeight = new SimpleLongProperty(0);
private final DownloadListener downloadListener = new DownloadListener();
private final List<Runnable> setupTaskHandlers = new ArrayList<>();
private final List<Runnable> setupCompletedHandlers = new ArrayList<>();
public final BooleanProperty shutDownComplete = new SimpleBooleanProperty();
@ -172,12 +163,12 @@ public class WalletsSetup {
backupWallets();
final Socks5Proxy socks5Proxy = socks5ProxyProvider.getSocks5Proxy();
log.info("Socks5Proxy for bitcoinj: socks5Proxy=" + socks5Proxy);
log.info("Using Socks5Proxy: " + socks5Proxy);
walletConfig = new WalletConfig(params, walletDir, "haveno") {
@Override
protected void onSetupCompleted() {
//We are here in the btcj thread Thread[ STARTING,5,main]
super.onSetupCompleted();
// run external startup handlers
@ -244,8 +235,6 @@ public class WalletsSetup {
}
}
walletConfig.setDownloadListener(downloadListener);
// If seed is non-null it means we are restoring from backup.
if (seed != null) {
walletConfig.restoreWalletFromSeed(seed);
@ -430,26 +419,6 @@ public class WalletsSetup {
return walletConfig;
}
public ReadOnlyIntegerProperty numPeersProperty() {
return numPeers;
}
public LongProperty chainHeightProperty() {
return chainHeight;
}
public ReadOnlyDoubleProperty downloadPercentageProperty() {
return downloadListener.percentageProperty();
}
public boolean isDownloadComplete() {
return downloadPercentageProperty().get() == 1d;
}
public boolean isChainHeightSyncedWithinTolerance() {
throw new RuntimeException("WalletsSetup.isChainHeightSyncedWithinTolerance() not implemented for BTC");
}
public Set<Address> getAddressesByContext(@SuppressWarnings("SameParameterValue") AddressEntry.Context context) {
return addressEntryList.getAddressEntriesAsListImmutable().stream()
.filter(addressEntry -> addressEntry.getContext() == context)
@ -463,10 +432,6 @@ public class WalletsSetup {
.collect(Collectors.toSet());
}
public boolean hasSufficientPeersForBroadcast() {
return numPeers.get() >= getMinBroadcastConnections();
}
public int getMinBroadcastConnections() {
return walletConfig.getMinBroadcastConnections();
}

View file

@ -92,6 +92,10 @@ public class BtcWalletService extends WalletService {
// Overridden Methods
///////////////////////////////////////////////////////////////////////////////////////////
public boolean isWalletSyncedWithinTolerance() {
throw new RuntimeException("Not implemented");
}
@Override
void decryptWallet(@NotNull KeyParameter key) {
super.decryptWallet(key);

View file

@ -538,9 +538,7 @@ public abstract class WalletService {
return isWalletReady() && chain != null ? chain.getBestChainHeight() : 0;
}
public boolean isChainHeightSyncedWithinTolerance() {
return walletsSetup.isChainHeightSyncedWithinTolerance();
}
public abstract boolean isWalletSyncedWithinTolerance();
public Transaction getClonedTransaction(Transaction tx) {
return new Transaction(params, tx.bitcoinSerialize());

View file

@ -97,7 +97,7 @@ public class WalletsManager {
}
public boolean areWalletsAvailable() {
return xmrWalletService.isWalletReady();
return xmrWalletService.isWalletAvailable();
}
public KeyCrypterScrypt getKeyCrypterScrypt() {

View file

@ -21,6 +21,7 @@ import haveno.core.user.Preferences;
import haveno.core.xmr.listeners.XmrBalanceListener;
import haveno.core.xmr.model.XmrAddressEntry;
import haveno.core.xmr.model.XmrAddressEntryList;
import haveno.core.xmr.setup.DownloadListener;
import haveno.core.xmr.setup.MoneroWalletRpcManager;
import haveno.core.xmr.setup.WalletsSetup;
import monero.common.MoneroError;
@ -69,11 +70,15 @@ import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.beans.property.LongProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.SimpleLongProperty;
import static com.google.common.base.Preconditions.checkState;
@ -104,6 +109,8 @@ public class XmrWalletService {
private final CoreMoneroConnectionsService connectionsService;
private final XmrAddressEntryList xmrAddressEntryList;
private final WalletsSetup walletsSetup;
private final DownloadListener downloadListener = new DownloadListener();
private final LongProperty walletHeight = new SimpleLongProperty(0);
private final File walletDir;
private final File xmrWalletFile;
@ -196,7 +203,7 @@ public class XmrWalletService {
saveWallet(getWallet(), backup);
}
public boolean isWalletReady() {
public boolean isWalletAvailable() {
try {
return getWallet() != null;
} catch (Exception e) {
@ -208,6 +215,22 @@ public class XmrWalletService {
return accountService.getPassword() != null;
}
public boolean isWalletSynced() {
return downloadPercentageProperty().get() == 1d;
}
public ReadOnlyDoubleProperty downloadPercentageProperty() {
return downloadListener.percentageProperty();
}
private void doneDownload() {
downloadListener.doneDownload();
}
public LongProperty walletHeightProperty() {
return walletHeight;
}
public MoneroDaemonRpc getDaemon() {
return connectionsService.getDaemon();
}
@ -651,12 +674,19 @@ public class XmrWalletService {
private void initialize() {
// set and listen to daemon connection
connectionsService.addConnectionListener(newConnection -> {
onConnectionChanged(newConnection);
});
// listen for connection changes
connectionsService.addConnectionListener(newConnection -> onConnectionChanged(newConnection));
// initialize main wallet if connected or previously created
// wait for monerod to sync
if (connectionsService.downloadPercentageProperty().get() != 1) {
CountDownLatch latch = new CountDownLatch(1);
connectionsService.downloadPercentageProperty().addListener((obs, oldVal, newVal) -> {
if (connectionsService.downloadPercentageProperty().get() == 1) latch.countDown();
});
HavenoUtils.awaitLatch(latch);
}
// initialize main wallet
maybeInitMainWallet(true);
}
@ -700,8 +730,8 @@ public class XmrWalletService {
// reapply connection after wallet synced
onConnectionChanged(connectionsService.getConnection());
// TODO: using this to signify both daemon and wallet synced, use separate sync handlers?
connectionsService.doneDownload();
// signal that main wallet is synced
doneDownload();
// notify setup that main wallet is initialized
// TODO: app fully initializes after this is set to true, even though wallet might not be initialized if unconnected. wallet will be created when connection detected
@ -1242,6 +1272,7 @@ public class XmrWalletService {
UserThread.execute(new Runnable() {
@Override
public void run() {
walletHeight.set(height);
for (MoneroWalletListenerI listener : walletListeners) listener.onNewBlock(height);
}
});

View file

@ -553,6 +553,7 @@ abstract class OfferBookViewModel extends ActivatableViewModel {
boolean canCreateOrTakeOffer() {
return GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation) &&
GUIUtil.isChainHeightSyncedWithinToleranceOrShowPopup(openOfferManager.getConnectionsService()) &&
GUIUtil.isBootstrappedOrShowPopup(p2PService);
}