diff --git a/common/src/main/java/bisq/common/storage/FileUtil.java b/common/src/main/java/bisq/common/storage/FileUtil.java index 85862a06de6..66c572e7870 100644 --- a/common/src/main/java/bisq/common/storage/FileUtil.java +++ b/common/src/main/java/bisq/common/storage/FileUtil.java @@ -102,7 +102,9 @@ public static void deleteDirectory(File file) throws IOException { deleteDirectory(file, null, true); } - public static void deleteDirectory(File file, @Nullable File exclude, boolean ignoreLockedFiles) throws IOException { + public static void deleteDirectory(File file, + @Nullable File exclude, + boolean ignoreLockedFiles) throws IOException { boolean excludeFileFound = false; if (file.isDirectory()) { File[] files = file.listFiles(); @@ -156,7 +158,8 @@ private static boolean isFileLocked(File file) { return !file.canWrite(); } - public static void resourceToFile(String resourcePath, File destinationFile) throws ResourceNotFoundException, IOException { + public static void resourceToFile(String resourcePath, + File destinationFile) throws ResourceNotFoundException, IOException { try (InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath)) { if (inputStream == null) { throw new ResourceNotFoundException(resourcePath); @@ -182,6 +185,20 @@ public static void renameFile(File oldFile, File newFile) throws IOException { } } + public static void copyFile(File origin, File target) throws IOException { + if (!origin.exists()) { + return; + } + + try { + Files.copy(origin, target); + } catch (IOException e) { + log.error("Copy file failed", e); + throw new IOException("Failed to copy " + origin + " to " + target); + } + + } + public static void copyDirectory(File source, File destination) throws IOException { FileUtils.copyDirectory(source, destination); } diff --git a/core/src/main/java/bisq/core/btc/model/AddressEntry.java b/core/src/main/java/bisq/core/btc/model/AddressEntry.java index e37a84fa86b..26c2c50956e 100644 --- a/core/src/main/java/bisq/core/btc/model/AddressEntry.java +++ b/core/src/main/java/bisq/core/btc/model/AddressEntry.java @@ -186,10 +186,6 @@ public boolean isTrade() { return context == Context.MULTI_SIG || context == Context.TRADE_PAYOUT; } - public boolean isTradable() { - return isOpenOffer() || isTrade(); - } - public Coin getCoinLockedInMultiSig() { return Coin.valueOf(coinLockedInMultiSig); } @@ -197,9 +193,10 @@ public Coin getCoinLockedInMultiSig() { @Override public String toString() { return "AddressEntry{" + - "offerId='" + getOfferId() + '\'' + + "address=" + address + ", context=" + context + - ", address=" + getAddressString() + - '}'; + ", offerId='" + offerId + '\'' + + ", coinLockedInMultiSig=" + coinLockedInMultiSig + + "}"; } } diff --git a/core/src/main/java/bisq/core/btc/model/AddressEntryList.java b/core/src/main/java/bisq/core/btc/model/AddressEntryList.java index becb46dfbef..a3b2ae78568 100644 --- a/core/src/main/java/bisq/core/btc/model/AddressEntryList.java +++ b/core/src/main/java/bisq/core/btc/model/AddressEntryList.java @@ -17,38 +17,41 @@ package bisq.core.btc.model; +import bisq.common.config.Config; import bisq.common.proto.persistable.PersistedDataHost; import bisq.common.proto.persistable.UserThreadMappedPersistableEnvelope; import bisq.common.storage.Storage; import com.google.protobuf.Message; +import org.bitcoinj.core.Address; import org.bitcoinj.core.Transaction; import org.bitcoinj.crypto.DeterministicKey; import org.bitcoinj.wallet.Wallet; import com.google.inject.Inject; -import java.util.ArrayList; -import java.util.List; +import com.google.common.collect.ImmutableList; + +import java.util.HashSet; import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; import java.util.stream.Collectors; -import java.util.stream.Stream; -import lombok.Getter; import lombok.ToString; import lombok.extern.slf4j.Slf4j; /** - * The List supporting our persistence solution. + * The AddressEntries was previously stored as list, now as hashSet. We still keep the old name to reflect the + * associated protobuf message. */ @ToString @Slf4j public final class AddressEntryList implements UserThreadMappedPersistableEnvelope, PersistedDataHost { transient private Storage storage; transient private Wallet wallet; - @Getter - private List list; + private final Set entrySet = new CopyOnWriteArraySet<>(); @Inject public AddressEntryList(Storage storage) { @@ -58,8 +61,10 @@ public AddressEntryList(Storage storage) { @Override public void readPersisted() { AddressEntryList persisted = storage.initAndGetPersisted(this, 50); - if (persisted != null) - list = new ArrayList<>(persisted.getList()); + if (persisted != null) { + entrySet.clear(); + entrySet.addAll(persisted.entrySet); + } } @@ -67,22 +72,22 @@ public void readPersisted() { // PROTO BUFFER /////////////////////////////////////////////////////////////////////////////////////////// - private AddressEntryList(List list) { - this.list = list; + private AddressEntryList(Set entrySet) { + this.entrySet.addAll(entrySet); } public static AddressEntryList fromProto(protobuf.AddressEntryList proto) { - return new AddressEntryList(new ArrayList<>(proto.getAddressEntryList().stream().map(AddressEntry::fromProto).collect(Collectors.toList()))); + Set entrySet = proto.getAddressEntryList().stream() + .map(AddressEntry::fromProto) + .collect(Collectors.toSet()); + return new AddressEntryList(entrySet); } @Override public Message toProtoMessage() { - // We clone list as we got ConcurrentModificationExceptions - List clone = new ArrayList<>(list); - List addressEntries = clone.stream() + Set addressEntries = entrySet.stream() .map(AddressEntry::toProtoMessage) - .collect(Collectors.toList()); - + .collect(Collectors.toSet()); return protobuf.PersistableEnvelope.newBuilder() .setAddressEntryList(protobuf.AddressEntryList.newBuilder() .addAllAddressEntry(addressEntries)) @@ -97,29 +102,50 @@ public Message toProtoMessage() { public void onWalletReady(Wallet wallet) { this.wallet = wallet; - if (list != null) { - list.forEach(addressEntry -> { + if (!entrySet.isEmpty()) { + Set toBeRemoved = new HashSet<>(); + entrySet.forEach(addressEntry -> { DeterministicKey keyFromPubHash = (DeterministicKey) wallet.findKeyFromPubHash(addressEntry.getPubKeyHash()); if (keyFromPubHash != null) { - addressEntry.setDeterministicKey(keyFromPubHash); + Address addressFromKey = keyFromPubHash.toAddress(Config.baseCurrencyNetworkParameters()); + // We want to ensure key and address matches in case we have address in entry available already + if (addressEntry.getAddress() == null || addressFromKey.equals(addressEntry.getAddress())) { + addressEntry.setDeterministicKey(keyFromPubHash); + } else { + log.error("We found an address entry without key but cannot apply the key as the address " + + "is not matching. " + + "We remove that entry as it seems it is not compatible with our wallet. " + + "addressFromKey={}, addressEntry.getAddress()={}", + addressFromKey, addressEntry.getAddress()); + toBeRemoved.add(addressEntry); + } } else { - log.error("Key from addressEntry not found in that wallet " + addressEntry.toString()); + log.error("Key from addressEntry {} not found in that wallet. We remove that entry. " + + "This is expected at restore from seeds.", addressEntry.toString()); + toBeRemoved.add(addressEntry); } }); + + toBeRemoved.forEach(entrySet::remove); } else { - list = new ArrayList<>(); - add(new AddressEntry(wallet.freshReceiveKey(), AddressEntry.Context.ARBITRATOR)); - - // In case we restore from seed words and have balance we need to add the relevant addresses to our list. - // IssuedReceiveAddresses does not contain all addresses where we expect balance so we need to listen to - // incoming txs at blockchain sync to add the rest. - if (wallet.getBalance().isPositive()) { - wallet.getIssuedReceiveAddresses().forEach(address -> { - log.info("Create AddressEntry for IssuedReceiveAddress. address={}", address.toString()); - add(new AddressEntry((DeterministicKey) wallet.findKeyFromPubHash(address.getHash160()), AddressEntry.Context.AVAILABLE)); - }); - } - persist(); + // As long the old arbitration domain is not removed from the code base we still support it here. + entrySet.add(new AddressEntry(wallet.freshReceiveKey(), AddressEntry.Context.ARBITRATOR)); + } + + // In case we restore from seed words and have balance we need to add the relevant addresses to our list. + // IssuedReceiveAddresses does not contain all addresses where we expect balance so we need to listen to + // incoming txs at blockchain sync to add the rest. + if (wallet.getBalance().isPositive()) { + wallet.getIssuedReceiveAddresses().stream() + .filter(this::isAddressNotInEntries) + .forEach(address -> { + log.info("Create AddressEntry for IssuedReceiveAddress. address={}", address.toString()); + DeterministicKey key = (DeterministicKey) wallet.findKeyFromPubHash(address.getHash160()); + if (key != null) { + // Address will be derived from key in getAddress method + entrySet.add(new AddressEntry(key, AddressEntry.Context.AVAILABLE)); + } + }); } // We add those listeners to get notified about potential new transactions and @@ -127,62 +153,41 @@ public void onWalletReady(Wallet wallet) { // but can help as well in case the addressEntry list would miss an address where the wallet was received // funds (e.g. if the user sends funds to an address which has not been provided in the main UI - like from the // wallet details window). - wallet.addCoinsReceivedEventListener((w, tx, prevBalance, newBalance) -> { - updateList(tx); + wallet.addCoinsReceivedEventListener((wallet1, tx, prevBalance, newBalance) -> { + maybeAddNewAddressEntry(tx); }); - wallet.addCoinsSentEventListener((w, tx, prevBalance, newBalance) -> { - updateList(tx); + wallet.addCoinsSentEventListener((wallet1, tx, prevBalance, newBalance) -> { + maybeAddNewAddressEntry(tx); }); - } - - private void updateList(Transaction tx) { - tx.getOutputs().stream() - .filter(output -> output.isMine(wallet)) - .map(output -> output.getAddressFromP2PKHScript(wallet.getNetworkParameters())) - .filter(Objects::nonNull) - .filter(address -> !listContainsEntryWithAddress(address.toBase58())) - .map(address -> (DeterministicKey) wallet.findKeyFromPubHash(address.getHash160())) - .filter(Objects::nonNull) - .map(deterministicKey -> new AddressEntry(deterministicKey, AddressEntry.Context.AVAILABLE)) - .forEach(addressEntry -> list.add(addressEntry)); - } - private boolean listContainsEntryWithAddress(String addressString) { - return list.stream().anyMatch(addressEntry -> Objects.equals(addressEntry.getAddressString(), addressString)); + persist(); } - private boolean add(AddressEntry addressEntry) { - return list.add(addressEntry); + public ImmutableList getAddressEntriesAsListImmutable() { + return ImmutableList.copyOf(entrySet); } - private boolean remove(AddressEntry addressEntry) { - return list.remove(addressEntry); - } - - public AddressEntry addAddressEntry(AddressEntry addressEntry) { - boolean changed = add(addressEntry); - if (changed) + public void addAddressEntry(AddressEntry addressEntry) { + boolean setChangedByAdd = entrySet.add(addressEntry); + if (setChangedByAdd) persist(); - return addressEntry; - } - - public void swapTradeToSavings(String offerId) { - list.stream().filter(addressEntry -> offerId.equals(addressEntry.getOfferId())) - .findAny().ifPresent(this::swapToAvailable); } public void swapToAvailable(AddressEntry addressEntry) { - boolean changed1 = remove(addressEntry); - boolean changed2 = add(new AddressEntry(addressEntry.getKeyPair(), AddressEntry.Context.AVAILABLE)); - if (changed1 || changed2) + boolean setChangedByRemove = entrySet.remove(addressEntry); + boolean setChangedByAdd = entrySet.add(new AddressEntry(addressEntry.getKeyPair(), AddressEntry.Context.AVAILABLE)); + if (setChangedByRemove || setChangedByAdd) { persist(); + } } - public AddressEntry swapAvailableToAddressEntryWithOfferId(AddressEntry addressEntry, AddressEntry.Context context, String offerId) { - boolean changed1 = remove(addressEntry); + public AddressEntry swapAvailableToAddressEntryWithOfferId(AddressEntry addressEntry, + AddressEntry.Context context, + String offerId) { + boolean setChangedByRemove = entrySet.remove(addressEntry); final AddressEntry newAddressEntry = new AddressEntry(addressEntry.getKeyPair(), context, offerId); - boolean changed2 = add(newAddressEntry); - if (changed1 || changed2) + boolean setChangedByAdd = entrySet.add(newAddressEntry); + if (setChangedByRemove || setChangedByAdd) persist(); return newAddressEntry; @@ -192,7 +197,24 @@ public void persist() { storage.queueUpForSave(50); } - public Stream stream() { - return list.stream(); + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + private void maybeAddNewAddressEntry(Transaction tx) { + tx.getOutputs().stream() + .filter(output -> output.isMine(wallet)) + .map(output -> output.getAddressFromP2PKHScript(wallet.getNetworkParameters())) + .filter(Objects::nonNull) + .filter(this::isAddressNotInEntries) + .map(address -> (DeterministicKey) wallet.findKeyFromPubHash(address.getHash160())) + .filter(Objects::nonNull) + .map(deterministicKey -> new AddressEntry(deterministicKey, AddressEntry.Context.AVAILABLE)) + .forEach(this::addAddressEntry); + } + + private boolean isAddressNotInEntries(Address address) { + return entrySet.stream().noneMatch(e -> address.equals(e.getAddress())); } } diff --git a/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java b/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java index a2a94dcab9f..d5694484575 100644 --- a/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java +++ b/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java @@ -59,7 +59,6 @@ import javax.inject.Named; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.Service; import org.apache.commons.lang3.StringUtils; @@ -498,7 +497,7 @@ public boolean isDownloadComplete() { } public Set
getAddressesByContext(@SuppressWarnings("SameParameterValue") AddressEntry.Context context) { - return ImmutableList.copyOf(addressEntryList.getList()).stream() + return addressEntryList.getAddressEntriesAsListImmutable().stream() .filter(addressEntry -> addressEntry.getContext() == context) .map(AddressEntry::getAddress) .collect(Collectors.toSet()); diff --git a/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java b/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java index 3bb4721b17a..d31ece52946 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java @@ -47,7 +47,6 @@ import javax.inject.Inject; import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; @@ -108,8 +107,8 @@ public BtcWalletService(WalletsSetup walletsSetup, void decryptWallet(@NotNull KeyParameter key) { super.decryptWallet(key); - addressEntryList.stream().forEach(e -> { - final DeterministicKey keyPair = e.getKeyPair(); + addressEntryList.getAddressEntriesAsListImmutable().stream().forEach(e -> { + DeterministicKey keyPair = e.getKeyPair(); if (keyPair.isEncrypted()) e.setDeterministicKey(keyPair.decrypt(key)); }); @@ -119,8 +118,8 @@ void decryptWallet(@NotNull KeyParameter key) { @Override void encryptWallet(KeyCrypterScrypt keyCrypterScrypt, KeyParameter key) { super.encryptWallet(keyCrypterScrypt, key); - addressEntryList.stream().forEach(e -> { - final DeterministicKey keyPair = e.getKeyPair(); + addressEntryList.getAddressEntriesAsListImmutable().stream().forEach(e -> { + DeterministicKey keyPair = e.getKeyPair(); if (keyPair.isEncrypted()) e.setDeterministicKey(keyPair.encrypt(keyCrypterScrypt, key)); }); @@ -157,12 +156,18 @@ public Transaction completePreparedBurnBsqTx(Transaction preparedBurnFeeTx, byte // Proposal txs /////////////////////////////////////////////////////////////////////////////////////////// - public Transaction completePreparedReimbursementRequestTx(Coin issuanceAmount, Address issuanceAddress, Transaction feeTx, byte[] opReturnData) + public Transaction completePreparedReimbursementRequestTx(Coin issuanceAmount, + Address issuanceAddress, + Transaction feeTx, + byte[] opReturnData) throws TransactionVerificationException, WalletException, InsufficientMoneyException { return completePreparedProposalTx(feeTx, opReturnData, issuanceAmount, issuanceAddress); } - public Transaction completePreparedCompensationRequestTx(Coin issuanceAmount, Address issuanceAddress, Transaction feeTx, byte[] opReturnData) + public Transaction completePreparedCompensationRequestTx(Coin issuanceAmount, + Address issuanceAddress, + Transaction feeTx, + byte[] opReturnData) throws TransactionVerificationException, WalletException, InsufficientMoneyException { return completePreparedProposalTx(feeTx, opReturnData, issuanceAmount, issuanceAddress); } @@ -292,7 +297,8 @@ public Transaction completePreparedBlindVoteTx(Transaction preparedTx, byte[] op return completePreparedBsqTxWithBtcFee(preparedTx, opReturnData); } - private Transaction completePreparedBsqTxWithBtcFee(Transaction preparedTx, byte[] opReturnData) throws InsufficientMoneyException, TransactionVerificationException, WalletException { + private Transaction completePreparedBsqTxWithBtcFee(Transaction preparedTx, + byte[] opReturnData) throws InsufficientMoneyException, TransactionVerificationException, WalletException { // Remember index for first BTC input int indexOfBtcFirstInput = preparedTx.getInputs().size(); @@ -306,7 +312,8 @@ private Transaction completePreparedBsqTxWithBtcFee(Transaction preparedTx, byte return tx; } - private Transaction addInputsForMinerFee(Transaction preparedTx, byte[] opReturnData) throws InsufficientMoneyException { + private Transaction addInputsForMinerFee(Transaction preparedTx, + byte[] opReturnData) throws InsufficientMoneyException { // safety check counter to avoid endless loops int counter = 0; // estimated size of input sig @@ -421,7 +428,9 @@ public Transaction completePreparedSendBsqTx(Transaction preparedBsqTx, boolean return completePreparedBsqTx(preparedBsqTx, isSendTx, null); } - public Transaction completePreparedBsqTx(Transaction preparedBsqTx, boolean useCustomTxFee, @Nullable byte[] opReturnData) throws + public Transaction completePreparedBsqTx(Transaction preparedBsqTx, + boolean useCustomTxFee, + @Nullable byte[] opReturnData) throws TransactionVerificationException, WalletException, InsufficientMoneyException { // preparedBsqTx has following structure: @@ -545,7 +554,8 @@ public void commitTx(Transaction tx) { // AddressEntry /////////////////////////////////////////////////////////////////////////////////////////// - public Optional getAddressEntry(String offerId, @SuppressWarnings("SameParameterValue") AddressEntry.Context context) { + public Optional getAddressEntry(String offerId, + @SuppressWarnings("SameParameterValue") AddressEntry.Context context) { return getAddressEntryListAsImmutableList().stream() .filter(e -> offerId.equals(e.getOfferId())) .filter(e -> context == e.getContext()) @@ -592,17 +602,14 @@ public AddressEntry getFreshAddressEntry() { return getOrCreateAddressEntry(context, addressEntry); } - public AddressEntry getNewAddressEntry(String offerId, AddressEntry.Context context) { + public void getNewAddressEntry(String offerId, AddressEntry.Context context) { AddressEntry entry = new AddressEntry(wallet.freshReceiveKey(), context, offerId); addressEntryList.addAddressEntry(entry); - return entry; } - public AddressEntry recoverAddressEntry(String offerId, String address, AddressEntry.Context context) { - var available = findAddressEntry(address, AddressEntry.Context.AVAILABLE); - if (!available.isPresent()) - return null; - return addressEntryList.swapAvailableToAddressEntryWithOfferId(available.get(), context, offerId); + public void recoverAddressEntry(String offerId, String address, AddressEntry.Context context) { + findAddressEntry(address, AddressEntry.Context.AVAILABLE).ifPresent(addressEntry -> + addressEntryList.swapAvailableToAddressEntryWithOfferId(addressEntry, context, offerId)); } private AddressEntry getOrCreateAddressEntry(AddressEntry.Context context, Optional addressEntry) { @@ -655,20 +662,18 @@ public List getFundedAvailableAddressEntries() { } public List getAddressEntryListAsImmutableList() { - return ImmutableList.copyOf(addressEntryList.getList()); + return addressEntryList.getAddressEntriesAsListImmutable(); } public void swapTradeEntryToAvailableEntry(String offerId, AddressEntry.Context context) { - Optional addressEntryOptional = getAddressEntryListAsImmutableList().stream() + getAddressEntryListAsImmutableList().stream() .filter(e -> offerId.equals(e.getOfferId())) .filter(e -> context == e.getContext()) - .findAny(); - addressEntryOptional.ifPresent(e -> { - log.info("swap addressEntry with address {} and offerId {} from context {} to available", - e.getAddressString(), e.getOfferId(), context); - addressEntryList.swapToAvailable(e); - saveAddressEntryList(); - }); + .forEach(e -> { + log.info("swap addressEntry with address {} and offerId {} from context {} to available", + e.getAddressString(), e.getOfferId(), context); + addressEntryList.swapToAvailable(e); + }); } public void resetAddressEntriesForOpenOffer(String offerId) { diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 78859db8fe0..753d8bc3394 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -3036,6 +3036,8 @@ Ideally you should specify the date your wallet seed was created.\n\n\n\ Are you sure you want to go ahead without specifying a wallet date? seed.restore.success=Wallets restored successfully with the new seed words.\n\nYou need to shut down and restart the application. seed.restore.error=An error occurred when restoring the wallets with seed words.{0} +seed.restore.openOffers.warn=You have open offers which will be removed if you restore from seed words.\n\ + Are you sure that you want to continue? #################################################################### diff --git a/desktop/src/main/java/bisq/desktop/main/SharedPresentation.java b/desktop/src/main/java/bisq/desktop/main/SharedPresentation.java new file mode 100644 index 00000000000..913529779d2 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/SharedPresentation.java @@ -0,0 +1,87 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.desktop.main; + +import bisq.desktop.app.BisqApp; +import bisq.desktop.main.overlays.popups.Popup; + +import bisq.core.btc.wallet.WalletsManager; +import bisq.core.locale.Res; +import bisq.core.offer.OpenOfferManager; + +import bisq.common.UserThread; +import bisq.common.storage.FileUtil; + +import org.bitcoinj.wallet.DeterministicSeed; + +import java.io.File; + +import java.util.concurrent.TimeUnit; + +import lombok.extern.slf4j.Slf4j; + +/** + * This serves as shared space for static methods used from different views where no common parent view would fit as + * owner of that code. We keep it strictly static. It should replace GUIUtil for those methods which are not utility + * methods. + */ +@Slf4j +public class SharedPresentation { + public static void restoreSeedWords(WalletsManager walletsManager, + OpenOfferManager openOfferManager, + DeterministicSeed seed, + File storageDir) { + if (!openOfferManager.getObservableList().isEmpty()) { + UserThread.runAfter(() -> + new Popup().warning(Res.get("seed.restore.openOffers.warn")) + .actionButtonText(Res.get("shared.yes")) + .onAction(() -> { + openOfferManager.removeAllOpenOffers(() -> { + doRestoreSeedWords(walletsManager, seed, storageDir); + }); + }) + .show(), 100, TimeUnit.MILLISECONDS); + } else { + doRestoreSeedWords(walletsManager, seed, storageDir); + } + } + + private static void doRestoreSeedWords(WalletsManager walletsManager, + DeterministicSeed seed, + File storageDir) { + try { + File backup = new File(storageDir, "AddressEntryList_backup_pre_wallet_restore_" + System.currentTimeMillis()); + FileUtil.copyFile(new File(storageDir, "AddressEntryList"), backup); + } catch (Throwable t) { + new Popup().error(Res.get("error.deleteAddressEntryListFailed", t)).show(); + } + + walletsManager.restoreSeedWords( + seed, + () -> UserThread.execute(() -> { + log.info("Wallets restored with seed words"); + new Popup().feedback(Res.get("seed.restore.success")).hideCloseButton().show(); + BisqApp.getShutDownHandler().run(); + }), + throwable -> UserThread.execute(() -> { + log.error(throwable.toString()); + new Popup().error(Res.get("seed.restore.error", Res.get("shared.errorMessageInline", throwable))) + .show(); + })); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/seedwords/SeedWordsView.java b/desktop/src/main/java/bisq/desktop/main/account/content/seedwords/SeedWordsView.java index a299b5434b3..ea3186a003e 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/seedwords/SeedWordsView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/seedwords/SeedWordsView.java @@ -19,14 +19,15 @@ import bisq.desktop.common.view.ActivatableView; import bisq.desktop.common.view.FxmlView; +import bisq.desktop.main.SharedPresentation; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.overlays.windows.WalletPasswordWindow; -import bisq.desktop.util.GUIUtil; import bisq.desktop.util.Layout; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.WalletsManager; import bisq.core.locale.Res; +import bisq.core.offer.OpenOfferManager; import bisq.core.user.DontShowAgainLookup; import bisq.common.config.Config; @@ -68,6 +69,7 @@ @FxmlView public class SeedWordsView extends ActivatableView { private final WalletsManager walletsManager; + private final OpenOfferManager openOfferManager; private final BtcWalletService btcWalletService; private final WalletPasswordWindow walletPasswordWindow; private final File storageDir; @@ -91,10 +93,12 @@ public class SeedWordsView extends ActivatableView { @Inject private SeedWordsView(WalletsManager walletsManager, + OpenOfferManager openOfferManager, BtcWalletService btcWalletService, WalletPasswordWindow walletPasswordWindow, @Named(Config.STORAGE_DIR) File storageDir) { this.walletsManager = walletsManager; + this.openOfferManager = openOfferManager; this.btcWalletService = btcWalletService; this.walletPasswordWindow = walletPasswordWindow; this.storageDir = storageDir; @@ -166,20 +170,18 @@ public void activate() { String key = "showBackupWarningAtSeedPhrase"; if (DontShowAgainLookup.showAgain(key)) { new Popup().warning(Res.get("account.seed.backup.warning")) - .onAction(() -> { - showSeedPhrase(); - }) - .actionButtonText(Res.get("shared.iUnderstand")) - .useIUnderstandButton() - .dontShowAgainId(key) - .hideCloseButton() - .show(); + .onAction(this::showSeedPhrase) + .actionButtonText(Res.get("shared.iUnderstand")) + .useIUnderstandButton() + .dontShowAgainId(key) + .hideCloseButton() + .show(); } else { showSeedPhrase(); } } - public void showSeedPhrase() { + private void showSeedPhrase() { DeterministicSeed keyChainSeed = btcWalletService.getKeyChainSeed(); // wallet creation date is not encrypted walletCreationDate = Instant.ofEpochSecond(walletsManager.getChainSeedCreationTimeSeconds()).atZone(ZoneId.systemDefault()).toLocalDate(); @@ -305,6 +307,6 @@ private void doRestore() { long date = localDateTime.toEpochSecond(ZoneOffset.UTC); DeterministicSeed seed = new DeterministicSeed(Splitter.on(" ").splitToList(seedWordsTextArea.getText()), null, "", date); - GUIUtil.restoreSeedWords(seed, walletsManager, storageDir); + SharedPresentation.restoreSeedWords(walletsManager, openOfferManager, seed, storageDir); } } diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/WalletPasswordWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/WalletPasswordWindow.java index b5ba8539ff7..640ee6fc62c 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/WalletPasswordWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/WalletPasswordWindow.java @@ -21,15 +21,16 @@ import bisq.desktop.components.AutoTooltipLabel; import bisq.desktop.components.BusyAnimation; import bisq.desktop.components.PasswordTextField; +import bisq.desktop.main.SharedPresentation; import bisq.desktop.main.overlays.Overlay; import bisq.desktop.main.overlays.popups.Popup; -import bisq.desktop.util.GUIUtil; import bisq.desktop.util.Layout; import bisq.desktop.util.Transitions; import bisq.core.btc.wallet.WalletsManager; import bisq.core.crypto.ScryptUtil; import bisq.core.locale.Res; +import bisq.core.offer.OpenOfferManager; import bisq.common.UserThread; import bisq.common.config.Config; @@ -88,6 +89,7 @@ @Slf4j public class WalletPasswordWindow extends Overlay { private final WalletsManager walletsManager; + private final OpenOfferManager openOfferManager; private File storageDir; private Button unlockButton; @@ -115,8 +117,10 @@ public interface AesKeyHandler { @Inject private WalletPasswordWindow(WalletsManager walletsManager, + OpenOfferManager openOfferManager, @Named(Config.STORAGE_DIR) File storageDir) { this.walletsManager = walletsManager; + this.openOfferManager = openOfferManager; this.storageDir = storageDir; type = Type.Attention; width = 900; @@ -277,7 +281,6 @@ private void showRestoreScreen() { gridPane.getChildren().add(headLine2Label); seedWordsTextArea = addTextArea(gridPane, ++rowIndex, Res.get("seed.enterSeedWords"), 5); - ; seedWordsTextArea.setPrefHeight(60); Tuple2 labelDatePickerTuple2 = addTopLabelDatePicker(gridPane, ++rowIndex, @@ -356,6 +359,6 @@ private void doRestore() { //TODO Is ZoneOffset correct? long date = value != null ? value.atStartOfDay().toEpochSecond(ZoneOffset.UTC) : 0; DeterministicSeed seed = new DeterministicSeed(Splitter.on(" ").splitToList(seedWordsTextArea.getText()), null, "", date); - GUIUtil.restoreSeedWords(seed, walletsManager, storageDir); + SharedPresentation.restoreSeedWords(walletsManager, openOfferManager, seed, storageDir); } } diff --git a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java index 5fabd2c94a9..0d26e80a96b 100644 --- a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java +++ b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java @@ -32,7 +32,6 @@ import bisq.core.account.witness.AccountAgeWitness; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.btc.setup.WalletsSetup; -import bisq.core.btc.wallet.WalletsManager; import bisq.core.locale.Country; import bisq.core.locale.CountryUtil; import bisq.core.locale.CurrencyUtil; @@ -63,7 +62,6 @@ import bisq.common.proto.persistable.PersistableList; import bisq.common.proto.persistable.PersistenceProtoResolver; import bisq.common.storage.CorruptedDatabaseFilesHandler; -import bisq.common.storage.FileUtil; import bisq.common.storage.Storage; import bisq.common.util.MathUtils; import bisq.common.util.Tuple2; @@ -75,7 +73,6 @@ import org.bitcoinj.core.TransactionConfidence; import org.bitcoinj.uri.BitcoinURI; import org.bitcoinj.utils.Fiat; -import org.bitcoinj.wallet.DeterministicSeed; import com.googlecode.jcsv.CSVStrategy; import com.googlecode.jcsv.writer.CSVEntryConverter; @@ -803,26 +800,6 @@ public static void reSyncSPVChain(Preferences preferences) { } } - public static void restoreSeedWords(DeterministicSeed seed, WalletsManager walletsManager, File storageDir) { - try { - FileUtil.renameFile(new File(storageDir, "AddressEntryList"), new File(storageDir, "AddressEntryList_wallet_restore_" + System.currentTimeMillis())); - } catch (Throwable t) { - new Popup().error(Res.get("error.deleteAddressEntryListFailed", t)).show(); - } - walletsManager.restoreSeedWords( - seed, - () -> UserThread.execute(() -> { - log.info("Wallets restored with seed words"); - new Popup().feedback(Res.get("seed.restore.success")).hideCloseButton().show(); - BisqApp.getShutDownHandler().run(); - }), - throwable -> UserThread.execute(() -> { - log.error(throwable.toString()); - new Popup().error(Res.get("seed.restore.error", Res.get("shared.errorMessageInline", throwable))) - .show(); - })); - } - public static void showSelectableTextModal(String title, String text) { TextArea textArea = new BisqTextArea(); textArea.setText(text);