support rolling backup of multisig wallets

This commit is contained in:
woodser 2022-06-28 10:34:20 -04:00
parent f17c5803b5
commit c28108a4b6
4 changed files with 45 additions and 21 deletions

View file

@ -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);

View file

@ -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();

View file

@ -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) {

View file

@ -124,7 +124,7 @@ public class SendSignContractRequestAfterMultisig extends TradeTask {
}
private void completeAux() {
processModel.getXmrWalletService().getWallet().save();
processModel.getXmrWalletService().saveWallet(processModel.getXmrWalletService().getWallet());
complete();
}
}