From c28108a4b6d08dd46bd3b54e13b7af1f0d8b1ca2 Mon Sep 17 00:00:00 2001 From: woodser Date: Tue, 28 Jun 2022 10:34:20 -0400 Subject: [PATCH] support rolling backup of multisig wallets --- .../java/bisq/common/crypto/KeyStorage.java | 3 +- .../main/java/bisq/common/file/FileUtil.java | 13 +++++ .../core/btc/wallet/XmrWalletService.java | 48 +++++++++++-------- .../SendSignContractRequestAfterMultisig.java | 2 +- 4 files changed, 45 insertions(+), 21 deletions(-) diff --git a/common/src/main/java/bisq/common/crypto/KeyStorage.java b/common/src/main/java/bisq/common/crypto/KeyStorage.java index 6484dcb0..86602b19 100644 --- a/common/src/main/java/bisq/common/crypto/KeyStorage.java +++ b/common/src/main/java/bisq/common/crypto/KeyStorage.java @@ -18,7 +18,7 @@ package bisq.common.crypto; import bisq.common.config.Config; - +import bisq.common.file.FileUtil; import com.google.inject.Inject; import javax.inject.Named; @@ -139,6 +139,7 @@ public class KeyStorage { * @param secretKey The symmetric key that protects the key entry file */ public KeyPair loadKeyPair(KeyEntry keyEntry, SecretKey secretKey) { + FileUtil.rollingBackup(storageDir, keyEntry.getFileName() + ".key", 20); try { KeyFactory keyFactory = KeyFactory.getInstance(keyEntry.getAlgorithm()); byte[] encodedPrivateKey = loadKeyBytes(keyEntry, secretKey); diff --git a/common/src/main/java/bisq/common/file/FileUtil.java b/common/src/main/java/bisq/common/file/FileUtil.java index 2a9ec2aa..12bb1adb 100644 --- a/common/src/main/java/bisq/common/file/FileUtil.java +++ b/common/src/main/java/bisq/common/file/FileUtil.java @@ -74,6 +74,19 @@ public class FileUtil { } } + public static void deleteRollingBackup(File dir, String fileName) { + File backupDir = new File(Paths.get(dir.getAbsolutePath(), "backup").toString()); + if (!backupDir.exists()) throw new RuntimeException("backup directory does not exist: " + backupDir); + String dirName = "backups_" + fileName; + if (dirName.contains(".")) dirName = dirName.replace(".", "_"); + File backupFileDir = new File(Paths.get(backupDir.getAbsolutePath(), dirName).toString()); + try { + FileUtils.deleteDirectory(backupFileDir); + } catch (IOException e) { + e.printStackTrace(); + } + } + private static void pruneBackup(File backupDir, int numMaxBackupFiles) { if (backupDir.isDirectory()) { File[] files = backupDir.listFiles(); diff --git a/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java b/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java index 98fd3be3..3d903432 100644 --- a/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java @@ -76,6 +76,7 @@ public class XmrWalletService { private static final String MONERO_WALLET_RPC_USERNAME = "haveno_user"; private static final String MONERO_WALLET_RPC_DEFAULT_PASSWORD = "password"; // only used if account password is null private static final String MONERO_WALLET_NAME = "haveno_XMR"; + private static final String MONERO_MULTISIG_WALLET_PREFIX = "xmr_multisig_trade_"; private static final long MONERO_WALLET_SYNC_PERIOD = 5000l; private final CoreAccountService accountService; @@ -168,7 +169,7 @@ public class XmrWalletService { } public boolean multisigWalletExists(String tradeId) { - return walletExists("xmr_multisig_trade_" + tradeId); + return walletExists(MONERO_MULTISIG_WALLET_PREFIX + tradeId); } // TODO (woodser): test retaking failed trade. create new multisig wallet or replace? cannot reuse @@ -177,7 +178,7 @@ public class XmrWalletService { Trade trade = tradeManager.getOpenTrade(tradeId).get(); synchronized (trade) { if (multisigWallets.containsKey(trade.getId())) return multisigWallets.get(trade.getId()); - String path = "xmr_multisig_trade_" + trade.getId(); + String path = MONERO_MULTISIG_WALLET_PREFIX + trade.getId(); MoneroWallet multisigWallet = createWallet(new MoneroWalletConfig().setPath(path).setPassword(getWalletPassword()), null); // auto-assign port multisigWallets.put(trade.getId(), multisigWallet); return multisigWallet; @@ -190,7 +191,7 @@ public class XmrWalletService { Trade trade = tradeManager.getTrade(tradeId); synchronized (trade) { if (multisigWallets.containsKey(trade.getId())) return multisigWallets.get(trade.getId()); - String path = "xmr_multisig_trade_" + trade.getId(); + String path = MONERO_MULTISIG_WALLET_PREFIX + trade.getId(); if (!walletExists(path)) throw new RuntimeException("Multisig wallet does not exist for trade " + trade.getId()); MoneroWallet multisigWallet = openWallet(new MoneroWalletConfig().setPath(path).setPassword(getWalletPassword()), null); multisigWallets.put(trade.getId(), multisigWallet); @@ -198,6 +199,11 @@ public class XmrWalletService { } } + public void saveWallet(MoneroWallet wallet) { + wallet.save(); + backupWallet(wallet.getPath()); + } + public void closeMultisigWallet(String tradeId) { log.info("{}.closeMultisigWallet({})", getClass().getSimpleName(), tradeId); Trade trade = tradeManager.getTrade(tradeId); @@ -212,7 +218,7 @@ public class XmrWalletService { log.info("{}.deleteMultisigWallet({})", getClass().getSimpleName(), tradeId); Trade trade = tradeManager.getTrade(tradeId); synchronized (trade) { - String walletName = "xmr_multisig_trade_" + tradeId; + String walletName = MONERO_MULTISIG_WALLET_PREFIX + tradeId; if (!walletExists(walletName)) return false; if (multisigWallets.containsKey(trade.getId())) closeMultisigWallet(tradeId); deleteWallet(walletName); @@ -358,7 +364,7 @@ public class XmrWalletService { if (miningFeePadding) depositThreshold = depositThreshold.add(feeThreshold.multiply(BigInteger.valueOf(3l))); // prove reserve of at least deposit amount + (3 * min mining fee) if (check.getReceivedAmount().compareTo(depositThreshold) < 0) throw new RuntimeException("Deposit amount is not enough, needed " + depositThreshold + " but was " + check.getReceivedAmount()); } finally { - + // flush tx from pool if we added it if (submittedToPool) daemon.flushTxPool(txHash); } @@ -372,9 +378,6 @@ public class XmrWalletService { private void initialize() { - // backup wallet files - backupWallets(); - // initialize main wallet if connected or previously created tryInitMainWallet(); @@ -402,7 +405,7 @@ public class XmrWalletService { try { wallet.sync(); // blocking connectionsService.doneDownload(); // TODO: using this to signify both daemon and wallet synced, refactor sync handling of both - wallet.save(); + saveWallet(wallet); } catch (Exception e) { e.printStackTrace(); } @@ -485,12 +488,6 @@ public class XmrWalletService { return MONERO_WALLET_RPC_MANAGER.startInstance(cmd); } - private void backupWallets() { - FileUtil.rollingBackup(walletDir, xmrWalletFile.getName(), 20); - FileUtil.rollingBackup(walletDir, xmrWalletFile.getName() + ".keys", 20); - FileUtil.rollingBackup(walletDir, xmrWalletFile.getName() + ".address.txt", 20); - } - private void setWalletDaemonConnections(MoneroRpcConnection connection) { log.info("Setting wallet daemon connections: " + (connection == null ? null : connection.getUri())); if (wallet == null) tryInitMainWallet(); @@ -520,7 +517,7 @@ public class XmrWalletService { public void run() { try { wallet.changePassword(oldPassword, newPassword); - wallet.save(); + saveWallet(wallet); } catch (Exception e) { e.printStackTrace(); throw e; @@ -534,7 +531,7 @@ public class XmrWalletService { MoneroWallet multisigWallet = getMultisigWallet(tradeId); // TODO (woodser): this unnecessarily connects and syncs unopen wallets and leaves open if (multisigWallet == null) return; multisigWallet.changePassword(oldPassword, newPassword); - multisigWallet.save(); + saveWallet(multisigWallet); } }); } @@ -552,7 +549,8 @@ public class XmrWalletService { log.info("{}.closeWallet({}, {})", getClass().getSimpleName(), walletRpc.getPath(), save); MoneroError err = null; try { - walletRpc.close(save); + if (save) saveWallet(walletRpc); + walletRpc.close(); } catch (MoneroError e) { err = e; } @@ -567,7 +565,7 @@ public class XmrWalletService { if (!new File(path).delete()) throw new RuntimeException("Failed to delete wallet file: " + path); if (!new File(path + ".keys").delete()) throw new RuntimeException("Failed to delete wallet file: " + path); if (!new File(path + ".address.txt").delete()) throw new RuntimeException("Failed to delete wallet file: " + path); - // WalletsSetup.deleteRollingBackup(walletName); // TODO (woodser): necessary to delete rolling backup? + deleteBackupWallets(walletName); } private void closeAllWallets() { @@ -609,6 +607,18 @@ public class XmrWalletService { multisigWallets.clear(); } + private void backupWallet(String walletName) { + FileUtil.rollingBackup(walletDir, walletName, 20); + FileUtil.rollingBackup(walletDir, walletName + ".keys", 20); + FileUtil.rollingBackup(walletDir, walletName + ".address.txt", 20); + } + + private void deleteBackupWallets(String walletName) { + FileUtil.deleteRollingBackup(walletDir, walletName); + FileUtil.deleteRollingBackup(walletDir, walletName + ".keys"); + FileUtil.deleteRollingBackup(walletDir, walletName + ".address.txt"); + } + // ----------------------------- LEGACY APP ------------------------------- public XmrAddressEntry recoverAddressEntry(String offerId, String address, XmrAddressEntry.Context context) { diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SendSignContractRequestAfterMultisig.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SendSignContractRequestAfterMultisig.java index e024c681..df106717 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/SendSignContractRequestAfterMultisig.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SendSignContractRequestAfterMultisig.java @@ -124,7 +124,7 @@ public class SendSignContractRequestAfterMultisig extends TradeTask { } private void completeAux() { - processModel.getXmrWalletService().getWallet().save(); + processModel.getXmrWalletService().saveWallet(processModel.getXmrWalletService().getWallet()); complete(); } }