diff --git a/core/src/main/java/haveno/core/trade/Trade.java b/core/src/main/java/haveno/core/trade/Trade.java index 680b568e71..a5aceeca42 100644 --- a/core/src/main/java/haveno/core/trade/Trade.java +++ b/core/src/main/java/haveno/core/trade/Trade.java @@ -119,6 +119,7 @@ public abstract class Trade implements Tradable, Model { private static final String MONERO_TRADE_WALLET_PREFIX = "xmr_trade_"; private static final long SHUTDOWN_TIMEOUT_MS = 90000; + private static final long DELETE_BACKUPS_AFTER_NUM_BLOCKS = 3600; // ~5 days private final Object walletLock = new Object(); private final Object pollLock = new Object(); private MoneroWallet wallet; @@ -918,12 +919,9 @@ public abstract class Trade implements Tradable, Model { log.info("Deleting wallet for {} {}", getClass().getSimpleName(), getId()); xmrWalletService.deleteWallet(getWalletName()); - // delete trade wallet backups unless deposits requested and payouts not unlocked - if (isDepositRequested() && !isDepositFailed() && !isPayoutUnlocked()) { - log.warn("Refusing to delete backup wallet for " + getClass().getSimpleName() + " " + getId() + " in the small chance it becomes funded"); - } else { - xmrWalletService.deleteWalletBackups(getWalletName()); - } + // record delete height and schedule backup deletion + processModel.setDeleteBackupsHeight(xmrConnectionService.getLastInfo().getHeight() + DELETE_BACKUPS_AFTER_NUM_BLOCKS); + maybeScheduleDeleteBackups(); } catch (Exception e) { log.warn(e.getMessage()); e.printStackTrace(); @@ -1201,6 +1199,9 @@ public abstract class Trade implements Tradable, Model { private void clearProcessData() { + // delete backup wallets after main wallet + blocks + maybeScheduleDeleteBackups(); + // delete trade wallet synchronized (walletLock) { if (!walletExists()) return; // done if already cleared @@ -1215,6 +1216,27 @@ public abstract class Trade implements Tradable, Model { } } + private void maybeScheduleDeleteBackups() { + if (processModel.getDeleteBackupsHeight() == 0) return; + if (xmrConnectionService.getLastInfo().getHeight() >= processModel.getDeleteBackupsHeight()) { + xmrWalletService.deleteWalletBackups(getWalletName()); + processModel.setDeleteBackupsHeight(0); // reset delete height + } else { + MoneroWalletListener deleteBackupsListener = new MoneroWalletListener() { + @Override + public synchronized void onNewBlock(long height) { // prevent concurrent deletion + if (processModel.getDeleteBackupsHeight() == 0) return; + if (xmrConnectionService.getLastInfo().getHeight() >= processModel.getDeleteBackupsHeight()) { + xmrWalletService.deleteWalletBackups(getWalletName()); + processModel.setDeleteBackupsHeight(0); // reset delete height + xmrWalletService.removeWalletListener(this); + } + } + }; + xmrWalletService.addWalletListener(deleteBackupsListener); + } + } + public void maybeClearSensitiveData() { String change = ""; if (removeAllChatMessages()) { diff --git a/core/src/main/java/haveno/core/trade/protocol/ProcessModel.java b/core/src/main/java/haveno/core/trade/protocol/ProcessModel.java index 2173da4666..412fd9d424 100644 --- a/core/src/main/java/haveno/core/trade/protocol/ProcessModel.java +++ b/core/src/main/java/haveno/core/trade/protocol/ProcessModel.java @@ -135,6 +135,9 @@ public class ProcessModel implements Model, PersistablePayload { @Getter @Setter private String multisigAddress; + @Getter + @Setter + private long deleteBackupsHeight; // We want to indicate the user the state of the message delivery of the // PaymentSentMessage. As well we do an automatic re-send in case it was not ACKed yet. @@ -179,7 +182,8 @@ public class ProcessModel implements Model, PersistablePayload { .setFundsNeededForTrade(fundsNeededForTrade) .setPaymentSentMessageState(paymentSentMessageStateProperty.get().name()) .setBuyerPayoutAmountFromMediation(buyerPayoutAmountFromMediation) - .setSellerPayoutAmountFromMediation(sellerPayoutAmountFromMediation); + .setSellerPayoutAmountFromMediation(sellerPayoutAmountFromMediation) + .setDeleteBackupsHeight(deleteBackupsHeight); Optional.ofNullable(maker).ifPresent(e -> builder.setMaker((protobuf.TradePeer) maker.toProtoMessage())); Optional.ofNullable(taker).ifPresent(e -> builder.setTaker((protobuf.TradePeer) taker.toProtoMessage())); Optional.ofNullable(arbitrator).ifPresent(e -> builder.setArbitrator((protobuf.TradePeer) arbitrator.toProtoMessage())); @@ -201,6 +205,7 @@ public class ProcessModel implements Model, PersistablePayload { processModel.setFundsNeededForTrade(proto.getFundsNeededForTrade()); processModel.setBuyerPayoutAmountFromMediation(proto.getBuyerPayoutAmountFromMediation()); processModel.setSellerPayoutAmountFromMediation(proto.getSellerPayoutAmountFromMediation()); + processModel.setDeleteBackupsHeight(proto.getDeleteBackupsHeight()); // nullable processModel.setPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getPayoutTxSignature())); diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 62c6e5d56d..228339107c 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -1547,6 +1547,7 @@ message ProcessModel { bytes mediated_payout_tx_signature = 17; // placeholder if mediation used in future int64 buyer_payout_amount_from_mediation = 18; int64 seller_payout_amount_from_mediation = 19; + int64 delete_backups_height = 31; } message TradePeer {