Skip to content

Commit

Permalink
automatically restore backup cache if corrupt
Browse files Browse the repository at this point in the history
  • Loading branch information
woodser committed Sep 4, 2024
1 parent 2d0f200 commit a8e76fd
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 11 deletions.
20 changes: 18 additions & 2 deletions common/src/main/java/haveno/common/file/FileUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,13 @@

@Slf4j
public class FileUtil {

private static final String BACKUP_DIR = "backup";

public static void rollingBackup(File dir, String fileName, int numMaxBackupFiles) {
if (numMaxBackupFiles <= 0) return;
if (dir.exists()) {
File backupDir = new File(Paths.get(dir.getAbsolutePath(), "backup").toString());
File backupDir = new File(Paths.get(dir.getAbsolutePath(), BACKUP_DIR).toString());
if (!backupDir.exists())
if (!backupDir.mkdir())
log.warn("make dir failed.\nBackupDir=" + backupDir.getAbsolutePath());
Expand Down Expand Up @@ -72,8 +75,21 @@ public static void rollingBackup(File dir, String fileName, int numMaxBackupFile
}
}

public static File getLatestBackupFile(File dir, String fileName) {
File backupDir = new File(Paths.get(dir.getAbsolutePath(), BACKUP_DIR).toString());
if (!backupDir.exists()) return null;
String dirName = "backups_" + fileName;
if (dirName.contains(".")) dirName = dirName.replace(".", "_");
File backupFileDir = new File(Paths.get(backupDir.getAbsolutePath(), dirName).toString());
if (!backupFileDir.exists()) return null;
File[] files = backupFileDir.listFiles();
if (files == null || files.length == 0) return null;
Arrays.sort(files, Comparator.comparing(File::getName));
return files[files.length - 1];
}

public static void deleteRollingBackup(File dir, String fileName) {
File backupDir = new File(Paths.get(dir.getAbsolutePath(), "backup").toString());
File backupDir = new File(Paths.get(dir.getAbsolutePath(), BACKUP_DIR).toString());
if (!backupDir.exists()) return;
String dirName = "backups_" + fileName;
if (dirName.contains(".")) dirName = dirName.replace(".", "_");
Expand Down
102 changes: 93 additions & 9 deletions core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ public class XmrWalletService extends XmrWalletBase {
private final WalletsSetup walletsSetup;

private final File walletDir;
private final File xmrWalletFile;
private final int rpcBindPort;
private final boolean useNativeXmrWallet;
protected final CopyOnWriteArraySet<XmrBalanceListener> balanceListeners = new CopyOnWriteArraySet<>();
Expand Down Expand Up @@ -181,7 +180,6 @@ public class XmrWalletService extends XmrWalletBase {
this.walletDir = walletDir;
this.rpcBindPort = rpcBindPort;
this.useNativeXmrWallet = useNativeXmrWallet;
this.xmrWalletFile = new File(walletDir, MONERO_WALLET_NAME);
HavenoUtils.xmrWalletService = this;
HavenoUtils.xmrConnectionService = xmrConnectionService;
this.xmrConnectionService = xmrConnectionService; // TODO: super's is null unless set here from injection
Expand Down Expand Up @@ -1327,7 +1325,7 @@ private void doMaybeInitMainWallet(boolean sync, int numAttempts) {
if (wallet == null) {
MoneroDaemonRpc daemon = xmrConnectionService.getDaemon();
log.info("Initializing main wallet with monerod=" + (daemon == null ? "null" : daemon.getRpcConnection().getUri()));
if (MoneroUtils.walletExists(xmrWalletFile.getPath())) {
if (walletExists(MONERO_WALLET_NAME)) {
wallet = openWallet(MONERO_WALLET_NAME, rpcBindPort, isProxyApplied(wasWalletSynced));
} else if (Boolean.TRUE.equals(xmrConnectionService.isConnected())) {
wallet = createWallet(MONERO_WALLET_NAME, rpcBindPort);
Expand Down Expand Up @@ -1475,11 +1473,54 @@ private MoneroWalletFull openWalletFull(MoneroWalletConfig config, boolean apply
MoneroRpcConnection connection = new MoneroRpcConnection(xmrConnectionService.getConnection());
if (!applyProxyUri) connection.setProxyUri(null);

// open wallet
// try opening wallet
config.setNetworkType(getMoneroNetworkType());
config.setServer(connection);
log.info("Opening full wallet " + config.getPath() + " with monerod=" + connection.getUri() + ", proxyUri=" + connection.getProxyUri());
walletFull = MoneroWalletFull.openWallet(config);
try {
walletFull = MoneroWalletFull.openWallet(config);
} catch (Exception e) {
log.warn("Failed to open full wallet '{}', attempting to use backup cache, error={}", config.getPath(), e.getMessage());
boolean retrySuccessful = false;
try {

// rename wallet cache to backup
String cachePath = walletDir.toString() + File.separator + MONERO_WALLET_NAME;
File originalCacheFile = new File(cachePath);
if (originalCacheFile.exists()) originalCacheFile.renameTo(new File(cachePath + ".backup"));

// copy latest wallet cache backup to main folder
File backupCacheFile = FileUtil.getLatestBackupFile(walletDir, MONERO_WALLET_NAME);
if (backupCacheFile != null) FileUtil.copyFile(backupCacheFile, new File(cachePath));

// retry opening wallet without original cache
try {
walletFull = MoneroWalletFull.openWallet(config);
log.info("Successfully opened full wallet using backup cache");
retrySuccessful = true;
} catch (Exception e2) {
// ignore
}

// handle success or failure
if (retrySuccessful) {
originalCacheFile.delete(); // delete original wallet cache backup
} else {

// restore original wallet cache
log.warn("Failed to open full wallet using backup cache, restoring original cache");
File cacheFile = new File(cachePath);
if (cacheFile.exists()) cacheFile.delete();
File originalCacheBackup = new File(cachePath + ".backup");
if (originalCacheBackup.exists()) originalCacheBackup.renameTo(new File(cachePath));

// throw exception
throw e;
}
} catch (Exception e2) {
throw e; // throw original exception
}
}
if (walletFull.getDaemonConnection() != null) walletFull.getDaemonConnection().setPrintStackTrace(PRINT_RPC_STACK_TRACE);
log.info("Done opening full wallet " + config.getPath());
return walletFull;
Expand Down Expand Up @@ -1518,7 +1559,7 @@ private MoneroWalletRpc createWalletRpc(MoneroWalletConfig config, Integer port)
} catch (Exception e) {
e.printStackTrace();
if (walletRpc != null) forceCloseWallet(walletRpc, config.getPath());
throw new IllegalStateException("Could not create wallet '" + config.getPath() + "'. Please close Haveno, stop all monero-wallet-rpc processes, and restart Haveno.");
throw new IllegalStateException("Could not create wallet '" + config.getPath() + "'. Please close Haveno, stop all monero-wallet-rpc processes in your task manager, and restart Haveno.");
}
}

Expand All @@ -1537,17 +1578,60 @@ private MoneroWalletRpc openWalletRpc(MoneroWalletConfig config, Integer port, b
MoneroRpcConnection connection = new MoneroRpcConnection(xmrConnectionService.getConnection());
if (!applyProxyUri) connection.setProxyUri(null);

// open wallet
// try opening wallet
log.info("Opening RPC wallet " + config.getPath() + " with monerod=" + connection.getUri() + ", proxyUri=" + connection.getProxyUri());
config.setServer(connection);
walletRpc.openWallet(config);
try {
walletRpc.openWallet(config);
} catch (Exception e) {
log.warn("Failed to open RPC wallet '{}', attempting to use backup cache, error={}", config.getPath(), e.getMessage());
boolean retrySuccessful = false;
try {

// rename wallet cache to backup
String cachePath = walletDir.toString() + File.separator + MONERO_WALLET_NAME;
File originalCacheFile = new File(cachePath);
if (originalCacheFile.exists()) originalCacheFile.renameTo(new File(cachePath + ".backup"));

// copy latest wallet cache backup to main folder
File backupCacheFile = FileUtil.getLatestBackupFile(walletDir, MONERO_WALLET_NAME);
if (backupCacheFile != null) FileUtil.copyFile(backupCacheFile, new File(cachePath));

// retry opening wallet without original cache
try {
walletRpc.openWallet(config);
log.info("Successfully opened RPC wallet using backup cache");
retrySuccessful = true;
} catch (Exception e2) {
// ignore
}

// handle success or failure
if (retrySuccessful) {
originalCacheFile.delete(); // delete original wallet cache backup
} else {

// restore original wallet cache
log.warn("Failed to open RPC wallet using backup cache, restoring original cache");
File cacheFile = new File(cachePath);
if (cacheFile.exists()) cacheFile.delete();
File originalCacheBackup = new File(cachePath + ".backup");
if (originalCacheBackup.exists()) originalCacheBackup.renameTo(new File(cachePath));

// throw exception
throw e;
}
} catch (Exception e2) {
throw e; // throw original exception
}
}
if (walletRpc.getDaemonConnection() != null) walletRpc.getDaemonConnection().setPrintStackTrace(PRINT_RPC_STACK_TRACE);
log.info("Done opening RPC wallet " + config.getPath());
return walletRpc;
} catch (Exception e) {
e.printStackTrace();
if (walletRpc != null) forceCloseWallet(walletRpc, config.getPath());
throw new IllegalStateException("Could not open wallet '" + config.getPath() + "'. Please close Haveno, stop all monero-wallet-rpc processes, and restart Haveno.\n\nError message: " + e.getMessage());
throw new IllegalStateException("Could not open wallet '" + config.getPath() + "'. Please close Haveno, stop all monero-wallet-rpc processes in your task manager, and restart Haveno.\n\nError message: " + e.getMessage());
}
}

Expand Down

0 comments on commit a8e76fd

Please sign in to comment.