diff --git a/common/src/main/java/bisq/common/crypto/KeyStorage.java b/common/src/main/java/bisq/common/crypto/KeyStorage.java index aaa66af5c12..c132ab0042f 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.storage.FileUtil; +import bisq.common.file.FileUtil; import com.google.inject.Inject; diff --git a/common/src/main/java/bisq/common/storage/CorruptedDatabaseFilesHandler.java b/common/src/main/java/bisq/common/file/CorruptedStorageFileHandler.java similarity index 50% rename from common/src/main/java/bisq/common/storage/CorruptedDatabaseFilesHandler.java rename to common/src/main/java/bisq/common/file/CorruptedStorageFileHandler.java index 3451f19d642..1a1c7d623ee 100644 --- a/common/src/main/java/bisq/common/storage/CorruptedDatabaseFilesHandler.java +++ b/common/src/main/java/bisq/common/file/CorruptedStorageFileHandler.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.common.storage; +package bisq.common.file; import javax.inject.Inject; import javax.inject.Singleton; @@ -28,29 +28,29 @@ @Slf4j @Singleton -public class CorruptedDatabaseFilesHandler { - private final List corruptedDatabaseFiles = new ArrayList<>(); +public class CorruptedStorageFileHandler { + private final List files = new ArrayList<>(); @Inject - public CorruptedDatabaseFilesHandler() { + public CorruptedStorageFileHandler() { } - public void onFileCorrupted(String fileName) { - corruptedDatabaseFiles.add(fileName); + public void addFile(String fileName) { + files.add(fileName); } - public Optional> getCorruptedDatabaseFiles() { - if (!corruptedDatabaseFiles.isEmpty()) { - if (corruptedDatabaseFiles.size() == 1 && corruptedDatabaseFiles.get(0).equals("ViewPathAsString")) { - log.debug("We detected incompatible data base file for Navigation. " + - "That is a minor issue happening with refactoring of UI classes " + - "and we don't display a warning popup to the user."); - return Optional.empty(); - } else { - return Optional.of(corruptedDatabaseFiles); - } - } else { + public Optional> getFiles() { + if (files.isEmpty()) { return Optional.empty(); } + + if (files.size() == 1 && files.get(0).equals("ViewPathAsString")) { + log.debug("We detected incompatible data base file for Navigation. " + + "That is a minor issue happening with refactoring of UI classes " + + "and we don't display a warning popup to the user."); + return Optional.empty(); + } + + return Optional.of(files); } } diff --git a/common/src/main/java/bisq/common/storage/FileUtil.java b/common/src/main/java/bisq/common/file/FileUtil.java similarity index 91% rename from common/src/main/java/bisq/common/storage/FileUtil.java rename to common/src/main/java/bisq/common/file/FileUtil.java index cf6eeb6c3be..f69c2df88d3 100644 --- a/common/src/main/java/bisq/common/storage/FileUtil.java +++ b/common/src/main/java/bisq/common/file/FileUtil.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.common.storage; +package bisq.common.file; import bisq.common.util.Utilities; @@ -37,14 +37,12 @@ import java.util.Date; import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; +@Slf4j public class FileUtil { - private static final Logger log = LoggerFactory.getLogger(FileUtil.class); - public static void rollingBackup(File dir, String fileName, int numMaxBackupFiles) { if (dir.exists()) { File backupDir = new File(Paths.get(dir.getAbsolutePath(), "backup").toString()); @@ -203,11 +201,24 @@ public static void copyDirectory(File source, File destination) throws IOExcepti FileUtils.copyDirectory(source, destination); } - static File createNewFile(Path path) throws IOException { + public static File createNewFile(Path path) throws IOException { File file = path.toFile(); if (!file.createNewFile()) { throw new IOException("There already exists a file with path: " + path); } return file; } + + public static void removeAndBackupFile(File dbDir, File storageFile, String fileName, String backupFolderName) + throws IOException { + File corruptedBackupDir = new File(Paths.get(dbDir.getAbsolutePath(), backupFolderName).toString()); + if (!corruptedBackupDir.exists() && !corruptedBackupDir.mkdir()) { + log.warn("make dir failed"); + } + + File corruptedFile = new File(Paths.get(dbDir.getAbsolutePath(), backupFolderName, fileName).toString()); + if (storageFile.exists()) { + renameFile(storageFile, corruptedFile); + } + } } diff --git a/common/src/main/java/bisq/common/storage/JsonFileManager.java b/common/src/main/java/bisq/common/file/JsonFileManager.java similarity index 86% rename from common/src/main/java/bisq/common/storage/JsonFileManager.java rename to common/src/main/java/bisq/common/file/JsonFileManager.java index 19db89545f2..9cbc91f7b29 100644 --- a/common/src/main/java/bisq/common/storage/JsonFileManager.java +++ b/common/src/main/java/bisq/common/file/JsonFileManager.java @@ -15,9 +15,8 @@ * along with Bisq. If not, see . */ -package bisq.common.storage; +package bisq.common.file; -import bisq.common.UserThread; import bisq.common.util.Utilities; import java.nio.file.Paths; @@ -26,13 +25,12 @@ import java.io.PrintWriter; import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; @Slf4j public class JsonFileManager { - private final ThreadPoolExecutor executor = Utilities.getThreadPoolExecutor("saveToDiscExecutor", 5, 50, 60); + private final ThreadPoolExecutor executor = Utilities.getThreadPoolExecutor("JsonFileManagerExecutor", 5, 50, 60); private final File dir; @@ -47,18 +45,12 @@ public JsonFileManager(File dir) { if (!dir.mkdir()) log.warn("make dir failed"); - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - UserThread.execute(JsonFileManager.this::shutDown); - }, "WriteOnlyFileManager.ShutDownHook")); + Runtime.getRuntime().addShutdownHook(new Thread(JsonFileManager.this::shutDown, + "JsonFileManager.ShutDownHook")); } public void shutDown() { executor.shutdown(); - try { - executor.awaitTermination(5, TimeUnit.SECONDS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } } public void writeToDisc(String json, String fileName) { diff --git a/common/src/main/java/bisq/common/storage/ResourceNotFoundException.java b/common/src/main/java/bisq/common/file/ResourceNotFoundException.java similarity index 96% rename from common/src/main/java/bisq/common/storage/ResourceNotFoundException.java rename to common/src/main/java/bisq/common/file/ResourceNotFoundException.java index e031f5ca9fe..fbe7797ff98 100644 --- a/common/src/main/java/bisq/common/storage/ResourceNotFoundException.java +++ b/common/src/main/java/bisq/common/file/ResourceNotFoundException.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.common.storage; +package bisq.common.file; public class ResourceNotFoundException extends Exception { public ResourceNotFoundException(String path) { diff --git a/common/src/main/java/bisq/common/persistence/PersistenceManager.java b/common/src/main/java/bisq/common/persistence/PersistenceManager.java new file mode 100644 index 00000000000..ec8d1ee005f --- /dev/null +++ b/common/src/main/java/bisq/common/persistence/PersistenceManager.java @@ -0,0 +1,377 @@ +/* + * 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.common.persistence; + +import bisq.common.Timer; +import bisq.common.UserThread; +import bisq.common.app.DevEnv; +import bisq.common.config.Config; +import bisq.common.file.CorruptedStorageFileHandler; +import bisq.common.file.FileUtil; +import bisq.common.handlers.ResultHandler; +import bisq.common.proto.persistable.PersistableEnvelope; +import bisq.common.proto.persistable.PersistenceProtoResolver; +import bisq.common.util.Utilities; + +import com.google.inject.Inject; + +import javax.inject.Named; + +import java.nio.file.Path; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; + +import static bisq.common.util.Preconditions.checkDir; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Responsible for reading persisted data and writing it on disk. We read usually only at start-up and keep data in RAM. + * We write all data which got a request for persistence at shut down at the very last moment when all other services + * are shut down, so allowing changes to the data in the very last moment. For critical data we set {@link Source} + * to HIGH which causes a timer to trigger a write to disk after 1 minute. We use that for not very frequently altered + * data and data which cannot be recovered from the network. + * + * We decided to not use threading (as it was in previous versions) as the read operation happens only at start-up and + * with the modified model that data is written at shut down we eliminate frequent and expensive disk I/O. Risks of + * deadlock or data inconsistency and a more complex model have been a further argument for that model. In fact + * previously we wasted a lot of resources as way too many threads have been created without doing actual work as well + * the write operations got triggered way too often specially for the very frequent changes at SequenceNumberMap and + * the very large DaoState (at dao blockchain sync that slowed down sync). + * + * + * @param The type of the {@link PersistableEnvelope} to be written or read from disk + */ +@Slf4j +public class PersistenceManager { + + /////////////////////////////////////////////////////////////////////////////////////////// + // Static + /////////////////////////////////////////////////////////////////////////////////////////// + + public static final Map> ALL_PERSISTENCE_MANAGERS = new HashMap<>(); + + // We don't know from which thread we are called so we map back to user thread + public static void flushAllDataToDisk(ResultHandler completeHandler) { + log.info("Start flushAllDataToDisk at shutdown"); + AtomicInteger openInstances = new AtomicInteger(ALL_PERSISTENCE_MANAGERS.size()); + + if (openInstances.get() == 0) { + log.info("flushAllDataToDisk completed"); + UserThread.execute(completeHandler::handleResult); + } + + new HashSet<>(ALL_PERSISTENCE_MANAGERS.values()).forEach(persistenceManager -> { + // For Priority.HIGH data we want to write to disk in any case to be on the safe side if we might have missed + // a requestPersistence call after an important state update. Those are usually rather small data stores. + // Otherwise we only persist if requestPersistence was called since the last persist call. + if (persistenceManager.source.flushAtShutDown || persistenceManager.persistenceRequested) { + // We don't know from which thread we are called so we map back to user thread when calling persistNow + UserThread.execute(() -> { + // We always get our completeHandler called even if exceptions happen. In case a file write fails + // we still call our shutdown and count down routine as the completeHandler is triggered in any case. + persistenceManager.persistNow(() -> + onWriteCompleted(completeHandler, openInstances, persistenceManager)); + }); + } else { + onWriteCompleted(completeHandler, openInstances, persistenceManager); + } + }); + } + + private static void onWriteCompleted(ResultHandler completeHandler, + AtomicInteger openInstances, + PersistenceManager persistenceManager) { + persistenceManager.shutdown(); + if (openInstances.decrementAndGet() == 0) { + log.info("flushAllDataToDisk completed"); + UserThread.execute(completeHandler::handleResult); + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Enum + /////////////////////////////////////////////////////////////////////////////////////////// + + public enum Source { + // For data stores we received from the network and which could be rebuilt. We store only for avoiding too much network traffic. + NETWORK(1, TimeUnit.HOURS.toSeconds(1), false), + + // For data stores which are created from private local data. This data could only be rebuilt from backup files. + PRIVATE(10, TimeUnit.SECONDS.toSeconds(30), true), + + // For data stores which are created from private local data. Loss of that data would not have any critical consequences. + PRIVATE_LOW_PRIO(4, TimeUnit.HOURS.toSeconds(2), false); + + + @Getter + private final int numMaxBackupFiles; + @Getter + private final long delayInSec; + @Getter + private final boolean flushAtShutDown; + + Source(int numMaxBackupFiles, long delayInSec, boolean flushAtShutDown) { + this.numMaxBackupFiles = numMaxBackupFiles; + this.delayInSec = delayInSec; + this.flushAtShutDown = flushAtShutDown; + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Class fields + /////////////////////////////////////////////////////////////////////////////////////////// + + private final File dir; + private final PersistenceProtoResolver persistenceProtoResolver; + private final CorruptedStorageFileHandler corruptedStorageFileHandler; + private File storageFile; + private T persistable; + private String fileName; + private Source source = Source.PRIVATE_LOW_PRIO; + private Path usedTempFilePath; + private volatile boolean persistenceRequested; + @Nullable + private Timer timer; + private ExecutorService writeToDiskExecutor; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + public PersistenceManager(@Named(Config.STORAGE_DIR) File dir, + PersistenceProtoResolver persistenceProtoResolver, + CorruptedStorageFileHandler corruptedStorageFileHandler) { + this.dir = checkDir(dir); + this.persistenceProtoResolver = persistenceProtoResolver; + this.corruptedStorageFileHandler = corruptedStorageFileHandler; + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public void initialize(T persistable, Source source) { + this.initialize(persistable, persistable.getDefaultStorageFileName(), source); + } + + public void initialize(T persistable, String fileName, Source source) { + this.persistable = persistable; + this.fileName = fileName; + this.source = source; + storageFile = new File(dir, fileName); + ALL_PERSISTENCE_MANAGERS.put(fileName, this); + } + + public void shutdown() { + ALL_PERSISTENCE_MANAGERS.remove(fileName); + + if (timer != null) { + timer.stop(); + } + + if (writeToDiskExecutor != null) { + writeToDiskExecutor.shutdown(); + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Reading file + /////////////////////////////////////////////////////////////////////////////////////////// + + @Nullable + public T getPersisted() { + return getPersisted(checkNotNull(fileName)); + } + + //TODO use threading here instead in the clients + // We get called at startup either by readAllPersisted or readFromResources. Both are wrapped in a thread so we + // are not on the user thread. + @Nullable + public T getPersisted(String fileName) { + File storageFile = new File(dir, fileName); + if (!storageFile.exists()) { + return null; + } + + long ts = System.currentTimeMillis(); + try (FileInputStream fileInputStream = new FileInputStream(storageFile)) { + protobuf.PersistableEnvelope proto = protobuf.PersistableEnvelope.parseDelimitedFrom(fileInputStream); + //noinspection unchecked + T persistableEnvelope = (T) persistenceProtoResolver.fromProto(proto); + log.info("Reading {} completed in {} ms", fileName, System.currentTimeMillis() - ts); + return persistableEnvelope; + } catch (Throwable t) { + log.error("Reading {} failed with {}.", fileName, t.getMessage()); + try { + // We keep a backup which might be used for recovery + FileUtil.removeAndBackupFile(dir, storageFile, fileName, "backup_of_corrupted_data"); + DevEnv.logErrorAndThrowIfDevMode(t.toString()); + } catch (IOException e1) { + e1.printStackTrace(); + log.error(e1.getMessage()); + // We swallow Exception if backup fails + } + if (corruptedStorageFileHandler != null) { + corruptedStorageFileHandler.addFile(storageFile.getName()); + } + } + return null; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Write file to disk + /////////////////////////////////////////////////////////////////////////////////////////// + + public void requestPersistence() { + persistenceRequested = true; + + // We write to disk with a delay to avoid frequent write operations. Depending on the priority those delays + // can be rather long. + if (timer == null) { + timer = UserThread.runAfter(() -> { + persistNow(null); + UserThread.execute(() -> timer = null); + }, source.delayInSec, TimeUnit.SECONDS); + } + } + + public void persistNow(@Nullable Runnable completeHandler) { + long ts = System.currentTimeMillis(); + try { + // The serialisation is done on the user thread to avoid threading issue with potential mutations of the + // persistable object. Keeping it on the user thread we are in a synchronize model. + protobuf.PersistableEnvelope serialized = (protobuf.PersistableEnvelope) persistable.toPersistableMessage(); + + // For the write to disk task we use a thread. We do not have any issues anymore if the persistable objects + // gets mutated while the thread is running as we have serialized it already and do not operate on the + // reference to the persistable object. + getWriteToDiskExecutor().execute(() -> writeToDisk(serialized, completeHandler)); + + log.info("Serializing {} took {} msec", fileName, System.currentTimeMillis() - ts); + } catch (Throwable e) { + log.error("Error in saveToFile toProtoMessage: {}, {}", persistable.getClass().getSimpleName(), fileName); + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + public void writeToDisk(protobuf.PersistableEnvelope serialized, @Nullable Runnable completeHandler) { + long ts = System.currentTimeMillis(); + File tempFile = null; + FileOutputStream fileOutputStream = null; + + try { + // Before we write we backup existing file + FileUtil.rollingBackup(dir, fileName, source.getNumMaxBackupFiles()); + + if (!dir.exists() && !dir.mkdir()) + log.warn("make dir failed {}", fileName); + + tempFile = usedTempFilePath != null + ? FileUtil.createNewFile(usedTempFilePath) + : File.createTempFile("temp", null, dir); + // Don't use a new temp file path each time, as that causes the delete-on-exit hook to leak memory: + tempFile.deleteOnExit(); + + fileOutputStream = new FileOutputStream(tempFile); + + serialized.writeDelimitedTo(fileOutputStream); + + // Attempt to force the bits to hit the disk. In reality the OS or hard disk itself may still decide + // to not write through to physical media for at least a few seconds, but this is the best we can do. + fileOutputStream.flush(); + fileOutputStream.getFD().sync(); + + // Close resources before replacing file with temp file because otherwise it causes problems on windows + // when rename temp file + fileOutputStream.close(); + + FileUtil.renameFile(tempFile, storageFile); + usedTempFilePath = tempFile.toPath(); + } catch (Throwable t) { + // If an error occurred, don't attempt to reuse this path again, in case temp file cleanup fails. + usedTempFilePath = null; + log.error("Error at saveToFile, storageFile={}", fileName, t); + } finally { + if (tempFile != null && tempFile.exists()) { + log.warn("Temp file still exists after failed save. We will delete it now. storageFile={}", fileName); + if (!tempFile.delete()) { + log.error("Cannot delete temp file."); + } + } + + try { + if (fileOutputStream != null) { + fileOutputStream.close(); + } + } catch (IOException e) { + // We swallow that + e.printStackTrace(); + log.error("Cannot close resources." + e.getMessage()); + } + log.info("Writing the serialized {} completed in {} msec", fileName, System.currentTimeMillis() - ts); + persistenceRequested = false; + if (completeHandler != null) { + UserThread.execute(completeHandler); + } + } + } + + private ExecutorService getWriteToDiskExecutor() { + if (writeToDiskExecutor == null) { + String name = "Write-" + fileName + "_to-disk"; + writeToDiskExecutor = Utilities.getSingleThreadExecutor(name); + } + return writeToDiskExecutor; + } + + + @Override + public String toString() { + return "PersistenceManager{" + + "\n fileName='" + fileName + '\'' + + ",\n dir=" + dir + + ",\n storageFile=" + storageFile + + ",\n persistable=" + persistable + + ",\n priority=" + source + + ",\n usedTempFilePath=" + usedTempFilePath + + ",\n persistenceRequested=" + persistenceRequested + + "\n}"; + } +} diff --git a/common/src/main/java/bisq/common/proto/persistable/PersistableEnvelope.java b/common/src/main/java/bisq/common/proto/persistable/PersistableEnvelope.java index 6700839eaf6..8087d0df77e 100644 --- a/common/src/main/java/bisq/common/proto/persistable/PersistableEnvelope.java +++ b/common/src/main/java/bisq/common/proto/persistable/PersistableEnvelope.java @@ -29,4 +29,8 @@ public interface PersistableEnvelope extends Envelope { default Message toPersistableMessage() { return toProtoMessage(); } + + default String getDefaultStorageFileName() { + return this.getClass().getSimpleName(); + } } diff --git a/common/src/main/java/bisq/common/proto/persistable/PersistableList.java b/common/src/main/java/bisq/common/proto/persistable/PersistableList.java index f184a32c296..9cdfc1348c4 100644 --- a/common/src/main/java/bisq/common/proto/persistable/PersistableList.java +++ b/common/src/main/java/bisq/common/proto/persistable/PersistableList.java @@ -18,42 +18,67 @@ package bisq.common.proto.persistable; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.function.Consumer; import java.util.stream.Stream; -import lombok.EqualsAndHashCode; import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Delegate; -@EqualsAndHashCode -public abstract class PersistableList implements PersistableEnvelope, Iterable { - @Delegate(excludes = ExcludesDelegateMethods.class) +public abstract class PersistableList implements PersistableEnvelope { + @Getter - @Setter - private List list; + public final List list = createList(); + + protected List createList() { + return new ArrayList<>(); + } public PersistableList() { - list = new ArrayList<>(); } - public PersistableList(List list) { - this.list = list; + protected PersistableList(Collection collection) { + setAll(collection); + } + + public void setAll(Collection collection) { + this.list.clear(); + this.list.addAll(collection); + } + + public boolean add(T item) { + if (!list.contains(item)) { + list.add(item); + return true; + } + return false; + } + + public boolean remove(T item) { + return list.remove(item); } - // this.stream() does not compile for unknown reasons, so add that manual delegate method public Stream stream() { return list.stream(); } - private interface ExcludesDelegateMethods { - Stream stream(); + public int size() { + return list.size(); + } + + public boolean contains(T item) { + return list.contains(item); + } + + public boolean isEmpty() { + return list.isEmpty(); + } + + public void forEach(Consumer action) { + list.forEach(action); } - @Override - public String toString() { - return "PersistableList{" + - "\n list=" + list + - "\n}"; + public void clear() { + list.clear(); } } diff --git a/common/src/main/java/bisq/common/proto/persistable/PersistableListAsObservable.java b/common/src/main/java/bisq/common/proto/persistable/PersistableListAsObservable.java new file mode 100644 index 00000000000..782e44b9e82 --- /dev/null +++ b/common/src/main/java/bisq/common/proto/persistable/PersistableListAsObservable.java @@ -0,0 +1,51 @@ +/* + * 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.common.proto.persistable; + +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; + +import java.util.Collection; +import java.util.List; + +public abstract class PersistableListAsObservable extends PersistableList { + + public PersistableListAsObservable() { + } + + protected PersistableListAsObservable(Collection collection) { + super(collection); + } + + protected List createList() { + return FXCollections.observableArrayList(); + } + + public ObservableList getObservableList() { + return (ObservableList) getList(); + } + + public void addListener(ListChangeListener listener) { + ((ObservableList) getList()).addListener(listener); + } + + public void removeListener(ListChangeListener listener) { + ((ObservableList) getList()).removeListener(listener); + } +} diff --git a/common/src/main/java/bisq/common/proto/persistable/ThreadedPersistableEnvelope.java b/common/src/main/java/bisq/common/proto/persistable/ThreadedPersistableEnvelope.java deleted file mode 100644 index 7cee389f07e..00000000000 --- a/common/src/main/java/bisq/common/proto/persistable/ThreadedPersistableEnvelope.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.common.proto.persistable; - -import com.google.protobuf.Message; - -/** - * Interface for the outer envelope object persisted to disk, where its serialization - * during persistence takes place on a separate thread (for performance). - *

- * To make the serialization thread-safe, all modifications of the object must be - * synchronized with it. This may be achieved by wrapping such modifications with the - * provided {@link ThreadedPersistableEnvelope#modifySynchronized(Runnable)} method. - */ -public interface ThreadedPersistableEnvelope extends PersistableEnvelope { - - @Override - default Message toPersistableMessage() { - synchronized (this) { - return toProtoMessage(); - } - } - - default void modifySynchronized(Runnable modifyTask) { - synchronized (this) { - modifyTask.run(); - } - } -} diff --git a/common/src/main/java/bisq/common/proto/persistable/UserThreadMappedPersistableEnvelope.java b/common/src/main/java/bisq/common/proto/persistable/UserThreadMappedPersistableEnvelope.java deleted file mode 100644 index 1b3a91a8ff6..00000000000 --- a/common/src/main/java/bisq/common/proto/persistable/UserThreadMappedPersistableEnvelope.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.common.proto.persistable; - -import bisq.common.UserThread; - -import com.google.protobuf.Message; - -import com.google.common.util.concurrent.Futures; - -import java.util.concurrent.FutureTask; - -/** - * Interface for the outer envelope object persisted to disk, where its serialization - * during persistence is forced to take place on the user thread. - *

- * To avoid jitter, this should be only be used for small, safely critical stores. Larger - * or frequently written stores should either implement {@link PersistableEnvelope} - * directly (where thread-safety isn't needed) or use {@link ThreadedPersistableEnvelope}. - */ -public interface UserThreadMappedPersistableEnvelope extends PersistableEnvelope { - - @Override - default Message toPersistableMessage() { - FutureTask toProtoOnUserThread = new FutureTask<>(this::toProtoMessage); - UserThread.execute(toProtoOnUserThread); - return Futures.getUnchecked(toProtoOnUserThread); - } -} diff --git a/common/src/main/java/bisq/common/proto/persistable/UserThreadMappedPersistableList.java b/common/src/main/java/bisq/common/proto/persistable/UserThreadMappedPersistableList.java deleted file mode 100644 index d9829910b5c..00000000000 --- a/common/src/main/java/bisq/common/proto/persistable/UserThreadMappedPersistableList.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.common.proto.persistable; - -import java.util.List; - -public abstract class UserThreadMappedPersistableList extends PersistableList - implements UserThreadMappedPersistableEnvelope { - - public UserThreadMappedPersistableList(List list) { - super(list); - } - - public UserThreadMappedPersistableList() { - } -} diff --git a/common/src/main/java/bisq/common/storage/FileManager.java b/common/src/main/java/bisq/common/storage/FileManager.java deleted file mode 100644 index fa9b00af89d..00000000000 --- a/common/src/main/java/bisq/common/storage/FileManager.java +++ /dev/null @@ -1,251 +0,0 @@ -/* - * 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.common.storage; - -import bisq.common.UserThread; -import bisq.common.proto.persistable.PersistableEnvelope; -import bisq.common.proto.persistable.PersistenceProtoResolver; -import bisq.common.util.Utilities; - -import java.nio.file.Path; -import java.nio.file.Paths; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.PrintWriter; - -import java.util.Random; -import java.util.concurrent.Callable; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -import lombok.extern.slf4j.Slf4j; - -@Slf4j -public class FileManager { - private final File dir; - private final File storageFile; - private final ScheduledThreadPoolExecutor executor; - private final long delay; - private final Callable saveFileTask; - private final AtomicReference nextWrite; - private final PersistenceProtoResolver persistenceProtoResolver; - private Path usedTempFilePath; - - /////////////////////////////////////////////////////////////////////////////////////////// - // Constructor - /////////////////////////////////////////////////////////////////////////////////////////// - - public FileManager(File dir, File storageFile, long delay, PersistenceProtoResolver persistenceProtoResolver) { - this.dir = dir; - this.storageFile = storageFile; - this.persistenceProtoResolver = persistenceProtoResolver; - this.nextWrite = new AtomicReference<>(null); - - executor = Utilities.getScheduledThreadPoolExecutor("FileManager", 1, 10, 5); - - // File must only be accessed from the auto-save executor from now on, to avoid simultaneous access. - this.delay = delay; - - saveFileTask = () -> { - try { - Thread.currentThread().setName("Save-file-task-" + new Random().nextInt(10000)); - - // Atomically take the next object to write and set the value to null so concurrent saveFileTask - // won't duplicate work. - T persistable = this.nextWrite.getAndSet(null); - - // If null, a concurrent saveFileTask already grabbed the data. Don't duplicate work. - if (persistable == null) - return null; - - long now = System.currentTimeMillis(); - saveToFile(persistable, dir, storageFile); - log.debug("Save {} completed in {} msec", storageFile, System.currentTimeMillis() - now); - } catch (Throwable e) { - log.error("Error during saveFileTask", e); - } - return null; - }; - Runtime.getRuntime().addShutdownHook(new Thread(() -> - UserThread.execute(FileManager.this::shutDown), "FileManager.ShutDownHook") - ); - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // API - /////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Queues up a save in the background. Useful for not very important wallet changes. - */ - void saveLater(T persistable) { - saveLater(persistable, delay); - } - - public void saveLater(T persistable, long delayInMilli) { - // Atomically set the value of the next write. This allows batching of multiple writes of the same data - // structure if there are multiple calls to saveLater within a given `delayInMillis`. - this.nextWrite.set(persistable); - - // Always schedule a write. It is possible that a previous saveLater was called with a larger `delayInMilli` - // and we want the lower delay to execute. The saveFileTask handles concurrent operations. - executor.schedule(saveFileTask, delayInMilli, TimeUnit.MILLISECONDS); - } - - @SuppressWarnings("unchecked") - public synchronized T read(File file) { - log.debug("Read from disc: {}", file.getName()); - - try (final FileInputStream fileInputStream = new FileInputStream(file)) { - protobuf.PersistableEnvelope persistable = protobuf.PersistableEnvelope.parseDelimitedFrom(fileInputStream); - return (T) persistenceProtoResolver.fromProto(persistable); - } catch (Throwable t) { - String errorMsg = "Exception at proto read: " + t.getMessage() + " file:" + file.getAbsolutePath(); - log.error(errorMsg, t); - //if(DevEnv.DEV_MODE) - throw new RuntimeException(errorMsg); - } - } - - synchronized void removeFile(String fileName) { - File file = new File(dir, fileName); - boolean result = file.delete(); - if (!result) - log.warn("Could not delete file: " + file.toString()); - - File backupDir = new File(Paths.get(dir.getAbsolutePath(), "backup").toString()); - if (backupDir.exists()) { - File backupFile = new File(Paths.get(dir.getAbsolutePath(), "backup", fileName).toString()); - if (backupFile.exists()) { - result = backupFile.delete(); - if (!result) - log.warn("Could not delete backupFile: " + file.toString()); - } - } - } - - - /** - * Shut down auto-saving. - */ - private void shutDown() { - executor.shutdown(); - try { - executor.awaitTermination(5, TimeUnit.SECONDS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - public static void removeAndBackupFile(File dbDir, File storageFile, String fileName, String backupFolderName) - throws IOException { - File corruptedBackupDir = new File(Paths.get(dbDir.getAbsolutePath(), backupFolderName).toString()); - if (!corruptedBackupDir.exists()) - if (!corruptedBackupDir.mkdir()) - log.warn("make dir failed"); - - File corruptedFile = new File(Paths.get(dbDir.getAbsolutePath(), backupFolderName, fileName).toString()); - if (storageFile.exists()) { - FileUtil.renameFile(storageFile, corruptedFile); - } - } - - synchronized void removeAndBackupFile(String fileName) throws IOException { - removeAndBackupFile(dir, storageFile, fileName, "backup_of_corrupted_data"); - } - - synchronized void backupFile(String fileName, int numMaxBackupFiles) { - FileUtil.rollingBackup(dir, fileName, numMaxBackupFiles); - } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Private - /////////////////////////////////////////////////////////////////////////////////////////// - - private synchronized void saveToFile(T persistable, File dir, File storageFile) { - File tempFile = null; - FileOutputStream fileOutputStream = null; - PrintWriter printWriter = null; - - try { - log.debug("Write to disc: {}", storageFile.getName()); - protobuf.PersistableEnvelope protoPersistable; - try { - protoPersistable = (protobuf.PersistableEnvelope) persistable.toPersistableMessage(); - if (protoPersistable.toByteArray().length == 0) - log.error("protoPersistable is empty. persistable=" + persistable.getClass().getSimpleName()); - } catch (Throwable e) { - log.error("Error in saveToFile toProtoMessage: {}, {}", persistable.getClass().getSimpleName(), storageFile); - e.printStackTrace(); - throw new RuntimeException(e); - } - - if (!dir.exists() && !dir.mkdir()) - log.warn("make dir failed"); - - tempFile = usedTempFilePath != null - ? FileUtil.createNewFile(usedTempFilePath) - : File.createTempFile("temp", null, dir); - // Don't use a new temp file path each time, as that causes the delete-on-exit hook to leak memory: - tempFile.deleteOnExit(); - - fileOutputStream = new FileOutputStream(tempFile); - - log.debug("Writing protobuffer class:{} to file:{}", persistable.getClass(), storageFile.getName()); - protoPersistable.writeDelimitedTo(fileOutputStream); - - // Attempt to force the bits to hit the disk. In reality the OS or hard disk itself may still decide - // to not write through to physical media for at least a few seconds, but this is the best we can do. - fileOutputStream.flush(); - fileOutputStream.getFD().sync(); - - // Close resources before replacing file with temp file because otherwise it causes problems on windows - // when rename temp file - fileOutputStream.close(); - - FileUtil.renameFile(tempFile, storageFile); - usedTempFilePath = tempFile.toPath(); - } catch (Throwable t) { - // If an error occurred, don't attempt to reuse this path again, in case temp file cleanup fails. - usedTempFilePath = null; - log.error("Error at saveToFile, storageFile=" + storageFile.toString(), t); - } finally { - if (tempFile != null && tempFile.exists()) { - log.warn("Temp file still exists after failed save. We will delete it now. storageFile=" + storageFile); - if (!tempFile.delete()) - log.error("Cannot delete temp file."); - } - - try { - if (fileOutputStream != null) - fileOutputStream.close(); - if (printWriter != null) - printWriter.close(); - } catch (IOException e) { - // We swallow that - e.printStackTrace(); - log.error("Cannot close resources." + e.getMessage()); - } - } - } -} diff --git a/common/src/main/java/bisq/common/storage/Storage.java b/common/src/main/java/bisq/common/storage/Storage.java deleted file mode 100644 index 3521d8c6844..00000000000 --- a/common/src/main/java/bisq/common/storage/Storage.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * 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.common.storage; - -import bisq.common.app.DevEnv; -import bisq.common.config.Config; -import bisq.common.proto.persistable.PersistableEnvelope; -import bisq.common.proto.persistable.PersistenceProtoResolver; - -import com.google.inject.Inject; - -import javax.inject.Named; - -import java.io.File; -import java.io.IOException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nullable; - -import static bisq.common.util.Preconditions.checkDir; -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * That class handles the storage of a particular object to disk using Protobuffer. - *

- * For every data object we write a separate file to minimize the risk of corrupted files in case of inconsistency from newer versions. - * In case of a corrupted file we backup the old file to a separate directory, so if it holds critical data it might be helpful for recovery. - *

- * We also backup at first read the file, so we have a valid file form the latest version in case a write operation corrupted the file. - *

- * The read operation is triggered just at object creation (startup) and is at the moment not executed on a background thread to avoid asynchronous behaviour. - * As the data are small and it is just one read access the performance penalty is small and might be even worse to create and setup a thread for it. - *

- * The write operation used a background thread and supports a delayed write to avoid too many repeated write operations. - */ -public class Storage { - private static final Logger log = LoggerFactory.getLogger(Storage.class); - - private final CorruptedDatabaseFilesHandler corruptedDatabaseFilesHandler; - - private final File dir; - private FileManager fileManager; - private File storageFile; - private T persistable; - private String fileName; - private int numMaxBackupFiles = 10; - private final PersistenceProtoResolver persistenceProtoResolver; - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Constructor - /////////////////////////////////////////////////////////////////////////////////////////// - - @Inject - public Storage(@Named(Config.STORAGE_DIR) File dir, - PersistenceProtoResolver persistenceProtoResolver, - CorruptedDatabaseFilesHandler corruptedDatabaseFilesHandler) { - this.dir = checkDir(dir); - this.persistenceProtoResolver = persistenceProtoResolver; - this.corruptedDatabaseFilesHandler = corruptedDatabaseFilesHandler; - } - - @Nullable - public T getPersisted(String fileName) { - return getPersisted(new File(dir, fileName)); - } - - @Nullable - public T initAndGetPersistedWithFileName(String fileName, long delay) { - this.fileName = fileName; - storageFile = new File(dir, fileName); - fileManager = new FileManager<>(dir, storageFile, delay, persistenceProtoResolver); - return getPersisted(storageFile); - } - - @Nullable - public T initAndGetPersisted(T persistable, long delay) { - return initAndGetPersisted(persistable, persistable.getClass().getSimpleName(), delay); - } - - @Nullable - public T initAndGetPersisted(T persistable, String fileName, long delay) { - this.persistable = persistable; - this.fileName = fileName; - storageFile = new File(dir, fileName); - fileManager = new FileManager<>(dir, storageFile, delay, persistenceProtoResolver); - return getPersisted(storageFile); - } - - public void queueUpForSave() { - queueUpForSave(persistable); - } - - public void queueUpForSave(long delayInMilli) { - queueUpForSave(persistable, delayInMilli); - } - - public void setNumMaxBackupFiles(int numMaxBackupFiles) { - this.numMaxBackupFiles = numMaxBackupFiles; - } - - // Save delayed and on a background thread - public void queueUpForSave(T persistable) { - if (persistable != null) { - checkNotNull(storageFile, "storageFile = null. Call setupFileStorage before using read/write."); - - fileManager.saveLater(persistable); - } else { - log.trace("queueUpForSave called but no persistable set"); - } - } - - public void queueUpForSave(T persistable, long delayInMilli) { - if (persistable != null) { - checkNotNull(storageFile, "storageFile = null. Call setupFileStorage before using read/write."); - - fileManager.saveLater(persistable, delayInMilli); - } else { - log.trace("queueUpForSave called but no persistable set"); - } - } - - public void remove(String fileName) { - fileManager.removeFile(fileName); - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Private - /////////////////////////////////////////////////////////////////////////////////////////// - - // We do the file read on the UI thread to avoid problems from multi threading. - // Data are small and read is done only at startup, so it is no performance issue. - @Nullable - private T getPersisted(File storageFile) { - if (storageFile.exists()) { - long now = System.currentTimeMillis(); - try { - T persistedObject = fileManager.read(storageFile); - log.trace("Read {} completed in {}msec", storageFile, System.currentTimeMillis() - now); - - // If we did not get any exception we can be sure the data are consistent so we make a backup - now = System.currentTimeMillis(); - fileManager.backupFile(fileName, numMaxBackupFiles); - log.trace("Backup {} completed in {}msec", storageFile, System.currentTimeMillis() - now); - - return persistedObject; - } catch (Throwable t) { - log.error("We cannot read the persisted data. " + - "We make a backup and remove the inconsistent file. fileName=" + fileName); - log.error(t.getMessage()); - try { - // We keep a backup which might be used for recovery - removeAndBackupFile(fileName); - DevEnv.logErrorAndThrowIfDevMode(t.toString()); - } catch (IOException e1) { - e1.printStackTrace(); - log.error(e1.getMessage()); - // We swallow Exception if backup fails - } - if (corruptedDatabaseFilesHandler != null) - corruptedDatabaseFilesHandler.onFileCorrupted(storageFile.getName()); - } - } - return null; - } - - public void removeAndBackupFile(String fileName) throws IOException { - fileManager.removeAndBackupFile(fileName); - } -} diff --git a/common/src/main/java/bisq/common/taskrunner/Model.java b/common/src/main/java/bisq/common/taskrunner/Model.java index 514add29490..4a0ead244c3 100644 --- a/common/src/main/java/bisq/common/taskrunner/Model.java +++ b/common/src/main/java/bisq/common/taskrunner/Model.java @@ -18,7 +18,5 @@ package bisq.common.taskrunner; public interface Model { - void persist(); - void onComplete(); } diff --git a/common/src/main/java/bisq/common/taskrunner/TaskRunner.java b/common/src/main/java/bisq/common/taskrunner/TaskRunner.java index f6f6d12d2bb..d8870b09f48 100644 --- a/common/src/main/java/bisq/common/taskrunner/TaskRunner.java +++ b/common/src/main/java/bisq/common/taskrunner/TaskRunner.java @@ -28,7 +28,7 @@ @Slf4j public class TaskRunner { - private final Queue> tasks = new LinkedBlockingQueue<>(); + private final Queue>> tasks = new LinkedBlockingQueue<>(); private final T sharedModel; private final Class sharedModelClass; private final ResultHandler resultHandler; @@ -36,7 +36,7 @@ public class TaskRunner { private boolean failed = false; private boolean isCanceled; - private Class currentTask; + private Class> currentTask; public TaskRunner(T sharedModel, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { @@ -82,7 +82,6 @@ public void cancel() { } void handleComplete() { - sharedModel.persist(); next(); } diff --git a/common/src/main/java/bisq/common/util/Utilities.java b/common/src/main/java/bisq/common/util/Utilities.java index 2a65a60cb89..426dcb162ba 100644 --- a/common/src/main/java/bisq/common/util/Utilities.java +++ b/common/src/main/java/bisq/common/util/Utilities.java @@ -58,6 +58,7 @@ import java.util.TimeZone; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; @@ -79,7 +80,6 @@ @Slf4j public class Utilities { - // TODO check out Jackson lib public static String objectToJson(Object object) { Gson gson = new GsonBuilder() .setExclusionStrategies(new AnnotationExclusionStrategy()) @@ -90,12 +90,16 @@ public static String objectToJson(Object object) { return gson.toJson(object); } - public static ListeningExecutorService getSingleThreadExecutor(String name) { + public static ExecutorService getSingleThreadExecutor(String name) { final ThreadFactory threadFactory = new ThreadFactoryBuilder() .setNameFormat(name) .setDaemon(true) .build(); - return MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(threadFactory)); + return Executors.newSingleThreadExecutor(threadFactory); + } + + public static ListeningExecutorService getSingleThreadListeningExecutor(String name) { + return MoreExecutors.listeningDecorator(getSingleThreadExecutor(name)); } public static ListeningExecutorService getListeningExecutorService(String name, diff --git a/core/src/main/java/bisq/core/account/sign/SignedWitnessStorageService.java b/core/src/main/java/bisq/core/account/sign/SignedWitnessStorageService.java index 722522cba78..a27c0ade90f 100644 --- a/core/src/main/java/bisq/core/account/sign/SignedWitnessStorageService.java +++ b/core/src/main/java/bisq/core/account/sign/SignedWitnessStorageService.java @@ -22,10 +22,10 @@ import bisq.network.p2p.storage.persistence.MapStoreService; import bisq.common.config.Config; -import bisq.common.storage.Storage; +import bisq.common.persistence.PersistenceManager; -import javax.inject.Named; import javax.inject.Inject; +import javax.inject.Named; import java.io.File; @@ -33,8 +33,6 @@ import lombok.extern.slf4j.Slf4j; -import static com.google.common.base.Preconditions.checkArgument; - @Slf4j public class SignedWitnessStorageService extends MapStoreService { private static final String FILE_NAME = "SignedWitnessStore"; @@ -46,14 +44,19 @@ public class SignedWitnessStorageService extends MapStoreService persistableNetworkPayloadMapStorage) { - super(storageDir, persistableNetworkPayloadMapStorage); + PersistenceManager persistenceManager) { + super(storageDir, persistenceManager); } /////////////////////////////////////////////////////////////////////////////////////////// // API /////////////////////////////////////////////////////////////////////////////////////////// + @Override + protected void initializePersistenceManager() { + persistenceManager.initialize(store, PersistenceManager.Source.NETWORK); + } + @Override public String getFileName() { return FILE_NAME; @@ -78,12 +81,4 @@ public boolean canHandle(PersistableNetworkPayload payload) { protected SignedWitnessStore createStore() { return new SignedWitnessStore(); } - - @Override - protected void readStore() { - super.readStore(); - checkArgument(store instanceof SignedWitnessStore, - "Store is not instance of SignedWitnessStore. That can happen if the ProtoBuffer " + - "file got changed. We cleared the data store and recreated it again."); - } } diff --git a/core/src/main/java/bisq/core/account/sign/SignedWitnessStore.java b/core/src/main/java/bisq/core/account/sign/SignedWitnessStore.java index a1e8372f8e1..d764ed3409b 100644 --- a/core/src/main/java/bisq/core/account/sign/SignedWitnessStore.java +++ b/core/src/main/java/bisq/core/account/sign/SignedWitnessStore.java @@ -18,7 +18,6 @@ package bisq.core.account.sign; -import bisq.network.p2p.storage.P2PDataStorage; import bisq.network.p2p.storage.persistence.PersistableNetworkPayloadStore; import com.google.protobuf.Message; @@ -35,7 +34,7 @@ * definition and provide a hashMap for the domain access. */ @Slf4j -public class SignedWitnessStore extends PersistableNetworkPayloadStore { +public class SignedWitnessStore extends PersistableNetworkPayloadStore { SignedWitnessStore() { } @@ -46,7 +45,7 @@ public class SignedWitnessStore extends PersistableNetworkPayloadStore { /////////////////////////////////////////////////////////////////////////////////////////// private SignedWitnessStore(List list) { - list.forEach(item -> map.put(new P2PDataStorage.ByteArray(item.getHash()), item)); + super(list); } public Message toProtoMessage() { @@ -68,8 +67,4 @@ public static SignedWitnessStore fromProto(protobuf.SignedWitnessStore proto) { .map(SignedWitness::fromProto).collect(Collectors.toList()); return new SignedWitnessStore(list); } - - public boolean containsKey(P2PDataStorage.ByteArray hash) { - return map.containsKey(hash); - } } diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java index bc261e7efe5..93ab89b0f94 100644 --- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java +++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java @@ -22,10 +22,10 @@ import bisq.network.p2p.storage.persistence.MapStoreService; import bisq.common.config.Config; -import bisq.common.storage.Storage; +import bisq.common.persistence.PersistenceManager; -import javax.inject.Named; import javax.inject.Inject; +import javax.inject.Named; import java.io.File; @@ -33,8 +33,6 @@ import lombok.extern.slf4j.Slf4j; -import static com.google.common.base.Preconditions.checkArgument; - @Slf4j public class AccountAgeWitnessStorageService extends MapStoreService { private static final String FILE_NAME = "AccountAgeWitnessStore"; @@ -46,14 +44,19 @@ public class AccountAgeWitnessStorageService extends MapStoreService persistableNetworkPayloadMapStorage) { - super(storageDir, persistableNetworkPayloadMapStorage); + PersistenceManager persistenceManager) { + super(storageDir, persistenceManager); } /////////////////////////////////////////////////////////////////////////////////////////// // API /////////////////////////////////////////////////////////////////////////////////////////// + @Override + protected void initializePersistenceManager() { + persistenceManager.initialize(store, PersistenceManager.Source.NETWORK); + } + @Override public String getFileName() { return FILE_NAME; @@ -78,12 +81,4 @@ public boolean canHandle(PersistableNetworkPayload payload) { protected AccountAgeWitnessStore createStore() { return new AccountAgeWitnessStore(); } - - @Override - protected void readStore() { - super.readStore(); - checkArgument(store instanceof AccountAgeWitnessStore, - "Store is not instance of AccountAgeWitnessStore. That can happen if the ProtoBuffer " + - "file got changed. We cleared the data store and recreated it again."); - } } diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStore.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStore.java index 0da7f9d211d..28661102ded 100644 --- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStore.java +++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStore.java @@ -17,7 +17,6 @@ package bisq.core.account.witness; -import bisq.network.p2p.storage.P2PDataStorage; import bisq.network.p2p.storage.persistence.PersistableNetworkPayloadStore; import com.google.protobuf.Message; @@ -34,7 +33,7 @@ * definition and provide a hashMap for the domain access. */ @Slf4j -public class AccountAgeWitnessStore extends PersistableNetworkPayloadStore { +public class AccountAgeWitnessStore extends PersistableNetworkPayloadStore { AccountAgeWitnessStore() { } @@ -45,7 +44,7 @@ public class AccountAgeWitnessStore extends PersistableNetworkPayloadStore { /////////////////////////////////////////////////////////////////////////////////////////// private AccountAgeWitnessStore(List list) { - list.forEach(item -> map.put(new P2PDataStorage.ByteArray(item.getHash()), item)); + super(list); } public Message toProtoMessage() { @@ -67,8 +66,4 @@ public static AccountAgeWitnessStore fromProto(protobuf.AccountAgeWitnessStore p .map(AccountAgeWitness::fromProto).collect(Collectors.toList()); return new AccountAgeWitnessStore(list); } - - public boolean containsKey(P2PDataStorage.ByteArray hash) { - return map.containsKey(hash); - } } diff --git a/core/src/main/java/bisq/core/app/AvoidStandbyModeService.java b/core/src/main/java/bisq/core/app/AvoidStandbyModeService.java index 91e6145bf47..4415b4acfa9 100644 --- a/core/src/main/java/bisq/core/app/AvoidStandbyModeService.java +++ b/core/src/main/java/bisq/core/app/AvoidStandbyModeService.java @@ -20,8 +20,8 @@ import bisq.core.user.Preferences; import bisq.common.config.Config; -import bisq.common.storage.FileUtil; -import bisq.common.storage.ResourceNotFoundException; +import bisq.common.file.FileUtil; +import bisq.common.file.ResourceNotFoundException; import bisq.common.util.Utilities; import javax.inject.Inject; diff --git a/core/src/main/java/bisq/core/app/BisqExecutable.java b/core/src/main/java/bisq/core/app/BisqExecutable.java index f1376be8d66..8e403b97dc1 100644 --- a/core/src/main/java/bisq/core/app/BisqExecutable.java +++ b/core/src/main/java/bisq/core/app/BisqExecutable.java @@ -31,11 +31,11 @@ import bisq.common.UserThread; import bisq.common.app.AppModule; -import bisq.common.app.DevEnv; import bisq.common.config.BisqHelpFormatter; import bisq.common.config.Config; import bisq.common.config.ConfigException; import bisq.common.handlers.ResultHandler; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.persistable.PersistedDataHost; import bisq.common.setup.CommonSetup; import bisq.common.setup.GracefulShutDownHandler; @@ -45,13 +45,18 @@ import com.google.inject.Guice; import com.google.inject.Injector; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + import lombok.extern.slf4j.Slf4j; +import javax.annotation.Nullable; + @Slf4j public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSetup.BisqSetupListener, UncaughtExceptionHandler { - private static final int EXIT_SUCCESS = 0; - private static final int EXIT_FAILURE = 1; + public static final int EXIT_SUCCESS = 0; + public static final int EXIT_FAILURE = 1; private final String fullName; private final String scriptName; @@ -127,7 +132,7 @@ protected void onApplicationLaunched() { CommonSetup.setupUncaughtExceptionHandler(this); setupGuice(); setupAvoidStandbyMode(); - startApplication(); + readAllPersisted(this::startApplication); } @@ -148,21 +153,30 @@ protected Injector getInjector() { } protected void applyInjector() { - setupPersistedDataHosts(injector); + // Subclasses might configure classes with the injector here } - protected void setupPersistedDataHosts(Injector injector) { - try { - PersistedDataHost.apply(CorePersistedDataHost.getPersistedDataHosts(injector)); - } catch (Throwable t) { - log.error("Error at PersistedDataHost.apply: {}", t.toString(), t); - // If we are in dev mode we want to get the exception if some db files are corrupted - // We need to delay it as the stage is not created yet and so popups would not be shown. - if (DevEnv.isDevMode()) - UserThread.runAfter(() -> { - throw t; - }, 2); + protected void readAllPersisted(Runnable completeHandler) { + readAllPersisted(null, completeHandler); + } + + protected void readAllPersisted(@Nullable List additionalHosts, Runnable completeHandler) { + List hosts = CorePersistedDataHost.getPersistedDataHosts(injector); + if (additionalHosts != null) { + hosts.addAll(additionalHosts); } + + AtomicInteger remaining = new AtomicInteger(hosts.size()); + hosts.forEach(e -> { + new Thread(() -> { + e.readPersisted(); + remaining.decrementAndGet(); + if (remaining.get() == 0) { + UserThread.execute(completeHandler); + } + + }, "BisqExecutable-read-" + e.getClass().getSimpleName()).start(); + }); } protected void setupAvoidStandbyMode() { @@ -199,9 +213,9 @@ public void gracefulShutDown(ResultHandler resultHandler) { isShutdownInProgress = true; if (injector == null) { - log.warn("Shut down called before injector was created"); + log.info("Shut down called before injector was created"); resultHandler.handleResult(); - System.exit(0); + System.exit(EXIT_SUCCESS); } try { @@ -222,11 +236,12 @@ public void gracefulShutDown(ResultHandler resultHandler) { injector.getInstance(P2PService.class).shutDown(() -> { log.info("P2PService shutdown completed"); - module.close(injector); - resultHandler.handleResult(); - log.info("Graceful shutdown completed. Exiting now."); - System.exit(0); + PersistenceManager.flushAllDataToDisk(() -> { + log.info("Graceful shutdown completed. Exiting now."); + resultHandler.handleResult(); + System.exit(EXIT_SUCCESS); + }); }); }); walletsSetup.shutDown(); @@ -236,13 +251,20 @@ public void gracefulShutDown(ResultHandler resultHandler) { // Wait max 20 sec. UserThread.runAfter(() -> { log.warn("Timeout triggered resultHandler"); - resultHandler.handleResult(); - System.exit(0); + PersistenceManager.flushAllDataToDisk(() -> { + log.info("Graceful shutdown resulted in a timeout. Exiting now."); + resultHandler.handleResult(); + System.exit(EXIT_SUCCESS); + }); }, 20); } catch (Throwable t) { log.error("App shutdown failed with exception {}", t.toString()); t.printStackTrace(); - System.exit(1); + PersistenceManager.flushAllDataToDisk(() -> { + log.info("Graceful shutdown resulted in an error. Exiting now."); + resultHandler.handleResult(); + System.exit(EXIT_FAILURE); + }); } } diff --git a/core/src/main/java/bisq/core/app/BisqHeadlessApp.java b/core/src/main/java/bisq/core/app/BisqHeadlessApp.java index ed17af065eb..71a6ab4e394 100644 --- a/core/src/main/java/bisq/core/app/BisqHeadlessApp.java +++ b/core/src/main/java/bisq/core/app/BisqHeadlessApp.java @@ -20,8 +20,8 @@ import bisq.core.trade.TradeManager; import bisq.common.UserThread; +import bisq.common.file.CorruptedStorageFileHandler; import bisq.common.setup.GracefulShutDownHandler; -import bisq.common.storage.CorruptedDatabaseFilesHandler; import com.google.inject.Injector; @@ -33,7 +33,6 @@ @Slf4j public class BisqHeadlessApp implements HeadlessApp { - private static final long LOG_MEMORY_PERIOD_MIN = 10; @Getter private static Runnable shutDownHandler; @@ -43,7 +42,7 @@ public class BisqHeadlessApp implements HeadlessApp { private GracefulShutDownHandler gracefulShutDownHandler; private boolean shutDownRequested; protected BisqSetup bisqSetup; - private CorruptedDatabaseFilesHandler corruptedDatabaseFilesHandler; + private CorruptedStorageFileHandler corruptedStorageFileHandler; private TradeManager tradeManager; public BisqHeadlessApp() { @@ -55,7 +54,7 @@ public void startApplication() { bisqSetup = injector.getInstance(BisqSetup.class); bisqSetup.addBisqSetupListener(this); - corruptedDatabaseFilesHandler = injector.getInstance(CorruptedDatabaseFilesHandler.class); + corruptedStorageFileHandler = injector.getInstance(CorruptedStorageFileHandler.class); tradeManager = injector.getInstance(TradeManager.class); setupHandlers(); @@ -96,8 +95,7 @@ protected void setupHandlers() { bisqSetup.setOsxKeyLoggerWarningHandler(() -> log.info("setOsxKeyLoggerWarningHandler")); bisqSetup.setQubesOSInfoHandler(() -> log.info("setQubesOSInfoHandler")); - //TODO move to bisqSetup - corruptedDatabaseFilesHandler.getCorruptedDatabaseFiles().ifPresent(files -> log.warn("getCorruptedDatabaseFiles. files={}", files)); + corruptedStorageFileHandler.getFiles().ifPresent(files -> log.warn("getCorruptedDatabaseFiles. files={}", files)); tradeManager.setTakeOfferRequestErrorMessageHandler(errorMessage -> log.error("onTakeOfferRequestErrorMessageHandler")); } diff --git a/core/src/main/java/bisq/core/app/BisqSetup.java b/core/src/main/java/bisq/core/app/BisqSetup.java index 4bafdc0058d..57a5b00fd61 100644 --- a/core/src/main/java/bisq/core/app/BisqSetup.java +++ b/core/src/main/java/bisq/core/app/BisqSetup.java @@ -122,15 +122,12 @@ public class BisqSetup { public interface BisqSetupListener { default void onInitP2pNetwork() { - log.info("onInitP2pNetwork"); } default void onInitWallet() { - log.info("onInitWallet"); } default void onRequestWalletPassword() { - log.info("onRequestWalletPassword"); } void onSetupComplete(); @@ -502,6 +499,7 @@ else if (displayTorNetworkSettingsHandler != null) }, STARTUP_TIMEOUT_MINUTES, TimeUnit.MINUTES); + log.info("Init P2P network"); bisqSetupListeners.forEach(BisqSetupListener::onInitP2pNetwork); p2pNetworkReady = p2PNetworkSetup.init(this::initWallet, displayTorNetworkSettingsHandler); @@ -530,6 +528,7 @@ else if (displayTorNetworkSettingsHandler != null) } private void initWallet() { + log.info("Init wallet"); bisqSetupListeners.forEach(BisqSetupListener::onInitWallet); Runnable walletPasswordHandler = () -> { log.info("Wallet password required"); diff --git a/core/src/main/java/bisq/core/app/SetupUtils.java b/core/src/main/java/bisq/core/app/SetupUtils.java index c0b3338dcd5..eb210da5fea 100644 --- a/core/src/main/java/bisq/core/app/SetupUtils.java +++ b/core/src/main/java/bisq/core/app/SetupUtils.java @@ -17,65 +17,22 @@ package bisq.core.app; -import bisq.common.config.BaseCurrencyNetwork; - -import bisq.network.crypto.DecryptedDataTuple; -import bisq.network.crypto.EncryptionService; -import bisq.network.p2p.peers.keepalive.messages.Ping; import bisq.network.p2p.storage.P2PDataStorage; import bisq.common.UserThread; +import bisq.common.config.BaseCurrencyNetwork; import bisq.common.config.Config; -import bisq.common.crypto.CryptoException; -import bisq.common.crypto.KeyRing; -import bisq.common.crypto.SealedAndSigned; -import bisq.common.handlers.ResultHandler; -import bisq.common.proto.ProtobufferException; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import java.util.Date; -import java.util.function.Consumer; import lombok.extern.slf4j.Slf4j; @Slf4j public class SetupUtils { - public static void checkCryptoSetup(KeyRing keyRing, EncryptionService encryptionService, - ResultHandler resultHandler, Consumer errorHandler) { - // We want to test if the client is compiled with the correct crypto provider (BountyCastle) - // and if the unlimited Strength for cryptographic keys is set. - // If users compile themselves they might miss that step and then would get an exception in the trade. - // To avoid that we add a sample encryption and signing here at startup to see if it doesn't cause an exception. - // See: https://github.com/bisq-network/exchange/blob/master/doc/build.md#7-enable-unlimited-strength-for-cryptographic-keys - Thread checkCryptoThread = new Thread(() -> { - try { - // just use any simple dummy msg - Ping payload = new Ping(1, 1); - SealedAndSigned sealedAndSigned = EncryptionService.encryptHybridWithSignature(payload, - keyRing.getSignatureKeyPair(), keyRing.getPubKeyRing().getEncryptionPubKey()); - DecryptedDataTuple tuple = encryptionService.decryptHybridWithSignature(sealedAndSigned, - keyRing.getEncryptionKeyPair().getPrivate()); - if (tuple.getNetworkEnvelope() instanceof Ping && - ((Ping) tuple.getNetworkEnvelope()).getNonce() == payload.getNonce() && - ((Ping) tuple.getNetworkEnvelope()).getLastRoundTripTime() == payload.getLastRoundTripTime()) { - log.debug("Crypto test succeeded"); - - UserThread.execute(resultHandler::handleResult); - } else { - errorHandler.accept(new CryptoException("Payload not correct after decryption")); - } - } catch (CryptoException | ProtobufferException e) { - log.error(e.toString()); - e.printStackTrace(); - errorHandler.accept(e); - } - }, "checkCryptoThread"); - checkCryptoThread.start(); - } - public static BooleanProperty readFromResources(P2PDataStorage p2PDataStorage, Config config) { BooleanProperty result = new SimpleBooleanProperty(); new Thread(() -> { @@ -86,7 +43,7 @@ public static BooleanProperty readFromResources(P2PDataStorage p2PDataStorage, C p2PDataStorage.readFromResources(postFix); log.info("readFromResources took {} ms", (new Date().getTime() - ts)); UserThread.execute(() -> result.set(true)); - }, "readFromResourcesThread").start(); + }, "BisqSetup-readFromResources").start(); return result; } } diff --git a/core/src/main/java/bisq/core/app/TorSetup.java b/core/src/main/java/bisq/core/app/TorSetup.java index 946058fd578..b2ff5378b79 100644 --- a/core/src/main/java/bisq/core/app/TorSetup.java +++ b/core/src/main/java/bisq/core/app/TorSetup.java @@ -18,8 +18,8 @@ package bisq.core.app; import bisq.common.config.Config; +import bisq.common.file.FileUtil; import bisq.common.handlers.ErrorMessageHandler; -import bisq.common.storage.FileUtil; import javax.inject.Inject; import javax.inject.Named; @@ -39,7 +39,7 @@ @Slf4j @Singleton public class TorSetup { - private File torDir; + private final File torDir; @Inject public TorSetup(@Named(Config.TOR_DIR) File torDir) { diff --git a/core/src/main/java/bisq/core/app/WalletAppSetup.java b/core/src/main/java/bisq/core/app/WalletAppSetup.java index 3c7c7466c39..74049ae6376 100644 --- a/core/src/main/java/bisq/core/app/WalletAppSetup.java +++ b/core/src/main/java/bisq/core/app/WalletAppSetup.java @@ -235,7 +235,7 @@ void setRejectedTxErrorMessageHandler(Consumer rejectedTxErrorMessageHan }, 1); }); - tradeManager.getTradesAsObservableList().stream() + tradeManager.getObservableList().stream() .filter(trade -> trade.getOffer() != null) .forEach(trade -> { String details = null; diff --git a/core/src/main/java/bisq/core/app/misc/AppSetup.java b/core/src/main/java/bisq/core/app/misc/AppSetup.java index 213c2b0a270..88e8b12f15b 100644 --- a/core/src/main/java/bisq/core/app/misc/AppSetup.java +++ b/core/src/main/java/bisq/core/app/misc/AppSetup.java @@ -17,13 +17,8 @@ package bisq.core.app.misc; -import bisq.core.app.SetupUtils; - -import bisq.network.crypto.EncryptionService; - import bisq.common.app.Version; import bisq.common.config.Config; -import bisq.common.crypto.KeyRing; import javax.inject.Inject; @@ -31,17 +26,11 @@ @Slf4j public abstract class AppSetup { - protected final EncryptionService encryptionService; - protected final KeyRing keyRing; protected final Config config; @Inject - public AppSetup(EncryptionService encryptionService, - KeyRing keyRing, - Config config) { + public AppSetup(Config config) { // we need to reference it so the seed node stores tradeStatistics - this.encryptionService = encryptionService; - this.keyRing = keyRing; this.config = config; Version.setBaseCryptoNetworkId(this.config.baseCurrencyNetwork.ordinal()); @@ -49,14 +38,8 @@ public AppSetup(EncryptionService encryptionService, } public void start() { - SetupUtils.checkCryptoSetup(keyRing, encryptionService, () -> { - initPersistedDataHosts(); - initBasicServices(); - }, throwable -> { - log.error(throwable.getMessage()); - throwable.printStackTrace(); - System.exit(1); - }); + initPersistedDataHosts(); + initBasicServices(); } abstract void initPersistedDataHosts(); diff --git a/core/src/main/java/bisq/core/app/misc/AppSetupWithP2P.java b/core/src/main/java/bisq/core/app/misc/AppSetupWithP2P.java index 78d5beaf90a..8b2dc545d6c 100644 --- a/core/src/main/java/bisq/core/app/misc/AppSetupWithP2P.java +++ b/core/src/main/java/bisq/core/app/misc/AppSetupWithP2P.java @@ -24,7 +24,6 @@ import bisq.core.filter.FilterManager; import bisq.core.trade.statistics.TradeStatisticsManager; -import bisq.network.crypto.EncryptionService; import bisq.network.p2p.P2PService; import bisq.network.p2p.P2PServiceListener; import bisq.network.p2p.network.CloseConnectionReason; @@ -32,7 +31,6 @@ import bisq.network.p2p.network.ConnectionListener; import bisq.common.config.Config; -import bisq.common.crypto.KeyRing; import bisq.common.proto.persistable.PersistedDataHost; import javax.inject.Inject; @@ -56,16 +54,14 @@ public class AppSetupWithP2P extends AppSetup { protected ArrayList persistedDataHosts; @Inject - public AppSetupWithP2P(EncryptionService encryptionService, - KeyRing keyRing, - P2PService p2PService, + public AppSetupWithP2P(P2PService p2PService, TradeStatisticsManager tradeStatisticsManager, AccountAgeWitnessService accountAgeWitnessService, SignedWitnessService signedWitnessService, FilterManager filterManager, TorSetup torSetup, Config config) { - super(encryptionService, keyRing, config); + super(config); this.p2PService = p2PService; this.tradeStatisticsManager = tradeStatisticsManager; this.accountAgeWitnessService = accountAgeWitnessService; diff --git a/core/src/main/java/bisq/core/app/misc/AppSetupWithP2PAndDAO.java b/core/src/main/java/bisq/core/app/misc/AppSetupWithP2PAndDAO.java index 8257e47e5ce..3331906c8dc 100644 --- a/core/src/main/java/bisq/core/app/misc/AppSetupWithP2PAndDAO.java +++ b/core/src/main/java/bisq/core/app/misc/AppSetupWithP2PAndDAO.java @@ -30,11 +30,9 @@ import bisq.core.filter.FilterManager; import bisq.core.trade.statistics.TradeStatisticsManager; -import bisq.network.crypto.EncryptionService; import bisq.network.p2p.P2PService; import bisq.common.config.Config; -import bisq.common.crypto.KeyRing; import javax.inject.Inject; @@ -45,9 +43,7 @@ public class AppSetupWithP2PAndDAO extends AppSetupWithP2P { private final DaoSetup daoSetup; @Inject - public AppSetupWithP2PAndDAO(EncryptionService encryptionService, - KeyRing keyRing, - P2PService p2PService, + public AppSetupWithP2PAndDAO(P2PService p2PService, TradeStatisticsManager tradeStatisticsManager, AccountAgeWitnessService accountAgeWitnessService, SignedWitnessService signedWitnessService, @@ -61,9 +57,7 @@ public AppSetupWithP2PAndDAO(EncryptionService encryptionService, MyProofOfBurnListService myProofOfBurnListService, TorSetup torSetup, Config config) { - super(encryptionService, - keyRing, - p2PService, + super(p2PService, tradeStatisticsManager, accountAgeWitnessService, signedWitnessService, diff --git a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java index 297c7bf4fd4..8a22a56bcae 100644 --- a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java +++ b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java @@ -32,9 +32,9 @@ import bisq.common.app.DevEnv; import bisq.common.config.Config; import bisq.common.handlers.ResultHandler; +import bisq.common.persistence.PersistenceManager; import bisq.common.setup.GracefulShutDownHandler; import bisq.common.util.Profiler; -import bisq.common.util.RestartUtil; import com.google.common.util.concurrent.ThreadFactoryBuilder; @@ -42,8 +42,6 @@ import java.time.ZoneId; import java.time.ZonedDateTime; -import java.io.IOException; - import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -82,16 +80,19 @@ public void onSetupComplete() { // We don't use the gracefulShutDown implementation of the super class as we have a limited set of modules @Override public void gracefulShutDown(ResultHandler resultHandler) { - log.debug("gracefulShutDown"); + log.info("gracefulShutDown"); try { if (injector != null) { injector.getInstance(ArbitratorManager.class).shutDown(); injector.getInstance(OpenOfferManager.class).shutDown(() -> injector.getInstance(P2PService.class).shutDown(() -> { injector.getInstance(WalletsSetup.class).shutDownComplete.addListener((ov, o, n) -> { module.close(injector); - resultHandler.handleResult(); - log.info("Graceful shutdown completed"); - System.exit(0); + + PersistenceManager.flushAllDataToDisk(() -> { + resultHandler.handleResult(); + log.info("Graceful shutdown completed. Exiting now."); + System.exit(BisqExecutable.EXIT_SUCCESS); + }); }); injector.getInstance(WalletsSetup.class).shutDown(); injector.getInstance(BtcWalletService.class).shutDown(); @@ -99,19 +100,27 @@ public void gracefulShutDown(ResultHandler resultHandler) { })); // we wait max 5 sec. UserThread.runAfter(() -> { - resultHandler.handleResult(); - System.exit(0); + PersistenceManager.flushAllDataToDisk(() -> { + resultHandler.handleResult(); + log.info("Graceful shutdown caused a timeout. Exiting now."); + System.exit(BisqExecutable.EXIT_SUCCESS); + }); }, 5); } else { UserThread.runAfter(() -> { resultHandler.handleResult(); - System.exit(0); + System.exit(BisqExecutable.EXIT_SUCCESS); }, 1); } } catch (Throwable t) { log.debug("App shutdown failed with exception"); t.printStackTrace(); - System.exit(1); + PersistenceManager.flushAllDataToDisk(() -> { + resultHandler.handleResult(); + log.info("Graceful shutdown resulted in an error. Exiting now."); + System.exit(BisqExecutable.EXIT_FAILURE); + }); + } } @@ -228,22 +237,4 @@ protected void shutDown(GracefulShutDownHandler gracefulShutDownHandler) { System.exit(1); }); } - - protected void restart(Config config, GracefulShutDownHandler gracefulShutDownHandler) { - stopped = true; - gracefulShutDownHandler.gracefulShutDown(() -> { - //noinspection finally - try { - final String[] tokens = config.appDataDir.getPath().split("_"); - String logPath = "error_" + (tokens.length > 1 ? tokens[tokens.length - 2] : "") + ".log"; - RestartUtil.restartApplication(logPath); - } catch (IOException e) { - log.error(e.toString()); - e.printStackTrace(); - } finally { - log.warn("Shutdown complete"); - System.exit(0); - } - }); - } } diff --git a/core/src/main/java/bisq/core/btc/Balances.java b/core/src/main/java/bisq/core/btc/Balances.java index e25352d3036..e162d547075 100644 --- a/core/src/main/java/bisq/core/btc/Balances.java +++ b/core/src/main/java/bisq/core/btc/Balances.java @@ -80,7 +80,7 @@ public Balances(TradeManager tradeManager, public void onAllServicesInitialized() { openOfferManager.getObservableList().addListener((ListChangeListener) c -> updateBalance()); - tradeManager.getTradesAsObservableList().addListener((ListChangeListener) change -> updateBalance()); + tradeManager.getObservableList().addListener((ListChangeListener) change -> updateBalance()); refundManager.getDisputesAsObservableList().addListener((ListChangeListener) c -> updateBalance()); btcWalletService.addBalanceListener(new BalanceListener() { @Override 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 d6741af0f57..4827fbc1621 100644 --- a/core/src/main/java/bisq/core/btc/model/AddressEntryList.java +++ b/core/src/main/java/bisq/core/btc/model/AddressEntryList.java @@ -18,9 +18,9 @@ package bisq.core.btc.model; import bisq.common.config.Config; +import bisq.common.persistence.PersistenceManager; +import bisq.common.proto.persistable.PersistableEnvelope; import bisq.common.proto.persistable.PersistedDataHost; -import bisq.common.proto.persistable.UserThreadMappedPersistableEnvelope; -import bisq.common.storage.Storage; import com.google.protobuf.Message; @@ -41,28 +41,28 @@ import java.util.concurrent.CopyOnWriteArraySet; import java.util.stream.Collectors; -import lombok.ToString; import lombok.extern.slf4j.Slf4j; /** * 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; +public final class AddressEntryList implements PersistableEnvelope, PersistedDataHost { + transient private PersistenceManager persistenceManager; transient private Wallet wallet; private final Set entrySet = new CopyOnWriteArraySet<>(); @Inject - public AddressEntryList(Storage storage) { - this.storage = storage; + public AddressEntryList(PersistenceManager persistenceManager) { + this.persistenceManager = persistenceManager; + + this.persistenceManager.initialize(this, PersistenceManager.Source.PRIVATE); } @Override public void readPersisted() { - AddressEntryList persisted = storage.initAndGetPersisted(this, 50); + AddressEntryList persisted = persistenceManager.getPersisted(); if (persisted != null) { entrySet.clear(); entrySet.addAll(persisted.entrySet); @@ -164,7 +164,7 @@ public void onWalletReady(Wallet wallet) { maybeAddNewAddressEntry(tx); }); - persist(); + requestPersistence(); } public ImmutableList getAddressEntriesAsListImmutable() { @@ -186,7 +186,7 @@ public void addAddressEntry(AddressEntry addressEntry) { boolean setChangedByAdd = entrySet.add(addressEntry); if (setChangedByAdd) - persist(); + requestPersistence(); } public void swapToAvailable(AddressEntry addressEntry) { @@ -194,7 +194,7 @@ public void swapToAvailable(AddressEntry addressEntry) { boolean setChangedByAdd = entrySet.add(new AddressEntry(addressEntry.getKeyPair(), AddressEntry.Context.AVAILABLE)); if (setChangedByRemove || setChangedByAdd) { - persist(); + requestPersistence(); } } @@ -205,13 +205,13 @@ public AddressEntry swapAvailableToAddressEntryWithOfferId(AddressEntry addressE final AddressEntry newAddressEntry = new AddressEntry(addressEntry.getKeyPair(), context, offerId); boolean setChangedByAdd = entrySet.add(newAddressEntry); if (setChangedByRemove || setChangedByAdd) - persist(); + requestPersistence(); return newAddressEntry; } - public void persist() { - storage.queueUpForSave(50); + public void requestPersistence() { + persistenceManager.requestPersistence(); } @@ -235,4 +235,11 @@ private void maybeAddNewAddressEntry(Transaction tx) { private boolean isAddressNotInEntries(Address address) { return entrySet.stream().noneMatch(e -> address.equals(e.getAddress())); } + + @Override + public String toString() { + return "AddressEntryList{" + + ",\n entrySet=" + entrySet + + "\n}"; + } } 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 e94e65bbea7..650f9297853 100644 --- a/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java +++ b/core/src/main/java/bisq/core/btc/setup/WalletsSetup.java @@ -34,11 +34,11 @@ import bisq.common.Timer; import bisq.common.UserThread; -import bisq.common.config.Config; import bisq.common.app.Version; +import bisq.common.config.Config; +import bisq.common.file.FileUtil; import bisq.common.handlers.ExceptionHandler; import bisq.common.handlers.ResultHandler; -import bisq.common.storage.FileUtil; import org.bitcoinj.core.Address; import org.bitcoinj.core.BlockChain; 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 7e2aa0d26ce..b5860e00491 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/BtcWalletService.java @@ -118,7 +118,7 @@ void decryptWallet(@NotNull KeyParameter key) { if (keyPair.isEncrypted()) e.setDeterministicKey(keyPair.decrypt(key)); }); - addressEntryList.persist(); + addressEntryList.requestPersistence(); } @Override @@ -129,7 +129,7 @@ void encryptWallet(KeyCrypterScrypt keyCrypterScrypt, KeyParameter key) { if (keyPair.isEncrypted()) e.setDeterministicKey(keyPair.encrypt(keyCrypterScrypt, key)); }); - addressEntryList.persist(); + addressEntryList.requestPersistence(); } @Override @@ -700,7 +700,7 @@ public void swapAnyTradeEntryContextToAvailableEntry(String offerId) { } public void saveAddressEntryList() { - addressEntryList.persist(); + addressEntryList.requestPersistence(); } public DeterministicKey getMultiSigKeyPair(String tradeId, byte[] pubKey) { diff --git a/core/src/main/java/bisq/core/dao/DaoFacade.java b/core/src/main/java/bisq/core/dao/DaoFacade.java index ce52e74e674..f330289ff20 100644 --- a/core/src/main/java/bisq/core/dao/DaoFacade.java +++ b/core/src/main/java/bisq/core/dao/DaoFacade.java @@ -54,7 +54,6 @@ import bisq.core.dao.governance.proposal.role.RoleProposalFactory; import bisq.core.dao.state.DaoStateListener; import bisq.core.dao.state.DaoStateService; -import bisq.core.dao.state.DaoStateStorageService; import bisq.core.dao.state.model.blockchain.BaseTx; import bisq.core.dao.state.model.blockchain.BaseTxOutput; import bisq.core.dao.state.model.blockchain.Block; @@ -70,6 +69,7 @@ import bisq.core.dao.state.model.governance.Role; import bisq.core.dao.state.model.governance.RoleProposal; import bisq.core.dao.state.model.governance.Vote; +import bisq.core.dao.state.storage.DaoStateStorageService; import bisq.asset.Asset; diff --git a/core/src/main/java/bisq/core/dao/DaoModule.java b/core/src/main/java/bisq/core/dao/DaoModule.java index ad8f2c30efc..87d5f9e7a00 100644 --- a/core/src/main/java/bisq/core/dao/DaoModule.java +++ b/core/src/main/java/bisq/core/dao/DaoModule.java @@ -80,9 +80,9 @@ import bisq.core.dao.node.parser.TxParser; import bisq.core.dao.state.DaoStateService; import bisq.core.dao.state.DaoStateSnapshotService; -import bisq.core.dao.state.DaoStateStorageService; import bisq.core.dao.state.GenesisTxInfo; import bisq.core.dao.state.model.DaoState; +import bisq.core.dao.state.storage.DaoStateStorageService; import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputListService; import bisq.common.app.AppModule; diff --git a/core/src/main/java/bisq/core/dao/governance/ballot/BallotListService.java b/core/src/main/java/bisq/core/dao/governance/ballot/BallotListService.java index 1760ba3f956..cb01694fdf6 100644 --- a/core/src/main/java/bisq/core/dao/governance/ballot/BallotListService.java +++ b/core/src/main/java/bisq/core/dao/governance/ballot/BallotListService.java @@ -28,8 +28,8 @@ import bisq.core.dao.state.model.governance.Vote; import bisq.common.app.DevEnv; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.persistable.PersistedDataHost; -import bisq.common.storage.Storage; import javax.inject.Inject; @@ -58,7 +58,7 @@ public interface BallotListChangeListener { private final ProposalService proposalService; private final PeriodService periodService; private final ProposalValidatorProvider validatorProvider; - private final Storage storage; + private final PersistenceManager persistenceManager; private final BallotList ballotList = new BallotList(); private final List listeners = new CopyOnWriteArrayList<>(); @@ -67,11 +67,13 @@ public interface BallotListChangeListener { public BallotListService(ProposalService proposalService, PeriodService periodService, ProposalValidatorProvider validatorProvider, - Storage storage) { + PersistenceManager persistenceManager) { this.proposalService = proposalService; this.periodService = periodService; this.validatorProvider = validatorProvider; - this.storage = storage; + this.persistenceManager = persistenceManager; + + this.persistenceManager.initialize(ballotList, PersistenceManager.Source.NETWORK); } @@ -93,7 +95,7 @@ private void onChanged(Change change) { .map(ProposalPayload::getProposal) .filter(this::isNewProposal) .forEach(this::registerProposalAsBallot); - persist(); + requestPersistence(); } } @@ -129,10 +131,9 @@ public void start() { @Override public void readPersisted() { if (DevEnv.isDaoActivated()) { - BallotList persisted = storage.initAndGetPersisted(ballotList, 100); + BallotList persisted = persistenceManager.getPersisted(); if (persisted != null) { - ballotList.clear(); - ballotList.addAll(persisted.getList()); + ballotList.setAll(persisted.getList()); listeners.forEach(l -> l.onListChanged(ballotList.getList())); } } @@ -145,7 +146,7 @@ public void readPersisted() { public void setVote(Ballot ballot, @Nullable Vote vote) { ballot.setVote(vote); - persist(); + requestPersistence(); } public void addListener(BallotListChangeListener listener) { @@ -170,7 +171,7 @@ public List getValidBallotsOfCycle() { // Private /////////////////////////////////////////////////////////////////////////////////////////// - private void persist() { - storage.queueUpForSave(); + private void requestPersistence() { + persistenceManager.requestPersistence(); } } diff --git a/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteList.java b/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteList.java index 313fc07d349..be0890b653a 100644 --- a/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteList.java +++ b/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteList.java @@ -19,7 +19,7 @@ import bisq.core.dao.governance.ConsensusCritical; -import bisq.common.proto.persistable.UserThreadMappedPersistableList; +import bisq.common.proto.persistable.PersistableList; import com.google.protobuf.Message; @@ -33,7 +33,7 @@ * List of my own blind votes. Blind votes received from other voters are stored in the BlindVoteStore. */ @EqualsAndHashCode(callSuper = true) -public class MyBlindVoteList extends UserThreadMappedPersistableList implements ConsensusCritical { +public class MyBlindVoteList extends PersistableList implements ConsensusCritical { MyBlindVoteList() { super(); diff --git a/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java b/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java index 62b64f22ada..cc70c4a002e 100644 --- a/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java +++ b/core/src/main/java/bisq/core/dao/governance/blindvote/MyBlindVoteListService.java @@ -52,8 +52,8 @@ import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ExceptionHandler; import bisq.common.handlers.ResultHandler; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.persistable.PersistedDataHost; -import bisq.common.storage.Storage; import bisq.common.util.Tuple2; import bisq.common.util.Utilities; @@ -99,7 +99,7 @@ public class MyBlindVoteListService implements PersistedDataHost, DaoStateListen private final DaoStateService daoStateService; private final PeriodService periodService; private final WalletsManager walletsManager; - private final Storage storage; + private final PersistenceManager persistenceManager; private final BsqWalletService bsqWalletService; private final BtcWalletService btcWalletService; private final BallotListService ballotListService; @@ -119,7 +119,7 @@ public MyBlindVoteListService(P2PService p2PService, DaoStateService daoStateService, PeriodService periodService, WalletsManager walletsManager, - Storage storage, + PersistenceManager persistenceManager, BsqWalletService bsqWalletService, BtcWalletService btcWalletService, BallotListService ballotListService, @@ -129,13 +129,15 @@ public MyBlindVoteListService(P2PService p2PService, this.daoStateService = daoStateService; this.periodService = periodService; this.walletsManager = walletsManager; - this.storage = storage; + this.persistenceManager = persistenceManager; this.bsqWalletService = bsqWalletService; this.btcWalletService = btcWalletService; this.ballotListService = ballotListService; this.myVoteListService = myVoteListService; this.myProposalListService = myProposalListService; + this.persistenceManager.initialize(myBlindVoteList, PersistenceManager.Source.PRIVATE); + numConnectedPeersListener = (observable, oldValue, newValue) -> maybeRePublishMyBlindVote(); } @@ -162,10 +164,9 @@ public void start() { @Override public void readPersisted() { if (DevEnv.isDaoActivated()) { - MyBlindVoteList persisted = storage.initAndGetPersisted(myBlindVoteList, 100); + MyBlindVoteList persisted = persistenceManager.getPersisted(); if (persisted != null) { - myBlindVoteList.clear(); - myBlindVoteList.addAll(persisted.getList()); + myBlindVoteList.setAll(persisted.getList()); } } } @@ -403,11 +404,11 @@ private void addToP2PNetwork(BlindVote blindVote, @Nullable ErrorMessageHandler private void addBlindVoteToList(BlindVote blindVote) { if (!myBlindVoteList.getList().contains(blindVote)) { myBlindVoteList.add(blindVote); - persist(); + requestPersistence(); } } - private void persist() { - storage.queueUpForSave(); + private void requestPersistence() { + persistenceManager.requestPersistence(); } } diff --git a/core/src/main/java/bisq/core/dao/governance/blindvote/storage/BlindVoteStorageService.java b/core/src/main/java/bisq/core/dao/governance/blindvote/storage/BlindVoteStorageService.java index 3f4df241c0f..0271fdce7f5 100644 --- a/core/src/main/java/bisq/core/dao/governance/blindvote/storage/BlindVoteStorageService.java +++ b/core/src/main/java/bisq/core/dao/governance/blindvote/storage/BlindVoteStorageService.java @@ -22,10 +22,10 @@ import bisq.network.p2p.storage.persistence.MapStoreService; import bisq.common.config.Config; -import bisq.common.storage.Storage; +import bisq.common.persistence.PersistenceManager; -import javax.inject.Named; import javax.inject.Inject; +import javax.inject.Named; import java.io.File; @@ -50,8 +50,8 @@ public class BlindVoteStorageService extends MapStoreService persistableNetworkPayloadMapStorage) { - super(storageDir, persistableNetworkPayloadMapStorage); + PersistenceManager persistenceManager) { + super(storageDir, persistenceManager); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -63,6 +63,11 @@ public String getFileName() { return FILE_NAME; } + @Override + protected void initializePersistenceManager() { + persistenceManager.initialize(store, PersistenceManager.Source.NETWORK); + } + @Override public Map getMap() { return store.getMap(); diff --git a/core/src/main/java/bisq/core/dao/governance/blindvote/storage/BlindVoteStore.java b/core/src/main/java/bisq/core/dao/governance/blindvote/storage/BlindVoteStore.java index 32e5b256b91..41ed5b0368a 100644 --- a/core/src/main/java/bisq/core/dao/governance/blindvote/storage/BlindVoteStore.java +++ b/core/src/main/java/bisq/core/dao/governance/blindvote/storage/BlindVoteStore.java @@ -17,7 +17,6 @@ package bisq.core.dao.governance.blindvote.storage; -import bisq.network.p2p.storage.P2PDataStorage; import bisq.network.p2p.storage.persistence.PersistableNetworkPayloadStore; import com.google.protobuf.Message; @@ -34,8 +33,7 @@ * definition and provide a hashMap for the domain access. */ @Slf4j -public class BlindVoteStore extends PersistableNetworkPayloadStore { - +public class BlindVoteStore extends PersistableNetworkPayloadStore { BlindVoteStore() { } @@ -45,7 +43,7 @@ public class BlindVoteStore extends PersistableNetworkPayloadStore { /////////////////////////////////////////////////////////////////////////////////////////// private BlindVoteStore(List list) { - list.forEach(item -> map.put(new P2PDataStorage.ByteArray(item.getHash()), item)); + super(list); } public Message toProtoMessage() { @@ -67,8 +65,4 @@ public static BlindVoteStore fromProto(protobuf.BlindVoteStore proto) { .map(BlindVotePayload::fromProto).collect(Collectors.toList()); return new BlindVoteStore(list); } - - public boolean containsKey(P2PDataStorage.ByteArray hash) { - return map.containsKey(hash); - } } diff --git a/core/src/main/java/bisq/core/dao/governance/bond/reputation/MyReputationList.java b/core/src/main/java/bisq/core/dao/governance/bond/reputation/MyReputationList.java index 9e705d2642f..2a39f783176 100644 --- a/core/src/main/java/bisq/core/dao/governance/bond/reputation/MyReputationList.java +++ b/core/src/main/java/bisq/core/dao/governance/bond/reputation/MyReputationList.java @@ -17,7 +17,7 @@ package bisq.core.dao.governance.bond.reputation; -import bisq.common.proto.persistable.UserThreadMappedPersistableList; +import bisq.common.proto.persistable.PersistableList; import java.util.ArrayList; import java.util.List; @@ -29,7 +29,7 @@ * PersistableEnvelope wrapper for list of MyReputations. */ @EqualsAndHashCode(callSuper = true) -public class MyReputationList extends UserThreadMappedPersistableList { +public class MyReputationList extends PersistableList { private MyReputationList(List list) { super(list); diff --git a/core/src/main/java/bisq/core/dao/governance/bond/reputation/MyReputationListService.java b/core/src/main/java/bisq/core/dao/governance/bond/reputation/MyReputationListService.java index 85e5477574b..c417b9c61ce 100644 --- a/core/src/main/java/bisq/core/dao/governance/bond/reputation/MyReputationListService.java +++ b/core/src/main/java/bisq/core/dao/governance/bond/reputation/MyReputationListService.java @@ -20,8 +20,8 @@ import bisq.core.dao.DaoSetupService; import bisq.common.app.DevEnv; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.persistable.PersistedDataHost; -import bisq.common.storage.Storage; import javax.inject.Inject; @@ -35,7 +35,7 @@ @Slf4j public class MyReputationListService implements PersistedDataHost, DaoSetupService { - private final Storage storage; + private final PersistenceManager persistenceManager; private final MyReputationList myReputationList = new MyReputationList(); @@ -44,8 +44,9 @@ public class MyReputationListService implements PersistedDataHost, DaoSetupServi /////////////////////////////////////////////////////////////////////////////////////////// @Inject - public MyReputationListService(Storage storage) { - this.storage = storage; + public MyReputationListService(PersistenceManager persistenceManager) { + this.persistenceManager = persistenceManager; + persistenceManager.initialize(myReputationList, PersistenceManager.Source.PRIVATE); } @@ -56,10 +57,9 @@ public MyReputationListService(Storage storage) { @Override public void readPersisted() { if (DevEnv.isDaoActivated()) { - MyReputationList persisted = storage.initAndGetPersisted(myReputationList, 100); + MyReputationList persisted = persistenceManager.getPersisted(); if (persisted != null) { - myReputationList.clear(); - myReputationList.addAll(persisted.getList()); + myReputationList.setAll(persisted.getList()); } } } @@ -84,7 +84,7 @@ public void start() { public void addReputation(MyReputation reputation) { if (!myReputationList.contains(reputation)) { myReputationList.add(reputation); - persist(); + requestPersistence(); } } @@ -97,7 +97,7 @@ public List getMyReputationList() { // Private /////////////////////////////////////////////////////////////////////////////////////////// - private void persist() { - storage.queueUpForSave(20); + private void requestPersistence() { + persistenceManager.requestPersistence(); } } diff --git a/core/src/main/java/bisq/core/dao/governance/myvote/MyVoteList.java b/core/src/main/java/bisq/core/dao/governance/myvote/MyVoteList.java index c840faf7e60..05610f8dcb8 100644 --- a/core/src/main/java/bisq/core/dao/governance/myvote/MyVoteList.java +++ b/core/src/main/java/bisq/core/dao/governance/myvote/MyVoteList.java @@ -17,7 +17,7 @@ package bisq.core.dao.governance.myvote; -import bisq.common.proto.persistable.UserThreadMappedPersistableList; +import bisq.common.proto.persistable.PersistableList; import com.google.protobuf.Message; @@ -28,7 +28,7 @@ import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) -public class MyVoteList extends UserThreadMappedPersistableList { +public class MyVoteList extends PersistableList { MyVoteList() { super(); diff --git a/core/src/main/java/bisq/core/dao/governance/myvote/MyVoteListService.java b/core/src/main/java/bisq/core/dao/governance/myvote/MyVoteListService.java index 8b5a7433c9b..390ee4ee608 100644 --- a/core/src/main/java/bisq/core/dao/governance/myvote/MyVoteListService.java +++ b/core/src/main/java/bisq/core/dao/governance/myvote/MyVoteListService.java @@ -25,8 +25,8 @@ import bisq.common.app.DevEnv; import bisq.common.crypto.Encryption; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.persistable.PersistedDataHost; -import bisq.common.storage.Storage; import bisq.common.util.Tuple2; import javax.inject.Inject; @@ -46,7 +46,7 @@ @Slf4j public class MyVoteListService implements PersistedDataHost { private final DaoStateService daoStateService; - private final Storage storage; + private final PersistenceManager persistenceManager; private final MyVoteList myVoteList = new MyVoteList(); @@ -56,9 +56,11 @@ public class MyVoteListService implements PersistedDataHost { @Inject public MyVoteListService(DaoStateService daoStateService, - Storage storage) { + PersistenceManager persistenceManager) { this.daoStateService = daoStateService; - this.storage = storage; + this.persistenceManager = persistenceManager; + + this.persistenceManager.initialize(myVoteList, PersistenceManager.Source.PRIVATE); } @@ -69,10 +71,9 @@ public MyVoteListService(DaoStateService daoStateService, @Override public void readPersisted() { if (DevEnv.isDaoActivated()) { - MyVoteList persisted = storage.initAndGetPersisted(myVoteList, 100); + MyVoteList persisted = persistenceManager.getPersisted(); if (persisted != null) { - this.myVoteList.clear(); - this.myVoteList.addAll(persisted.getList()); + this.myVoteList.setAll(persisted.getList()); } } } @@ -87,13 +88,13 @@ public void createAndAddMyVote(BallotList sortedBallotListForCycle, SecretKey se MyVote myVote = new MyVote(daoStateService.getChainHeight(), sortedBallotListForCycle, secretKeyBytes, blindVote); log.info("Add new MyVote to myVotesList list.\nMyVote=" + myVote); myVoteList.add(myVote); - persist(); + requestPersistence(); } public void applyRevealTxId(MyVote myVote, String voteRevealTxId) { myVote.setRevealTxId(voteRevealTxId); log.info("Applied revealTxId to myVote.\nmyVote={}\nvoteRevealTxId={}", myVote, voteRevealTxId); - persist(); + requestPersistence(); } public Tuple2 getMeritAndStakeForProposal(String proposalTxId, MyBlindVoteListService myBlindVoteListService) { @@ -102,7 +103,7 @@ public Tuple2 getMeritAndStakeForProposal(String proposalTxId, MyBli List list = new ArrayList<>(myVoteList.getList()); list.sort(Comparator.comparing(MyVote::getDate)); for (MyVote myVote : list) { - for (Ballot ballot : myVote.getBallotList()) { + for (Ballot ballot : myVote.getBallotList().getList()) { if (ballot.getTxId().equals(proposalTxId)) { merit = myVote.getMerit(myBlindVoteListService, daoStateService); stake = myVote.getBlindVote().getStake(); @@ -129,7 +130,7 @@ public List getMyVoteListForCycle() { // Private /////////////////////////////////////////////////////////////////////////////////////////// - private void persist() { - storage.queueUpForSave(); + private void requestPersistence() { + persistenceManager.requestPersistence(); } } diff --git a/core/src/main/java/bisq/core/dao/governance/proofofburn/MyProofOfBurnList.java b/core/src/main/java/bisq/core/dao/governance/proofofburn/MyProofOfBurnList.java index 0845b4efb74..a1f55c24996 100644 --- a/core/src/main/java/bisq/core/dao/governance/proofofburn/MyProofOfBurnList.java +++ b/core/src/main/java/bisq/core/dao/governance/proofofburn/MyProofOfBurnList.java @@ -17,7 +17,7 @@ package bisq.core.dao.governance.proofofburn; -import bisq.common.proto.persistable.UserThreadMappedPersistableList; +import bisq.common.proto.persistable.PersistableList; import java.util.ArrayList; import java.util.List; @@ -29,7 +29,7 @@ * PersistableEnvelope wrapper for list of MyProofOfBurn objects. */ @EqualsAndHashCode(callSuper = true) -public class MyProofOfBurnList extends UserThreadMappedPersistableList { +public class MyProofOfBurnList extends PersistableList { private MyProofOfBurnList(List list) { super(list); diff --git a/core/src/main/java/bisq/core/dao/governance/proofofburn/MyProofOfBurnListService.java b/core/src/main/java/bisq/core/dao/governance/proofofburn/MyProofOfBurnListService.java index 05ea0f973d5..39d8ed5a525 100644 --- a/core/src/main/java/bisq/core/dao/governance/proofofburn/MyProofOfBurnListService.java +++ b/core/src/main/java/bisq/core/dao/governance/proofofburn/MyProofOfBurnListService.java @@ -20,8 +20,8 @@ import bisq.core.dao.DaoSetupService; import bisq.common.app.DevEnv; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.persistable.PersistedDataHost; -import bisq.common.storage.Storage; import javax.inject.Inject; @@ -35,7 +35,7 @@ @Slf4j public class MyProofOfBurnListService implements PersistedDataHost, DaoSetupService { - private final Storage storage; + private final PersistenceManager persistenceManager; private final MyProofOfBurnList myProofOfBurnList = new MyProofOfBurnList(); @@ -44,8 +44,9 @@ public class MyProofOfBurnListService implements PersistedDataHost, DaoSetupServ /////////////////////////////////////////////////////////////////////////////////////////// @Inject - public MyProofOfBurnListService(Storage storage) { - this.storage = storage; + public MyProofOfBurnListService(PersistenceManager persistenceManager) { + this.persistenceManager = persistenceManager; + persistenceManager.initialize(myProofOfBurnList, PersistenceManager.Source.PRIVATE); } @@ -56,10 +57,9 @@ public MyProofOfBurnListService(Storage storage) { @Override public void readPersisted() { if (DevEnv.isDaoActivated()) { - MyProofOfBurnList persisted = storage.initAndGetPersisted(myProofOfBurnList, 100); + MyProofOfBurnList persisted = persistenceManager.getPersisted(); if (persisted != null) { - myProofOfBurnList.clear(); - myProofOfBurnList.addAll(persisted.getList()); + myProofOfBurnList.setAll(persisted.getList()); } } } @@ -85,7 +85,7 @@ public void start() { public void addMyProofOfBurn(MyProofOfBurn myProofOfBurn) { if (!myProofOfBurnList.contains(myProofOfBurn)) { myProofOfBurnList.add(myProofOfBurn); - persist(); + requestPersistence(); } } @@ -98,7 +98,7 @@ public List getMyProofOfBurnList() { // Private /////////////////////////////////////////////////////////////////////////////////////////// - private void persist() { - storage.queueUpForSave(20); + private void requestPersistence() { + persistenceManager.requestPersistence(); } } diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/MyProposalList.java b/core/src/main/java/bisq/core/dao/governance/proposal/MyProposalList.java index 0729043232c..7ec120d719b 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/MyProposalList.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/MyProposalList.java @@ -20,7 +20,7 @@ import bisq.core.dao.governance.ConsensusCritical; import bisq.core.dao.state.model.governance.Proposal; -import bisq.common.proto.persistable.UserThreadMappedPersistableList; +import bisq.common.proto.persistable.PersistableList; import java.util.ArrayList; import java.util.List; @@ -32,7 +32,7 @@ * PersistableEnvelope wrapper for list of proposals. Used in vote consensus, so changes can break consensus! */ @EqualsAndHashCode(callSuper = true) -public class MyProposalList extends UserThreadMappedPersistableList implements ConsensusCritical { +public class MyProposalList extends PersistableList implements ConsensusCritical { public MyProposalList(List list) { super(list); diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/MyProposalListService.java b/core/src/main/java/bisq/core/dao/governance/proposal/MyProposalListService.java index 9c4ac114e8f..19284a0c042 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/MyProposalListService.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/MyProposalListService.java @@ -35,8 +35,8 @@ import bisq.common.crypto.PubKeyRing; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.persistable.PersistedDataHost; -import bisq.common.storage.Storage; import org.bitcoinj.core.Transaction; @@ -67,7 +67,7 @@ public interface Listener { private final DaoStateService daoStateService; private final PeriodService periodService; private final WalletsManager walletsManager; - private final Storage storage; + private final PersistenceManager persistenceManager; private final PublicKey signaturePubKey; private final MyProposalList myProposalList = new MyProposalList(); @@ -84,13 +84,15 @@ public MyProposalListService(P2PService p2PService, DaoStateService daoStateService, PeriodService periodService, WalletsManager walletsManager, - Storage storage, + PersistenceManager persistenceManager, PubKeyRing pubKeyRing) { this.p2PService = p2PService; this.daoStateService = daoStateService; this.periodService = periodService; this.walletsManager = walletsManager; - this.storage = storage; + this.persistenceManager = persistenceManager; + + this.persistenceManager.initialize(myProposalList, PersistenceManager.Source.PRIVATE); signaturePubKey = pubKeyRing.getSignaturePubKey(); @@ -107,10 +109,9 @@ public MyProposalListService(P2PService p2PService, @Override public void readPersisted() { if (DevEnv.isDaoActivated()) { - MyProposalList persisted = storage.initAndGetPersisted(myProposalList, 100); + MyProposalList persisted = persistenceManager.getPersisted(); if (persisted != null) { - myProposalList.clear(); - myProposalList.addAll(persisted.getList()); + myProposalList.setAll(persisted.getList()); listeners.forEach(l -> l.onListChanged(getList())); } } @@ -157,7 +158,7 @@ public void onFailure(TxBroadcastException exception) { if (!getList().contains(proposal)) { myProposalList.add(proposal); listeners.forEach(l -> l.onListChanged(getList())); - persist(); + requestPersistence(); } } @@ -169,7 +170,7 @@ public boolean remove(Proposal proposal) { if (myProposalList.remove(proposal)) { listeners.forEach(l -> l.onListChanged(getList())); - persist(); + requestPersistence(); } else { log.warn("We called remove at a proposal which was not in our list"); } @@ -234,8 +235,8 @@ private void rePublishMyProposalsOnceWellConnected() { } } - private void persist() { - storage.queueUpForSave(); + private void requestPersistence() { + persistenceManager.requestPersistence(); } private boolean canRemoveProposal(Proposal proposal, DaoStateService daoStateService, PeriodService periodService) { diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/storage/appendonly/ProposalStorageService.java b/core/src/main/java/bisq/core/dao/governance/proposal/storage/appendonly/ProposalStorageService.java index 1cc3dee5eb5..b6f2022201b 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/storage/appendonly/ProposalStorageService.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/storage/appendonly/ProposalStorageService.java @@ -22,10 +22,10 @@ import bisq.network.p2p.storage.persistence.MapStoreService; import bisq.common.config.Config; -import bisq.common.storage.Storage; +import bisq.common.persistence.PersistenceManager; -import javax.inject.Named; import javax.inject.Inject; +import javax.inject.Named; import java.io.File; @@ -44,8 +44,8 @@ public class ProposalStorageService extends MapStoreService persistableNetworkPayloadMapStorage) { - super(storageDir, persistableNetworkPayloadMapStorage); + PersistenceManager persistenceManager) { + super(storageDir, persistenceManager); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -57,6 +57,11 @@ public String getFileName() { return FILE_NAME; } + @Override + protected void initializePersistenceManager() { + persistenceManager.initialize(store, PersistenceManager.Source.NETWORK); + } + @Override public Map getMap() { return store.getMap(); diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/storage/appendonly/ProposalStore.java b/core/src/main/java/bisq/core/dao/governance/proposal/storage/appendonly/ProposalStore.java index 6652362d14a..23c9b3732ed 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/storage/appendonly/ProposalStore.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/storage/appendonly/ProposalStore.java @@ -17,7 +17,6 @@ package bisq.core.dao.governance.proposal.storage.appendonly; -import bisq.network.p2p.storage.P2PDataStorage; import bisq.network.p2p.storage.persistence.PersistableNetworkPayloadStore; import com.google.protobuf.Message; @@ -34,7 +33,7 @@ * definition and provide a hashMap for the domain access. */ @Slf4j -public class ProposalStore extends PersistableNetworkPayloadStore { +public class ProposalStore extends PersistableNetworkPayloadStore { ProposalStore() { } @@ -45,7 +44,7 @@ public class ProposalStore extends PersistableNetworkPayloadStore { /////////////////////////////////////////////////////////////////////////////////////////// private ProposalStore(List list) { - list.forEach(item -> map.put(new P2PDataStorage.ByteArray(item.getHash()), item)); + super(list); } public Message toProtoMessage() { @@ -67,8 +66,4 @@ public static ProposalStore fromProto(protobuf.ProposalStore proto) { .map(ProposalPayload::fromProto).collect(Collectors.toList()); return new ProposalStore(list); } - - public boolean containsKey(P2PDataStorage.ByteArray hash) { - return map.containsKey(hash); - } } diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/storage/temp/TempProposalStorageService.java b/core/src/main/java/bisq/core/dao/governance/proposal/storage/temp/TempProposalStorageService.java index 5dd2fc8507d..d39b6f5470d 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/storage/temp/TempProposalStorageService.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/storage/temp/TempProposalStorageService.java @@ -22,10 +22,10 @@ import bisq.network.p2p.storage.persistence.MapStoreService; import bisq.common.config.Config; -import bisq.common.storage.Storage; +import bisq.common.persistence.PersistenceManager; -import javax.inject.Named; import javax.inject.Inject; +import javax.inject.Named; import java.io.File; @@ -44,8 +44,8 @@ public class TempProposalStorageService extends MapStoreService persistableNetworkPayloadMapStorage) { - super(storageDir, persistableNetworkPayloadMapStorage); + PersistenceManager persistenceManager) { + super(storageDir, persistenceManager); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -57,6 +57,11 @@ public String getFileName() { return FILE_NAME; } + @Override + protected void initializePersistenceManager() { + persistenceManager.initialize(store, PersistenceManager.Source.NETWORK); + } + @Override public Map getMap() { return store.getMap(); @@ -67,6 +72,12 @@ public boolean canHandle(ProtectedStorageEntry entry) { return entry.getProtectedStoragePayload() instanceof TempProposalPayload; } + @Override + protected void readFromResources(String postFix) { + // We do not have a resource file for that store, so we just call the readStore method instead. + readStore(); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Protected diff --git a/core/src/main/java/bisq/core/dao/governance/proposal/storage/temp/TempProposalStore.java b/core/src/main/java/bisq/core/dao/governance/proposal/storage/temp/TempProposalStore.java index 229f173368b..6ff483eae2e 100644 --- a/core/src/main/java/bisq/core/dao/governance/proposal/storage/temp/TempProposalStore.java +++ b/core/src/main/java/bisq/core/dao/governance/proposal/storage/temp/TempProposalStore.java @@ -21,7 +21,7 @@ import bisq.network.p2p.storage.payload.ProtectedStorageEntry; import bisq.common.proto.network.NetworkProtoResolver; -import bisq.common.proto.persistable.ThreadedPersistableEnvelope; +import bisq.common.proto.persistable.PersistableEnvelope; import com.google.protobuf.Message; @@ -42,9 +42,9 @@ * definition and provide a hashMap for the domain access. */ @Slf4j -public class TempProposalStore implements ThreadedPersistableEnvelope { +public class TempProposalStore implements PersistableEnvelope { @Getter - private Map map = new ConcurrentHashMap<>(); + private final Map map = new ConcurrentHashMap<>(); @Inject TempProposalStore() { diff --git a/core/src/main/java/bisq/core/dao/monitoring/DaoStateMonitoringService.java b/core/src/main/java/bisq/core/dao/monitoring/DaoStateMonitoringService.java index c486ee4a047..604746bcbba 100644 --- a/core/src/main/java/bisq/core/dao/monitoring/DaoStateMonitoringService.java +++ b/core/src/main/java/bisq/core/dao/monitoring/DaoStateMonitoringService.java @@ -39,7 +39,7 @@ import bisq.common.UserThread; import bisq.common.config.Config; import bisq.common.crypto.Hash; -import bisq.common.storage.FileManager; +import bisq.common.file.FileUtil; import bisq.common.util.Utilities; import javax.inject.Inject; @@ -419,7 +419,7 @@ private void removeFile(String storeName) { File corrupted = new File(storageDir, storeName); try { if (corrupted.exists()) { - FileManager.removeAndBackupFile(storageDir, corrupted, newFileName, backupDirName); + FileUtil.removeAndBackupFile(storageDir, corrupted, newFileName, backupDirName); } } catch (Throwable t) { t.printStackTrace(); diff --git a/core/src/main/java/bisq/core/dao/node/explorer/ExportJsonFilesService.java b/core/src/main/java/bisq/core/dao/node/explorer/ExportJsonFilesService.java index 8babe94535c..1c9c605f04d 100644 --- a/core/src/main/java/bisq/core/dao/node/explorer/ExportJsonFilesService.java +++ b/core/src/main/java/bisq/core/dao/node/explorer/ExportJsonFilesService.java @@ -27,8 +27,8 @@ import bisq.core.dao.state.model.blockchain.TxType; import bisq.common.config.Config; -import bisq.common.storage.FileUtil; -import bisq.common.storage.JsonFileManager; +import bisq.common.file.FileUtil; +import bisq.common.file.JsonFileManager; import bisq.common.util.Utilities; import org.bitcoinj.core.Utils; diff --git a/core/src/main/java/bisq/core/dao/node/full/RpcService.java b/core/src/main/java/bisq/core/dao/node/full/RpcService.java index 62c613bf79a..d694cee91c2 100644 --- a/core/src/main/java/bisq/core/dao/node/full/RpcService.java +++ b/core/src/main/java/bisq/core/dao/node/full/RpcService.java @@ -82,7 +82,7 @@ public class RpcService { // We could use multiple threads but then we need to support ordering of results in a queue // Keep that for optimization after measuring performance differences - private final ListeningExecutorService executor = Utilities.getSingleThreadExecutor("RpcService"); + private final ListeningExecutorService executor = Utilities.getSingleThreadListeningExecutor("RpcService"); /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/dao/state/DaoStateService.java b/core/src/main/java/bisq/core/dao/state/DaoStateService.java index 5ad729e25d5..620415a12ca 100644 --- a/core/src/main/java/bisq/core/dao/state/DaoStateService.java +++ b/core/src/main/java/bisq/core/dao/state/DaoStateService.java @@ -149,10 +149,6 @@ public DaoState getClone() { return DaoState.getClone(daoState); } - DaoState getClone(DaoState snapshotCandidate) { - return DaoState.getClone(snapshotCandidate); - } - public byte[] getSerializedStateForHashChain() { return daoState.getSerializedStateForHashChain(); } diff --git a/core/src/main/java/bisq/core/dao/state/DaoStateSnapshotService.java b/core/src/main/java/bisq/core/dao/state/DaoStateSnapshotService.java index df98840cd02..0c0e506016e 100644 --- a/core/src/main/java/bisq/core/dao/state/DaoStateSnapshotService.java +++ b/core/src/main/java/bisq/core/dao/state/DaoStateSnapshotService.java @@ -22,6 +22,7 @@ import bisq.core.dao.monitoring.model.DaoStateHash; import bisq.core.dao.state.model.DaoState; import bisq.core.dao.state.model.blockchain.Block; +import bisq.core.dao.state.storage.DaoStateStorageService; import javax.inject.Inject; @@ -92,13 +93,12 @@ public void maybeCreateSnapshot(Block block) { // At trigger event we store the latest snapshotCandidate to disc long ts = System.currentTimeMillis(); if (daoStateSnapshotCandidate != null) { - // We clone because storage is in a threaded context and we set the snapshotCandidate to our current - // state in the next step - DaoState clonedDaoState = daoStateService.getClone(daoStateSnapshotCandidate); - LinkedList clonedDaoStateHashChain = new LinkedList<>(daoStateHashChainSnapshotCandidate); - daoStateStorageService.persist(clonedDaoState, clonedDaoStateHashChain); - - log.debug("Saved snapshotCandidate with height {} to Disc at height {} took {} ms", + // Serialisation happens on the userThread so we do not need to clone the data. Write to disk happens + // in a thread but does not interfere with our objects as they got already serialized when passed to the + // write thread. We use requestPersistence so we do not write immediately but at next scheduled interval. + // This avoids frequent write at dao sync and better performance. + daoStateStorageService.requestPersistence(daoStateSnapshotCandidate, daoStateHashChainSnapshotCandidate); + log.info("Serializing snapshotCandidate for writing to Disc with height {} at height {} took {} ms", daoStateSnapshotCandidate.getChainHeight(), chainHeight, System.currentTimeMillis() - ts); } diff --git a/core/src/main/java/bisq/core/dao/state/model/governance/BallotList.java b/core/src/main/java/bisq/core/dao/state/model/governance/BallotList.java index d3b4a1f97eb..25eaa415e62 100644 --- a/core/src/main/java/bisq/core/dao/state/model/governance/BallotList.java +++ b/core/src/main/java/bisq/core/dao/state/model/governance/BallotList.java @@ -20,7 +20,7 @@ import bisq.core.dao.governance.ConsensusCritical; import bisq.core.dao.state.model.ImmutableDaoStateModel; -import bisq.common.proto.persistable.UserThreadMappedPersistableList; +import bisq.common.proto.persistable.PersistableList; import java.util.ArrayList; import java.util.List; @@ -35,7 +35,7 @@ */ @Immutable @EqualsAndHashCode(callSuper = true) -public class BallotList extends UserThreadMappedPersistableList implements ConsensusCritical, ImmutableDaoStateModel { +public class BallotList extends PersistableList implements ConsensusCritical, ImmutableDaoStateModel { public BallotList(List list) { super(list); diff --git a/core/src/main/java/bisq/core/dao/state/DaoStateStorageService.java b/core/src/main/java/bisq/core/dao/state/storage/DaoStateStorageService.java similarity index 67% rename from core/src/main/java/bisq/core/dao/state/DaoStateStorageService.java rename to core/src/main/java/bisq/core/dao/state/storage/DaoStateStorageService.java index 8571eb2b12e..ece0166da8e 100644 --- a/core/src/main/java/bisq/core/dao/state/DaoStateStorageService.java +++ b/core/src/main/java/bisq/core/dao/state/storage/DaoStateStorageService.java @@ -15,7 +15,7 @@ * along with bisq. If not, see . */ -package bisq.core.dao.state; +package bisq.core.dao.state.storage; import bisq.core.dao.monitoring.DaoStateMonitoringService; import bisq.core.dao.monitoring.model.DaoStateHash; @@ -24,10 +24,9 @@ import bisq.network.p2p.storage.persistence.ResourceDataStoreService; import bisq.network.p2p.storage.persistence.StoreService; -import bisq.common.UserThread; import bisq.common.config.Config; -import bisq.common.storage.FileManager; -import bisq.common.storage.Storage; +import bisq.common.file.FileUtil; +import bisq.common.persistence.PersistenceManager; import javax.inject.Inject; import javax.inject.Named; @@ -36,7 +35,6 @@ import java.io.IOException; import java.util.LinkedList; -import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; @@ -45,11 +43,6 @@ */ @Slf4j public class DaoStateStorageService extends StoreService { - // We needed to rename the db file as we have a new file structure with the hashChain feature and need to enforce the - // new file to be used. - // We can rename to DaoStateStore before mainnet launch again. - // Another update due to some data field changes which would cause diff. hashes, so to enforce users to get the new - // data we rename it to DaoStateStore private static final String FILE_NAME = "DaoStateStore"; private final DaoState daoState; @@ -65,8 +58,8 @@ public DaoStateStorageService(ResourceDataStoreService resourceDataStoreService, DaoState daoState, DaoStateMonitoringService daoStateMonitoringService, @Named(Config.STORAGE_DIR) File storageDir, - Storage daoSnapshotStorage) { - super(storageDir, daoSnapshotStorage); + PersistenceManager persistenceManager) { + super(storageDir, persistenceManager); this.daoState = daoState; this.daoStateMonitoringService = daoStateMonitoringService; @@ -83,16 +76,10 @@ public String getFileName() { return FILE_NAME; } - public void persist(DaoState daoState, LinkedList daoStateHashChain) { - persist(daoState, daoStateHashChain, 200); - } - - private void persist(DaoState daoState, LinkedList daoStateHashChain, long delayInMilli) { - store.modifySynchronized(() -> { - store.setDaoState(daoState); - store.setDaoStateHashChain(daoStateHashChain); - }); - storage.queueUpForSave(store, delayInMilli); + public void requestPersistence(DaoState daoState, LinkedList daoStateHashChain) { + store.setDaoState(daoState); + store.setDaoStateHashChain(daoStateHashChain); + persistenceManager.requestPersistence(); } public DaoState getPersistedBsqState() { @@ -104,8 +91,9 @@ public LinkedList getPersistedDaoStateHashChain() { } public void resyncDaoStateFromGenesis(Runnable resultHandler) { - persist(new DaoState(), new LinkedList<>(), 1); - UserThread.runAfter(resultHandler, 300, TimeUnit.MILLISECONDS); + store.setDaoState(new DaoState()); + store.setDaoStateHashChain(new LinkedList<>()); + persistenceManager.persistNow(resultHandler); } public void resyncDaoStateFromResources(File storageDir) throws IOException { @@ -114,20 +102,20 @@ public void resyncDaoStateFromResources(File storageDir) throws IOException { long currentTime = System.currentTimeMillis(); String backupDirName = "out_of_sync_dao_data"; String newFileName = "BlindVoteStore_" + currentTime; - FileManager.removeAndBackupFile(storageDir, new File(storageDir, "BlindVoteStore"), newFileName, backupDirName); + FileUtil.removeAndBackupFile(storageDir, new File(storageDir, "BlindVoteStore"), newFileName, backupDirName); newFileName = "ProposalStore_" + currentTime; - FileManager.removeAndBackupFile(storageDir, new File(storageDir, "ProposalStore"), newFileName, backupDirName); + FileUtil.removeAndBackupFile(storageDir, new File(storageDir, "ProposalStore"), newFileName, backupDirName); // We also need to remove ballot list as it contains the proposals as well. It will be recreated at resync newFileName = "BallotList_" + currentTime; - FileManager.removeAndBackupFile(storageDir, new File(storageDir, "BallotList"), newFileName, backupDirName); + FileUtil.removeAndBackupFile(storageDir, new File(storageDir, "BallotList"), newFileName, backupDirName); newFileName = "UnconfirmedBsqChangeOutputList_" + currentTime; - FileManager.removeAndBackupFile(storageDir, new File(storageDir, "UnconfirmedBsqChangeOutputList"), newFileName, backupDirName); + FileUtil.removeAndBackupFile(storageDir, new File(storageDir, "UnconfirmedBsqChangeOutputList"), newFileName, backupDirName); newFileName = "DaoStateStore_" + currentTime; - FileManager.removeAndBackupFile(storageDir, new File(storageDir, "DaoStateStore"), newFileName, backupDirName); + FileUtil.removeAndBackupFile(storageDir, new File(storageDir, "DaoStateStore"), newFileName, backupDirName); } @@ -139,4 +127,9 @@ public void resyncDaoStateFromResources(File storageDir) throws IOException { protected DaoStateStore createStore() { return new DaoStateStore(DaoState.getClone(daoState), new LinkedList<>(daoStateMonitoringService.getDaoStateHashChain())); } + + @Override + protected void initializePersistenceManager() { + persistenceManager.initialize(store, PersistenceManager.Source.NETWORK); + } } diff --git a/core/src/main/java/bisq/core/dao/state/DaoStateStore.java b/core/src/main/java/bisq/core/dao/state/storage/DaoStateStore.java similarity index 94% rename from core/src/main/java/bisq/core/dao/state/DaoStateStore.java rename to core/src/main/java/bisq/core/dao/state/storage/DaoStateStore.java index 144479c2817..fe99e39724a 100644 --- a/core/src/main/java/bisq/core/dao/state/DaoStateStore.java +++ b/core/src/main/java/bisq/core/dao/state/storage/DaoStateStore.java @@ -15,12 +15,12 @@ * along with Bisq. If not, see . */ -package bisq.core.dao.state; +package bisq.core.dao.state.storage; import bisq.core.dao.monitoring.model.DaoStateHash; import bisq.core.dao.state.model.DaoState; -import bisq.common.proto.persistable.ThreadedPersistableEnvelope; +import bisq.common.proto.persistable.PersistableEnvelope; import com.google.protobuf.Message; @@ -35,7 +35,7 @@ @Slf4j -public class DaoStateStore implements ThreadedPersistableEnvelope { +public class DaoStateStore implements PersistableEnvelope { // DaoState is always a clone and must not be used for read access beside initial read from disc when we apply // the snapshot! @Getter diff --git a/core/src/main/java/bisq/core/dao/state/unconfirmed/UnconfirmedBsqChangeOutputList.java b/core/src/main/java/bisq/core/dao/state/unconfirmed/UnconfirmedBsqChangeOutputList.java index 391afda22f7..2deae256979 100644 --- a/core/src/main/java/bisq/core/dao/state/unconfirmed/UnconfirmedBsqChangeOutputList.java +++ b/core/src/main/java/bisq/core/dao/state/unconfirmed/UnconfirmedBsqChangeOutputList.java @@ -17,7 +17,7 @@ package bisq.core.dao.state.unconfirmed; -import bisq.common.proto.persistable.UserThreadMappedPersistableList; +import bisq.common.proto.persistable.PersistableList; import com.google.protobuf.Message; @@ -28,7 +28,7 @@ import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) -public class UnconfirmedBsqChangeOutputList extends UserThreadMappedPersistableList { +public class UnconfirmedBsqChangeOutputList extends PersistableList { UnconfirmedBsqChangeOutputList() { super(); diff --git a/core/src/main/java/bisq/core/dao/state/unconfirmed/UnconfirmedBsqChangeOutputListService.java b/core/src/main/java/bisq/core/dao/state/unconfirmed/UnconfirmedBsqChangeOutputListService.java index 432f3c86959..467800748ff 100644 --- a/core/src/main/java/bisq/core/dao/state/unconfirmed/UnconfirmedBsqChangeOutputListService.java +++ b/core/src/main/java/bisq/core/dao/state/unconfirmed/UnconfirmedBsqChangeOutputListService.java @@ -20,8 +20,8 @@ import bisq.core.dao.state.model.blockchain.TxType; import bisq.common.app.DevEnv; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.persistable.PersistedDataHost; -import bisq.common.storage.Storage; import org.bitcoinj.core.Coin; import org.bitcoinj.core.Transaction; @@ -40,11 +40,13 @@ @Slf4j public class UnconfirmedBsqChangeOutputListService implements PersistedDataHost { private final UnconfirmedBsqChangeOutputList unconfirmedBsqChangeOutputList = new UnconfirmedBsqChangeOutputList(); - private final Storage storage; + private final PersistenceManager persistenceManager; @Inject - public UnconfirmedBsqChangeOutputListService(Storage storage) { - this.storage = storage; + public UnconfirmedBsqChangeOutputListService(PersistenceManager persistenceManager) { + this.persistenceManager = persistenceManager; + + this.persistenceManager.initialize(unconfirmedBsqChangeOutputList, PersistenceManager.Source.PRIVATE); } @@ -55,10 +57,9 @@ public UnconfirmedBsqChangeOutputListService(Storage { unconfirmedBsqChangeOutputList.remove(txOutput); - persist(); + requestPersistence(); }); } private void reset() { unconfirmedBsqChangeOutputList.clear(); - persist(); + requestPersistence(); } - private void persist() { - storage.queueUpForSave(); + private void requestPersistence() { + persistenceManager.requestPersistence(); } } diff --git a/core/src/main/java/bisq/core/notifications/alerts/DisputeMsgEvents.java b/core/src/main/java/bisq/core/notifications/alerts/DisputeMsgEvents.java index f6c4d97e9d1..ca4322f3e16 100644 --- a/core/src/main/java/bisq/core/notifications/alerts/DisputeMsgEvents.java +++ b/core/src/main/java/bisq/core/notifications/alerts/DisputeMsgEvents.java @@ -21,6 +21,7 @@ import bisq.core.notifications.MobileMessage; import bisq.core.notifications.MobileMessageType; import bisq.core.notifications.MobileNotificationService; +import bisq.core.support.SupportType; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.mediation.MediationManager; import bisq.core.support.dispute.refund.RefundManager; @@ -123,6 +124,11 @@ private void onChatMessage(ChatMessage chatMessage, Dispute dispute) { // trader/mediator/arbitrator who has reopened the case if (dispute.isClosed() && !chatMessages.isEmpty() && !chatMessages.get(chatMessages.size() - 1).isResultMessage(dispute)) { dispute.setIsClosed(false); + if (dispute.getSupportType() == SupportType.MEDIATION) { + mediationManager.requestPersistence(); + } else if (dispute.getSupportType() == SupportType.REFUND) { + refundManager.requestPersistence(); + } } } } diff --git a/core/src/main/java/bisq/core/notifications/alerts/TradeEvents.java b/core/src/main/java/bisq/core/notifications/alerts/TradeEvents.java index 007583885ce..7bfe9556930 100644 --- a/core/src/main/java/bisq/core/notifications/alerts/TradeEvents.java +++ b/core/src/main/java/bisq/core/notifications/alerts/TradeEvents.java @@ -53,13 +53,13 @@ public TradeEvents(TradeManager tradeManager, KeyRing keyRing, MobileNotificatio } public void onAllServicesInitialized() { - tradeManager.getTradesAsObservableList().addListener((ListChangeListener) c -> { + tradeManager.getObservableList().addListener((ListChangeListener) c -> { c.next(); if (c.wasAdded()) { c.getAddedSubList().forEach(this::setTradePhaseListener); } }); - tradeManager.getTradesAsObservableList().forEach(this::setTradePhaseListener); + tradeManager.getObservableList().forEach(this::setTradePhaseListener); } private void setTradePhaseListener(Trade trade) { diff --git a/core/src/main/java/bisq/core/notifications/alerts/market/MarketAlerts.java b/core/src/main/java/bisq/core/notifications/alerts/market/MarketAlerts.java index ee2d092ac83..bd170c08792 100644 --- a/core/src/main/java/bisq/core/notifications/alerts/market/MarketAlerts.java +++ b/core/src/main/java/bisq/core/notifications/alerts/market/MarketAlerts.java @@ -194,7 +194,7 @@ else if (!isFiatCurrency && !isSellOffer) // In case we have disabled alerts wasSent is false and we do not // persist the offer marketAlertFilter.addAlertId(alertId); - user.persist(); + user.requestPersistence(); } } catch (Exception e) { e.printStackTrace(); diff --git a/core/src/main/java/bisq/core/offer/OfferBookService.java b/core/src/main/java/bisq/core/offer/OfferBookService.java index 2085429d054..4176132baa9 100644 --- a/core/src/main/java/bisq/core/offer/OfferBookService.java +++ b/core/src/main/java/bisq/core/offer/OfferBookService.java @@ -28,9 +28,9 @@ import bisq.common.UserThread; import bisq.common.config.Config; +import bisq.common.file.JsonFileManager; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; -import bisq.common.storage.JsonFileManager; import bisq.common.util.Utilities; import javax.inject.Inject; diff --git a/core/src/main/java/bisq/core/offer/OpenOffer.java b/core/src/main/java/bisq/core/offer/OpenOffer.java index ced8cce9834..5d9cfb4794e 100644 --- a/core/src/main/java/bisq/core/offer/OpenOffer.java +++ b/core/src/main/java/bisq/core/offer/OpenOffer.java @@ -18,14 +18,12 @@ package bisq.core.offer; import bisq.core.trade.Tradable; -import bisq.core.trade.TradableList; import bisq.network.p2p.NodeAddress; import bisq.common.Timer; import bisq.common.UserThread; import bisq.common.proto.ProtoUtil; -import bisq.common.storage.Storage; import java.util.Date; import java.util.Optional; @@ -71,11 +69,9 @@ public enum State { @Nullable private NodeAddress refundAgentNodeAddress; - transient private Storage> storage; - public OpenOffer(Offer offer, Storage> storage) { + public OpenOffer(Offer offer) { this.offer = offer; - this.storage = storage; state = State.AVAILABLE; } @@ -139,21 +135,15 @@ public String getShortId() { return offer.getShortId(); } - public void setStorage(Storage> storage) { - this.storage = storage; - } - public void setState(State state) { - boolean changed = this.state != state; this.state = state; - if (changed && storage != null) - storage.queueUpForSave(); // We keep it reserved for a limited time, if trade preparation fails we revert to available state - if (this.state == State.RESERVED) + if (this.state == State.RESERVED) { startTimeout(); - else + } else { stopTimeout(); + } } public boolean isDeactivated() { @@ -164,9 +154,11 @@ private void startTimeout() { stopTimeout(); timeoutTimer = UserThread.runAfter(() -> { - log.debug("Timeout for resettin State.RESERVED reached"); - if (state == State.RESERVED) + log.debug("Timeout for resetting State.RESERVED reached"); + if (state == State.RESERVED) { + // we do not need to persist that as at startup any RESERVED state would be reset to AVAILABLE anyway setState(State.AVAILABLE); + } }, TIMEOUT); } diff --git a/core/src/main/java/bisq/core/offer/OpenOfferManager.java b/core/src/main/java/bisq/core/offer/OpenOfferManager.java index 5b74864790c..1e72532a612 100644 --- a/core/src/main/java/bisq/core/offer/OpenOfferManager.java +++ b/core/src/main/java/bisq/core/offer/OpenOfferManager.java @@ -60,9 +60,9 @@ import bisq.common.crypto.PubKeyRing; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.network.NetworkEnvelope; import bisq.common.proto.persistable.PersistedDataHost; -import bisq.common.storage.Storage; import org.bitcoinj.core.Coin; @@ -113,11 +113,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe private final RefundAgentManager refundAgentManager; private final DaoFacade daoFacade; private final FilterManager filterManager; - private final Storage> openOfferTradableListStorage; + private final PersistenceManager> persistenceManager; private final Map offersToBeEdited = new HashMap<>(); + private final TradableList openOffers = new TradableList<>(); private boolean stopped; private Timer periodicRepublishOffersTimer, periodicRefreshOffersTimer, retryRepublishOffersTimer; - private TradableList openOffers; /////////////////////////////////////////////////////////////////////////////////////////// @@ -142,7 +142,7 @@ public OpenOfferManager(CreateOfferService createOfferService, RefundAgentManager refundAgentManager, DaoFacade daoFacade, FilterManager filterManager, - Storage> storage) { + PersistenceManager> persistenceManager) { this.createOfferService = createOfferService; this.keyRing = keyRing; this.user = user; @@ -160,17 +160,18 @@ public OpenOfferManager(CreateOfferService createOfferService, this.refundAgentManager = refundAgentManager; this.daoFacade = daoFacade; this.filterManager = filterManager; + this.persistenceManager = persistenceManager; - openOfferTradableListStorage = storage; - - // In case the app did get killed the shutDown from the modules is not called, so we use a shutdown hook - Runtime.getRuntime().addShutdownHook(new Thread(() -> - UserThread.execute(OpenOfferManager.this::shutDown), "OpenOfferManager.ShutDownHook")); + this.persistenceManager.initialize(openOffers, "OpenOffers", PersistenceManager.Source.PRIVATE); } @Override public void readPersisted() { - openOffers = new TradableList<>(openOfferTradableListStorage, "OpenOffers"); + TradableList persisted = persistenceManager.getPersisted(); + if (persisted != null) { + openOffers.setAll(persisted.getList()); + } + openOffers.forEach(e -> { Offer offer = e.getOffer(); offer.setPriceFeedService(priceFeedService); @@ -221,7 +222,7 @@ public void shutDown(@Nullable Runnable completeHandler) { // we remove own offers from offerbook when we go offline // Normally we use a delay for broadcasting to the peers, but at shut down we want to get it fast out - int size = openOffers != null ? openOffers.size() : 0; + int size = openOffers.size(); log.info("Remove open offers at shutDown. Number of open offers: {}", size); if (offerBookService.isBootstrapped() && size > 0) { UserThread.execute(() -> openOffers.forEach( @@ -240,7 +241,7 @@ public void removeAllOpenOffers(@Nullable Runnable completeHandler) { } private void removeOpenOffers(List openOffers, @Nullable Runnable completeHandler) { - final int size = openOffers.size(); + int size = openOffers.size(); // Copy list as we remove in the loop List openOffersList = new ArrayList<>(openOffers); openOffersList.forEach(openOffer -> removeOpenOffer(openOffer, () -> { @@ -370,9 +371,9 @@ public void placeOffer(Offer offer, PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol( model, transaction -> { - OpenOffer openOffer = new OpenOffer(offer, openOfferTradableListStorage); + OpenOffer openOffer = new OpenOffer(offer); openOffers.add(openOffer); - openOfferTradableListStorage.queueUpForSave(); + requestPersistence(); resultHandler.handleResult(transaction); if (!stopped) { startPeriodicRepublishOffersTimer(); @@ -406,10 +407,10 @@ public void activateOpenOffer(OpenOffer openOffer, ErrorMessageHandler errorMessageHandler) { if (!offersToBeEdited.containsKey(openOffer.getId())) { Offer offer = openOffer.getOffer(); - openOffer.setStorage(openOfferTradableListStorage); offerBookService.activateOffer(offer, () -> { openOffer.setState(OpenOffer.State.AVAILABLE); + requestPersistence(); log.debug("activateOpenOffer, offerId={}", offer.getId()); resultHandler.handleResult(); }, @@ -423,10 +424,10 @@ public void deactivateOpenOffer(OpenOffer openOffer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { Offer offer = openOffer.getOffer(); - openOffer.setStorage(openOfferTradableListStorage); offerBookService.deactivateOffer(offer.getOfferPayload(), () -> { openOffer.setState(OpenOffer.State.DEACTIVATED); + requestPersistence(); log.debug("deactivateOpenOffer, offerId={}", offer.getId()); resultHandler.handleResult(); }, @@ -439,7 +440,6 @@ public void removeOpenOffer(OpenOffer openOffer, if (!offersToBeEdited.containsKey(openOffer.getId())) { Offer offer = openOffer.getOffer(); if (openOffer.isDeactivated()) { - openOffer.setStorage(openOfferTradableListStorage); onRemoved(openOffer, resultHandler, offer); } else { offerBookService.removeOffer(offer.getOfferPayload(), @@ -481,15 +481,13 @@ public void editOpenOfferPublish(Offer editedOffer, Optional openOfferOptional = getOpenOfferById(editedOffer.getId()); if (openOfferOptional.isPresent()) { - final OpenOffer openOffer = openOfferOptional.get(); - - openOffer.setStorage(openOfferTradableListStorage); + OpenOffer openOffer = openOfferOptional.get(); openOffer.getOffer().setState(Offer.State.REMOVED); openOffer.setState(OpenOffer.State.CANCELED); openOffers.remove(openOffer); - final OpenOffer editedOpenOffer = new OpenOffer(editedOffer, openOfferTradableListStorage); + OpenOffer editedOpenOffer = new OpenOffer(editedOffer); editedOpenOffer.setState(originalState); openOffers.add(editedOpenOffer); @@ -498,7 +496,7 @@ public void editOpenOfferPublish(Offer editedOffer, republishOffer(editedOpenOffer); offersToBeEdited.remove(openOffer.getId()); - + requestPersistence(); resultHandler.handleResult(); } else { errorMessageHandler.handleErrorMessage("There is no offer with this id existing to be published."); @@ -528,6 +526,7 @@ private void onRemoved(@NotNull OpenOffer openOffer, ResultHandler resultHandler closedTradableManager.add(openOffer); log.info("onRemoved offerId={}", offer.getId()); btcWalletService.resetAddressEntriesForOpenOffer(offer.getId()); + requestPersistence(); resultHandler.handleResult(); } @@ -539,11 +538,13 @@ public void closeOpenOffer(Offer offer) { offerBookService.removeOffer(openOffer.getOffer().getOfferPayload(), () -> log.trace("Successful removed offer"), log::error); + requestPersistence(); }); } public void reserveOpenOffer(OpenOffer openOffer) { openOffer.setState(OpenOffer.State.RESERVED); + requestPersistence(); } @@ -556,7 +557,7 @@ public boolean isMyOffer(Offer offer) { } public ObservableList getObservableList() { - return openOffers.getList(); + return openOffers.getObservableList(); } public Optional getOpenOfferById(String offerId) { @@ -828,7 +829,6 @@ private void maybeUpdatePersistedOffers() { // remove old offer originalOffer.setState(Offer.State.REMOVED); originalOpenOffer.setState(OpenOffer.State.CANCELED); - originalOpenOffer.setStorage(openOfferTradableListStorage); openOffers.remove(originalOpenOffer); // Create new Offer @@ -836,10 +836,10 @@ private void maybeUpdatePersistedOffers() { updatedOffer.setPriceFeedService(priceFeedService); updatedOffer.setState(originalOfferState); - OpenOffer updatedOpenOffer = new OpenOffer(updatedOffer, openOfferTradableListStorage); + OpenOffer updatedOpenOffer = new OpenOffer(updatedOffer); updatedOpenOffer.setState(originalOpenOfferState); - updatedOpenOffer.setStorage(openOfferTradableListStorage); openOffers.add(updatedOpenOffer); + requestPersistence(); log.info("Updating offer completed. id={}", originalOffer.getId()); } @@ -899,7 +899,6 @@ private void republishOffer(OpenOffer openOffer) { log.debug("We have stopped already. We ignore that offerBookService.republishOffers.onFault call."); } }); - openOffer.setStorage(openOfferTradableListStorage); } private void startPeriodicRepublishOffersTimer() { @@ -969,6 +968,10 @@ private void restart() { startPeriodicRepublishOffersTimer(); } + private void requestPersistence() { + persistenceManager.requestPersistence(); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Private diff --git a/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java b/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java index aad97d33f42..8d183d40857 100644 --- a/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java +++ b/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java @@ -101,10 +101,6 @@ public long getTakersTradePrice() { return offer.getPrice() != null ? offer.getPrice().getValue() : 0; } - @Override - public void persist() { - } - @Override public void onComplete() { } diff --git a/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java b/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java index 6862661b391..f6c04fa1982 100644 --- a/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java +++ b/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java @@ -34,7 +34,8 @@ @Slf4j public class ProcessOfferAvailabilityResponse extends Task { - public ProcessOfferAvailabilityResponse(TaskRunner taskHandler, OfferAvailabilityModel model) { + public ProcessOfferAvailabilityResponse(TaskRunner taskHandler, + OfferAvailabilityModel model) { super(taskHandler, model); } diff --git a/core/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java b/core/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java index 73f1cb7c1f1..3ee88536c64 100644 --- a/core/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java +++ b/core/src/main/java/bisq/core/offer/availability/tasks/SendOfferAvailabilityRequest.java @@ -30,7 +30,7 @@ @Slf4j public class SendOfferAvailabilityRequest extends Task { - public SendOfferAvailabilityRequest(TaskRunner taskHandler, OfferAvailabilityModel model) { + public SendOfferAvailabilityRequest(TaskRunner taskHandler, OfferAvailabilityModel model) { super(taskHandler, model); } diff --git a/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java b/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java index 8a139de6eea..0c54d733ed0 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferModel.java @@ -87,10 +87,6 @@ public PlaceOfferModel(Offer offer, this.filterManager = filterManager; } - @Override - public void persist() { - } - @Override public void onComplete() { } diff --git a/core/src/main/java/bisq/core/offer/placeoffer/tasks/AddToOfferBook.java b/core/src/main/java/bisq/core/offer/placeoffer/tasks/AddToOfferBook.java index 49c3702ba78..16def612ba0 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/tasks/AddToOfferBook.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/tasks/AddToOfferBook.java @@ -22,15 +22,9 @@ import bisq.common.taskrunner.Task; import bisq.common.taskrunner.TaskRunner; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - public class AddToOfferBook extends Task { - @SuppressWarnings("unused") - private static final Logger log = LoggerFactory.getLogger(AddToOfferBook.class); - @SuppressWarnings({"WeakerAccess", "unused"}) - public AddToOfferBook(TaskRunner taskHandler, PlaceOfferModel model) { + public AddToOfferBook(TaskRunner taskHandler, PlaceOfferModel model) { super(taskHandler, model); } diff --git a/core/src/main/java/bisq/core/offer/placeoffer/tasks/CheckNumberOfUnconfirmedTransactions.java b/core/src/main/java/bisq/core/offer/placeoffer/tasks/CheckNumberOfUnconfirmedTransactions.java index 0dbe944ea4f..31e8d300032 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/tasks/CheckNumberOfUnconfirmedTransactions.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/tasks/CheckNumberOfUnconfirmedTransactions.java @@ -7,7 +7,7 @@ import bisq.common.taskrunner.TaskRunner; public class CheckNumberOfUnconfirmedTransactions extends Task { - public CheckNumberOfUnconfirmedTransactions(TaskRunner taskHandler, PlaceOfferModel model) { + public CheckNumberOfUnconfirmedTransactions(TaskRunner taskHandler, PlaceOfferModel model) { super(taskHandler, model); } diff --git a/core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java b/core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java index 51cd12b6f4f..df697134319 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/tasks/CreateMakerFeeTx.java @@ -46,7 +46,7 @@ public class CreateMakerFeeTx extends Task { private static final Logger log = LoggerFactory.getLogger(CreateMakerFeeTx.class); @SuppressWarnings({"unused"}) - public CreateMakerFeeTx(TaskRunner taskHandler, PlaceOfferModel model) { + public CreateMakerFeeTx(TaskRunner taskHandler, PlaceOfferModel model) { super(taskHandler, model); } diff --git a/core/src/main/java/bisq/core/offer/placeoffer/tasks/ValidateOffer.java b/core/src/main/java/bisq/core/offer/placeoffer/tasks/ValidateOffer.java index 05c88acf468..35d619feb4d 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/tasks/ValidateOffer.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/tasks/ValidateOffer.java @@ -26,18 +26,11 @@ import org.bitcoinj.core.Coin; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; public class ValidateOffer extends Task { - @SuppressWarnings("unused") - private static final Logger log = LoggerFactory.getLogger(ValidateOffer.class); - - @SuppressWarnings({"WeakerAccess", "unused"}) - public ValidateOffer(TaskRunner taskHandler, PlaceOfferModel model) { + public ValidateOffer(TaskRunner taskHandler, PlaceOfferModel model) { super(taskHandler, model); } diff --git a/core/src/main/java/bisq/core/payment/PaymentAccountList.java b/core/src/main/java/bisq/core/payment/PaymentAccountList.java index efe9cccc229..a35801705ce 100644 --- a/core/src/main/java/bisq/core/payment/PaymentAccountList.java +++ b/core/src/main/java/bisq/core/payment/PaymentAccountList.java @@ -19,7 +19,7 @@ import bisq.core.proto.CoreProtoResolver; -import bisq.common.proto.persistable.UserThreadMappedPersistableList; +import bisq.common.proto.persistable.PersistableList; import com.google.protobuf.Message; @@ -30,7 +30,7 @@ import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) -public class PaymentAccountList extends UserThreadMappedPersistableList { +public class PaymentAccountList extends PersistableList { public PaymentAccountList(List list) { super(list); diff --git a/core/src/main/java/bisq/core/presentation/TradePresentation.java b/core/src/main/java/bisq/core/presentation/TradePresentation.java index e7f43c7f538..678f827b590 100644 --- a/core/src/main/java/bisq/core/presentation/TradePresentation.java +++ b/core/src/main/java/bisq/core/presentation/TradePresentation.java @@ -36,9 +36,6 @@ public class TradePresentation { @Inject public TradePresentation(TradeManager tradeManager) { - // TODO replace for tradeManager.getTradesAsObservableList() once tradableList is refactored to a final field - // (part of the persistence refactor PR) - tradeManager.getNumPendingTrades().addListener((observable, oldValue, newValue) -> { long numPendingTrades = (long) newValue; if (numPendingTrades > 0) diff --git a/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java b/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java index b38a015fbb1..efefffe881a 100644 --- a/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java +++ b/core/src/main/java/bisq/core/proto/persistable/CorePersistenceProtoResolver.java @@ -29,8 +29,8 @@ import bisq.core.dao.governance.proposal.MyProposalList; import bisq.core.dao.governance.proposal.storage.appendonly.ProposalStore; import bisq.core.dao.governance.proposal.storage.temp.TempProposalStore; -import bisq.core.dao.state.DaoStateStore; import bisq.core.dao.state.model.governance.BallotList; +import bisq.core.dao.state.storage.DaoStateStore; import bisq.core.dao.state.unconfirmed.UnconfirmedBsqChangeOutputList; import bisq.core.payment.PaymentAccountList; import bisq.core.proto.CoreProtoResolver; @@ -45,23 +45,17 @@ import bisq.network.p2p.peers.peerexchange.PeerList; import bisq.network.p2p.storage.persistence.SequenceNumberMap; -import bisq.common.config.Config; import bisq.common.proto.ProtobufferRuntimeException; import bisq.common.proto.network.NetworkProtoResolver; import bisq.common.proto.persistable.NavigationPath; import bisq.common.proto.persistable.PersistableEnvelope; import bisq.common.proto.persistable.PersistenceProtoResolver; -import bisq.common.storage.CorruptedDatabaseFilesHandler; -import bisq.common.storage.Storage; import com.google.inject.Provider; import javax.inject.Inject; -import javax.inject.Named; import javax.inject.Singleton; -import java.io.File; - import lombok.extern.slf4j.Slf4j; // TODO Use ProtobufferException instead of ProtobufferRuntimeException @@ -70,19 +64,12 @@ public class CorePersistenceProtoResolver extends CoreProtoResolver implements PersistenceProtoResolver { private final Provider btcWalletService; private final NetworkProtoResolver networkProtoResolver; - private final File storageDir; - private final CorruptedDatabaseFilesHandler corruptedDatabaseFilesHandler; @Inject public CorePersistenceProtoResolver(Provider btcWalletService, - NetworkProtoResolver networkProtoResolver, - @Named(Config.STORAGE_DIR) File storageDir, - CorruptedDatabaseFilesHandler corruptedDatabaseFilesHandler) { + NetworkProtoResolver networkProtoResolver) { this.btcWalletService = btcWalletService; this.networkProtoResolver = networkProtoResolver; - this.storageDir = storageDir; - - this.corruptedDatabaseFilesHandler = corruptedDatabaseFilesHandler; } @Override @@ -96,22 +83,13 @@ public PersistableEnvelope fromProto(protobuf.PersistableEnvelope proto) { case ADDRESS_ENTRY_LIST: return AddressEntryList.fromProto(proto.getAddressEntryList()); case TRADABLE_LIST: - return TradableList.fromProto(proto.getTradableList(), - this, - new Storage<>(storageDir, this, corruptedDatabaseFilesHandler), - btcWalletService.get()); + return TradableList.fromProto(proto.getTradableList(), this, btcWalletService.get()); case ARBITRATION_DISPUTE_LIST: - return ArbitrationDisputeList.fromProto(proto.getArbitrationDisputeList(), - this, - new Storage<>(storageDir, this, corruptedDatabaseFilesHandler)); + return ArbitrationDisputeList.fromProto(proto.getArbitrationDisputeList(), this); case MEDIATION_DISPUTE_LIST: - return MediationDisputeList.fromProto(proto.getMediationDisputeList(), - this, - new Storage<>(storageDir, this, corruptedDatabaseFilesHandler)); + return MediationDisputeList.fromProto(proto.getMediationDisputeList(), this); case REFUND_DISPUTE_LIST: - return RefundDisputeList.fromProto(proto.getRefundDisputeList(), - this, - new Storage<>(storageDir, this, corruptedDatabaseFilesHandler)); + return RefundDisputeList.fromProto(proto.getRefundDisputeList(), this); case PREFERENCES_PAYLOAD: return PreferencesPayload.fromProto(proto.getPreferencesPayload(), this); case USER_PAYLOAD: diff --git a/core/src/main/java/bisq/core/support/SupportManager.java b/core/src/main/java/bisq/core/support/SupportManager.java index 5d3f33200c5..4017686fa8a 100644 --- a/core/src/main/java/bisq/core/support/SupportManager.java +++ b/core/src/main/java/bisq/core/support/SupportManager.java @@ -94,7 +94,7 @@ public SupportManager(P2PService p2PService, WalletsSetup walletsSetup) { public abstract void addAndPersistChatMessage(ChatMessage message); - public abstract void persist(); + public abstract void requestPersistence(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -174,7 +174,7 @@ private void onAckMessage(AckMessage ackMessage, else msg.setAckError(ackMessage.getErrorMessage()); }); - persist(); + requestPersistence(); if (decryptedMessageWithPubKey != null) p2PService.removeEntryFromMailbox(decryptedMessageWithPubKey); @@ -204,7 +204,7 @@ public void onArrived() { log.info("{} arrived at peer {}. tradeId={}, uid={}", message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); message.setArrived(true); - persist(); + requestPersistence(); } @Override @@ -212,7 +212,7 @@ public void onStoredInMailbox() { log.info("{} stored in mailbox for peer {}. tradeId={}, uid={}", message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid()); message.setStoredInMailbox(true); - persist(); + requestPersistence(); } @Override @@ -220,7 +220,7 @@ public void onFault(String errorMessage) { log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}", message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage); message.setSendMessageError(errorMessage); - persist(); + requestPersistence(); } } ); diff --git a/core/src/main/java/bisq/core/support/dispute/Dispute.java b/core/src/main/java/bisq/core/support/dispute/Dispute.java index 2cfd0850d7f..a622d6bc738 100644 --- a/core/src/main/java/bisq/core/support/dispute/Dispute.java +++ b/core/src/main/java/bisq/core/support/dispute/Dispute.java @@ -25,7 +25,7 @@ import bisq.common.crypto.PubKeyRing; import bisq.common.proto.ProtoUtil; import bisq.common.proto.network.NetworkPayload; -import bisq.common.storage.Storage; +import bisq.common.proto.persistable.PersistablePayload; import bisq.common.util.Utilities; import com.google.protobuf.ByteString; @@ -57,7 +57,7 @@ @Slf4j @EqualsAndHashCode @Getter -public final class Dispute implements NetworkPayload { +public final class Dispute implements NetworkPayload, PersistablePayload { private final String tradeId; private final String id; private final int traderId; @@ -85,15 +85,14 @@ public final class Dispute implements NetworkPayload { private final PubKeyRing agentPubKeyRing; // dispute agent private final boolean isSupportTicket; private final ObservableList chatMessages = FXCollections.observableArrayList(); - private BooleanProperty isClosedProperty = new SimpleBooleanProperty(); + private final BooleanProperty isClosedProperty = new SimpleBooleanProperty(); // disputeResultProperty.get is Nullable! - private ObjectProperty disputeResultProperty = new SimpleObjectProperty<>(); + private final ObjectProperty disputeResultProperty = new SimpleObjectProperty<>(); + private final long openingDate; @Nullable + @Setter private String disputePayoutTxId; - private long openingDate; - - transient private Storage storage; - + @Setter // Added v1.2.0 private SupportType supportType; // Only used at refundAgent so that he knows how the mediator resolved the case @@ -118,7 +117,7 @@ public final class Dispute implements NetworkPayload { // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - public Dispute(Storage storage, + public Dispute(long openingDate, String tradeId, int traderId, boolean disputeOpenerIsBuyer, @@ -137,51 +136,7 @@ public Dispute(Storage storage, PubKeyRing agentPubKeyRing, boolean isSupportTicket, SupportType supportType) { - this(tradeId, - traderId, - disputeOpenerIsBuyer, - disputeOpenerIsMaker, - traderPubKeyRing, - tradeDate, - contract, - contractHash, - depositTxSerialized, - payoutTxSerialized, - depositTxId, - payoutTxId, - contractAsJson, - makerContractSignature, - takerContractSignature, - agentPubKeyRing, - isSupportTicket, - supportType); - this.storage = storage; - openingDate = new Date().getTime(); - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // PROTO BUFFER - /////////////////////////////////////////////////////////////////////////////////////////// - - public Dispute(String tradeId, - int traderId, - boolean disputeOpenerIsBuyer, - boolean disputeOpenerIsMaker, - PubKeyRing traderPubKeyRing, - long tradeDate, - Contract contract, - @Nullable byte[] contractHash, - @Nullable byte[] depositTxSerialized, - @Nullable byte[] payoutTxSerialized, - @Nullable String depositTxId, - @Nullable String payoutTxId, - String contractAsJson, - @Nullable String makerContractSignature, - @Nullable String takerContractSignature, - PubKeyRing agentPubKeyRing, - boolean isSupportTicket, - SupportType supportType) { + this.openingDate = openingDate; this.tradeId = tradeId; this.traderId = traderId; this.disputeOpenerIsBuyer = disputeOpenerIsBuyer; @@ -205,6 +160,11 @@ public Dispute(String tradeId, uid = UUID.randomUUID().toString(); } + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + @Override public protobuf.Dispute toProtoMessage() { // Needed to avoid ConcurrentModificationException @@ -244,7 +204,8 @@ public protobuf.Dispute toProtoMessage() { } public static Dispute fromProto(protobuf.Dispute proto, CoreProtoResolver coreProtoResolver) { - Dispute dispute = new Dispute(proto.getTradeId(), + Dispute dispute = new Dispute(proto.getOpeningDate(), + proto.getTradeId(), proto.getTraderId(), proto.getDisputeOpenerIsBuyer(), proto.getDisputeOpenerIsMaker(), @@ -267,7 +228,6 @@ public static Dispute fromProto(protobuf.Dispute proto, CoreProtoResolver corePr .map(ChatMessage::fromPayloadProto) .collect(Collectors.toList())); - dispute.openingDate = proto.getOpeningDate(); dispute.isClosedProperty.set(proto.getIsClosed()); if (proto.hasDisputeResult()) dispute.disputeResultProperty.set(DisputeResult.fromProto(proto.getDisputeResult())); @@ -299,7 +259,6 @@ public static Dispute fromProto(protobuf.Dispute proto, CoreProtoResolver corePr public void addAndPersistChatMessage(ChatMessage chatMessage) { if (!chatMessages.contains(chatMessage)) { chatMessages.add(chatMessage); - storage.queueUpForSave(); } else { log.error("disputeDirectMessage already exists"); } @@ -310,35 +269,14 @@ public void addAndPersistChatMessage(ChatMessage chatMessage) { // Setters /////////////////////////////////////////////////////////////////////////////////////////// - // In case we get the object via the network storage is not set as its transient, so we need to set it. - public void setStorage(Storage storage) { - this.storage = storage; - } - public void setIsClosed(boolean isClosed) { - boolean changed = this.isClosedProperty.get() != isClosed; this.isClosedProperty.set(isClosed); - if (changed) - storage.queueUpForSave(); } public void setDisputeResult(DisputeResult disputeResult) { - boolean changed = disputeResultProperty.get() == null || !disputeResultProperty.get().equals(disputeResult); disputeResultProperty.set(disputeResult); - if (changed) - storage.queueUpForSave(); } - public void setDisputePayoutTxId(String disputePayoutTxId) { - boolean changed = this.disputePayoutTxId == null || !this.disputePayoutTxId.equals(disputePayoutTxId); - this.disputePayoutTxId = disputePayoutTxId; - if (changed) - storage.queueUpForSave(); - } - - public void setSupportType(SupportType supportType) { - this.supportType = supportType; - } /////////////////////////////////////////////////////////////////////////////////////////// // Getters @@ -396,7 +334,6 @@ public String toString() { ",\n disputeResultProperty=" + disputeResultProperty + ",\n disputePayoutTxId='" + disputePayoutTxId + '\'' + ",\n openingDate=" + openingDate + - ",\n storage=" + storage + ",\n supportType=" + supportType + ",\n mediatorsDisputeResult='" + mediatorsDisputeResult + '\'' + ",\n delayedPayoutTxId='" + delayedPayoutTxId + '\'' + diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeList.java b/core/src/main/java/bisq/core/support/dispute/DisputeList.java index 468699c4abf..15069649567 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeList.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeList.java @@ -17,18 +17,11 @@ package bisq.core.support.dispute; -import bisq.common.proto.persistable.PersistableEnvelope; -import bisq.common.proto.persistable.PersistedDataHost; -import bisq.common.proto.persistable.UserThreadMappedPersistableEnvelope; -import bisq.common.storage.Storage; +import bisq.common.proto.persistable.PersistableListAsObservable; +import bisq.common.proto.persistable.PersistablePayload; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; +import java.util.Collection; -import java.util.List; -import java.util.stream.Stream; - -import lombok.Getter; import lombok.ToString; import lombok.extern.slf4j.Slf4j; @@ -40,67 +33,12 @@ * Calls to the List are delegated because this class intercepts the add/remove calls so changes * can be saved to disc. */ -public abstract class DisputeList implements UserThreadMappedPersistableEnvelope, PersistedDataHost { - transient protected final Storage storage; - - @Getter - protected final ObservableList list = FXCollections.observableArrayList(); - - public DisputeList(Storage storage) { - this.storage = storage; - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // PROTO BUFFER - /////////////////////////////////////////////////////////////////////////////////////////// - - protected DisputeList(Storage storage, List list) { - this.storage = storage; - this.list.addAll(list); - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // API - /////////////////////////////////////////////////////////////////////////////////////////// - - public boolean add(Dispute dispute) { - if (!list.contains(dispute)) { - list.add(dispute); - persist(); - return true; - } else { - return false; - } - } - - public boolean remove(Object dispute) { - //noinspection SuspiciousMethodCalls - boolean changed = list.remove(dispute); - if (changed) - persist(); - return changed; - } - - public void persist() { - storage.queueUpForSave(); - } - - public int size() { - return list.size(); - } - - public boolean isEmpty() { - return list.isEmpty(); - } +public abstract class DisputeList extends PersistableListAsObservable { - @SuppressWarnings({"SuspiciousMethodCalls"}) - public boolean contains(Object o) { - return list.contains(o); + public DisputeList() { } - public Stream stream() { - return list.stream(); + protected DisputeList(Collection collection) { + super(collection); } } diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeListService.java b/core/src/main/java/bisq/core/support/dispute/DisputeListService.java index d3f57cb03a2..ffbec7dc985 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeListService.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeListService.java @@ -22,8 +22,8 @@ import bisq.network.p2p.NodeAddress; import bisq.common.UserThread; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.persistable.PersistedDataHost; -import bisq.common.storage.Storage; import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.Subscription; @@ -31,8 +31,6 @@ import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; -import javafx.collections.FXCollections; -import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import java.util.HashMap; @@ -47,12 +45,11 @@ import javax.annotation.Nullable; @Slf4j -public abstract class DisputeListService> implements PersistedDataHost { +public abstract class DisputeListService> implements PersistedDataHost { @Getter - protected final Storage storage; - @Nullable + protected final PersistenceManager persistenceManager; @Getter - private T disputeList; + private final T disputeList; private final Map disputeIsClosedSubscriptionsMap = new HashMap<>(); @Getter private final IntegerProperty numOpenDisputes = new SimpleIntegerProperty(); @@ -62,8 +59,11 @@ public abstract class DisputeListService storage) { - this.storage = storage; + public DisputeListService(PersistenceManager persistenceManager) { + this.persistenceManager = persistenceManager; + disputeList = getConcreteDisputeList(); + + this.persistenceManager.initialize(disputeList, getFileName(), PersistenceManager.Source.PRIVATE); } @@ -80,9 +80,14 @@ public DisputeListService(Storage storage) { @Override public void readPersisted() { - disputeList = getConcreteDisputeList(); - disputeList.readPersisted(); - disputeList.stream().forEach(dispute -> dispute.setStorage(storage)); + T persisted = persistenceManager.getPersisted(getFileName()); + if (persisted != null) { + disputeList.setAll(persisted.getList()); + } + } + + protected String getFileName() { + return disputeList.getDefaultStorageFileName(); } @@ -91,19 +96,12 @@ public void readPersisted() { /////////////////////////////////////////////////////////////////////////////////////////// public void cleanupDisputes(@Nullable Consumer closedDisputeHandler) { - if (disputeList != null) { - disputeList.stream().forEach(dispute -> { - dispute.setStorage(storage); - String tradeId = dispute.getTradeId(); - if (dispute.isClosed()) { - if (closedDisputeHandler != null) { - closedDisputeHandler.accept(tradeId); - } - } - }); - } else { - log.warn("disputes is null"); - } + disputeList.stream().forEach(dispute -> { + String tradeId = dispute.getTradeId(); + if (dispute.isClosed() && closedDisputeHandler != null) { + closedDisputeHandler.accept(tradeId); + } + }); } @@ -112,19 +110,15 @@ public void cleanupDisputes(@Nullable Consumer closedDisputeHandler) { /////////////////////////////////////////////////////////////////////////////////////////// void onAllServicesInitialized() { - if (disputeList != null) { - disputeList.getList().addListener((ListChangeListener) change -> { - change.next(); - onDisputesChangeListener(change.getAddedSubList(), change.getRemoved()); - }); - onDisputesChangeListener(disputeList.getList(), null); - } else { - log.warn("disputes is null"); - } + disputeList.addListener(change -> { + change.next(); + onDisputesChangeListener(change.getAddedSubList(), change.getRemoved()); + }); + onDisputesChangeListener(disputeList.getList(), null); } String getNrOfDisputes(boolean isBuyer, Contract contract) { - return String.valueOf(getDisputesAsObservableList().stream() + return String.valueOf(getObservableList().stream() .filter(e -> { Contract contract1 = e.getContract(); if (contract1 == null) @@ -141,12 +135,8 @@ String getNrOfDisputes(boolean isBuyer, Contract contract) { .collect(Collectors.toSet()).size()); } - ObservableList getDisputesAsObservableList() { - if (disputeList == null) { - log.warn("disputes is null"); - return FXCollections.observableArrayList(); - } - return disputeList.getList(); + ObservableList getObservableList() { + return disputeList.getObservableList(); } @@ -169,22 +159,18 @@ private void onDisputesChangeListener(List addedList, String id = dispute.getId(); Subscription disputeStateSubscription = EasyBind.subscribe(dispute.isClosedProperty(), isClosed -> { - if (disputeList != null) { - // We get the event before the list gets updated, so we execute on next frame - UserThread.execute(() -> { - int openDisputes = (int) disputeList.getList().stream() - .filter(e -> !e.isClosed()).count(); - numOpenDisputes.set(openDisputes); - }); - } + // We get the event before the list gets updated, so we execute on next frame + UserThread.execute(() -> { + int openDisputes = (int) disputeList.getList().stream() + .filter(e -> !e.isClosed()).count(); + numOpenDisputes.set(openDisputes); + }); }); disputeIsClosedSubscriptionsMap.put(id, disputeStateSubscription); }); } - public void persist() { - if (disputeList != null) { - disputeList.persist(); - } + public void requestPersistence() { + persistenceManager.requestPersistence(); } } diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java index 63ad2ef092b..45fc43f35e1 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -53,7 +53,6 @@ import bisq.common.crypto.PubKeyRing; import bisq.common.handlers.FaultHandler; import bisq.common.handlers.ResultHandler; -import bisq.common.storage.Storage; import bisq.common.util.MathUtils; import bisq.common.util.Tuple2; @@ -67,6 +66,7 @@ import java.security.KeyPair; +import java.util.Date; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -81,7 +81,7 @@ import static com.google.common.base.Preconditions.checkNotNull; @Slf4j -public abstract class DisputeManager> extends SupportManager { +public abstract class DisputeManager> extends SupportManager { protected final TradeWalletService tradeWalletService; protected final BtcWalletService btcWalletService; protected final TradeManager tradeManager; @@ -137,8 +137,8 @@ public DisputeManager(P2PService p2PService, /////////////////////////////////////////////////////////////////////////////////////////// @Override - public void persist() { - disputeListService.persist(); + public void requestPersistence() { + disputeListService.requestPersistence(); } @Override @@ -181,6 +181,7 @@ public void addAndPersistChatMessage(ChatMessage message) { findDispute(message).ifPresent(dispute -> { if (dispute.getChatMessages().stream().noneMatch(m -> m.getUid().equals(message.getUid()))) { dispute.addAndPersistChatMessage(message); + requestPersistence(); } else { log.warn("We got a chatMessage that we have already stored. UId = {} TradeId = {}", message.getUid(), message.getTradeId()); @@ -218,12 +219,8 @@ public IntegerProperty getNumOpenDisputes() { return disputeListService.getNumOpenDisputes(); } - public Storage getStorage() { - return disputeListService.getStorage(); - } - public ObservableList getDisputesAsObservableList() { - return disputeListService.getDisputesAsObservableList(); + return disputeListService.getObservableList(); } public String getNrOfDisputes(boolean isBuyer, Contract contract) { @@ -263,7 +260,7 @@ public void onUpdatedDataReceived() { tryApplyMessages(); cleanupDisputes(); - ObservableList disputes = getDisputeList().getList(); + List disputes = getDisputeList().getList(); disputes.forEach(dispute -> { try { TradeDataValidation.validateDonationAddress(dispute, dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade); @@ -296,6 +293,7 @@ public Optional findOwnDispute(String tradeId) { return disputeList.stream().filter(e -> e.getTradeId().equals(tradeId)).findAny(); } + /////////////////////////////////////////////////////////////////////////////////////////// // Message handler /////////////////////////////////////////////////////////////////////////////////////////// @@ -310,7 +308,6 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa String errorMessage = null; Dispute dispute = openNewDisputeMessage.getDispute(); - dispute.setStorage(disputeListService.getStorage()); // Disputes from clients < 1.2.0 always have support type ARBITRATION in dispute as the field didn't exist before dispute.setSupportType(openNewDisputeMessage.getSupportType()); @@ -359,6 +356,7 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa log.error(e.toString()); validationExceptions.add(e); } + requestPersistence(); } // Not-dispute-requester receives that msg from dispute agent @@ -395,7 +393,6 @@ protected void onPeerOpenedDisputeMessage(PeerOpenedDisputeMessage peerOpenedDis if (!disputeList.contains(dispute)) { Optional storedDisputeOptional = findDispute(dispute); if (!storedDisputeOptional.isPresent()) { - dispute.setStorage(disputeListService.getStorage()); disputeList.add(dispute); trade.setDisputeState(getDisputeStateStartedByPeer()); errorMessage = null; @@ -421,6 +418,7 @@ protected void onPeerOpenedDisputeMessage(PeerOpenedDisputeMessage peerOpenedDis } sendAckMessage(peerOpenedDisputeMessage, dispute.getAgentPubKeyRing(), errorMessage == null, errorMessage); + requestPersistence(); } @@ -499,7 +497,7 @@ public void onArrived() { // We use the chatMessage wrapped inside the openNewDisputeMessage for // the state, as that is displayed to the user and we only persist that msg chatMessage.setArrived(true); - disputeList.persist(); + requestPersistence(); resultHandler.handleResult(); } @@ -514,7 +512,7 @@ public void onStoredInMailbox() { // We use the chatMessage wrapped inside the openNewDisputeMessage for // the state, as that is displayed to the user and we only persist that msg chatMessage.setStoredInMailbox(true); - disputeList.persist(); + requestPersistence(); resultHandler.handleResult(); } @@ -529,7 +527,7 @@ public void onFault(String errorMessage) { // We use the chatMessage wrapped inside the openNewDisputeMessage for // the state, as that is displayed to the user and we only persist that msg chatMessage.setSendMessageError(errorMessage); - disputeList.persist(); + requestPersistence(); faultHandler.handleFault("Sending dispute message failed: " + errorMessage, new DisputeMessageDeliveryFailedException()); } @@ -541,6 +539,7 @@ public void onFault(String errorMessage) { log.warn(msg); faultHandler.handleFault(msg, new DisputeAlreadyOpenException()); } + requestPersistence(); } // Dispute agent sends that to trading peer when he received openDispute request @@ -566,7 +565,7 @@ private void doSendPeerOpenedDisputeMessage(Dispute disputeFromOpener, return; } - Dispute dispute = new Dispute(disputeListService.getStorage(), + Dispute dispute = new Dispute(new Date().getTime(), disputeFromOpener.getTradeId(), pubKeyRing.hashCode(), !disputeFromOpener.isDisputeOpenerIsBuyer(), @@ -592,8 +591,7 @@ private void doSendPeerOpenedDisputeMessage(Dispute disputeFromOpener, // Valid case if both have opened a dispute and agent was not online. if (storedDisputeOptional.isPresent()) { - log.info("We got a dispute already open for that trade and trading peer. TradeId = {}", - dispute.getTradeId()); + log.info("We got a dispute already open for that trade and trading peer. TradeId = {}", dispute.getTradeId()); return; } @@ -645,7 +643,7 @@ public void onArrived() { // We use the chatMessage wrapped inside the peerOpenedDisputeMessage for // the state, as that is displayed to the user and we only persist that msg chatMessage.setArrived(true); - disputeList.persist(); + requestPersistence(); } @Override @@ -659,7 +657,7 @@ public void onStoredInMailbox() { // We use the chatMessage wrapped inside the peerOpenedDisputeMessage for // the state, as that is displayed to the user and we only persist that msg chatMessage.setStoredInMailbox(true); - disputeList.persist(); + requestPersistence(); } @Override @@ -673,10 +671,11 @@ public void onFault(String errorMessage) { // We use the chatMessage wrapped inside the peerOpenedDisputeMessage for // the state, as that is displayed to the user and we only persist that msg chatMessage.setSendMessageError(errorMessage); - disputeList.persist(); + requestPersistence(); } } ); + requestPersistence(); } // dispute agent send result to trader @@ -726,7 +725,7 @@ public void onArrived() { // We use the chatMessage wrapped inside the disputeResultMessage for // the state, as that is displayed to the user and we only persist that msg chatMessage.setArrived(true); - disputeList.persist(); + requestPersistence(); } @Override @@ -740,7 +739,7 @@ public void onStoredInMailbox() { // We use the chatMessage wrapped inside the disputeResultMessage for // the state, as that is displayed to the user and we only persist that msg chatMessage.setStoredInMailbox(true); - disputeList.persist(); + requestPersistence(); } @Override @@ -754,10 +753,11 @@ public void onFault(String errorMessage) { // We use the chatMessage wrapped inside the disputeResultMessage for // the state, as that is displayed to the user and we only persist that msg chatMessage.setSendMessageError(errorMessage); - disputeList.persist(); + requestPersistence(); } } ); + requestPersistence(); } @@ -837,6 +837,7 @@ private void addMediationResultMessage(Dispute dispute) { p2PService.getAddress()); mediatorsDisputeResultMessage.setSystemMessage(true); dispute.addAndPersistChatMessage(mediatorsDisputeResultMessage); + requestPersistence(); } } @@ -909,6 +910,7 @@ protected void addPriceInfoMessage(Dispute dispute, int counter) { p2PService.getAddress()); priceInfoMessage.setSystemMessage(true); dispute.addAndPersistChatMessage(priceInfoMessage); + requestPersistence(); } @Nullable diff --git a/core/src/main/java/bisq/core/support/dispute/agent/MultipleHolderNameDetection.java b/core/src/main/java/bisq/core/support/dispute/agent/MultipleHolderNameDetection.java index 80a64808c64..3bcbf50aa92 100644 --- a/core/src/main/java/bisq/core/support/dispute/agent/MultipleHolderNameDetection.java +++ b/core/src/main/java/bisq/core/support/dispute/agent/MultipleHolderNameDetection.java @@ -113,20 +113,20 @@ private static String getIsBuyerSubString(boolean isBuyer) { // Class fields /////////////////////////////////////////////////////////////////////////////////////////// - private final DisputeManager> disputeManager; + private final DisputeManager> disputeManager; // Key is hex of hash of sig pubKey which we consider a trader identity. We could use onion address as well but // once we support multiple onion addresses that would not work anymore. @Getter - private Map> suspiciousDisputesByTraderMap = new HashMap<>(); - private List listeners = new CopyOnWriteArrayList<>(); + private final Map> suspiciousDisputesByTraderMap = new HashMap<>(); + private final List listeners = new CopyOnWriteArrayList<>(); /////////////////////////////////////////////////////////////////////////////////////////// // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - public MultipleHolderNameDetection(DisputeManager> disputeManager) { + public MultipleHolderNameDetection(DisputeManager> disputeManager) { this.disputeManager = disputeManager; disputeManager.getDisputesAsObservableList().addListener((ListChangeListener) c -> { diff --git a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationDisputeList.java b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationDisputeList.java index 9d625f4a01e..aa6bcc23721 100644 --- a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationDisputeList.java +++ b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationDisputeList.java @@ -23,11 +23,10 @@ import bisq.core.support.dispute.DisputeList; import bisq.common.proto.ProtoUtil; -import bisq.common.storage.Storage; import com.google.protobuf.Message; -import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.stream.Collectors; @@ -44,19 +43,10 @@ * Calls to the List are delegated because this class intercepts the add/remove calls so changes * can be saved to disc. */ -public final class ArbitrationDisputeList extends DisputeList { +public final class ArbitrationDisputeList extends DisputeList { - ArbitrationDisputeList(Storage storage) { - super(storage); - } - - @Override - public void readPersisted() { - // We need to use DisputeList as file name to not lose existing disputes which are stored in the DisputeList file - ArbitrationDisputeList persisted = storage.initAndGetPersisted(this, "DisputeList", 50); - if (persisted != null) { - list.addAll(persisted.getList()); - } + ArbitrationDisputeList() { + super(); } @@ -64,30 +54,26 @@ public void readPersisted() { // PROTO BUFFER /////////////////////////////////////////////////////////////////////////////////////////// - private ArbitrationDisputeList(Storage storage, List list) { - super(storage, list); + protected ArbitrationDisputeList(Collection collection) { + super(collection); } @Override public Message toProtoMessage() { - list.forEach(dispute -> checkArgument(dispute.getSupportType().equals(SupportType.ARBITRATION), "Support type has to be ARBITRATION")); + forEach(dispute -> checkArgument(dispute.getSupportType().equals(SupportType.ARBITRATION), "Support type has to be ARBITRATION")); return protobuf.PersistableEnvelope.newBuilder().setArbitrationDisputeList(protobuf.ArbitrationDisputeList.newBuilder() - .addAllDispute(ProtoUtil.collectionToProto(new ArrayList<>(list), protobuf.Dispute.class))).build(); + .addAllDispute(ProtoUtil.collectionToProto(getList(), protobuf.Dispute.class))).build(); } public static ArbitrationDisputeList fromProto(protobuf.ArbitrationDisputeList proto, - CoreProtoResolver coreProtoResolver, - Storage storage) { + CoreProtoResolver coreProtoResolver) { List list = proto.getDisputeList().stream() .map(disputeProto -> Dispute.fromProto(disputeProto, coreProtoResolver)) + .filter(e -> e.getSupportType().equals(SupportType.ARBITRATION)) .collect(Collectors.toList()); - list.forEach(e -> { - checkArgument(e.getSupportType().equals(SupportType.ARBITRATION), "Support type has to be ARBITRATION"); - e.setStorage(storage); - }); - return new ArbitrationDisputeList(storage, list); + return new ArbitrationDisputeList(list); } } diff --git a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationDisputeListService.java b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationDisputeListService.java index 3d456f686ef..d9b57bb595f 100644 --- a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationDisputeListService.java +++ b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationDisputeListService.java @@ -19,7 +19,7 @@ import bisq.core.support.dispute.DisputeListService; -import bisq.common.storage.Storage; +import bisq.common.persistence.PersistenceManager; import javax.inject.Inject; import javax.inject.Singleton; @@ -32,8 +32,8 @@ public final class ArbitrationDisputeListService extends DisputeListService storage) { - super(storage); + public ArbitrationDisputeListService(PersistenceManager persistenceManager) { + super(persistenceManager); } @@ -43,6 +43,11 @@ public ArbitrationDisputeListService(Storage storage) { @Override protected ArbitrationDisputeList getConcreteDisputeList() { - return new ArbitrationDisputeList(storage); + return new ArbitrationDisputeList(); + } + + @Override + protected String getFileName() { + return "DisputeList"; } } diff --git a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java index dd21166f7d3..8552003199d 100644 --- a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java +++ b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java @@ -337,6 +337,8 @@ public void onFailure(TxBroadcastException exception) { // If we would use the disputeResultMessage we could not lookup for the msg when we receive the AckMessage. sendAckMessage(chatMessage, dispute.getAgentPubKeyRing(), success, errorMessage); } + + requestPersistence(); } // Losing trader or in case of 50/50 the seller gets the tx sent from the winner or buyer @@ -371,6 +373,7 @@ private void onDisputedPayoutTxMessage(PeerPublishedDisputePayoutTxMessage peerP // We can only send the ack msg if we have the peersPubKeyRing which requires the dispute sendAckMessage(peerPublishedDisputePayoutTxMessage, peersPubKeyRing, true, null); + requestPersistence(); } diff --git a/core/src/main/java/bisq/core/support/dispute/mediation/MediationDisputeList.java b/core/src/main/java/bisq/core/support/dispute/mediation/MediationDisputeList.java index 59208c23b7d..00b7b4df821 100644 --- a/core/src/main/java/bisq/core/support/dispute/mediation/MediationDisputeList.java +++ b/core/src/main/java/bisq/core/support/dispute/mediation/MediationDisputeList.java @@ -18,15 +18,15 @@ package bisq.core.support.dispute.mediation; import bisq.core.proto.CoreProtoResolver; +import bisq.core.support.SupportType; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeList; import bisq.common.proto.ProtoUtil; -import bisq.common.storage.Storage; import com.google.protobuf.Message; -import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.stream.Collectors; @@ -41,18 +41,10 @@ * Calls to the List are delegated because this class intercepts the add/remove calls so changes * can be saved to disc. */ -public final class MediationDisputeList extends DisputeList { +public final class MediationDisputeList extends DisputeList { - MediationDisputeList(Storage storage) { - super(storage); - } - - @Override - public void readPersisted() { - MediationDisputeList persisted = storage.initAndGetPersisted(this, "MediationDisputeList", 0); - if (persisted != null) { - list.addAll(persisted.getList()); - } + MediationDisputeList() { + super(); } @@ -60,23 +52,22 @@ public void readPersisted() { // PROTO BUFFER /////////////////////////////////////////////////////////////////////////////////////////// - private MediationDisputeList(Storage storage, List list) { - super(storage, list); + protected MediationDisputeList(Collection collection) { + super(collection); } @Override public Message toProtoMessage() { return protobuf.PersistableEnvelope.newBuilder().setMediationDisputeList(protobuf.MediationDisputeList.newBuilder() - .addAllDispute(ProtoUtil.collectionToProto(new ArrayList<>(list), protobuf.Dispute.class))).build(); + .addAllDispute(ProtoUtil.collectionToProto(getList(), protobuf.Dispute.class))).build(); } public static MediationDisputeList fromProto(protobuf.MediationDisputeList proto, - CoreProtoResolver coreProtoResolver, - Storage storage) { + CoreProtoResolver coreProtoResolver) { List list = proto.getDisputeList().stream() .map(disputeProto -> Dispute.fromProto(disputeProto, coreProtoResolver)) + .filter(e -> e.getSupportType().equals(SupportType.MEDIATION)) .collect(Collectors.toList()); - list.forEach(e -> e.setStorage(storage)); - return new MediationDisputeList(storage, list); + return new MediationDisputeList(list); } } diff --git a/core/src/main/java/bisq/core/support/dispute/mediation/MediationDisputeListService.java b/core/src/main/java/bisq/core/support/dispute/mediation/MediationDisputeListService.java index cb4fbf40010..70cbd5bf786 100644 --- a/core/src/main/java/bisq/core/support/dispute/mediation/MediationDisputeListService.java +++ b/core/src/main/java/bisq/core/support/dispute/mediation/MediationDisputeListService.java @@ -19,7 +19,7 @@ import bisq.core.support.dispute.DisputeListService; -import bisq.common.storage.Storage; +import bisq.common.persistence.PersistenceManager; import javax.inject.Inject; import javax.inject.Singleton; @@ -32,8 +32,8 @@ public final class MediationDisputeListService extends DisputeListService storage) { - super(storage); + public MediationDisputeListService(PersistenceManager persistenceManager) { + super(persistenceManager); } @@ -43,6 +43,6 @@ public MediationDisputeListService(Storage storage) { @Override protected MediationDisputeList getConcreteDisputeList() { - return new MediationDisputeList(storage); + return new MediationDisputeList(); } } diff --git a/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java index 09b40f513b7..7e00146ee8d 100644 --- a/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java +++ b/core/src/main/java/bisq/core/support/dispute/mediation/MediationManager.java @@ -209,6 +209,8 @@ public void onDisputeResultMessage(DisputeResultMessage disputeResultMessage) { openOfferOptional.ifPresent(openOffer -> openOfferManager.closeOpenOffer(openOffer.getOffer())); } sendAckMessage(chatMessage, dispute.getAgentPubKeyRing(), true, null); + + requestPersistence(); } diff --git a/core/src/main/java/bisq/core/support/dispute/refund/RefundDisputeList.java b/core/src/main/java/bisq/core/support/dispute/refund/RefundDisputeList.java index 6602008a2e6..1194ac9c0d0 100644 --- a/core/src/main/java/bisq/core/support/dispute/refund/RefundDisputeList.java +++ b/core/src/main/java/bisq/core/support/dispute/refund/RefundDisputeList.java @@ -23,11 +23,10 @@ import bisq.core.support.dispute.DisputeList; import bisq.common.proto.ProtoUtil; -import bisq.common.storage.Storage; import com.google.protobuf.Message; -import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.stream.Collectors; @@ -44,19 +43,10 @@ * Calls to the List are delegated because this class intercepts the add/remove calls so changes * can be saved to disc. */ -public final class RefundDisputeList extends DisputeList { +public final class RefundDisputeList extends DisputeList { - RefundDisputeList(Storage storage) { - super(storage); - } - - @Override - public void readPersisted() { - // We need to use DisputeList as file name to not lose existing disputes which are stored in the DisputeList file - RefundDisputeList persisted = storage.initAndGetPersisted(this, "RefundDisputeList", 50); - if (persisted != null) { - list.addAll(persisted.getList()); - } + RefundDisputeList() { + super(); } @@ -64,30 +54,24 @@ public void readPersisted() { // PROTO BUFFER /////////////////////////////////////////////////////////////////////////////////////////// - private RefundDisputeList(Storage storage, List list) { - super(storage, list); + protected RefundDisputeList(Collection collection) { + super(collection); } @Override public Message toProtoMessage() { - - list.forEach(dispute -> checkArgument(dispute.getSupportType().equals(SupportType.REFUND), "Support type has to be REFUND")); + forEach(dispute -> checkArgument(dispute.getSupportType().equals(SupportType.REFUND), "Support type has to be REFUND")); return protobuf.PersistableEnvelope.newBuilder().setRefundDisputeList(protobuf.RefundDisputeList.newBuilder() - .addAllDispute(ProtoUtil.collectionToProto(new ArrayList<>(list), protobuf.Dispute.class))).build(); + .addAllDispute(ProtoUtil.collectionToProto(getList(), protobuf.Dispute.class))).build(); } public static RefundDisputeList fromProto(protobuf.RefundDisputeList proto, - CoreProtoResolver coreProtoResolver, - Storage storage) { + CoreProtoResolver coreProtoResolver) { List list = proto.getDisputeList().stream() .map(disputeProto -> Dispute.fromProto(disputeProto, coreProtoResolver)) + .filter(e -> e.getSupportType().equals(SupportType.REFUND)) .collect(Collectors.toList()); - - list.forEach(e -> { - checkArgument(e.getSupportType().equals(SupportType.REFUND), "Support type has to be REFUND"); - e.setStorage(storage); - }); - return new RefundDisputeList(storage, list); + return new RefundDisputeList(list); } } diff --git a/core/src/main/java/bisq/core/support/dispute/refund/RefundDisputeListService.java b/core/src/main/java/bisq/core/support/dispute/refund/RefundDisputeListService.java index afdac5c9f3b..1709fa2f79c 100644 --- a/core/src/main/java/bisq/core/support/dispute/refund/RefundDisputeListService.java +++ b/core/src/main/java/bisq/core/support/dispute/refund/RefundDisputeListService.java @@ -19,7 +19,7 @@ import bisq.core.support.dispute.DisputeListService; -import bisq.common.storage.Storage; +import bisq.common.persistence.PersistenceManager; import javax.inject.Inject; import javax.inject.Singleton; @@ -32,8 +32,8 @@ public final class RefundDisputeListService extends DisputeListService storage) { - super(storage); + public RefundDisputeListService(PersistenceManager persistenceManager) { + super(persistenceManager); } @@ -43,6 +43,6 @@ public RefundDisputeListService(Storage storage) { @Override protected RefundDisputeList getConcreteDisputeList() { - return new RefundDisputeList(storage); + return new RefundDisputeList(); } } diff --git a/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java index 25e6b182753..e7919346f3e 100644 --- a/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java +++ b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java @@ -219,6 +219,8 @@ public void onDisputeResultMessage(DisputeResultMessage disputeResultMessage) { Optional openOfferOptional = openOfferManager.getOpenOfferById(tradeId); openOfferOptional.ifPresent(openOffer -> openOfferManager.closeOpenOffer(openOffer.getOffer())); } + + requestPersistence(); } diff --git a/core/src/main/java/bisq/core/support/traderchat/TraderChatManager.java b/core/src/main/java/bisq/core/support/traderchat/TraderChatManager.java index e4562f48bf6..ef4a0157e00 100644 --- a/core/src/main/java/bisq/core/support/traderchat/TraderChatManager.java +++ b/core/src/main/java/bisq/core/support/traderchat/TraderChatManager.java @@ -74,8 +74,8 @@ public SupportType getSupportType() { } @Override - public void persist() { - tradeManager.persistTrades(); + public void requestPersistence() { + tradeManager.requestPersistence(); } @Override @@ -102,7 +102,7 @@ public PubKeyRing getPeerPubKeyRing(ChatMessage message) { @Override public List getAllChatMessages() { - return tradeManager.getTradesAsObservableList().stream() + return tradeManager.getObservableList().stream() .flatMap(trade -> trade.getChatMessages().stream()) .collect(Collectors.toList()); } @@ -121,6 +121,7 @@ public void addAndPersistChatMessage(ChatMessage message) { addSystemMsg(trade); } trade.addAndPersistChatMessage(message); + tradeManager.requestPersistence(); } else { log.warn("Trade got a chatMessage that we have already stored. UId = {} TradeId = {}", message.getUid(), message.getTradeId()); diff --git a/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java b/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java index cf7b6d09094..272edad6dfd 100644 --- a/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java +++ b/core/src/main/java/bisq/core/trade/BuyerAsMakerTrade.java @@ -24,8 +24,6 @@ import bisq.network.p2p.NodeAddress; -import bisq.common.storage.Storage; - import org.bitcoinj.core.Coin; import lombok.extern.slf4j.Slf4j; @@ -46,7 +44,6 @@ public BuyerAsMakerTrade(Offer offer, @Nullable NodeAddress arbitratorNodeAddress, @Nullable NodeAddress mediatorNodeAddress, @Nullable NodeAddress refundAgentNodeAddress, - Storage storage, BtcWalletService btcWalletService, ProcessModel processModel) { super(offer, @@ -56,7 +53,6 @@ public BuyerAsMakerTrade(Offer offer, arbitratorNodeAddress, mediatorNodeAddress, refundAgentNodeAddress, - storage, btcWalletService, processModel); } @@ -74,7 +70,6 @@ public protobuf.Tradable toProtoMessage() { } public static Tradable fromProto(protobuf.BuyerAsMakerTrade buyerAsMakerTradeProto, - Storage storage, BtcWalletService btcWalletService, CoreProtoResolver coreProtoResolver) { protobuf.Trade proto = buyerAsMakerTradeProto.getTrade(); @@ -87,7 +82,6 @@ public static Tradable fromProto(protobuf.BuyerAsMakerTrade buyerAsMakerTradePro proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null, proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null, proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null, - storage, btcWalletService, processModel); diff --git a/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java b/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java index 3e0283c20ee..46ba45e4240 100644 --- a/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java +++ b/core/src/main/java/bisq/core/trade/BuyerAsTakerTrade.java @@ -24,8 +24,6 @@ import bisq.network.p2p.NodeAddress; -import bisq.common.storage.Storage; - import org.bitcoinj.core.Coin; import lombok.extern.slf4j.Slf4j; @@ -49,7 +47,6 @@ public BuyerAsTakerTrade(Offer offer, @Nullable NodeAddress arbitratorNodeAddress, @Nullable NodeAddress mediatorNodeAddress, @Nullable NodeAddress refundAgentNodeAddress, - Storage storage, BtcWalletService btcWalletService, ProcessModel processModel) { super(offer, @@ -62,7 +59,6 @@ public BuyerAsTakerTrade(Offer offer, arbitratorNodeAddress, mediatorNodeAddress, refundAgentNodeAddress, - storage, btcWalletService, processModel); } @@ -81,7 +77,6 @@ public protobuf.Tradable toProtoMessage() { } public static Tradable fromProto(protobuf.BuyerAsTakerTrade buyerAsTakerTradeProto, - Storage storage, BtcWalletService btcWalletService, CoreProtoResolver coreProtoResolver) { protobuf.Trade proto = buyerAsTakerTradeProto.getTrade(); @@ -97,7 +92,6 @@ public static Tradable fromProto(protobuf.BuyerAsTakerTrade buyerAsTakerTradePro proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null, proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null, proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null, - storage, btcWalletService, processModel), proto, diff --git a/core/src/main/java/bisq/core/trade/BuyerTrade.java b/core/src/main/java/bisq/core/trade/BuyerTrade.java index 7e4a8672299..77e0dc85848 100644 --- a/core/src/main/java/bisq/core/trade/BuyerTrade.java +++ b/core/src/main/java/bisq/core/trade/BuyerTrade.java @@ -23,8 +23,6 @@ import bisq.network.p2p.NodeAddress; -import bisq.common.storage.Storage; - import org.bitcoinj.core.Coin; import lombok.extern.slf4j.Slf4j; @@ -45,7 +43,6 @@ public abstract class BuyerTrade extends Trade { @Nullable NodeAddress arbitratorNodeAddress, @Nullable NodeAddress mediatorNodeAddress, @Nullable NodeAddress refundAgentNodeAddress, - Storage storage, BtcWalletService btcWalletService, ProcessModel processModel) { super(offer, @@ -58,7 +55,6 @@ public abstract class BuyerTrade extends Trade { arbitratorNodeAddress, mediatorNodeAddress, refundAgentNodeAddress, - storage, btcWalletService, processModel); } @@ -70,7 +66,6 @@ public abstract class BuyerTrade extends Trade { @Nullable NodeAddress arbitratorNodeAddress, @Nullable NodeAddress mediatorNodeAddress, @Nullable NodeAddress refundAgentNodeAddress, - Storage storage, BtcWalletService btcWalletService, ProcessModel processModel) { super(offer, @@ -80,7 +75,6 @@ public abstract class BuyerTrade extends Trade { arbitratorNodeAddress, mediatorNodeAddress, refundAgentNodeAddress, - storage, btcWalletService, processModel); } diff --git a/core/src/main/java/bisq/core/trade/DumpDelayedPayoutTx.java b/core/src/main/java/bisq/core/trade/DumpDelayedPayoutTx.java index 3b31f6f8adb..c407126a9db 100644 --- a/core/src/main/java/bisq/core/trade/DumpDelayedPayoutTx.java +++ b/core/src/main/java/bisq/core/trade/DumpDelayedPayoutTx.java @@ -18,7 +18,7 @@ package bisq.core.trade; import bisq.common.config.Config; -import bisq.common.storage.JsonFileManager; +import bisq.common.file.JsonFileManager; import bisq.common.util.Utilities; import javax.inject.Inject; diff --git a/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java b/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java index 903e934dbb2..cd871fd9572 100644 --- a/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java +++ b/core/src/main/java/bisq/core/trade/SellerAsMakerTrade.java @@ -24,8 +24,6 @@ import bisq.network.p2p.NodeAddress; -import bisq.common.storage.Storage; - import org.bitcoinj.core.Coin; import lombok.extern.slf4j.Slf4j; @@ -46,7 +44,6 @@ public SellerAsMakerTrade(Offer offer, @Nullable NodeAddress arbitratorNodeAddress, @Nullable NodeAddress mediatorNodeAddress, @Nullable NodeAddress refundAgentNodeAddress, - Storage storage, BtcWalletService btcWalletService, ProcessModel processModel) { super(offer, @@ -56,7 +53,6 @@ public SellerAsMakerTrade(Offer offer, arbitratorNodeAddress, mediatorNodeAddress, refundAgentNodeAddress, - storage, btcWalletService, processModel); } @@ -75,7 +71,6 @@ public protobuf.Tradable toProtoMessage() { } public static Tradable fromProto(protobuf.SellerAsMakerTrade sellerAsMakerTradeProto, - Storage storage, BtcWalletService btcWalletService, CoreProtoResolver coreProtoResolver) { protobuf.Trade proto = sellerAsMakerTradeProto.getTrade(); @@ -88,7 +83,6 @@ public static Tradable fromProto(protobuf.SellerAsMakerTrade sellerAsMakerTradeP proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null, proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null, proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null, - storage, btcWalletService, processModel); diff --git a/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java b/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java index 870f04c3e11..5fb88bf479d 100644 --- a/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java +++ b/core/src/main/java/bisq/core/trade/SellerAsTakerTrade.java @@ -24,8 +24,6 @@ import bisq.network.p2p.NodeAddress; -import bisq.common.storage.Storage; - import org.bitcoinj.core.Coin; import lombok.extern.slf4j.Slf4j; @@ -49,7 +47,6 @@ public SellerAsTakerTrade(Offer offer, @Nullable NodeAddress arbitratorNodeAddress, @Nullable NodeAddress mediatorNodeAddress, @Nullable NodeAddress refundAgentNodeAddress, - Storage storage, BtcWalletService btcWalletService, ProcessModel processModel) { super(offer, @@ -62,7 +59,6 @@ public SellerAsTakerTrade(Offer offer, arbitratorNodeAddress, mediatorNodeAddress, refundAgentNodeAddress, - storage, btcWalletService, processModel); } @@ -81,7 +77,6 @@ public protobuf.Tradable toProtoMessage() { } public static Tradable fromProto(protobuf.SellerAsTakerTrade sellerAsTakerTradeProto, - Storage storage, BtcWalletService btcWalletService, CoreProtoResolver coreProtoResolver) { protobuf.Trade proto = sellerAsTakerTradeProto.getTrade(); @@ -97,7 +92,6 @@ public static Tradable fromProto(protobuf.SellerAsTakerTrade sellerAsTakerTradeP proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null, proto.hasMediatorNodeAddress() ? NodeAddress.fromProto(proto.getMediatorNodeAddress()) : null, proto.hasRefundAgentNodeAddress() ? NodeAddress.fromProto(proto.getRefundAgentNodeAddress()) : null, - storage, btcWalletService, processModel), proto, diff --git a/core/src/main/java/bisq/core/trade/SellerTrade.java b/core/src/main/java/bisq/core/trade/SellerTrade.java index 6701c8b5a0b..2825d60a9d1 100644 --- a/core/src/main/java/bisq/core/trade/SellerTrade.java +++ b/core/src/main/java/bisq/core/trade/SellerTrade.java @@ -23,8 +23,6 @@ import bisq.network.p2p.NodeAddress; -import bisq.common.storage.Storage; - import org.bitcoinj.core.Coin; import lombok.extern.slf4j.Slf4j; @@ -45,7 +43,6 @@ public abstract class SellerTrade extends Trade { @Nullable NodeAddress arbitratorNodeAddress, @Nullable NodeAddress mediatorNodeAddress, @Nullable NodeAddress refundAgentNodeAddress, - Storage storage, BtcWalletService btcWalletService, ProcessModel processModel) { super(offer, @@ -58,7 +55,6 @@ public abstract class SellerTrade extends Trade { arbitratorNodeAddress, mediatorNodeAddress, refundAgentNodeAddress, - storage, btcWalletService, processModel); } @@ -70,7 +66,6 @@ public abstract class SellerTrade extends Trade { @Nullable NodeAddress arbitratorNodeAddress, @Nullable NodeAddress mediatorNodeAddress, @Nullable NodeAddress refundAgentNodeAddress, - Storage storage, BtcWalletService btcWalletService, ProcessModel processModel) { super(offer, @@ -80,7 +75,6 @@ public abstract class SellerTrade extends Trade { arbitratorNodeAddress, mediatorNodeAddress, refundAgentNodeAddress, - storage, btcWalletService, processModel); } diff --git a/core/src/main/java/bisq/core/trade/TradableList.java b/core/src/main/java/bisq/core/trade/TradableList.java index 1b3b2dad224..c3a668708ad 100644 --- a/core/src/main/java/bisq/core/trade/TradableList.java +++ b/core/src/main/java/bisq/core/trade/TradableList.java @@ -23,40 +23,24 @@ import bisq.common.proto.ProtoUtil; import bisq.common.proto.ProtobufferRuntimeException; -import bisq.common.proto.persistable.UserThreadMappedPersistableEnvelope; -import bisq.common.storage.Storage; +import bisq.common.proto.persistable.PersistableListAsObservable; import com.google.protobuf.Message; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; - -import java.util.ArrayList; +import java.util.Collection; import java.util.List; -import java.util.function.Consumer; import java.util.stream.Collectors; -import java.util.stream.Stream; -import lombok.Getter; import lombok.extern.slf4j.Slf4j; @Slf4j -public final class TradableList implements UserThreadMappedPersistableEnvelope { - transient final private Storage> storage; - @Getter - private final ObservableList list = FXCollections.observableArrayList(); - +public final class TradableList extends PersistableListAsObservable { /////////////////////////////////////////////////////////////////////////////////////////// // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - public TradableList(Storage> storage, String fileName) { - this.storage = storage; - - TradableList persisted = storage.initAndGetPersisted(this, fileName, 50); - if (persisted != null) - list.addAll(persisted.getList()); + public TradableList() { } @@ -64,23 +48,20 @@ public TradableList(Storage> storage, String fileName) { // PROTO BUFFER /////////////////////////////////////////////////////////////////////////////////////////// - private TradableList(Storage> storage, List list) { - this.storage = storage; - this.list.addAll(list); + protected TradableList(Collection collection) { + super(collection); } @Override public Message toProtoMessage() { - ArrayList clonedList = new ArrayList<>(this.list); return protobuf.PersistableEnvelope.newBuilder() .setTradableList(protobuf.TradableList.newBuilder() - .addAllTradable(ProtoUtil.collectionToProto(clonedList, protobuf.Tradable.class))) + .addAllTradable(ProtoUtil.collectionToProto(getList(), protobuf.Tradable.class))) .build(); } public static TradableList fromProto(protobuf.TradableList proto, CoreProtoResolver coreProtoResolver, - Storage> storage, BtcWalletService btcWalletService) { List list = proto.getTradableList().stream() .map(tradable -> { @@ -88,66 +69,28 @@ public static TradableList fromProto(protobuf.TradableList proto, case OPEN_OFFER: return OpenOffer.fromProto(tradable.getOpenOffer()); case BUYER_AS_MAKER_TRADE: - return BuyerAsMakerTrade.fromProto(tradable.getBuyerAsMakerTrade(), storage, btcWalletService, coreProtoResolver); + return BuyerAsMakerTrade.fromProto(tradable.getBuyerAsMakerTrade(), btcWalletService, coreProtoResolver); case BUYER_AS_TAKER_TRADE: - return BuyerAsTakerTrade.fromProto(tradable.getBuyerAsTakerTrade(), storage, btcWalletService, coreProtoResolver); + return BuyerAsTakerTrade.fromProto(tradable.getBuyerAsTakerTrade(), btcWalletService, coreProtoResolver); case SELLER_AS_MAKER_TRADE: - return SellerAsMakerTrade.fromProto(tradable.getSellerAsMakerTrade(), storage, btcWalletService, coreProtoResolver); + return SellerAsMakerTrade.fromProto(tradable.getSellerAsMakerTrade(), btcWalletService, coreProtoResolver); case SELLER_AS_TAKER_TRADE: - return SellerAsTakerTrade.fromProto(tradable.getSellerAsTakerTrade(), storage, btcWalletService, coreProtoResolver); + return SellerAsTakerTrade.fromProto(tradable.getSellerAsTakerTrade(), btcWalletService, coreProtoResolver); default: log.error("Unknown messageCase. tradable.getMessageCase() = " + tradable.getMessageCase()); - throw new ProtobufferRuntimeException("Unknown messageCase. tradable.getMessageCase() = " + tradable.getMessageCase()); + throw new ProtobufferRuntimeException("Unknown messageCase. tradable.getMessageCase() = " + + tradable.getMessageCase()); } }) .collect(Collectors.toList()); - return new TradableList<>(storage, list); - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // API - /////////////////////////////////////////////////////////////////////////////////////////// - - public boolean add(T tradable) { - boolean changed = list.add(tradable); - if (changed) - storage.queueUpForSave(); - return changed; - } - - public boolean remove(T tradable) { - boolean changed = list.remove(tradable); - if (changed) - storage.queueUpForSave(); - return changed; - } - - public void persist() { - storage.queueUpForSave(); - } - - public Stream stream() { - return list.stream(); - } - - public void forEach(Consumer action) { - list.forEach(action); - } - - public int size() { - return list.size(); - } - - public boolean contains(T thing) { - return list.contains(thing); + return new TradableList<>(list); } @Override public String toString() { return "TradableList{" + - ",\n list=" + list + + ",\n list=" + getList() + "\n}"; } } diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index 8e5d3490b22..81e9296149e 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -37,7 +37,6 @@ import bisq.common.crypto.PubKeyRing; import bisq.common.proto.ProtoUtil; -import bisq.common.storage.Storage; import bisq.common.taskrunner.Model; import bisq.common.util.Utilities; @@ -65,8 +64,6 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import java.time.temporal.ChronoUnit; - import java.util.Date; import java.util.Optional; import java.util.stream.Collectors; @@ -368,10 +365,8 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt transient final private Coin txFee; @Getter transient final private Coin takerFee; - @Getter // to set in constructor so not final but set at init - transient private Storage storage; - @Getter // to set in constructor so not final but set at init - transient private BtcWalletService btcWalletService; + @Getter + transient final private BtcWalletService btcWalletService; transient final private ObjectProperty stateProperty = new SimpleObjectProperty<>(state); transient final private ObjectProperty statePhaseProperty = new SimpleObjectProperty<>(state.phase); @@ -424,14 +419,6 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt private RefundResultState refundResultState = RefundResultState.UNDEFINED_REFUND_RESULT; transient final private ObjectProperty refundResultStateProperty = new SimpleObjectProperty<>(refundResultState); - // Added in v1.2.6 - @Getter - @Setter - private long lastRefreshRequestDate; - @Getter - private final long refreshInterval; - private static final long MAX_REFRESH_INTERVAL = 4 * ChronoUnit.HOURS.getDuration().toMillis(); - // Added at v1.3.8 // We use that for the XMR txKey but want to keep it generic to be flexible for other payment methods or assets. @Getter @@ -463,7 +450,6 @@ protected Trade(Offer offer, @Nullable NodeAddress arbitratorNodeAddress, @Nullable NodeAddress mediatorNodeAddress, @Nullable NodeAddress refundAgentNodeAddress, - Storage storage, BtcWalletService btcWalletService, ProcessModel processModel) { this.offer = offer; @@ -473,15 +459,12 @@ protected Trade(Offer offer, this.arbitratorNodeAddress = arbitratorNodeAddress; this.mediatorNodeAddress = mediatorNodeAddress; this.refundAgentNodeAddress = refundAgentNodeAddress; - this.storage = storage; this.btcWalletService = btcWalletService; this.processModel = processModel; txFeeAsLong = txFee.value; takerFeeAsLong = takerFee.value; takeOfferDate = new Date().getTime(); - lastRefreshRequestDate = takeOfferDate; - refreshInterval = Math.min(offer.getPaymentMethod().getMaxTradePeriod() / 5, MAX_REFRESH_INTERVAL); } @@ -497,7 +480,6 @@ protected Trade(Offer offer, @Nullable NodeAddress arbitratorNodeAddress, @Nullable NodeAddress mediatorNodeAddress, @Nullable NodeAddress refundAgentNodeAddress, - Storage storage, BtcWalletService btcWalletService, ProcessModel processModel) { @@ -508,7 +490,6 @@ protected Trade(Offer offer, arbitratorNodeAddress, mediatorNodeAddress, refundAgentNodeAddress, - storage, btcWalletService, processModel); this.tradePrice = tradePrice; @@ -539,8 +520,7 @@ public Message toProtoMessage() { .addAllChatMessage(chatMessages.stream() .map(msg -> msg.toProtoNetworkEnvelope().getChatMessage()) .collect(Collectors.toList())) - .setLockTime(lockTime) - .setLastRefreshRequestDate(lastRefreshRequestDate); + .setLockTime(lockTime); Optional.ofNullable(takerFeeTxId).ifPresent(builder::setTakerFeeTxId); Optional.ofNullable(depositTxId).ifPresent(builder::setDepositTxId); @@ -597,7 +577,6 @@ public static Trade fromProto(Trade trade, protobuf.Trade proto, CoreProtoResolv trade.setRefundResultState(RefundResultState.fromProto(proto.getRefundResultState())); trade.setDelayedPayoutTxBytes(ProtoUtil.byteArrayOrNullFromProto(proto.getDelayedPayoutTxBytes())); trade.setLockTime(proto.getLockTime()); - trade.setLastRefreshRequestDate(proto.getLastRefreshRequestDate()); trade.setCounterCurrencyExtraData(ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyExtraData())); AssetTxProofResult persistedAssetTxProofResult = ProtoUtil.enumFromProto(AssetTxProofResult.class, proto.getAssetTxProofResult()); @@ -619,26 +598,18 @@ public static Trade fromProto(Trade trade, protobuf.Trade proto, CoreProtoResolv // API /////////////////////////////////////////////////////////////////////////////////////////// - public void setTransientFields(Storage storage, BtcWalletService btcWalletService) { - this.storage = storage; - this.btcWalletService = btcWalletService; - } - public void initialize(ProcessModelServiceProvider serviceProvider) { serviceProvider.getArbitratorManager().getDisputeAgentByNodeAddress(arbitratorNodeAddress).ifPresent(arbitrator -> { arbitratorBtcPubKey = arbitrator.getBtcPubKey(); arbitratorPubKeyRing = arbitrator.getPubKeyRing(); - persist(); }); serviceProvider.getMediatorManager().getDisputeAgentByNodeAddress(mediatorNodeAddress).ifPresent(mediator -> { mediatorPubKeyRing = mediator.getPubKeyRing(); - persist(); }); serviceProvider.getRefundAgentManager().getDisputeAgentByNodeAddress(refundAgentNodeAddress).ifPresent(refundAgent -> { refundAgentPubKeyRing = refundAgent.getPubKeyRing(); - persist(); }); isInitialized = true; @@ -659,7 +630,6 @@ public void applyDepositTx(Transaction tx) { this.depositTx = tx; depositTxId = depositTx.getTxId().toString(); setupConfidenceListener(); - persist(); } @Nullable @@ -673,12 +643,10 @@ public Transaction getDepositTx() { public void applyDelayedPayoutTx(Transaction delayedPayoutTx) { this.delayedPayoutTx = delayedPayoutTx; this.delayedPayoutTxBytes = delayedPayoutTx.bitcoinSerialize(); - persist(); } public void applyDelayedPayoutTxBytes(byte[] delayedPayoutTxBytes) { this.delayedPayoutTxBytes = delayedPayoutTxBytes; - persist(); } @Nullable @@ -710,7 +678,6 @@ public Transaction getDelayedPayoutTx(BtcWalletService btcWalletService) { public void addAndPersistChatMessage(ChatMessage chatMessage) { if (!chatMessages.contains(chatMessage)) { chatMessages.add(chatMessage); - storage.queueUpForSave(); } else { log.error("Trade ChatMessage already exists"); } @@ -720,35 +687,13 @@ public void appendErrorMessage(String msg) { errorMessage = errorMessage == null ? msg : errorMessage + "\n" + msg; } - public boolean allowedRefresh() { - var allowRefresh = new Date().getTime() > lastRefreshRequestDate + getRefreshInterval(); - if (!allowRefresh) { - log.info("Refresh not allowed, last refresh at {}", lastRefreshRequestDate); - } - return allowRefresh; - } - - public void logRefresh() { - var time = new Date().getTime(); - log.debug("Log refresh at {}", time); - lastRefreshRequestDate = time; - } - /////////////////////////////////////////////////////////////////////////////////////////// // Model implementation /////////////////////////////////////////////////////////////////////////////////////////// - // Get called from taskRunner after each completed task - @Override - public void persist() { - if (storage != null) - storage.queueUpForSave(); - } - @Override public void onComplete() { - persist(); } @@ -774,6 +719,7 @@ public void setStateIfValidTransitionTo(State newState) { public void setState(State state) { if (isInitialized) { + // We don't want to log at startup the setState calls from all persisted trades log.info("Set new state at {} (id={}): {}", this.getClass().getSimpleName(), getShortId(), state); } if (state.getPhase().ordinal() < this.state.getPhase().ordinal()) { @@ -782,46 +728,29 @@ public void setState(State state) { log.warn(message); } - boolean changed = this.state != state; this.state = state; stateProperty.set(state); statePhaseProperty.set(state.getPhase()); - - if (changed) - persist(); } public void setDisputeState(DisputeState disputeState) { - boolean changed = this.disputeState != disputeState; this.disputeState = disputeState; disputeStateProperty.set(disputeState); - if (changed) - persist(); } public void setMediationResultState(MediationResultState mediationResultState) { - boolean changed = this.mediationResultState != mediationResultState; this.mediationResultState = mediationResultState; mediationResultStateProperty.set(mediationResultState); - if (changed) - persist(); } public void setRefundResultState(RefundResultState refundResultState) { - boolean changed = this.refundResultState != refundResultState; this.refundResultState = refundResultState; refundResultStateProperty.set(refundResultState); - if (changed) - persist(); } - public void setTradePeriodState(TradePeriodState tradePeriodState) { - boolean changed = this.tradePeriodState != tradePeriodState; this.tradePeriodState = tradePeriodState; tradePeriodStateProperty.set(tradePeriodState); - if (changed) - persist(); } public void setTradingPeerNodeAddress(NodeAddress tradingPeerNodeAddress) { @@ -851,7 +780,6 @@ public void setErrorMessage(String errorMessage) { public void setAssetTxProofResult(@Nullable AssetTxProofResult assetTxProofResult) { this.assetTxProofResult = assetTxProofResult; assetTxProofResultUpdateProperty.set(assetTxProofResultUpdateProperty.get() + 1); - persist(); } @@ -1188,7 +1116,6 @@ public String toString() { ",\n chatMessages=" + chatMessages + ",\n txFee=" + txFee + ",\n takerFee=" + takerFee + - ",\n storage=" + storage + ",\n btcWalletService=" + btcWalletService + ",\n stateProperty=" + stateProperty + ",\n statePhaseProperty=" + statePhaseProperty + @@ -1209,7 +1136,6 @@ public String toString() { ",\n refundAgentPubKeyRing=" + refundAgentPubKeyRing + ",\n refundResultState=" + refundResultState + ",\n refundResultStateProperty=" + refundResultStateProperty + - ",\n lastRefreshRequestDate=" + lastRefreshRequestDate + "\n}"; } } diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index 58bba982cfb..42cb6b9e000 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -56,9 +56,9 @@ import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.FaultHandler; import bisq.common.handlers.ResultHandler; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.network.NetworkEnvelope; import bisq.common.proto.persistable.PersistedDataHost; -import bisq.common.storage.Storage; import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.Coin; @@ -123,9 +123,9 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi private final ProcessModelServiceProvider processModelServiceProvider; private final ClockWatcher clockWatcher; - private final Storage> tradableListStorage; private final Map tradeProtocolByTradeId = new HashMap<>(); - private TradableList tradableList; + private final PersistenceManager> persistenceManager; + private final TradableList tradableList = new TradableList<>(); @Getter private final BooleanProperty persistedTradesInitialized = new SimpleBooleanProperty(); @Setter @@ -157,7 +157,7 @@ public TradeManager(User user, MediatorManager mediatorManager, ProcessModelServiceProvider processModelServiceProvider, ClockWatcher clockWatcher, - Storage> storage, + PersistenceManager> persistenceManager, DumpDelayedPayoutTx dumpDelayedPayoutTx, @Named(Config.ALLOW_FAULTY_DELAYED_TXS) boolean allowFaultyDelayedTxs) { this.user = user; @@ -176,8 +176,9 @@ public TradeManager(User user, this.clockWatcher = clockWatcher; this.dumpDelayedPayoutTx = dumpDelayedPayoutTx; this.allowFaultyDelayedTxs = allowFaultyDelayedTxs; + this.persistenceManager = persistenceManager; - tradableListStorage = storage; + this.persistenceManager.initialize(tradableList, "PendingTrades", PersistenceManager.Source.PRIVATE); p2PService.addDecryptedDirectMessageListener(this); @@ -191,9 +192,12 @@ public TradeManager(User user, @Override public void readPersisted() { - tradableList = new TradableList<>(tradableListStorage, "PendingTrades"); + TradableList persisted = persistenceManager.getPersisted(); + if (persisted != null) { + tradableList.setAll(persisted.getList()); + } + tradableList.forEach(trade -> { - trade.setTransientFields(tradableListStorage, btcWalletService); Offer offer = trade.getOffer(); if (offer != null) offer.setPriceFeedService(priceFeedService); @@ -248,7 +252,6 @@ private void handleTakeOfferRequest(NodeAddress peer, InputsForDepositTxRequest openOffer.getArbitratorNodeAddress(), openOffer.getMediatorNodeAddress(), openOffer.getRefundAgentNodeAddress(), - tradableListStorage, btcWalletService, getNewProcessModel(offer)); } else { @@ -259,7 +262,6 @@ private void handleTakeOfferRequest(NodeAddress peer, InputsForDepositTxRequest openOffer.getArbitratorNodeAddress(), openOffer.getMediatorNodeAddress(), openOffer.getRefundAgentNodeAddress(), - tradableListStorage, btcWalletService, getNewProcessModel(offer)); } @@ -272,6 +274,8 @@ private void handleTakeOfferRequest(NodeAddress peer, InputsForDepositTxRequest if (takeOfferRequestErrorMessageHandler != null) takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); }); + + requestPersistence(); } @@ -291,7 +295,7 @@ public void onUpdatedDataReceived() { }); } - getTradesAsObservableList().addListener((ListChangeListener) change -> onTradesChanged()); + getObservableList().addListener((ListChangeListener) change -> onTradesChanged()); onTradesChanged(); btcWalletService.getAddressEntriesForAvailableBalanceStream() @@ -302,10 +306,6 @@ public void onUpdatedDataReceived() { }); } - public void persistTrades() { - tradableList.persist(); - } - public TradeProtocol getTradeProtocol(Trade trade) { if (tradeProtocolByTradeId.containsKey(trade.getId())) { return tradeProtocolByTradeId.get(trade.getId()); @@ -336,6 +336,10 @@ private void initTradeAndProtocol(Trade trade, TradeProtocol tradeProtocol) { trade.initialize(processModelServiceProvider); } + public void requestPersistence() { + persistenceManager.requestPersistence(); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Take offer @@ -386,7 +390,6 @@ public void onTakeOffer(Coin amount, model.getSelectedArbitrator(), model.getSelectedMediator(), model.getSelectedRefundAgent(), - tradableListStorage, btcWalletService, getNewProcessModel(offer)); } else { @@ -400,7 +403,6 @@ public void onTakeOffer(Coin amount, model.getSelectedArbitrator(), model.getSelectedMediator(), model.getSelectedRefundAgent(), - tradableListStorage, btcWalletService, getNewProcessModel(offer)); } @@ -419,6 +421,8 @@ public void onTakeOffer(Coin amount, } }, errorMessageHandler); + + requestPersistence(); } private ProcessModel getNewProcessModel(Offer offer) { @@ -454,6 +458,7 @@ public void onSuccess(@javax.annotation.Nullable Transaction transaction) { onTradeCompleted(trade); trade.setState(Trade.State.WITHDRAW_COMPLETED); getTradeProtocol(trade).onWithdrawCompleted(); + requestPersistence(); resultHandler.handleResult(); } } @@ -481,6 +486,7 @@ public void onTradeCompleted(Trade trade) { // TODO The address entry should have been removed already. Check and if its the case remove that. btcWalletService.resetAddressEntriesForPendingTrade(trade.getId()); + requestPersistence(); } @@ -495,6 +501,7 @@ public void closeDisputedTrade(String tradeId, Trade.DisputeState disputeState) trade.setDisputeState(disputeState); onTradeCompleted(trade); btcWalletService.swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT); + requestPersistence(); } } @@ -518,7 +525,7 @@ public void onMinuteTick() { } private void updateTradePeriodState() { - getTradesAsObservableList().forEach(trade -> { + getObservableList().forEach(trade -> { if (!trade.isPayoutPublished()) { Date maxTradePeriodDate = trade.getMaxTradePeriodDate(); Date halfTradePeriodDate = trade.getHalfTradePeriodDate(); @@ -553,7 +560,7 @@ public void addFailedTradeToPendingTrades(Trade trade) { } public Stream getTradesStreamWithFundsLockedIn() { - return getTradesAsObservableList().stream().filter(Trade::isFundsLockedIn); + return getObservableList().stream().filter(Trade::isFundsLockedIn); } public Set getSetOfFailedOrClosedTradeIdsFromLockedInFunds() throws TradeTxException { @@ -630,8 +637,8 @@ private boolean recoverAddresses(Trade trade) { // Getters, Utils /////////////////////////////////////////////////////////////////////////////////////////// - public ObservableList getTradesAsObservableList() { - return tradableList.getList(); + public ObservableList getObservableList() { + return tradableList.getObservableList(); } public BooleanProperty persistedTradesInitializedProperty() { @@ -661,18 +668,20 @@ public Optional getTradeById(String tradeId) { } private void removeTrade(Trade trade) { - tradableList.remove(trade); + if (tradableList.remove(trade)) { + requestPersistence(); + } } private void addTrade(Trade trade) { - if (!tradableList.contains(trade)) { - tradableList.add(trade); + if (tradableList.add(trade)) { + requestPersistence(); } } // TODO Remove once tradableList is refactored to a final field // (part of the persistence refactor PR) private void onTradesChanged() { - this.numPendingTrades.set(getTradesAsObservableList().size()); + this.numPendingTrades.set(getObservableList().size()); } } diff --git a/core/src/main/java/bisq/core/trade/closed/ClosedTradableManager.java b/core/src/main/java/bisq/core/trade/closed/ClosedTradableManager.java index 7922511c00f..69567270cbf 100644 --- a/core/src/main/java/bisq/core/trade/closed/ClosedTradableManager.java +++ b/core/src/main/java/bisq/core/trade/closed/ClosedTradableManager.java @@ -17,7 +17,6 @@ package bisq.core.trade.closed; -import bisq.core.btc.wallet.BtcWalletService; import bisq.core.offer.Offer; import bisq.core.provider.price.PriceFeedService; import bisq.core.trade.DumpDelayedPayoutTx; @@ -26,8 +25,8 @@ import bisq.core.trade.Trade; import bisq.common.crypto.KeyRing; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.persistable.PersistedDataHost; -import bisq.common.storage.Storage; import com.google.inject.Inject; @@ -41,60 +40,59 @@ import java.util.stream.Stream; public class ClosedTradableManager implements PersistedDataHost { - private final Storage> tradableListStorage; - private TradableList closedTradables; + private final PersistenceManager> persistenceManager; + private final TradableList closedTradables = new TradableList<>(); private final KeyRing keyRing; private final PriceFeedService priceFeedService; - private final BtcWalletService btcWalletService; private final DumpDelayedPayoutTx dumpDelayedPayoutTx; @Inject public ClosedTradableManager(KeyRing keyRing, PriceFeedService priceFeedService, - BtcWalletService btcWalletService, - Storage> storage, + PersistenceManager> persistenceManager, DumpDelayedPayoutTx dumpDelayedPayoutTx) { this.keyRing = keyRing; this.priceFeedService = priceFeedService; - this.btcWalletService = btcWalletService; - tradableListStorage = storage; this.dumpDelayedPayoutTx = dumpDelayedPayoutTx; - // The ClosedTrades object can become a few MB so we don't keep so many backups - tradableListStorage.setNumMaxBackupFiles(3); + this.persistenceManager = persistenceManager; + + this.persistenceManager.initialize(closedTradables, "ClosedTrades", PersistenceManager.Source.PRIVATE); } @Override public void readPersisted() { - closedTradables = new TradableList<>(tradableListStorage, "ClosedTrades"); - closedTradables.forEach(tradable -> { - tradable.getOffer().setPriceFeedService(priceFeedService); - if (tradable instanceof Trade) { - Trade trade = (Trade) tradable; - trade.setTransientFields(tradableListStorage, btcWalletService); - } - }); + TradableList persisted = persistenceManager.getPersisted(); + if (persisted != null) { + closedTradables.setAll(persisted.getList()); + } + + closedTradables.forEach(tradable -> tradable.getOffer().setPriceFeedService(priceFeedService)); dumpDelayedPayoutTx.maybeDumpDelayedPayoutTxs(closedTradables, "delayed_payout_txs_closed"); } public void add(Tradable tradable) { - closedTradables.add(tradable); + if (closedTradables.add(tradable)) { + persistenceManager.requestPersistence(); + } } public void remove(Tradable tradable) { - closedTradables.remove(tradable); + if (closedTradables.remove(tradable)) { + persistenceManager.requestPersistence(); + } } public boolean wasMyOffer(Offer offer) { return offer.isMyOffer(keyRing); } - public ObservableList getClosedTradables() { - return closedTradables.getList(); + public ObservableList getObservableList() { + return closedTradables.getObservableList(); } public List getClosedTrades() { - return ImmutableList.copyOf(getClosedTradables().stream() + return ImmutableList.copyOf(getObservableList().stream() .filter(e -> e instanceof Trade) .map(e -> (Trade) e) .collect(Collectors.toList())); diff --git a/core/src/main/java/bisq/core/trade/failed/FailedTradesManager.java b/core/src/main/java/bisq/core/trade/failed/FailedTradesManager.java index 29cdd9a7888..a584f6b783f 100644 --- a/core/src/main/java/bisq/core/trade/failed/FailedTradesManager.java +++ b/core/src/main/java/bisq/core/trade/failed/FailedTradesManager.java @@ -27,8 +27,8 @@ import bisq.core.trade.TradeUtils; import bisq.common.crypto.KeyRing; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.persistable.PersistedDataHost; -import bisq.common.storage.Storage; import com.google.inject.Inject; @@ -45,11 +45,11 @@ public class FailedTradesManager implements PersistedDataHost { private static final Logger log = LoggerFactory.getLogger(FailedTradesManager.class); - private TradableList failedTrades; + private final TradableList failedTrades = new TradableList<>(); private final KeyRing keyRing; private final PriceFeedService priceFeedService; private final BtcWalletService btcWalletService; - private final Storage> tradableListStorage; + private final PersistenceManager> persistenceManager; private final DumpDelayedPayoutTx dumpDelayedPayoutTx; @Setter private Function unFailTradeCallback; @@ -58,45 +58,51 @@ public class FailedTradesManager implements PersistedDataHost { public FailedTradesManager(KeyRing keyRing, PriceFeedService priceFeedService, BtcWalletService btcWalletService, - Storage> storage, + PersistenceManager> persistenceManager, DumpDelayedPayoutTx dumpDelayedPayoutTx) { this.keyRing = keyRing; this.priceFeedService = priceFeedService; this.btcWalletService = btcWalletService; - tradableListStorage = storage; this.dumpDelayedPayoutTx = dumpDelayedPayoutTx; + this.persistenceManager = persistenceManager; + + this.persistenceManager.initialize(failedTrades, "FailedTrades", PersistenceManager.Source.PRIVATE); } @Override public void readPersisted() { - this.failedTrades = new TradableList<>(tradableListStorage, "FailedTrades"); + TradableList persisted = persistenceManager.getPersisted(); + if (persisted != null) { + failedTrades.setAll(persisted.getList()); + } + failedTrades.forEach(trade -> { if (trade.getOffer() != null) { trade.getOffer().setPriceFeedService(priceFeedService); } - - trade.setTransientFields(tradableListStorage, btcWalletService); }); dumpDelayedPayoutTx.maybeDumpDelayedPayoutTxs(failedTrades, "delayed_payout_txs_failed"); } public void add(Trade trade) { - if (!failedTrades.contains(trade)) { - failedTrades.add(trade); + if (failedTrades.add(trade)) { + persistenceManager.requestPersistence(); } } public void removeTrade(Trade trade) { - failedTrades.remove(trade); + if (failedTrades.remove(trade)) { + persistenceManager.requestPersistence(); + } } public boolean wasMyOffer(Offer offer) { return offer.isMyOffer(keyRing); } - public ObservableList getFailedTrades() { - return failedTrades.getList(); + public ObservableList getObservableList() { + return failedTrades.getObservableList(); } public Optional getTradeById(String id) { @@ -114,7 +120,9 @@ public void unFailTrade(Trade trade) { if (unFailTradeCallback.apply(trade)) { log.info("Unfailing trade {}", trade.getId()); - failedTrades.remove(trade); + if (failedTrades.remove(trade)) { + persistenceManager.requestPersistence(); + } } } diff --git a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java index ee7338b0944..017e640fddb 100644 --- a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java +++ b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java @@ -248,11 +248,6 @@ public static ProcessModel fromProto(protobuf.ProcessModel proto, CoreProtoResol // API /////////////////////////////////////////////////////////////////////////////////////////// - @Override - public void persist() { - log.warn("persist is not implemented in that class"); - } - @Override public void onComplete() { } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java index 50e302db54c..1ad8d8da534 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer_as_taker/BuyerAsTakerSignsDepositTx.java @@ -51,10 +51,10 @@ protected void run() { try { runInterceptHook(); - log.debug("\n\n------------------------------------------------------------\n" + /* log.debug("\n\n------------------------------------------------------------\n" + "Contract as json\n" + trade.getContractAsJson() - + "\n------------------------------------------------------------\n"); + + "\n------------------------------------------------------------\n");*/ byte[] contractHash = Hash.getSha256Hash(checkNotNull(trade.getContractAsJson())); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessesInputsForDepositTxRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessesInputsForDepositTxRequest.java index 1c7f4be239b..d048ba2ef46 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessesInputsForDepositTxRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerProcessesInputsForDepositTxRequest.java @@ -107,7 +107,7 @@ protected void run() { trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); - trade.persist(); + processModel.getTradeManager().requestPersistence(); complete(); } catch (Throwable t) { diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendPayoutTxPublishedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendPayoutTxPublishedMessage.java index 80599ef7b66..db73f1910a6 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendPayoutTxPublishedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerSendPayoutTxPublishedMessage.java @@ -33,7 +33,6 @@ import static com.google.common.base.Preconditions.checkNotNull; -//TODO add repeated msg send @EqualsAndHashCode(callSuper = true) @Slf4j public class SellerSendPayoutTxPublishedMessage extends SendMailboxMessageTask { diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2StorageService.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2StorageService.java index b5a9699e134..52822e2f2e0 100644 --- a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2StorageService.java +++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2StorageService.java @@ -22,7 +22,7 @@ import bisq.network.p2p.storage.persistence.MapStoreService; import bisq.common.config.Config; -import bisq.common.storage.Storage; +import bisq.common.persistence.PersistenceManager; import javax.inject.Inject; import javax.inject.Named; @@ -44,8 +44,8 @@ public class TradeStatistics2StorageService extends MapStoreService persistableNetworkPayloadMapStorage) { - super(storageDir, persistableNetworkPayloadMapStorage); + PersistenceManager persistenceManager) { + super(storageDir, persistenceManager); } @@ -53,6 +53,11 @@ public TradeStatistics2StorageService(@Named(Config.STORAGE_DIR) File storageDir // API /////////////////////////////////////////////////////////////////////////////////////////// + @Override + protected void initializePersistenceManager() { + persistenceManager.initialize(store, PersistenceManager.Source.NETWORK); + } + @Override public String getFileName() { return FILE_NAME; diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2Store.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2Store.java index 8a4b8fb8648..266c6a0d3b3 100644 --- a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2Store.java +++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2Store.java @@ -17,7 +17,6 @@ package bisq.core.trade.statistics; -import bisq.network.p2p.storage.P2PDataStorage; import bisq.network.p2p.storage.persistence.PersistableNetworkPayloadStore; import com.google.protobuf.Message; @@ -33,7 +32,7 @@ * definition and provide a hashMap for the domain access. */ @Slf4j -public class TradeStatistics2Store extends PersistableNetworkPayloadStore { +public class TradeStatistics2Store extends PersistableNetworkPayloadStore { TradeStatistics2Store() { } @@ -44,7 +43,7 @@ public class TradeStatistics2Store extends PersistableNetworkPayloadStore { /////////////////////////////////////////////////////////////////////////////////////////// private TradeStatistics2Store(List list) { - list.forEach(item -> map.put(new P2PDataStorage.ByteArray(item.getHash()), item)); + super(list); } public Message toProtoMessage() { @@ -66,8 +65,4 @@ public static TradeStatistics2Store fromProto(protobuf.TradeStatistics2Store pro .map(TradeStatistics2::fromProto).collect(Collectors.toList()); return new TradeStatistics2Store(list); } - - public boolean containsKey(P2PDataStorage.ByteArray hash) { - return map.containsKey(hash); - } } diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsManager.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsManager.java index dfa64953000..4dbe86c45bb 100644 --- a/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsManager.java +++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatisticsManager.java @@ -26,7 +26,7 @@ import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService; import bisq.common.config.Config; -import bisq.common.storage.JsonFileManager; +import bisq.common.file.JsonFileManager; import bisq.common.util.Utilities; import com.google.inject.Inject; diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java index e954c83e32f..30349d3f784 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java @@ -177,12 +177,13 @@ private void onP2pNetworkAndWalletReady() { servicesByTradeId.values().stream().map(XmrTxProofRequestsPerTrade::getTrade).forEach(trade -> trade.setAssetTxProofResult(AssetTxProofResult.FEATURE_DISABLED .details(Res.get("portfolio.pending.autoConf.state.filterDisabledFeature")))); + tradeManager.requestPersistence(); shutDown(); } }); // We listen on new trades - ObservableList tradableList = tradeManager.getTradesAsObservableList(); + ObservableList tradableList = tradeManager.getObservableList(); tradableList.addListener((ListChangeListener) c -> { c.next(); if (c.wasAdded()) { @@ -230,18 +231,21 @@ private void startRequestsIfValid(SellerTrade trade) { String txHash = trade.getCounterCurrencyExtraData(); if (is32BitHexStringInValid(txId) || is32BitHexStringInValid(txHash)) { trade.setAssetTxProofResult(AssetTxProofResult.INVALID_DATA.details(Res.get("portfolio.pending.autoConf.state.txKeyOrTxIdInvalid"))); + tradeManager.requestPersistence(); return; } if (isAutoConfDisabledByFilter()) { trade.setAssetTxProofResult(AssetTxProofResult.FEATURE_DISABLED .details(Res.get("portfolio.pending.autoConf.state.filterDisabledFeature"))); + tradeManager.requestPersistence(); return; } - if (wasTxKeyReUsed(trade, tradeManager.getTradesAsObservableList())) { + if (wasTxKeyReUsed(trade, tradeManager.getObservableList())) { trade.setAssetTxProofResult(AssetTxProofResult.INVALID_DATA .details(Res.get("portfolio.pending.autoConf.state.xmr.txKeyReused"))); + tradeManager.requestPersistence(); return; } @@ -272,6 +276,8 @@ private void startRequests(SellerTrade trade) { if (assetTxProofResult.isTerminal()) { servicesByTradeId.remove(trade.getId()); } + + tradeManager.requestPersistence(); }, (errorMessage, throwable) -> { log.error(errorMessage); @@ -368,8 +374,8 @@ private boolean wasTxKeyReUsed(Trade trade, List activeTrades) { // We need to prevent that a user tries to scam by reusing a txKey and txHash of a previous XMR trade with // the same user (same address) and same amount. We check only for the txKey as a same txHash but different // txKey is not possible to get a valid result at proof. - Stream failedAndOpenTrades = Stream.concat(activeTrades.stream(), failedTradesManager.getFailedTrades().stream()); - Stream closedTrades = closedTradableManager.getClosedTradables().stream() + Stream failedAndOpenTrades = Stream.concat(activeTrades.stream(), failedTradesManager.getObservableList().stream()); + Stream closedTrades = closedTradableManager.getObservableList().stream() .filter(tradable -> tradable instanceof Trade) .map(tradable -> (Trade) tradable); Stream allTrades = Stream.concat(failedAndOpenTrades, closedTrades); diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index f04b4c6a5f3..656f056522a 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -35,8 +35,8 @@ import bisq.common.config.BaseCurrencyNetwork; import bisq.common.config.Config; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.persistable.PersistedDataHost; -import bisq.common.storage.Storage; import bisq.common.util.Utilities; import javax.inject.Inject; @@ -153,7 +153,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid private final ObservableList tradeCurrenciesAsObservable = FXCollections.observableArrayList(); private final ObservableMap dontShowAgainMapAsObservable = FXCollections.observableHashMap(); - private final Storage storage; + private final PersistenceManager persistenceManager; private final Config config; private final LocalBitcoinNode localBitcoinNode; private final String btcNodesFromOptions, referralIdFromOptions, @@ -171,7 +171,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid @SuppressWarnings("WeakerAccess") @Inject - public Preferences(Storage storage, + public Preferences(PersistenceManager persistenceManager, Config config, LocalBitcoinNode localBitcoinNode, @Named(Config.BTC_NODES) String btcNodesFromOptions, @@ -181,7 +181,7 @@ public Preferences(Storage storage, @Named(Config.RPC_PASSWORD) String rpcPassword, @Named(Config.RPC_BLOCK_NOTIFICATION_PORT) int rpcBlockNotificationPort) { - this.storage = storage; + this.persistenceManager = persistenceManager; this.config = config; this.localBitcoinNode = localBitcoinNode; this.btcNodesFromOptions = btcNodesFromOptions; @@ -194,30 +194,30 @@ public Preferences(Storage storage, useAnimationsProperty.addListener((ov) -> { prefPayload.setUseAnimations(useAnimationsProperty.get()); GlobalSettings.setUseAnimations(prefPayload.isUseAnimations()); - persist(); + requestPersistence(); }); cssThemeProperty.addListener((ov) -> { prefPayload.setCssTheme(cssThemeProperty.get()); - persist(); + requestPersistence(); }); useStandbyModeProperty.addListener((ov) -> { prefPayload.setUseStandbyMode(useStandbyModeProperty.get()); - persist(); + requestPersistence(); }); fiatCurrenciesAsObservable.addListener((javafx.beans.Observable ov) -> { prefPayload.getFiatCurrencies().clear(); prefPayload.getFiatCurrencies().addAll(fiatCurrenciesAsObservable); prefPayload.getFiatCurrencies().sort(TradeCurrency::compareTo); - persist(); + requestPersistence(); }); cryptoCurrenciesAsObservable.addListener((javafx.beans.Observable ov) -> { prefPayload.getCryptoCurrencies().clear(); prefPayload.getCryptoCurrencies().addAll(cryptoCurrenciesAsObservable); prefPayload.getCryptoCurrencies().sort(TradeCurrency::compareTo); - persist(); + requestPersistence(); }); fiatCurrenciesAsObservable.addListener(this::updateTradeCurrencies); @@ -226,9 +226,10 @@ public Preferences(Storage storage, @Override public void readPersisted() { - PreferencesPayload persisted = storage.initAndGetPersistedWithFileName("PreferencesPayload", 100); BaseCurrencyNetwork baseCurrencyNetwork = Config.baseCurrencyNetwork(); TradeCurrency preferredTradeCurrency; + + PreferencesPayload persisted = persistenceManager.getPersisted("PreferencesPayload"); if (persisted != null) { prefPayload = persisted; GlobalSettings.setLocale(new Locale(prefPayload.getUserLanguage(), prefPayload.getUserCountry().code)); @@ -249,13 +250,11 @@ public void readPersisted() { setFiatCurrencies(CurrencyUtil.getMainFiatCurrencies()); setCryptoCurrencies(CurrencyUtil.getMainCryptoCurrencies()); - switch (baseCurrencyNetwork.getCurrencyCode()) { - case "BTC": - setBlockChainExplorerMainNet(BTC_MAIN_NET_EXPLORERS.get(0)); - setBlockChainExplorerTestNet(BTC_TEST_NET_EXPLORERS.get(0)); - break; - default: - throw new RuntimeException("BaseCurrencyNetwork not defined. BaseCurrencyNetwork=" + baseCurrencyNetwork); + if ("BTC".equals(baseCurrencyNetwork.getCurrencyCode())) { + setBlockChainExplorerMainNet(BTC_MAIN_NET_EXPLORERS.get(0)); + setBlockChainExplorerTestNet(BTC_TEST_NET_EXPLORERS.get(0)); + } else { + throw new RuntimeException("BaseCurrencyNetwork not defined. BaseCurrencyNetwork=" + baseCurrencyNetwork); } prefPayload.setDirectoryChooserPath(Utilities.getSystemHomeDirectory()); @@ -266,6 +265,8 @@ public void readPersisted() { prefPayload.setSellScreenCurrencyCode(preferredTradeCurrency.getCode()); } + persistenceManager.initialize(prefPayload, PersistenceManager.Source.PRIVATE); + // We don't want to pass Preferences to all popups where the don't show again checkbox is used, so we use // that static lookup class to avoid static access to the Preferences directly. DontShowAgainLookup.setPreferences(this); @@ -332,7 +333,7 @@ public void readPersisted() { CoreNetworkCapabilities.maybeApplyDaoFullMode(config); initialReadDone = true; - persist(); + requestPersistence(); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -341,14 +342,14 @@ public void readPersisted() { public void dontShowAgain(String key, boolean dontShowAgain) { prefPayload.getDontShowAgainMap().put(key, dontShowAgain); - persist(); + requestPersistence(); dontShowAgainMapAsObservable.put(key, dontShowAgain); } public void resetDontShowAgain() { prefPayload.getDontShowAgainMap().clear(); - persist(); dontShowAgainMapAsObservable.clear(); + requestPersistence(); } @@ -407,12 +408,12 @@ public void setBlockChainExplorer(BlockChainExplorer blockChainExplorer) { public void setTacAccepted(boolean tacAccepted) { prefPayload.setTacAccepted(tacAccepted); - persist(); + requestPersistence(); } public void setTacAcceptedV120(boolean tacAccepted) { prefPayload.setTacAcceptedV120(tacAccepted); - persist(); + requestPersistence(); } public Optional findAutoConfirmSettings(String currencyCode) { @@ -424,146 +425,146 @@ public Optional findAutoConfirmSettings(String currencyCode public void setAutoConfServiceAddresses(String currencyCode, List serviceAddresses) { findAutoConfirmSettings(currencyCode).ifPresent(e -> { e.setServiceAddresses(serviceAddresses); - persist(); + requestPersistence(); }); } public void setAutoConfEnabled(String currencyCode, boolean enabled) { findAutoConfirmSettings(currencyCode).ifPresent(e -> { e.setEnabled(enabled); - persist(); + requestPersistence(); }); } public void setAutoConfRequiredConfirmations(String currencyCode, int requiredConfirmations) { findAutoConfirmSettings(currencyCode).ifPresent(e -> { e.setRequiredConfirmations(requiredConfirmations); - persist(); + requestPersistence(); }); } public void setAutoConfTradeLimit(String currencyCode, long tradeLimit) { findAutoConfirmSettings(currencyCode).ifPresent(e -> { e.setTradeLimit(tradeLimit); - persist(); + requestPersistence(); }); } - private void persist() { + private void requestPersistence() { if (initialReadDone) - storage.queueUpForSave(prefPayload); + persistenceManager.requestPersistence(); } public void setUserLanguage(@NotNull String userLanguageCode) { prefPayload.setUserLanguage(userLanguageCode); if (prefPayload.getUserCountry() != null && prefPayload.getUserLanguage() != null) GlobalSettings.setLocale(new Locale(prefPayload.getUserLanguage(), prefPayload.getUserCountry().code)); - persist(); + requestPersistence(); } public void setUserCountry(@NotNull Country userCountry) { prefPayload.setUserCountry(userCountry); if (prefPayload.getUserLanguage() != null) GlobalSettings.setLocale(new Locale(prefPayload.getUserLanguage(), userCountry.code)); - persist(); + requestPersistence(); } public void setPreferredTradeCurrency(TradeCurrency preferredTradeCurrency) { if (preferredTradeCurrency != null) { prefPayload.setPreferredTradeCurrency(preferredTradeCurrency); GlobalSettings.setDefaultTradeCurrency(preferredTradeCurrency); - persist(); + requestPersistence(); } } public void setUseTorForBitcoinJ(boolean useTorForBitcoinJ) { prefPayload.setUseTorForBitcoinJ(useTorForBitcoinJ); - persist(); + requestPersistence(); } public void setShowOwnOffersInOfferBook(boolean showOwnOffersInOfferBook) { prefPayload.setShowOwnOffersInOfferBook(showOwnOffersInOfferBook); - persist(); + requestPersistence(); } public void setMaxPriceDistanceInPercent(double maxPriceDistanceInPercent) { prefPayload.setMaxPriceDistanceInPercent(maxPriceDistanceInPercent); - persist(); + requestPersistence(); } public void setBackupDirectory(String backupDirectory) { prefPayload.setBackupDirectory(backupDirectory); - persist(); + requestPersistence(); } public void setAutoSelectArbitrators(boolean autoSelectArbitrators) { prefPayload.setAutoSelectArbitrators(autoSelectArbitrators); - persist(); + requestPersistence(); } public void setUsePercentageBasedPrice(boolean usePercentageBasedPrice) { prefPayload.setUsePercentageBasedPrice(usePercentageBasedPrice); - persist(); + requestPersistence(); } public void setTagForPeer(String fullAddress, String tag) { prefPayload.getPeerTagMap().put(fullAddress, tag); - persist(); + requestPersistence(); } public void setOfferBookChartScreenCurrencyCode(String offerBookChartScreenCurrencyCode) { prefPayload.setOfferBookChartScreenCurrencyCode(offerBookChartScreenCurrencyCode); - persist(); + requestPersistence(); } public void setBuyScreenCurrencyCode(String buyScreenCurrencyCode) { prefPayload.setBuyScreenCurrencyCode(buyScreenCurrencyCode); - persist(); + requestPersistence(); } public void setSellScreenCurrencyCode(String sellScreenCurrencyCode) { prefPayload.setSellScreenCurrencyCode(sellScreenCurrencyCode); - persist(); + requestPersistence(); } public void setIgnoreTradersList(List ignoreTradersList) { prefPayload.setIgnoreTradersList(ignoreTradersList); - persist(); + requestPersistence(); } public void setDirectoryChooserPath(String directoryChooserPath) { prefPayload.setDirectoryChooserPath(directoryChooserPath); - persist(); + requestPersistence(); } public void setTradeChartsScreenCurrencyCode(String tradeChartsScreenCurrencyCode) { prefPayload.setTradeChartsScreenCurrencyCode(tradeChartsScreenCurrencyCode); - persist(); + requestPersistence(); } public void setTradeStatisticsTickUnitIndex(int tradeStatisticsTickUnitIndex) { prefPayload.setTradeStatisticsTickUnitIndex(tradeStatisticsTickUnitIndex); - persist(); + requestPersistence(); } public void setSortMarketCurrenciesNumerically(boolean sortMarketCurrenciesNumerically) { prefPayload.setSortMarketCurrenciesNumerically(sortMarketCurrenciesNumerically); - persist(); + requestPersistence(); } public void setBitcoinNodes(String bitcoinNodes) { prefPayload.setBitcoinNodes(bitcoinNodes); - persist(); + requestPersistence(); } public void setUseCustomWithdrawalTxFee(boolean useCustomWithdrawalTxFee) { prefPayload.setUseCustomWithdrawalTxFee(useCustomWithdrawalTxFee); - persist(); + requestPersistence(); } public void setWithdrawalTxFeeInBytes(long withdrawalTxFeeInBytes) { prefPayload.setWithdrawalTxFeeInBytes(withdrawalTxFeeInBytes); - persist(); + requestPersistence(); } public void setBuyerSecurityDepositAsPercent(double buyerSecurityDepositAsPercent, PaymentAccount paymentAccount) { @@ -574,110 +575,112 @@ public void setBuyerSecurityDepositAsPercent(double buyerSecurityDepositAsPercen prefPayload.setBuyerSecurityDepositAsPercentForCrypto(Math.min(max, Math.max(min, buyerSecurityDepositAsPercent))); else prefPayload.setBuyerSecurityDepositAsPercent(Math.min(max, Math.max(min, buyerSecurityDepositAsPercent))); - persist(); + requestPersistence(); } public void setSelectedPaymentAccountForCreateOffer(@Nullable PaymentAccount paymentAccount) { prefPayload.setSelectedPaymentAccountForCreateOffer(paymentAccount); - persist(); + requestPersistence(); } public void setPayFeeInBtc(boolean payFeeInBtc) { prefPayload.setPayFeeInBtc(payFeeInBtc); - persist(); + requestPersistence(); } private void setFiatCurrencies(List currencies) { fiatCurrenciesAsObservable.setAll(currencies.stream() .map(fiatCurrency -> new FiatCurrency(fiatCurrency.getCurrency())) .distinct().collect(Collectors.toList())); + requestPersistence(); } private void setCryptoCurrencies(List currencies) { cryptoCurrenciesAsObservable.setAll(currencies.stream().distinct().collect(Collectors.toList())); + requestPersistence(); } public void setBsqBlockChainExplorer(BlockChainExplorer bsqBlockChainExplorer) { prefPayload.setBsqBlockChainExplorer(bsqBlockChainExplorer); - persist(); + requestPersistence(); } private void setBlockChainExplorerTestNet(BlockChainExplorer blockChainExplorerTestNet) { prefPayload.setBlockChainExplorerTestNet(blockChainExplorerTestNet); - persist(); + requestPersistence(); } private void setBlockChainExplorerMainNet(BlockChainExplorer blockChainExplorerMainNet) { prefPayload.setBlockChainExplorerMainNet(blockChainExplorerMainNet); - persist(); + requestPersistence(); } public void setResyncSpvRequested(boolean resyncSpvRequested) { prefPayload.setResyncSpvRequested(resyncSpvRequested); // We call that before shutdown so we dont want a delay here - storage.queueUpForSave(prefPayload, 1); + requestPersistence(); } public void setBridgeAddresses(List bridgeAddresses) { prefPayload.setBridgeAddresses(bridgeAddresses); // We call that before shutdown so we dont want a delay here - storage.queueUpForSave(prefPayload, 1); + requestPersistence(); } // Only used from PB but keep it explicit as it may be used from the client and then we want to persist public void setPeerTagMap(Map peerTagMap) { prefPayload.setPeerTagMap(peerTagMap); - persist(); + requestPersistence(); } public void setBridgeOptionOrdinal(int bridgeOptionOrdinal) { prefPayload.setBridgeOptionOrdinal(bridgeOptionOrdinal); - persist(); + requestPersistence(); } public void setTorTransportOrdinal(int torTransportOrdinal) { prefPayload.setTorTransportOrdinal(torTransportOrdinal); - persist(); + requestPersistence(); } public void setCustomBridges(String customBridges) { prefPayload.setCustomBridges(customBridges); - persist(); + requestPersistence(); } public void setBitcoinNodesOptionOrdinal(int bitcoinNodesOptionOrdinal) { prefPayload.setBitcoinNodesOptionOrdinal(bitcoinNodesOptionOrdinal); - persist(); + requestPersistence(); } public void setReferralId(String referralId) { prefPayload.setReferralId(referralId); - persist(); + requestPersistence(); } public void setPhoneKeyAndToken(String phoneKeyAndToken) { prefPayload.setPhoneKeyAndToken(phoneKeyAndToken); - persist(); + requestPersistence(); } public void setUseSoundForMobileNotifications(boolean value) { prefPayload.setUseSoundForMobileNotifications(value); - persist(); + requestPersistence(); } public void setUseTradeNotifications(boolean value) { prefPayload.setUseTradeNotifications(value); - persist(); + requestPersistence(); } public void setUseMarketNotifications(boolean value) { prefPayload.setUseMarketNotifications(value); - persist(); + requestPersistence(); } public void setUsePriceNotifications(boolean value) { prefPayload.setUsePriceNotifications(value); - persist(); + requestPersistence(); } public void setUseStandbyMode(boolean useStandbyMode) { @@ -686,14 +689,14 @@ public void setUseStandbyMode(boolean useStandbyMode) { public void setTakeOfferSelectedPaymentAccountId(String value) { prefPayload.setTakeOfferSelectedPaymentAccountId(value); - persist(); + requestPersistence(); } public void setDaoFullNode(boolean value) { // We only persist if we have not set the program argument if (config.fullDaoNodeOptionSetExplicitly) { prefPayload.setDaoFullNode(value); - persist(); + requestPersistence(); } } @@ -701,17 +704,16 @@ public void setRpcUser(String value) { // We only persist if we have not set the program argument if (!rpcUserFromOptions.isEmpty()) { prefPayload.setRpcUser(value); - persist(); } prefPayload.setRpcUser(value); - persist(); + requestPersistence(); } public void setRpcPw(String value) { // We only persist if we have not set the program argument if (rpcPwFromOptions.isEmpty()) { prefPayload.setRpcPw(value); - persist(); + requestPersistence(); } } @@ -719,13 +721,13 @@ public void setBlockNotifyPort(int value) { // We only persist if we have not set the program argument if (blockNotifyPortFromOptions == Config.UNSPECIFIED_PORT) { prefPayload.setBlockNotifyPort(value); - persist(); + requestPersistence(); } } public void setIgnoreDustThreshold(int value) { prefPayload.setIgnoreDustThreshold(value); - persist(); + requestPersistence(); } @@ -896,6 +898,8 @@ private void updateTradeCurrencies(ListChangeListener.Change explorers, diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java index 3af5ecfa86d..d84862e9132 100644 --- a/core/src/main/java/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java @@ -25,7 +25,7 @@ import bisq.core.proto.CoreProtoResolver; import bisq.common.proto.ProtoUtil; -import bisq.common.proto.persistable.UserThreadMappedPersistableEnvelope; +import bisq.common.proto.persistable.PersistableEnvelope; import com.google.protobuf.Message; @@ -49,7 +49,7 @@ @Slf4j @Data @AllArgsConstructor -public final class PreferencesPayload implements UserThreadMappedPersistableEnvelope { +public final class PreferencesPayload implements PersistableEnvelope { private String userLanguage; private Country userCountry; private List fiatCurrencies = new ArrayList<>(); diff --git a/core/src/main/java/bisq/core/user/User.java b/core/src/main/java/bisq/core/user/User.java index 86cb5048b78..f101dc5f41a 100644 --- a/core/src/main/java/bisq/core/user/User.java +++ b/core/src/main/java/bisq/core/user/User.java @@ -31,8 +31,8 @@ import bisq.network.p2p.NodeAddress; import bisq.common.crypto.KeyRing; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.persistable.PersistedDataHost; -import bisq.common.storage.Storage; import javax.inject.Inject; import javax.inject.Singleton; @@ -67,8 +67,8 @@ @AllArgsConstructor @Singleton public class User implements PersistedDataHost { - final private Storage storage; - final private KeyRing keyRing; + private final PersistenceManager persistenceManager; + private final KeyRing keyRing; private ObservableSet paymentAccountsAsObservable; private ObjectProperty currentPaymentAccountProperty; @@ -77,27 +77,31 @@ public class User implements PersistedDataHost { private boolean isPaymentAccountImport = false; @Inject - public User(Storage storage, KeyRing keyRing) { - this.storage = storage; + public User(PersistenceManager persistenceManager, KeyRing keyRing) { + this.persistenceManager = persistenceManager; this.keyRing = keyRing; } // for unit tests public User() { - storage = null; + persistenceManager = null; keyRing = null; } @Override public void readPersisted() { - UserPayload persisted = storage.initAndGetPersistedWithFileName("UserPayload", 100); - userPayload = persisted != null ? persisted : new UserPayload(); + UserPayload persisted = checkNotNull(persistenceManager).getPersisted("UserPayload"); + if (persisted != null) { + userPayload = persisted; + } + + persistenceManager.initialize(userPayload, PersistenceManager.Source.PRIVATE); checkNotNull(userPayload.getPaymentAccounts(), "userPayload.getPaymentAccounts() must not be null"); checkNotNull(userPayload.getAcceptedLanguageLocaleCodes(), "userPayload.getAcceptedLanguageLocaleCodes() must not be null"); paymentAccountsAsObservable = FXCollections.observableSet(userPayload.getPaymentAccounts()); currentPaymentAccountProperty = new SimpleObjectProperty<>(userPayload.getCurrentPaymentAccount()); - userPayload.setAccountId(String.valueOf(Math.abs(keyRing.getPubKeyRing().hashCode()))); + userPayload.setAccountId(String.valueOf(Math.abs(checkNotNull(keyRing).getPubKeyRing().hashCode()))); // language setup if (!userPayload.getAcceptedLanguageLocaleCodes().contains(LanguageUtil.getDefaultLanguageLocaleAsCode())) @@ -108,17 +112,19 @@ public void readPersisted() { paymentAccountsAsObservable.addListener((SetChangeListener) change -> { userPayload.setPaymentAccounts(new HashSet<>(paymentAccountsAsObservable)); - persist(); + requestPersistence(); }); currentPaymentAccountProperty.addListener((ov) -> { userPayload.setCurrentPaymentAccount(currentPaymentAccountProperty.get()); - persist(); + requestPersistence(); }); + + requestPersistence(); } - public void persist() { - if (storage != null) - storage.queueUpForSave(userPayload); + public void requestPersistence() { + if (persistenceManager != null) + persistenceManager.requestPersistence(); } @@ -192,6 +198,7 @@ public boolean hasPaymentAccountForCurrency(TradeCurrency tradeCurrency) { public void addPaymentAccountIfNotExists(PaymentAccount paymentAccount) { if (!paymentAccountExists(paymentAccount)) { addPaymentAccount(paymentAccount); + requestPersistence(); } } @@ -201,16 +208,16 @@ public void addPaymentAccount(PaymentAccount paymentAccount) { boolean changed = paymentAccountsAsObservable.add(paymentAccount); setCurrentPaymentAccount(paymentAccount); if (changed) - persist(); + requestPersistence(); } public void addImportedPaymentAccounts(Collection paymentAccounts) { isPaymentAccountImport = true; boolean changed = paymentAccountsAsObservable.addAll(paymentAccounts); - setCurrentPaymentAccount(paymentAccounts.stream().findFirst().get()); + paymentAccounts.stream().findFirst().ifPresent(this::setCurrentPaymentAccount); if (changed) - persist(); + requestPersistence(); isPaymentAccountImport = false; } @@ -218,16 +225,15 @@ public void addImportedPaymentAccounts(Collection paymentAccount public void removePaymentAccount(PaymentAccount paymentAccount) { boolean changed = paymentAccountsAsObservable.remove(paymentAccount); if (changed) - persist(); + requestPersistence(); } public boolean addAcceptedArbitrator(Arbitrator arbitrator) { - final List arbitrators = userPayload.getAcceptedArbitrators(); + List arbitrators = userPayload.getAcceptedArbitrators(); if (arbitrators != null && !arbitrators.contains(arbitrator) && !isMyOwnRegisteredArbitrator(arbitrator)) { - boolean changed = arbitrators.add(arbitrator); - if (changed) - persist(); - return changed; + arbitrators.add(arbitrator); + requestPersistence(); + return true; } else { return false; } @@ -237,24 +243,23 @@ public void removeAcceptedArbitrator(Arbitrator arbitrator) { if (userPayload.getAcceptedArbitrators() != null) { boolean changed = userPayload.getAcceptedArbitrators().remove(arbitrator); if (changed) - persist(); + requestPersistence(); } } public void clearAcceptedArbitrators() { if (userPayload.getAcceptedArbitrators() != null) { userPayload.getAcceptedArbitrators().clear(); - persist(); + requestPersistence(); } } public boolean addAcceptedMediator(Mediator mediator) { - final List mediators = userPayload.getAcceptedMediators(); + List mediators = userPayload.getAcceptedMediators(); if (mediators != null && !mediators.contains(mediator) && !isMyOwnRegisteredMediator(mediator)) { - boolean changed = mediators.add(mediator); - if (changed) - persist(); - return changed; + mediators.add(mediator); + requestPersistence(); + return true; } else { return false; } @@ -264,24 +269,23 @@ public void removeAcceptedMediator(Mediator mediator) { if (userPayload.getAcceptedMediators() != null) { boolean changed = userPayload.getAcceptedMediators().remove(mediator); if (changed) - persist(); + requestPersistence(); } } public void clearAcceptedMediators() { if (userPayload.getAcceptedMediators() != null) { userPayload.getAcceptedMediators().clear(); - persist(); + requestPersistence(); } } public boolean addAcceptedRefundAgent(RefundAgent refundAgent) { - final List refundAgents = userPayload.getAcceptedRefundAgents(); + List refundAgents = userPayload.getAcceptedRefundAgents(); if (refundAgents != null && !refundAgents.contains(refundAgent) && !isMyOwnRegisteredRefundAgent(refundAgent)) { - boolean changed = refundAgents.add(refundAgent); - if (changed) - persist(); - return changed; + refundAgents.add(refundAgent); + requestPersistence(); + return true; } else { return false; } @@ -291,14 +295,14 @@ public void removeAcceptedRefundAgent(RefundAgent refundAgent) { if (userPayload.getAcceptedRefundAgents() != null) { boolean changed = userPayload.getAcceptedRefundAgents().remove(refundAgent); if (changed) - persist(); + requestPersistence(); } } public void clearAcceptedRefundAgents() { if (userPayload.getAcceptedRefundAgents() != null) { userPayload.getAcceptedRefundAgents().clear(); - persist(); + requestPersistence(); } } @@ -309,57 +313,57 @@ public void clearAcceptedRefundAgents() { public void setCurrentPaymentAccount(PaymentAccount paymentAccount) { currentPaymentAccountProperty.set(paymentAccount); - persist(); + requestPersistence(); } public void setRegisteredArbitrator(@Nullable Arbitrator arbitrator) { userPayload.setRegisteredArbitrator(arbitrator); - persist(); + requestPersistence(); } public void setRegisteredMediator(@Nullable Mediator mediator) { userPayload.setRegisteredMediator(mediator); - persist(); + requestPersistence(); } public void setRegisteredRefundAgent(@Nullable RefundAgent refundAgent) { userPayload.setRegisteredRefundAgent(refundAgent); - persist(); + requestPersistence(); } public void setDevelopersFilter(@Nullable Filter developersFilter) { userPayload.setDevelopersFilter(developersFilter); - persist(); + requestPersistence(); } public void setDevelopersAlert(@Nullable Alert developersAlert) { userPayload.setDevelopersAlert(developersAlert); - persist(); + requestPersistence(); } public void setDisplayedAlert(@Nullable Alert displayedAlert) { userPayload.setDisplayedAlert(displayedAlert); - persist(); + requestPersistence(); } public void addMarketAlertFilter(MarketAlertFilter filter) { getMarketAlertFilters().add(filter); - persist(); + requestPersistence(); } public void removeMarketAlertFilter(MarketAlertFilter filter) { getMarketAlertFilters().remove(filter); - persist(); + requestPersistence(); } public void setPriceAlertFilter(PriceAlertFilter filter) { userPayload.setPriceAlertFilter(filter); - persist(); + requestPersistence(); } public void removePriceAlertFilter() { userPayload.setPriceAlertFilter(null); - persist(); + requestPersistence(); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -378,10 +382,6 @@ public String getAccountId() { return userPayload.getAccountId(); } - private PaymentAccount getCurrentPaymentAccount() { - return userPayload.getCurrentPaymentAccount(); - } - public ReadOnlyObjectProperty currentPaymentAccountProperty() { return currentPaymentAccountProperty; } @@ -395,7 +395,6 @@ public ObservableSet getPaymentAccountsAsObservable() { return paymentAccountsAsObservable; } - /** * If this user is an arbitrator it returns the registered arbitrator. * @@ -416,8 +415,6 @@ public RefundAgent getRegisteredRefundAgent() { return userPayload.getRegisteredRefundAgent(); } - - //TODO @Nullable public List getAcceptedArbitrators() { return userPayload.getAcceptedArbitrators(); @@ -435,17 +432,23 @@ public List getAcceptedRefundAgents() { @Nullable public List getAcceptedArbitratorAddresses() { - return userPayload.getAcceptedArbitrators() != null ? userPayload.getAcceptedArbitrators().stream().map(Arbitrator::getNodeAddress).collect(Collectors.toList()) : null; + return userPayload.getAcceptedArbitrators() != null ? + userPayload.getAcceptedArbitrators().stream().map(Arbitrator::getNodeAddress).collect(Collectors.toList()) : + null; } @Nullable public List getAcceptedMediatorAddresses() { - return userPayload.getAcceptedMediators() != null ? userPayload.getAcceptedMediators().stream().map(Mediator::getNodeAddress).collect(Collectors.toList()) : null; + return userPayload.getAcceptedMediators() != null ? + userPayload.getAcceptedMediators().stream().map(Mediator::getNodeAddress).collect(Collectors.toList()) : + null; } @Nullable public List getAcceptedRefundAgentAddresses() { - return userPayload.getAcceptedRefundAgents() != null ? userPayload.getAcceptedRefundAgents().stream().map(RefundAgent::getNodeAddress).collect(Collectors.toList()) : null; + return userPayload.getAcceptedRefundAgents() != null ? + userPayload.getAcceptedRefundAgents().stream().map(RefundAgent::getNodeAddress).collect(Collectors.toList()) : + null; } public boolean hasAcceptedArbitrators() { diff --git a/core/src/main/java/bisq/core/user/UserPayload.java b/core/src/main/java/bisq/core/user/UserPayload.java index f7bbb5ab03d..212570a6f72 100644 --- a/core/src/main/java/bisq/core/user/UserPayload.java +++ b/core/src/main/java/bisq/core/user/UserPayload.java @@ -28,7 +28,7 @@ import bisq.core.support.dispute.refund.refundagent.RefundAgent; import bisq.common.proto.ProtoUtil; -import bisq.common.proto.persistable.UserThreadMappedPersistableEnvelope; +import bisq.common.proto.persistable.PersistableEnvelope; import java.util.ArrayList; import java.util.HashSet; @@ -46,7 +46,7 @@ @Slf4j @Data @AllArgsConstructor -public class UserPayload implements UserThreadMappedPersistableEnvelope { +public class UserPayload implements PersistableEnvelope { @Nullable private String accountId; @Nullable diff --git a/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java b/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java index 10840861ddc..06ffd4470c1 100644 --- a/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java +++ b/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java @@ -177,7 +177,7 @@ public void testArbitratorSignWitness() { service.addToMap(sellerAccountAgeWitness); long now = new Date().getTime() + 1000; Contract contract = mock(Contract.class); - disputes.add(new Dispute( + disputes.add(new Dispute(new Date().getTime(), "trade1", 0, true, diff --git a/core/src/test/java/bisq/core/crypto/EncryptionTest.java b/core/src/test/java/bisq/core/crypto/EncryptionTest.java index cda7f846902..15928da85d7 100644 --- a/core/src/test/java/bisq/core/crypto/EncryptionTest.java +++ b/core/src/test/java/bisq/core/crypto/EncryptionTest.java @@ -20,7 +20,7 @@ import bisq.common.crypto.CryptoException; import bisq.common.crypto.KeyRing; import bisq.common.crypto.KeyStorage; -import bisq.common.storage.FileUtil; +import bisq.common.file.FileUtil; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; diff --git a/core/src/test/java/bisq/core/crypto/SigTest.java b/core/src/test/java/bisq/core/crypto/SigTest.java index 170a1383e91..4255d10fad2 100644 --- a/core/src/test/java/bisq/core/crypto/SigTest.java +++ b/core/src/test/java/bisq/core/crypto/SigTest.java @@ -21,7 +21,7 @@ import bisq.common.crypto.KeyRing; import bisq.common.crypto.KeyStorage; import bisq.common.crypto.Sig; -import bisq.common.storage.FileUtil; +import bisq.common.file.FileUtil; import java.io.File; import java.io.IOException; diff --git a/core/src/test/java/bisq/core/dao/governance/ballot/BallotListServiceTest.java b/core/src/test/java/bisq/core/dao/governance/ballot/BallotListServiceTest.java index f6b912497fb..3c5da9c1693 100644 --- a/core/src/test/java/bisq/core/dao/governance/ballot/BallotListServiceTest.java +++ b/core/src/test/java/bisq/core/dao/governance/ballot/BallotListServiceTest.java @@ -6,7 +6,7 @@ import bisq.core.dao.governance.proposal.ProposalValidatorProvider; import bisq.core.dao.governance.proposal.storage.appendonly.ProposalPayload; -import bisq.common.storage.Storage; +import bisq.common.persistence.PersistenceManager; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -30,7 +30,7 @@ public void testAddListenersWhenNewPayloadAdded() { when(proposalService.getProposalPayloads()).thenReturn(payloads); BallotListService service = new BallotListService(proposalService, mock(PeriodService.class), - mock(ProposalValidatorProvider.class), mock(Storage.class)); + mock(ProposalValidatorProvider.class), mock(PersistenceManager.class)); BallotListChangeListener listener = mock(BallotListChangeListener.class); service.addListener(listener); diff --git a/core/src/test/java/bisq/core/dao/governance/proposal/MyProposalListServiceTest.java b/core/src/test/java/bisq/core/dao/governance/proposal/MyProposalListServiceTest.java index 2a80becf2b4..a851e6cdab8 100644 --- a/core/src/test/java/bisq/core/dao/governance/proposal/MyProposalListServiceTest.java +++ b/core/src/test/java/bisq/core/dao/governance/proposal/MyProposalListServiceTest.java @@ -7,7 +7,7 @@ import bisq.network.p2p.P2PService; import bisq.common.crypto.PubKeyRing; -import bisq.common.storage.Storage; +import bisq.common.persistence.PersistenceManager; import javafx.beans.property.SimpleIntegerProperty; @@ -21,12 +21,10 @@ public class MyProposalListServiceTest { public void canInstantiate() { P2PService p2PService = mock(P2PService.class); when(p2PService.getNumConnectedPeers()).thenReturn(new SimpleIntegerProperty(0)); - Storage storage = mock(Storage.class); + PersistenceManager persistenceManager = mock(PersistenceManager.class); MyProposalListService service = new MyProposalListService(p2PService, mock(DaoStateService.class), - mock(PeriodService.class), mock(WalletsManager.class), storage, mock(PubKeyRing.class) + mock(PeriodService.class), mock(WalletsManager.class), persistenceManager, mock(PubKeyRing.class) ); } - - } diff --git a/core/src/test/java/bisq/core/dao/state/DaoStateSnapshotServiceTest.java b/core/src/test/java/bisq/core/dao/state/DaoStateSnapshotServiceTest.java index 806c3ebb642..9cc7190d5b5 100644 --- a/core/src/test/java/bisq/core/dao/state/DaoStateSnapshotServiceTest.java +++ b/core/src/test/java/bisq/core/dao/state/DaoStateSnapshotServiceTest.java @@ -19,6 +19,7 @@ import bisq.core.dao.governance.period.CycleService; import bisq.core.dao.monitoring.DaoStateMonitoringService; +import bisq.core.dao.state.storage.DaoStateStorageService; import org.junit.Before; import org.junit.Test; diff --git a/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java b/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java index 16de7b79254..d5ae649e006 100644 --- a/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java +++ b/core/src/test/java/bisq/core/offer/OpenOfferManagerTest.java @@ -3,10 +3,10 @@ import bisq.network.p2p.P2PService; import bisq.network.p2p.peers.PeerManager; +import bisq.common.file.CorruptedStorageFileHandler; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; -import bisq.common.storage.CorruptedDatabaseFilesHandler; -import bisq.common.storage.Storage; +import bisq.common.persistence.PersistenceManager; import java.nio.file.Files; @@ -26,12 +26,12 @@ public class OpenOfferManagerTest { - private CorruptedDatabaseFilesHandler corruptedDatabaseFilesHandler; + private CorruptedStorageFileHandler corruptedStorageFileHandler; private File storageDir; @Before public void setUp() throws Exception { - corruptedDatabaseFilesHandler = mock(CorruptedDatabaseFilesHandler.class); + corruptedStorageFileHandler = mock(CorruptedStorageFileHandler.class); storageDir = Files.createTempDirectory("storage").toFile(); } @@ -46,7 +46,7 @@ public void testStartEditOfferForActiveOffer() { null, null, null, offerBookService, null, null, null, null, null, null, null, null, null, - new Storage<>(storageDir, null, corruptedDatabaseFilesHandler)); + new PersistenceManager<>(storageDir, null, corruptedStorageFileHandler)); AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false); @@ -56,7 +56,7 @@ public void testStartEditOfferForActiveOffer() { return null; }).when(offerBookService).deactivateOffer(any(OfferPayload.class), any(ResultHandler.class), any(ErrorMessageHandler.class)); - final OpenOffer openOffer = new OpenOffer(make(btcUsdOffer), null); + final OpenOffer openOffer = new OpenOffer(make(btcUsdOffer)); ResultHandler resultHandler = () -> { startEditOfferSuccessful.set(true); @@ -74,15 +74,13 @@ public void testStartEditOfferForActiveOffer() { public void testStartEditOfferForDeactivatedOffer() throws IOException { P2PService p2PService = mock(P2PService.class); OfferBookService offerBookService = mock(OfferBookService.class); - Storage storage = mock(Storage.class); - when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class)); final OpenOfferManager manager = new OpenOfferManager(null, null, null, p2PService, null, null, null, offerBookService, null, null, null, null, null, null, null, null, null, - new Storage<>(storageDir, null, corruptedDatabaseFilesHandler)); + new PersistenceManager<>(storageDir, null, corruptedStorageFileHandler)); AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false); @@ -90,7 +88,7 @@ public void testStartEditOfferForDeactivatedOffer() throws IOException { startEditOfferSuccessful.set(true); }; - final OpenOffer openOffer = new OpenOffer(make(btcUsdOffer), storage); + final OpenOffer openOffer = new OpenOffer(make(btcUsdOffer)); openOffer.setState(OpenOffer.State.DEACTIVATED); manager.editOpenOfferStart(openOffer, resultHandler, null); @@ -102,7 +100,6 @@ public void testStartEditOfferForDeactivatedOffer() throws IOException { public void testStartEditOfferForOfferThatIsCurrentlyEdited() { P2PService p2PService = mock(P2PService.class); OfferBookService offerBookService = mock(OfferBookService.class); - Storage storage = mock(Storage.class); when(p2PService.getPeerManager()).thenReturn(mock(PeerManager.class)); @@ -110,7 +107,7 @@ public void testStartEditOfferForOfferThatIsCurrentlyEdited() { null, null, null, offerBookService, null, null, null, null, null, null, null, null, null, - new Storage<>(storageDir, null, corruptedDatabaseFilesHandler)); + new PersistenceManager<>(storageDir, null, corruptedStorageFileHandler)); AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false); @@ -118,7 +115,7 @@ public void testStartEditOfferForOfferThatIsCurrentlyEdited() { startEditOfferSuccessful.set(true); }; - final OpenOffer openOffer = new OpenOffer(make(btcUsdOffer), storage); + final OpenOffer openOffer = new OpenOffer(make(btcUsdOffer)); openOffer.setState(OpenOffer.State.DEACTIVATED); manager.editOpenOfferStart(openOffer, resultHandler, null); diff --git a/core/src/test/java/bisq/core/trade/TradableListTest.java b/core/src/test/java/bisq/core/trade/TradableListTest.java index 72b9e25f82f..e6a41bca6ae 100644 --- a/core/src/test/java/bisq/core/trade/TradableListTest.java +++ b/core/src/test/java/bisq/core/trade/TradableListTest.java @@ -21,18 +21,9 @@ import bisq.core.offer.OfferPayload; import bisq.core.offer.OpenOffer; -import bisq.common.storage.CorruptedDatabaseFilesHandler; -import bisq.common.storage.Storage; - -import java.nio.file.Files; - -import java.io.File; -import java.io.IOException; - import org.junit.Test; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; import static protobuf.PersistableEnvelope.MessageCase.TRADABLE_LIST; @@ -40,18 +31,15 @@ public class TradableListTest { @Test - public void protoTesting() throws IOException { + public void protoTesting() { OfferPayload offerPayload = mock(OfferPayload.class, RETURNS_DEEP_STUBS); - File storageDir = Files.createTempDirectory("storage").toFile(); - Storage> storage = new Storage<>(storageDir, null, mock(CorruptedDatabaseFilesHandler.class)); - TradableList openOfferTradableList = new TradableList<>(storage, "filename"); + TradableList openOfferTradableList = new TradableList<>(); protobuf.PersistableEnvelope message = (protobuf.PersistableEnvelope) openOfferTradableList.toProtoMessage(); - assertTrue(message.getMessageCase().equals(TRADABLE_LIST)); + assertEquals(message.getMessageCase(), TRADABLE_LIST); // test adding an OpenOffer and convert toProto Offer offer = new Offer(offerPayload); - OpenOffer openOffer = new OpenOffer(offer, storage); - //openOfferTradableList = new TradableList(storage,Lists.newArrayList(openOffer)); + OpenOffer openOffer = new OpenOffer(offer); openOfferTradableList.add(openOffer); message = (protobuf.PersistableEnvelope) openOfferTradableList.toProtoMessage(); assertEquals(message.getMessageCase(), TRADABLE_LIST); diff --git a/core/src/test/java/bisq/core/user/PreferencesTest.java b/core/src/test/java/bisq/core/user/PreferencesTest.java index b29ea9520c1..87d83bc3470 100644 --- a/core/src/test/java/bisq/core/user/PreferencesTest.java +++ b/core/src/test/java/bisq/core/user/PreferencesTest.java @@ -26,7 +26,7 @@ import bisq.core.locale.Res; import bisq.common.config.Config; -import bisq.common.storage.Storage; +import bisq.common.persistence.PersistenceManager; import javafx.collections.ObservableList; @@ -40,7 +40,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -48,7 +47,7 @@ public class PreferencesTest { private Preferences preferences; - private Storage storage; + private PersistenceManager persistenceManager; @Before public void setUp() { @@ -58,11 +57,11 @@ public void setUp() { Res.setBaseCurrencyCode("BTC"); Res.setBaseCurrencyName("Bitcoin"); - storage = mock(Storage.class); + persistenceManager = mock(PersistenceManager.class); Config config = new Config(); LocalBitcoinNode localBitcoinNode = new LocalBitcoinNode(config); preferences = new Preferences( - storage, config, localBitcoinNode, null, null, Config.DEFAULT_FULL_DAO_NODE, + persistenceManager, config, localBitcoinNode, null, null, Config.DEFAULT_FULL_DAO_NODE, null, null, Config.UNSPECIFIED_PORT); } @@ -89,7 +88,7 @@ public void testGetUniqueListOfFiatCurrencies() { final FiatCurrency usd = new FiatCurrency("USD"); fiatCurrencies.add(usd); - when(storage.initAndGetPersistedWithFileName(anyString(), anyLong())).thenReturn(payload); + when(persistenceManager.getPersisted(anyString())).thenReturn(payload); when(payload.getUserLanguage()).thenReturn("en"); when(payload.getUserCountry()).thenReturn(CountryUtil.getDefaultCountry()); when(payload.getPreferredTradeCurrency()).thenReturn(usd); @@ -110,7 +109,7 @@ public void testGetUniqueListOfCryptoCurrencies() { final CryptoCurrency dash = new CryptoCurrency("DASH", "Dash"); cryptoCurrencies.add(dash); - when(storage.initAndGetPersistedWithFileName(anyString(), anyLong())).thenReturn(payload); + when(persistenceManager.getPersisted(anyString())).thenReturn(payload); when(payload.getUserLanguage()).thenReturn("en"); when(payload.getUserCountry()).thenReturn(CountryUtil.getDefaultCountry()); when(payload.getPreferredTradeCurrency()).thenReturn(new FiatCurrency("USD")); @@ -131,7 +130,7 @@ public void testUpdateOfPersistedFiatCurrenciesAfterLocaleChanged() { assertEquals("US-Dollar (USD)", usd.getNameAndCode()); - when(storage.initAndGetPersistedWithFileName(anyString(), anyLong())).thenReturn(payload); + when(persistenceManager.getPersisted(anyString())).thenReturn(payload); when(payload.getUserLanguage()).thenReturn("en"); when(payload.getUserCountry()).thenReturn(CountryUtil.getDefaultCountry()); when(payload.getPreferredTradeCurrency()).thenReturn(usd); diff --git a/desktop/src/main/java/bisq/desktop/Navigation.java b/desktop/src/main/java/bisq/desktop/Navigation.java index 0efeb8cc987..adb73000231 100644 --- a/desktop/src/main/java/bisq/desktop/Navigation.java +++ b/desktop/src/main/java/bisq/desktop/Navigation.java @@ -22,9 +22,9 @@ import bisq.desktop.main.MainView; import bisq.desktop.main.market.MarketView; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.persistable.NavigationPath; import bisq.common.proto.persistable.PersistedDataHost; -import bisq.common.storage.Storage; import com.google.inject.Inject; @@ -57,7 +57,7 @@ default void onNavigationRequested(ViewPath path, @Nullable Object data) { // New listeners can be added during iteration so we use CopyOnWriteArrayList to // prevent invalid array modification private final CopyOnWriteArraySet listeners = new CopyOnWriteArraySet<>(); - private final Storage storage; + private final PersistenceManager persistenceManager; private ViewPath currentPath; // Used for returning to the last important view. After setup is done we want to // return to the last opened view (e.g. sell/buy) @@ -72,14 +72,15 @@ default void onNavigationRequested(ViewPath path, @Nullable Object data) { @Inject - public Navigation(Storage storage) { - this.storage = storage; - storage.setNumMaxBackupFiles(3); + public Navigation(PersistenceManager persistenceManager) { + this.persistenceManager = persistenceManager; + + persistenceManager.initialize(navigationPath, PersistenceManager.Source.PRIVATE_LOW_PRIO); } @Override public void readPersisted() { - NavigationPath persisted = storage.initAndGetPersisted(navigationPath, "NavigationPath", 300); + NavigationPath persisted = persistenceManager.getPersisted(); if (persisted != null) { List> viewClasses = persisted.getPath().stream() .map(className -> { @@ -134,16 +135,16 @@ public void navigateTo(ViewPath newPath, @Nullable Object data) { currentPath = newPath; previousPath = currentPath; - queueUpForSave(); listeners.forEach((e) -> e.onNavigationRequested(currentPath)); listeners.forEach((e) -> e.onNavigationRequested(currentPath, data)); + requestPersistence(); } - private void queueUpForSave() { + private void requestPersistence() { if (currentPath.tip() != null) { navigationPath.setPath(currentPath.stream().map(Class::getName).collect(Collectors.toUnmodifiableList())); } - storage.queueUpForSave(navigationPath, 1000); + persistenceManager.requestPersistence(); } public void navigateToPreviousVisitedView() { diff --git a/desktop/src/main/java/bisq/desktop/app/BisqAppMain.java b/desktop/src/main/java/bisq/desktop/app/BisqAppMain.java index e28d4a36812..7edc7046c5c 100644 --- a/desktop/src/main/java/bisq/desktop/app/BisqAppMain.java +++ b/desktop/src/main/java/bisq/desktop/app/BisqAppMain.java @@ -27,9 +27,6 @@ import bisq.common.UserThread; import bisq.common.app.AppModule; import bisq.common.app.Version; -import bisq.common.proto.persistable.PersistedDataHost; - -import com.google.inject.Injector; import javafx.application.Application; import javafx.application.Platform; @@ -117,9 +114,8 @@ protected void applyInjector() { } @Override - protected void setupPersistedDataHosts(Injector injector) { - super.setupPersistedDataHosts(injector); - PersistedDataHost.apply(DesktopPersistedDataHost.getPersistedDataHosts(injector)); + protected void readAllPersisted(Runnable completeHandler) { + super.readAllPersisted(DesktopPersistedDataHost.getPersistedDataHosts(injector), completeHandler); } @Override diff --git a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java index 43fdd833d9a..d892c1322c5 100644 --- a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java @@ -68,7 +68,7 @@ import bisq.common.UserThread; import bisq.common.app.DevEnv; import bisq.common.config.Config; -import bisq.common.storage.CorruptedDatabaseFilesHandler; +import bisq.common.file.CorruptedStorageFileHandler; import com.google.inject.Inject; @@ -127,17 +127,17 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener { private final AccountAgeWitnessService accountAgeWitnessService; @Getter private final TorNetworkSettingsWindow torNetworkSettingsWindow; - private final CorruptedDatabaseFilesHandler corruptedDatabaseFilesHandler; + private final CorruptedStorageFileHandler corruptedStorageFileHandler; @Getter - private BooleanProperty showAppScreen = new SimpleBooleanProperty(); - private DoubleProperty combinedSyncProgress = new SimpleDoubleProperty(-1); + private final BooleanProperty showAppScreen = new SimpleBooleanProperty(); + private final DoubleProperty combinedSyncProgress = new SimpleDoubleProperty(-1); private final BooleanProperty isSplashScreenRemoved = new SimpleBooleanProperty(); private Timer checkNumberOfBtcPeersTimer; private Timer checkNumberOfP2pNetworkPeersTimer; @SuppressWarnings("FieldCanBeLocal") private MonadicBinding tradesAndUIReady; - private Queue> popupQueue = new PriorityQueue<>(Comparator.comparing(Overlay::getDisplayOrderPriority)); + private final Queue> popupQueue = new PriorityQueue<>(Comparator.comparing(Overlay::getDisplayOrderPriority)); /////////////////////////////////////////////////////////////////////////////////////////// @@ -169,7 +169,7 @@ public MainViewModel(BisqSetup bisqSetup, LocalBitcoinNode localBitcoinNode, AccountAgeWitnessService accountAgeWitnessService, TorNetworkSettingsWindow torNetworkSettingsWindow, - CorruptedDatabaseFilesHandler corruptedDatabaseFilesHandler) { + CorruptedStorageFileHandler corruptedStorageFileHandler) { this.bisqSetup = bisqSetup; this.walletsSetup = walletsSetup; this.user = user; @@ -192,7 +192,7 @@ public MainViewModel(BisqSetup bisqSetup, this.localBitcoinNode = localBitcoinNode; this.accountAgeWitnessService = accountAgeWitnessService; this.torNetworkSettingsWindow = torNetworkSettingsWindow; - this.corruptedDatabaseFilesHandler = corruptedDatabaseFilesHandler; + this.corruptedStorageFileHandler = corruptedStorageFileHandler; TxIdTextField.setPreferences(preferences); @@ -220,7 +220,7 @@ public void onSetupComplete() { if (newValue) { tradeManager.applyTradePeriodState(); - tradeManager.getTradesAsObservableList().forEach(trade -> { + tradeManager.getObservableList().forEach(trade -> { Date maxTradePeriodDate = trade.getMaxTradePeriodDate(); String key; switch (trade.getTradePeriodState()) { @@ -401,7 +401,7 @@ private void setupHandlers() { } }); - corruptedDatabaseFilesHandler.getCorruptedDatabaseFiles().ifPresent(files -> new Popup() + corruptedStorageFileHandler.getFiles().ifPresent(files -> new Popup() .warning(Res.get("popup.warning.incompatibleDB", files.toString(), config.appDataDir)) .useShutDownButton() .show()); diff --git a/desktop/src/main/java/bisq/desktop/main/SharedPresentation.java b/desktop/src/main/java/bisq/desktop/main/SharedPresentation.java index 913529779d2..5b7e6e5b800 100644 --- a/desktop/src/main/java/bisq/desktop/main/SharedPresentation.java +++ b/desktop/src/main/java/bisq/desktop/main/SharedPresentation.java @@ -25,7 +25,7 @@ import bisq.core.offer.OpenOfferManager; import bisq.common.UserThread; -import bisq.common.storage.FileUtil; +import bisq.common.file.FileUtil; import org.bitcoinj.wallet.DeterministicSeed; diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsDataModel.java b/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsDataModel.java index 99a4570dcf9..7a434359ff7 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/altcoinaccounts/AltCoinAccountsDataModel.java @@ -31,8 +31,8 @@ import bisq.core.user.Preferences; import bisq.core.user.User; +import bisq.common.file.CorruptedStorageFileHandler; import bisq.common.proto.persistable.PersistenceProtoResolver; -import bisq.common.storage.CorruptedDatabaseFilesHandler; import com.google.inject.Inject; @@ -57,7 +57,7 @@ class AltCoinAccountsDataModel extends ActivatableDataModel { private final SetChangeListener setChangeListener; private final String accountsFileName = "AltcoinPaymentAccounts"; private final PersistenceProtoResolver persistenceProtoResolver; - private final CorruptedDatabaseFilesHandler corruptedDatabaseFilesHandler; + private final CorruptedStorageFileHandler corruptedStorageFileHandler; @Inject public AltCoinAccountsDataModel(User user, @@ -66,14 +66,14 @@ public AltCoinAccountsDataModel(User user, TradeManager tradeManager, AccountAgeWitnessService accountAgeWitnessService, PersistenceProtoResolver persistenceProtoResolver, - CorruptedDatabaseFilesHandler corruptedDatabaseFilesHandler) { + CorruptedStorageFileHandler corruptedStorageFileHandler) { this.user = user; this.preferences = preferences; this.openOfferManager = openOfferManager; this.tradeManager = tradeManager; this.accountAgeWitnessService = accountAgeWitnessService; this.persistenceProtoResolver = persistenceProtoResolver; - this.corruptedDatabaseFilesHandler = corruptedDatabaseFilesHandler; + this.corruptedStorageFileHandler = corruptedStorageFileHandler; setChangeListener = change -> fillAndSortPaymentAccounts(); } @@ -111,7 +111,7 @@ public void onSaveNewAccount(PaymentAccount paymentAccount) { else preferences.addCryptoCurrency((CryptoCurrency) singleTradeCurrency); } else if (tradeCurrencies != null && !tradeCurrencies.isEmpty()) { - tradeCurrencies.stream().forEach(tradeCurrency -> { + tradeCurrencies.forEach(tradeCurrency -> { if (tradeCurrency instanceof FiatCurrency) preferences.addFiatCurrency((FiatCurrency) tradeCurrency); else @@ -130,7 +130,7 @@ public boolean onDeleteAccount(PaymentAccount paymentAccount) { .filter(o -> o.getOffer().getMakerPaymentAccountId().equals(paymentAccount.getId())) .findAny() .isPresent(); - isPaymentAccountUsed = isPaymentAccountUsed || tradeManager.getTradesAsObservableList().stream() + isPaymentAccountUsed = isPaymentAccountUsed || tradeManager.getObservableList().stream() .filter(t -> t.getOffer().getMakerPaymentAccountId().equals(paymentAccount.getId()) || paymentAccount.getId().equals(t.getTakerPaymentAccountId())) .findAny() @@ -149,11 +149,11 @@ public void exportAccounts(Stage stage) { ArrayList accounts = new ArrayList<>(user.getPaymentAccounts().stream() .filter(paymentAccount -> paymentAccount instanceof AssetAccount) .collect(Collectors.toList())); - GUIUtil.exportAccounts(accounts, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedDatabaseFilesHandler); + GUIUtil.exportAccounts(accounts, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler); } } public void importAccounts(Stage stage) { - GUIUtil.importAccounts(user, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedDatabaseFilesHandler); + GUIUtil.importAccounts(user, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler); } } diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/backup/BackupView.java b/desktop/src/main/java/bisq/desktop/main/account/content/backup/BackupView.java index 2e72c73a968..c421e183a75 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/backup/BackupView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/backup/BackupView.java @@ -26,7 +26,7 @@ import bisq.core.user.Preferences; import bisq.common.config.Config; -import bisq.common.storage.FileUtil; +import bisq.common.file.FileUtil; import bisq.common.util.Tuple2; import bisq.common.util.Utilities; diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/fiataccounts/FiatAccountsDataModel.java b/desktop/src/main/java/bisq/desktop/main/account/content/fiataccounts/FiatAccountsDataModel.java index bbc2c86f7f7..6e3c6c07e74 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/fiataccounts/FiatAccountsDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/fiataccounts/FiatAccountsDataModel.java @@ -32,8 +32,8 @@ import bisq.core.user.Preferences; import bisq.core.user.User; +import bisq.common.file.CorruptedStorageFileHandler; import bisq.common.proto.persistable.PersistenceProtoResolver; -import bisq.common.storage.CorruptedDatabaseFilesHandler; import com.google.inject.Inject; @@ -59,7 +59,7 @@ class FiatAccountsDataModel extends ActivatableDataModel { private final SetChangeListener setChangeListener; private final String accountsFileName = "FiatPaymentAccounts"; private final PersistenceProtoResolver persistenceProtoResolver; - private final CorruptedDatabaseFilesHandler corruptedDatabaseFilesHandler; + private final CorruptedStorageFileHandler corruptedStorageFileHandler; @Inject public FiatAccountsDataModel(User user, @@ -68,14 +68,14 @@ public FiatAccountsDataModel(User user, TradeManager tradeManager, AccountAgeWitnessService accountAgeWitnessService, PersistenceProtoResolver persistenceProtoResolver, - CorruptedDatabaseFilesHandler corruptedDatabaseFilesHandler) { + CorruptedStorageFileHandler corruptedStorageFileHandler) { this.user = user; this.preferences = preferences; this.openOfferManager = openOfferManager; this.tradeManager = tradeManager; this.accountAgeWitnessService = accountAgeWitnessService; this.persistenceProtoResolver = persistenceProtoResolver; - this.corruptedDatabaseFilesHandler = corruptedDatabaseFilesHandler; + this.corruptedStorageFileHandler = corruptedStorageFileHandler; setChangeListener = change -> fillAndSortPaymentAccounts(); } @@ -136,7 +136,7 @@ public void onSaveNewAccount(PaymentAccount paymentAccount) { public boolean onDeleteAccount(PaymentAccount paymentAccount) { boolean isPaymentAccountUsed = openOfferManager.getObservableList().stream() .anyMatch(o -> o.getOffer().getMakerPaymentAccountId().equals(paymentAccount.getId())); - isPaymentAccountUsed = isPaymentAccountUsed || tradeManager.getTradesAsObservableList().stream() + isPaymentAccountUsed = isPaymentAccountUsed || tradeManager.getObservableList().stream() .anyMatch(t -> t.getOffer().getMakerPaymentAccountId().equals(paymentAccount.getId()) || paymentAccount.getId().equals(t.getTakerPaymentAccountId())); if (!isPaymentAccountUsed) @@ -153,11 +153,11 @@ public void exportAccounts(Stage stage) { ArrayList accounts = new ArrayList<>(user.getPaymentAccounts().stream() .filter(paymentAccount -> !(paymentAccount instanceof AssetAccount)) .collect(Collectors.toList())); - GUIUtil.exportAccounts(accounts, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedDatabaseFilesHandler); + GUIUtil.exportAccounts(accounts, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler); } } public void importAccounts(Stage stage) { - GUIUtil.importAccounts(user, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedDatabaseFilesHandler); + GUIUtil.importAccounts(user, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler); } } diff --git a/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedView.java b/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedView.java index 6206a56a3ac..2c5fb909be4 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedView.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedView.java @@ -143,7 +143,7 @@ public void onBalanceChanged(Coin balance, Transaction tx) { @Override protected void activate() { openOfferManager.getObservableList().addListener(openOfferListChangeListener); - tradeManager.getTradesAsObservableList().addListener(tradeListChangeListener); + tradeManager.getObservableList().addListener(tradeListChangeListener); sortedList.comparatorProperty().bind(tableView.comparatorProperty()); tableView.setItems(sortedList); updateList(); @@ -154,7 +154,7 @@ protected void activate() { @Override protected void deactivate() { openOfferManager.getObservableList().removeListener(openOfferListChangeListener); - tradeManager.getTradesAsObservableList().removeListener(tradeListChangeListener); + tradeManager.getObservableList().removeListener(tradeListChangeListener); sortedList.comparatorProperty().unbind(); observableList.forEach(LockedListItem::cleanup); btcWalletService.removeBalanceListener(balanceListener); @@ -258,7 +258,7 @@ public void updateItem(final LockedListItem item, boolean empty) { Optional tradableOptional = getTradable(item); AddressEntry addressEntry = item.getAddressEntry(); if (tradableOptional.isPresent()) { - field = new HyperlinkWithIcon(Res.get("funds.locked.locked", addressEntry.getShortOfferId()), + field = new HyperlinkWithIcon(Res.get("funds.locked.locked", item.getTrade().getShortId()), AwesomeIcon.INFO_SIGN); field.setOnAction(event -> openDetailPopup(item)); field.setTooltip(new Tooltip(Res.get("tooltip.openPopupForDetails"))); diff --git a/desktop/src/main/java/bisq/desktop/main/funds/reserved/ReservedView.java b/desktop/src/main/java/bisq/desktop/main/funds/reserved/ReservedView.java index 3d8c6b10e0c..061ed666e38 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/reserved/ReservedView.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/reserved/ReservedView.java @@ -143,7 +143,7 @@ public void onBalanceChanged(Coin balance, Transaction tx) { @Override protected void activate() { openOfferManager.getObservableList().addListener(openOfferListChangeListener); - tradeManager.getTradesAsObservableList().addListener(tradeListChangeListener); + tradeManager.getObservableList().addListener(tradeListChangeListener); sortedList.comparatorProperty().bind(tableView.comparatorProperty()); tableView.setItems(sortedList); updateList(); @@ -154,7 +154,7 @@ protected void activate() { @Override protected void deactivate() { openOfferManager.getObservableList().removeListener(openOfferListChangeListener); - tradeManager.getTradesAsObservableList().removeListener(tradeListChangeListener); + tradeManager.getObservableList().removeListener(tradeListChangeListener); sortedList.comparatorProperty().unbind(); observableList.forEach(ReservedListItem::cleanup); btcWalletService.removeBalanceListener(balanceListener); @@ -257,7 +257,7 @@ public void updateItem(final ReservedListItem item, boolean empty) { if (item != null && !empty) { Optional tradableOptional = getTradable(item); if (tradableOptional.isPresent()) { - field = new HyperlinkWithIcon(Res.get("funds.reserved.reserved", item.getAddressEntry().getShortOfferId()), + field = new HyperlinkWithIcon(Res.get("funds.reserved.reserved", item.getOpenOffer().getShortId()), AwesomeIcon.INFO_SIGN); field.setOnAction(event -> openDetailPopup(item)); field.setTooltip(new Tooltip(Res.get("tooltip.openPopupForDetails"))); diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TradableRepository.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TradableRepository.java index d780d79058d..e58221dac11 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TradableRepository.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TradableRepository.java @@ -51,9 +51,9 @@ public class TradableRepository { Set getAll() { return ImmutableSet.builder() .addAll(openOfferManager.getObservableList()) - .addAll(tradeManager.getTradesAsObservableList()) - .addAll(closedTradableManager.getClosedTradables()) - .addAll(failedTradesManager.getFailedTrades()) + .addAll(tradeManager.getObservableList()) + .addAll(closedTradableManager.getObservableList()) + .addAll(failedTradesManager.getObservableList()) .build(); } } diff --git a/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java b/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java index 1d7814a341f..530e203d23f 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/withdrawal/WithdrawalView.java @@ -135,7 +135,7 @@ public class WithdrawalView extends ActivatableView { private final WalletPasswordWindow walletPasswordWindow; private final ObservableList observableList = FXCollections.observableArrayList(); private final SortedList sortedList = new SortedList<>(observableList); - private Set selectedItems = new HashSet<>(); + private final Set selectedItems = new HashSet<>(); private BalanceListener balanceListener; private Set fromAddresses = new HashSet<>(); private Coin totalAvailableAmountOfSelectedItems = Coin.ZERO; @@ -392,7 +392,7 @@ public void onSuccess(@javax.annotation.Nullable Transaction transaction) { log.error("onWithdraw transaction is null"); } - List trades = new ArrayList<>(tradeManager.getTradesAsObservableList()); + List trades = new ArrayList<>(tradeManager.getObservableList()); trades.stream() .filter(Trade::isPayoutPublished) .forEach(trade -> btcWalletService.getAddressEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT) diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java index 4f39b8742ba..1df383ea071 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBookViewModel.java @@ -613,7 +613,7 @@ private boolean isEditEntry(String id) { } int getNumTrades(Offer offer) { - return closedTradableManager.getClosedTradables().stream() + return closedTradableManager.getObservableList().stream() .filter(e -> { final NodeAddress tradingPeerNodeAddress = e instanceof Trade ? ((Trade) e).getTradingPeerNodeAddress() : null; return tradingPeerNodeAddress != null && diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/notifications/NotificationCenter.java b/desktop/src/main/java/bisq/desktop/main/overlays/notifications/NotificationCenter.java index 1da0754ca52..994a9768675 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/notifications/NotificationCenter.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/notifications/NotificationCenter.java @@ -109,10 +109,10 @@ public NotificationCenter(TradeManager tradeManager, } public void onAllServicesAndViewsInitialized() { - tradeManager.getTradesAsObservableList().addListener((ListChangeListener) change -> { + tradeManager.getObservableList().addListener((ListChangeListener) change -> { change.next(); if (change.wasRemoved()) { - change.getRemoved().stream().forEach(trade -> { + change.getRemoved().forEach(trade -> { String tradeId = trade.getId(); if (disputeStateSubscriptionsMap.containsKey(tradeId)) { disputeStateSubscriptionsMap.get(tradeId).unsubscribe(); @@ -126,7 +126,7 @@ public void onAllServicesAndViewsInitialized() { }); } if (change.wasAdded()) { - change.getAddedSubList().stream().forEach(trade -> { + change.getAddedSubList().forEach(trade -> { String tradeId = trade.getId(); if (disputeStateSubscriptionsMap.containsKey(tradeId)) { log.debug("We have already an entry in disputeStateSubscriptionsMap."); @@ -147,18 +147,17 @@ public void onAllServicesAndViewsInitialized() { } }); - tradeManager.getTradesAsObservableList().stream() - .forEach(trade -> { - String tradeId = trade.getId(); - Subscription disputeStateSubscription = EasyBind.subscribe(trade.disputeStateProperty(), - disputeState -> onDisputeStateChanged(trade, disputeState)); - disputeStateSubscriptionsMap.put(tradeId, disputeStateSubscription); - - Subscription tradePhaseSubscription = EasyBind.subscribe(trade.statePhaseProperty(), - phase -> onTradePhaseChanged(trade, phase)); - tradePhaseSubscriptionsMap.put(tradeId, tradePhaseSubscription); - } - ); + tradeManager.getObservableList().forEach(trade -> { + String tradeId = trade.getId(); + Subscription disputeStateSubscription = EasyBind.subscribe(trade.disputeStateProperty(), + disputeState -> onDisputeStateChanged(trade, disputeState)); + disputeStateSubscriptionsMap.put(tradeId, disputeStateSubscription); + + Subscription tradePhaseSubscription = EasyBind.subscribe(trade.statePhaseProperty(), + phase -> onTradePhaseChanged(trade, phase)); + tradePhaseSubscriptionsMap.put(tradeId, tradePhaseSubscription); + } + ); } /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java index ec8e10ede0b..a1bbf472989 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/ContractWindow.java @@ -181,7 +181,7 @@ private void addContent() { getAccountAge(contract.getBuyerPaymentAccountPayload(), contract.getBuyerPubKeyRing(), offer.getCurrencyCode()) + " / " + getAccountAge(contract.getSellerPaymentAccountPayload(), contract.getSellerPubKeyRing(), offer.getCurrencyCode())); - DisputeManager> disputeManager = getDisputeManager(dispute); + DisputeManager> disputeManager = getDisputeManager(dispute); String nrOfDisputesAsBuyer = disputeManager != null ? disputeManager.getNrOfDisputes(true, contract) : ""; String nrOfDisputesAsSeller = disputeManager != null ? disputeManager.getNrOfDisputes(false, contract) : ""; addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, Res.get("contractWindow.numDisputes"), @@ -313,7 +313,7 @@ private void addContent() { }); } - private DisputeManager> getDisputeManager(Dispute dispute) { + private DisputeManager> getDisputeManager(Dispute dispute) { if (dispute.getSupportType() != null) { switch (dispute.getSupportType()) { case ARBITRATION: diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java index 82fd1c09f30..b3231a4155a 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/DisputeSummaryWindow.java @@ -661,6 +661,7 @@ private void addButtons(Contract contract) { cancelButton.setOnAction(e -> { dispute.setDisputeResult(disputeResult); + checkNotNull(getDisputeManager(dispute)).requestPersistence(); hide(); }); } @@ -804,7 +805,7 @@ private void doCloseIfValid(Button closeTicketButton) { } private void doClose(Button closeTicketButton) { - DisputeManager> disputeManager = getDisputeManager(dispute); + DisputeManager> disputeManager = getDisputeManager(dispute); if (disputeManager == null) { return; } @@ -860,12 +861,14 @@ private void doClose(Button closeTicketButton) { finalizeDisputeHandlerOptional.ifPresent(Runnable::run); + disputeManager.requestPersistence(); + closeTicketButton.disableProperty().unbind(); hide(); } - private DisputeManager> getDisputeManager(Dispute dispute) { + private DisputeManager> getDisputeManager(Dispute dispute) { if (dispute.getSupportType() != null) { switch (dispute.getSupportType()) { case ARBITRATION: diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/UpdateRevolutAccountWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/UpdateRevolutAccountWindow.java index 0c72c5a1a05..92f5d407ee5 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/UpdateRevolutAccountWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/UpdateRevolutAccountWindow.java @@ -84,7 +84,7 @@ protected void addButtons() { String userName = userNameInputTextField.getText(); if (revolutValidator.validate(userName).isValid) { revolutAccount.setUserName(userName); - user.persist(); + user.requestPersistence(); closeHandlerOptional.ifPresent(Runnable::run); hide(); } diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/downloadupdate/DownloadTask.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/downloadupdate/DownloadTask.java index 454978a4719..61b2604e435 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/downloadupdate/DownloadTask.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/downloadupdate/DownloadTask.java @@ -17,7 +17,7 @@ package bisq.desktop.main.overlays.windows.downloadupdate; -import bisq.common.storage.FileUtil; +import bisq.common.file.FileUtil; import com.google.common.collect.Lists; diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/PortfolioView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/PortfolioView.java index acfd093b2ec..c23e4607959 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/PortfolioView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/PortfolioView.java @@ -127,11 +127,11 @@ private void onEditOpenOfferRemoved() { @Override protected void activate() { - failedTradesManager.getFailedTrades().addListener((ListChangeListener) c -> { - if (failedTradesManager.getFailedTrades().size() > 0 && root.getTabs().size() == 3) + failedTradesManager.getObservableList().addListener((ListChangeListener) c -> { + if (failedTradesManager.getObservableList().size() > 0 && root.getTabs().size() == 3) root.getTabs().add(failedTradesTab); }); - if (failedTradesManager.getFailedTrades().size() > 0 && root.getTabs().size() == 3) + if (failedTradesManager.getObservableList().size() > 0 && root.getTabs().size() == 3) root.getTabs().add(failedTradesTab); root.getSelectionModel().selectedItemProperty().addListener(tabChangeListener); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesDataModel.java index f0249b401d7..47eaf7e0189 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesDataModel.java @@ -48,12 +48,12 @@ public ClosedTradesDataModel(ClosedTradableManager closedTradableManager) { @Override protected void activate() { applyList(); - closedTradableManager.getClosedTradables().addListener(tradesListChangeListener); + closedTradableManager.getObservableList().addListener(tradesListChangeListener); } @Override protected void deactivate() { - closedTradableManager.getClosedTradables().removeListener(tradesListChangeListener); + closedTradableManager.getObservableList().removeListener(tradesListChangeListener); } public ObservableList getList() { @@ -67,7 +67,7 @@ public OfferPayload.Direction getDirection(Offer offer) { private void applyList() { list.clear(); - list.addAll(closedTradableManager.getClosedTradables().stream().map(ClosedTradableListItem::new).collect(Collectors.toList())); + list.addAll(closedTradableManager.getObservableList().stream().map(ClosedTradableListItem::new).collect(Collectors.toList())); // we sort by date, earliest first list.sort((o1, o2) -> o2.getTradable().getDate().compareTo(o1.getTradable().getDate())); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesViewModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesViewModel.java index e359e4f0ee8..6e023f00368 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesViewModel.java @@ -184,7 +184,7 @@ String getState(ClosedTradableListItem item) { int getNumPastTrades(Tradable tradable) { //noinspection ConstantConditions - return dataModel.closedTradableManager.getClosedTradables().stream() + return dataModel.closedTradableManager.getObservableList().stream() .filter(e -> e instanceof Trade && tradable instanceof Trade && ((Trade) e).getTradingPeerNodeAddress() != null && diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesDataModel.java index 5d33f4c16af..a4ce6e375e0 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesDataModel.java @@ -52,12 +52,12 @@ public FailedTradesDataModel(FailedTradesManager failedTradesManager, TradeManag @Override protected void activate() { applyList(); - failedTradesManager.getFailedTrades().addListener(tradesListChangeListener); + failedTradesManager.getObservableList().addListener(tradesListChangeListener); } @Override protected void deactivate() { - failedTradesManager.getFailedTrades().removeListener(tradesListChangeListener); + failedTradesManager.getObservableList().removeListener(tradesListChangeListener); } public ObservableList getList() { @@ -71,7 +71,7 @@ public OfferPayload.Direction getDirection(Offer offer) { private void applyList() { list.clear(); - list.addAll(failedTradesManager.getFailedTrades().stream().map(FailedTradesListItem::new).collect(Collectors.toList())); + list.addAll(failedTradesManager.getObservableList().stream().map(FailedTradesListItem::new).collect(Collectors.toList())); // we sort by date, earliest first list.sort((o1, o2) -> o2.getTrade().getDate().compareTo(o1.getTrade().getDate())); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java index deab6dc9127..fa000d8e6f7 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java @@ -82,6 +82,7 @@ import org.bouncycastle.crypto.params.KeyParameter; +import java.util.Date; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @@ -93,6 +94,7 @@ import static com.google.common.base.Preconditions.checkNotNull; public class PendingTradesDataModel extends ActivatableDataModel { + @Getter public final TradeManager tradeManager; public final BtcWalletService btcWalletService; public final MediationManager mediationManager; @@ -120,7 +122,7 @@ public class PendingTradesDataModel extends ActivatableDataModel { private ChangeListener tradeStateChangeListener; private Trade selectedTrade; @Getter - private PubKeyRing pubKeyRing; + private final PubKeyRing pubKeyRing; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, initialization @@ -162,7 +164,7 @@ public PendingTradesDataModel(TradeManager tradeManager, @Override protected void activate() { - tradeManager.getTradesAsObservableList().addListener(tradesListChangeListener); + tradeManager.getObservableList().addListener(tradesListChangeListener); onListChanged(); if (selectedItemProperty.get() != null) notificationCenter.setSelectedTradeId(selectedItemProperty.get().getTrade().getId()); @@ -172,7 +174,7 @@ protected void activate() { @Override protected void deactivate() { - tradeManager.getTradesAsObservableList().removeListener(tradesListChangeListener); + tradeManager.getObservableList().removeListener(tradesListChangeListener); notificationCenter.setSelectedTradeId(null); activated = false; } @@ -377,7 +379,7 @@ public String getReference() { private void onListChanged() { list.clear(); - list.addAll(tradeManager.getTradesAsObservableList().stream().map(PendingTradesListItem::new).collect(Collectors.toList())); + list.addAll(tradeManager.getObservableList().stream().map(PendingTradesListItem::new).collect(Collectors.toList())); // we sort by date, earliest first list.sort((o1, o2) -> o2.getTrade().getDate().compareTo(o1.getTrade().getDate())); @@ -489,7 +491,7 @@ private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { payoutTxHashAsString = payoutTx.getTxId().toString(); } Trade.DisputeState disputeState = trade.getDisputeState(); - DisputeManager> disputeManager; + DisputeManager> disputeManager; boolean useMediation; boolean useRefundAgent; // In case we re-open a dispute we allow Trade.DisputeState.MEDIATION_REQUESTED @@ -527,7 +529,7 @@ private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { PubKeyRing mediatorPubKeyRing = trade.getMediatorPubKeyRing(); checkNotNull(mediatorPubKeyRing, "mediatorPubKeyRing must not be null"); byte[] depositTxSerialized = depositTx.bitcoinSerialize(); - Dispute dispute = new Dispute(disputeManager.getStorage(), + Dispute dispute = new Dispute(new Date().getTime(), trade.getId(), pubKeyRing.hashCode(), // traderId (offer.getDirection() == OfferPayload.Direction.BUY) == isMaker, @@ -606,7 +608,7 @@ private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { checkNotNull(refundAgentPubKeyRing, "refundAgentPubKeyRing must not be null"); byte[] depositTxSerialized = depositTx.bitcoinSerialize(); String depositTxHashAsString = depositTx.getTxId().toString(); - Dispute dispute = new Dispute(disputeManager.getStorage(), + Dispute dispute = new Dispute(new Date().getTime(), trade.getId(), pubKeyRing.hashCode(), // traderId (offer.getDirection() == OfferPayload.Direction.BUY) == isMaker, @@ -674,6 +676,7 @@ private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { } else { log.warn("Invalid dispute state {}", disputeState.name()); } + tradeManager.requestPersistence(); } public boolean isReadyForTxBroadcast() { diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesView.java index 8f7b2d0a651..a72ab7ab770 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesView.java @@ -399,7 +399,7 @@ private void openChat(Trade trade) { } trade.getChatMessages().forEach(m -> m.setWasDisplayed(true)); - trade.persist(); + model.dataModel.getTradeManager().requestPersistence(); tradeIdOfOpenChat = trade.getId(); ChatView chatView = new ChatView(traderChatManager, formatter); @@ -458,7 +458,7 @@ private void openChat(Trade trade) { chatView.deactivate(); // at close we set all as displayed. While open we ignore updates of the numNewMsg in the list icon. trade.getChatMessages().forEach(m -> m.setWasDisplayed(true)); - trade.persist(); + model.dataModel.getTradeManager().requestPersistence(); tradeIdOfOpenChat = null; if (xPositionListener != null) { @@ -545,6 +545,7 @@ private void onListChanged() { updateMoveTradeToFailedColumnState(); } + /////////////////////////////////////////////////////////////////////////////////////////// // CellFactories /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java index 2e7f538c7e2..35934419fdf 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java @@ -348,7 +348,7 @@ public boolean isBlockChainMethod() { } public int getNumPastTrades(Trade trade) { - return closedTradableManager.getClosedTradables().stream() + return closedTradableManager.getObservableList().stream() .filter(e -> { if (e instanceof Trade) { Trade t = (Trade) e; diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeSummaryVerification.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeSummaryVerification.java index c806da3cd95..8ddec2de708 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeSummaryVerification.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeSummaryVerification.java @@ -18,6 +18,7 @@ package bisq.desktop.main.support.dispute; import bisq.core.locale.Res; +import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeList; import bisq.core.support.dispute.DisputeManager; import bisq.core.support.dispute.DisputeResult; @@ -45,7 +46,7 @@ public class DisputeSummaryVerification { private static final String SEPARATOR1 = "\n-----BEGIN SIGNATURE-----\n"; private static final String SEPARATOR2 = "\n-----END SIGNATURE-----\n"; - public static String signAndApply(DisputeManager> disputeManager, + public static String signAndApply(DisputeManager> disputeManager, DisputeResult disputeResult, String textToSign) { diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java index e23df1f2eb6..f102f4e56c6 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java @@ -139,7 +139,7 @@ public enum FilterResult { } } - protected final DisputeManager> disputeManager; + protected final DisputeManager> disputeManager; protected final KeyRing keyRing; private final TradeManager tradeManager; protected final CoinFormatter formatter; @@ -168,7 +168,7 @@ public enum FilterResult { protected InputTextField filterTextField; private ChangeListener filterTextFieldListener; protected AutoTooltipButton sigCheckButton, reOpenButton, sendPrivateNotificationButton, reportButton, fullReportButton; - private Map> disputeChatMessagesListeners = new HashMap<>(); + private final Map> disputeChatMessagesListeners = new HashMap<>(); @Nullable private ListChangeListener disputesListener; // Only set in mediation cases protected Label alertIconLabel; @@ -179,7 +179,7 @@ public enum FilterResult { // Constructor, lifecycle /////////////////////////////////////////////////////////////////////////////////////////// - public DisputeView(DisputeManager> disputeManager, + public DisputeView(DisputeManager> disputeManager, KeyRing keyRing, TradeManager tradeManager, CoinFormatter formatter, @@ -489,6 +489,7 @@ protected void reOpenDispute() { if (selectedDispute != null) { selectedDispute.setIsClosed(false); handleOnSelectDispute(selectedDispute); + disputeManager.requestPersistence(); } } diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java index 41a1f2a3b6d..b3111962fb4 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java @@ -70,7 +70,7 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo private final MultipleHolderNameDetection multipleHolderNameDetection; private ListChangeListener validationExceptionListener; - public DisputeAgentView(DisputeManager> disputeManager, + public DisputeAgentView(DisputeManager> disputeManager, KeyRing keyRing, TradeManager tradeManager, CoinFormatter formatter, diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/DisputeClientView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/DisputeClientView.java index 33e31ce3bb5..7414b0b15fe 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/client/DisputeClientView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/client/DisputeClientView.java @@ -37,7 +37,7 @@ import bisq.common.crypto.KeyRing; public abstract class DisputeClientView extends DisputeView { - public DisputeClientView(DisputeManager> DisputeManager, + public DisputeClientView(DisputeManager> DisputeManager, KeyRing keyRing, TradeManager tradeManager, CoinFormatter formatter, diff --git a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java index 321eacea701..42f95f7e738 100644 --- a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java +++ b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java @@ -58,10 +58,10 @@ import bisq.common.UserThread; import bisq.common.app.DevEnv; import bisq.common.config.Config; -import bisq.common.proto.persistable.PersistableList; +import bisq.common.file.CorruptedStorageFileHandler; +import bisq.common.persistence.PersistenceManager; +import bisq.common.proto.persistable.PersistableEnvelope; import bisq.common.proto.persistable.PersistenceProtoResolver; -import bisq.common.storage.CorruptedDatabaseFilesHandler; -import bisq.common.storage.Storage; import bisq.common.util.MathUtils; import bisq.common.util.Tuple2; import bisq.common.util.Tuple3; @@ -209,14 +209,19 @@ public static void exportAccounts(ArrayList accounts, Preferences preferences, Stage stage, PersistenceProtoResolver persistenceProtoResolver, - CorruptedDatabaseFilesHandler corruptedDatabaseFilesHandler) { + CorruptedStorageFileHandler corruptedStorageFileHandler) { if (!accounts.isEmpty()) { String directory = getDirectoryFromChooser(preferences, stage); if (!directory.isEmpty()) { - Storage> paymentAccountsStorage = new Storage<>(new File(directory), persistenceProtoResolver, corruptedDatabaseFilesHandler); - paymentAccountsStorage.initAndGetPersisted(new PaymentAccountList(accounts), fileName, 100); - paymentAccountsStorage.queueUpForSave(); - new Popup().feedback(Res.get("guiUtil.accountExport.savedToPath", Paths.get(directory, fileName).toAbsolutePath())).show(); + PersistenceManager persistenceManager = new PersistenceManager<>(new File(directory), persistenceProtoResolver, corruptedStorageFileHandler); + PaymentAccountList paymentAccounts = new PaymentAccountList(accounts); + persistenceManager.initialize(paymentAccounts, fileName, PersistenceManager.Source.PRIVATE_LOW_PRIO); + persistenceManager.persistNow(() -> { + persistenceManager.shutdown(); + new Popup().feedback(Res.get("guiUtil.accountExport.savedToPath", + Paths.get(directory, fileName).toAbsolutePath())) + .show(); + }); } } else { new Popup().warning(Res.get("guiUtil.accountExport.noAccountSetup")).show(); @@ -228,7 +233,7 @@ public static void importAccounts(User user, Preferences preferences, Stage stage, PersistenceProtoResolver persistenceProtoResolver, - CorruptedDatabaseFilesHandler corruptedDatabaseFilesHandler) { + CorruptedStorageFileHandler corruptedStorageFileHandler) { FileChooser fileChooser = new FileChooser(); File initDir = new File(preferences.getDirectoryChooserPath()); if (initDir.isDirectory()) { @@ -241,8 +246,8 @@ public static void importAccounts(User user, if (Paths.get(path).getFileName().toString().equals(fileName)) { String directory = Paths.get(path).getParent().toString(); preferences.setDirectoryChooserPath(directory); - Storage paymentAccountsStorage = new Storage<>(new File(directory), persistenceProtoResolver, corruptedDatabaseFilesHandler); - PaymentAccountList persisted = paymentAccountsStorage.initAndGetPersistedWithFileName(fileName, 100); + PersistenceManager persistenceManager = new PersistenceManager<>(new File(directory), persistenceProtoResolver, corruptedStorageFileHandler); + PaymentAccountList persisted = persistenceManager.getPersisted(fileName); if (persisted != null) { final StringBuilder msg = new StringBuilder(); final HashSet paymentAccounts = new HashSet<>(); diff --git a/desktop/src/test/java/bisq/desktop/GuiceSetupTest.java b/desktop/src/test/java/bisq/desktop/GuiceSetupTest.java index aa8ab19ed20..72111708124 100644 --- a/desktop/src/test/java/bisq/desktop/GuiceSetupTest.java +++ b/desktop/src/test/java/bisq/desktop/GuiceSetupTest.java @@ -56,10 +56,10 @@ import bisq.common.crypto.KeyRing; import bisq.common.crypto.KeyStorage; import bisq.common.crypto.PubKeyRing; +import bisq.common.file.CorruptedStorageFileHandler; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.network.NetworkProtoResolver; import bisq.common.proto.persistable.PersistenceProtoResolver; -import bisq.common.storage.CorruptedDatabaseFilesHandler; -import bisq.common.storage.Storage; import com.google.inject.Guice; import com.google.inject.Injector; @@ -118,7 +118,7 @@ public void testGuiceSetup() { assertSingleton(ClockWatcher.class); assertSingleton(Preferences.class); assertSingleton(BridgeAddressProvider.class); - assertSingleton(CorruptedDatabaseFilesHandler.class); + assertSingleton(CorruptedStorageFileHandler.class); assertSingleton(AvoidStandbyModeService.class); assertSingleton(DefaultSeedNodeRepository.class); assertSingleton(SeedNodeRepository.class); @@ -148,7 +148,7 @@ public void testGuiceSetup() { assertSingleton(MediationDisputeListService.class); assertSingleton(TraderChatManager.class); - assertNotSingleton(Storage.class); + assertNotSingleton(PersistenceManager.class); } private void assertSingleton(Class type) { diff --git a/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java b/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java index 9865f12fe14..a820789916d 100644 --- a/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java +++ b/desktop/src/test/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModelTest.java @@ -107,7 +107,7 @@ public void testEditOfferOfRemovedAsset() { when(user.getPaymentAccount(anyString())).thenReturn(bitcoinClashicAccount); - model.applyOpenOffer(new OpenOffer(make(btcBCHCOffer), null)); + model.applyOpenOffer(new OpenOffer(make(btcBCHCOffer))); assertNull(model.getPreselectedPaymentAccount()); } diff --git a/desktop/src/test/java/bisq/desktop/maker/PreferenceMakers.java b/desktop/src/test/java/bisq/desktop/maker/PreferenceMakers.java index a3edfa8a189..9acd80b5fd4 100644 --- a/desktop/src/test/java/bisq/desktop/maker/PreferenceMakers.java +++ b/desktop/src/test/java/bisq/desktop/maker/PreferenceMakers.java @@ -21,7 +21,7 @@ import bisq.core.user.Preferences; import bisq.common.config.Config; -import bisq.common.storage.Storage; +import bisq.common.persistence.PersistenceManager; import com.natpryce.makeiteasy.Instantiator; import com.natpryce.makeiteasy.Property; @@ -32,14 +32,14 @@ public class PreferenceMakers { - public static final Property storage = new Property<>(); + public static final Property storage = new Property<>(); public static final Property config = new Property<>(); public static final Property localBitcoinNode = new Property<>(); public static final Property useTorFlagFromOptions = new Property<>(); public static final Property referralID = new Property<>(); public static final Instantiator Preferences = lookup -> new Preferences( - lookup.valueOf(storage, new SameValueDonor(null)), + lookup.valueOf(storage, new SameValueDonor(null)), lookup.valueOf(config, new SameValueDonor(null)), lookup.valueOf(localBitcoinNode, new SameValueDonor(null)), lookup.valueOf(useTorFlagFromOptions, new SameValueDonor(null)), diff --git a/monitor/src/main/java/bisq/monitor/metric/P2PMarketStats.java b/monitor/src/main/java/bisq/monitor/metric/P2PMarketStats.java index c89a7437f11..189c739b3b0 100644 --- a/monitor/src/main/java/bisq/monitor/metric/P2PMarketStats.java +++ b/monitor/src/main/java/bisq/monitor/metric/P2PMarketStats.java @@ -21,7 +21,6 @@ import bisq.monitor.Reporter; import bisq.core.account.witness.AccountAgeWitnessStore; -import bisq.common.config.BaseCurrencyNetwork; import bisq.core.offer.OfferPayload; import bisq.core.proto.persistable.CorePersistenceProtoResolver; import bisq.core.trade.statistics.TradeStatistics2Store; @@ -30,14 +29,14 @@ import bisq.network.p2p.network.Connection; import bisq.network.p2p.peers.getdata.messages.GetDataResponse; import bisq.network.p2p.peers.getdata.messages.PreliminaryGetDataRequest; -import bisq.network.p2p.storage.payload.PersistableNetworkPayload; import bisq.network.p2p.storage.payload.ProtectedStorageEntry; import bisq.network.p2p.storage.payload.ProtectedStoragePayload; import bisq.common.app.Version; +import bisq.common.config.BaseCurrencyNetwork; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.network.NetworkEnvelope; import bisq.common.proto.persistable.PersistableEnvelope; -import bisq.common.storage.Storage; import java.io.File; @@ -163,11 +162,11 @@ public void configure(Properties properties) { File dir = new File(configuration.getProperty(DATABASE_DIR)); String networkPostfix = "_" + BaseCurrencyNetwork.values()[Version.getBaseCurrencyNetwork()].toString(); try { - Storage storage = new Storage<>(dir, new CorePersistenceProtoResolver(null, null, null, null), null); - TradeStatistics2Store tradeStatistics2Store = (TradeStatistics2Store) storage.initAndGetPersistedWithFileName(TradeStatistics2Store.class.getSimpleName() + networkPostfix, 0); + PersistenceManager persistenceManager = new PersistenceManager<>(dir, new CorePersistenceProtoResolver(null, null), null); + TradeStatistics2Store tradeStatistics2Store = (TradeStatistics2Store) persistenceManager.getPersisted(TradeStatistics2Store.class.getSimpleName() + networkPostfix); hashes.addAll(tradeStatistics2Store.getMap().keySet().stream().map(byteArray -> byteArray.bytes).collect(Collectors.toList())); - AccountAgeWitnessStore accountAgeWitnessStore = (AccountAgeWitnessStore) storage.initAndGetPersistedWithFileName(AccountAgeWitnessStore.class.getSimpleName() + networkPostfix, 0); + AccountAgeWitnessStore accountAgeWitnessStore = (AccountAgeWitnessStore) persistenceManager.getPersisted(AccountAgeWitnessStore.class.getSimpleName() + networkPostfix); hashes.addAll(accountAgeWitnessStore.getMap().keySet().stream().map(byteArray -> byteArray.bytes).collect(Collectors.toList())); } catch (NullPointerException e) { // in case there is no store file diff --git a/monitor/src/main/java/bisq/monitor/metric/P2PNetworkLoad.java b/monitor/src/main/java/bisq/monitor/metric/P2PNetworkLoad.java index 1c2a2582395..b9fbefb5551 100644 --- a/monitor/src/main/java/bisq/monitor/metric/P2PNetworkLoad.java +++ b/monitor/src/main/java/bisq/monitor/metric/P2PNetworkLoad.java @@ -35,15 +35,14 @@ import bisq.network.p2p.peers.PeerManager; import bisq.network.p2p.peers.keepalive.KeepAliveManager; import bisq.network.p2p.peers.peerexchange.PeerExchangeManager; -import bisq.network.p2p.peers.peerexchange.PeerList; import bisq.network.p2p.storage.messages.BroadcastMessage; import bisq.common.ClockWatcher; import bisq.common.config.Config; +import bisq.common.file.CorruptedStorageFileHandler; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.network.NetworkEnvelope; import bisq.common.proto.network.NetworkProtoResolver; -import bisq.common.storage.CorruptedDatabaseFilesHandler; -import bisq.common.storage.Storage; import java.time.Clock; @@ -127,14 +126,14 @@ protected void execute() { File storageDir = torHiddenServiceDir; try { Config config = new Config(); - CorruptedDatabaseFilesHandler corruptedDatabaseFilesHandler = new CorruptedDatabaseFilesHandler(); + CorruptedStorageFileHandler corruptedStorageFileHandler = new CorruptedStorageFileHandler(); int maxConnections = Integer.parseInt(configuration.getProperty(MAX_CONNECTIONS, "12")); NetworkProtoResolver networkProtoResolver = new CoreNetworkProtoResolver(Clock.systemDefaultZone()); CorePersistenceProtoResolver persistenceProtoResolver = new CorePersistenceProtoResolver(null, - networkProtoResolver, storageDir, corruptedDatabaseFilesHandler); + networkProtoResolver); DefaultSeedNodeRepository seedNodeRepository = new DefaultSeedNodeRepository(config); PeerManager peerManager = new PeerManager(networkNode, seedNodeRepository, new ClockWatcher(), - maxConnections, new Storage(storageDir, persistenceProtoResolver, corruptedDatabaseFilesHandler)); + maxConnections, new PersistenceManager<>(storageDir, persistenceProtoResolver, corruptedStorageFileHandler)); // init file storage peerManager.readPersisted(); diff --git a/monitor/src/main/java/bisq/monitor/metric/P2PSeedNodeSnapshot.java b/monitor/src/main/java/bisq/monitor/metric/P2PSeedNodeSnapshot.java index 8da68d450d2..e87d7468253 100644 --- a/monitor/src/main/java/bisq/monitor/metric/P2PSeedNodeSnapshot.java +++ b/monitor/src/main/java/bisq/monitor/metric/P2PSeedNodeSnapshot.java @@ -21,7 +21,6 @@ import bisq.monitor.Reporter; import bisq.core.account.witness.AccountAgeWitnessStore; -import bisq.common.config.BaseCurrencyNetwork; import bisq.core.dao.monitoring.model.StateHash; import bisq.core.dao.monitoring.network.messages.GetBlindVoteStateHashesRequest; import bisq.core.dao.monitoring.network.messages.GetDaoStateHashesRequest; @@ -34,14 +33,14 @@ import bisq.network.p2p.network.Connection; import bisq.network.p2p.peers.getdata.messages.GetDataResponse; import bisq.network.p2p.peers.getdata.messages.PreliminaryGetDataRequest; -import bisq.network.p2p.storage.payload.PersistableNetworkPayload; import bisq.network.p2p.storage.payload.ProtectedStorageEntry; import bisq.network.p2p.storage.payload.ProtectedStoragePayload; import bisq.common.app.Version; +import bisq.common.config.BaseCurrencyNetwork; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.network.NetworkEnvelope; import bisq.common.proto.persistable.PersistableEnvelope; -import bisq.common.storage.Storage; import java.net.MalformedURLException; @@ -137,11 +136,11 @@ public void configure(Properties properties) { File dir = new File(configuration.getProperty(DATABASE_DIR)); String networkPostfix = "_" + BaseCurrencyNetwork.values()[Version.getBaseCurrencyNetwork()].toString(); try { - Storage storage = new Storage<>(dir, new CorePersistenceProtoResolver(null, null, null, null), null); - TradeStatistics2Store tradeStatistics2Store = (TradeStatistics2Store) storage.initAndGetPersistedWithFileName(TradeStatistics2Store.class.getSimpleName() + networkPostfix, 0); + PersistenceManager persistenceManager = new PersistenceManager<>(dir, new CorePersistenceProtoResolver(null, null), null); + TradeStatistics2Store tradeStatistics2Store = (TradeStatistics2Store) persistenceManager.getPersisted(TradeStatistics2Store.class.getSimpleName() + networkPostfix); hashes.addAll(tradeStatistics2Store.getMap().keySet().stream().map(byteArray -> byteArray.bytes).collect(Collectors.toList())); - AccountAgeWitnessStore accountAgeWitnessStore = (AccountAgeWitnessStore) storage.initAndGetPersistedWithFileName(AccountAgeWitnessStore.class.getSimpleName() + networkPostfix, 0); + AccountAgeWitnessStore accountAgeWitnessStore = (AccountAgeWitnessStore) persistenceManager.getPersisted(AccountAgeWitnessStore.class.getSimpleName() + networkPostfix); hashes.addAll(accountAgeWitnessStore.getMap().keySet().stream().map(byteArray -> byteArray.bytes).collect(Collectors.toList())); } catch (NullPointerException e) { // in case there is no store file @@ -191,21 +190,21 @@ void report() { // - calculate diffs messagesPerHost.forEach( - (host, statistics) -> { - statistics.values().forEach((messageType, set) -> { + (host, statistics) -> { + statistics.values().forEach((messageType, set) -> { + try { + report.put(OnionParser.prettyPrint(host) + ".relativeNumberOfMessages." + messageType, + String.valueOf(set.size() - referenceValues.get(messageType).size())); + } catch (MalformedURLException | NullPointerException ignore) { + log.error("we should never have gotten here", ignore); + } + }); try { - report.put(OnionParser.prettyPrint(host) + ".relativeNumberOfMessages." + messageType, - String.valueOf(set.size() - referenceValues.get(messageType).size())); - } catch (MalformedURLException | NullPointerException ignore) { - log.error("we should never have gotten here", ignore); + report.put(OnionParser.prettyPrint(host) + ".referenceHost", referenceHost); + } catch (MalformedURLException ignore) { + log.error("we should never got here"); } }); - try { - report.put(OnionParser.prettyPrint(host) + ".referenceHost", referenceHost); - } catch (MalformedURLException ignore) { - log.error("we should never got here"); - } - }); // cleanup for next run bucketsPerHost.forEach((host, statistics) -> statistics.reset()); diff --git a/p2p/src/main/java/bisq/network/p2p/network/TorMode.java b/p2p/src/main/java/bisq/network/p2p/network/TorMode.java index b1a8e88b823..ac696307bbb 100644 --- a/p2p/src/main/java/bisq/network/p2p/network/TorMode.java +++ b/p2p/src/main/java/bisq/network/p2p/network/TorMode.java @@ -17,18 +17,18 @@ package bisq.network.p2p.network; -import java.io.File; -import java.io.IOException; +import bisq.common.file.FileUtil; import org.berndpruenster.netlayer.tor.Tor; import org.berndpruenster.netlayer.tor.TorCtlException; -import bisq.common.storage.FileUtil; +import java.io.File; +import java.io.IOException; /** * Holds information on how tor should be created and delivers a respective * {@link Tor} object when asked. - * + * * @author Florian Reimair * */ @@ -45,11 +45,6 @@ public abstract class TorMode { /** * @param torDir points to the place, where we will persist private * key and address data - * @param hiddenServiceDir The directory where the private_key file - * sits in. Note that, due to the inner workings of the - * Netlayer dependency, it does not - * necessarily equal - * {@link TorMode#getHiddenServiceDirectory()}. */ public TorMode(File torDir) { this.torDir = torDir; @@ -57,7 +52,7 @@ public TorMode(File torDir) { /** * Returns a fresh {@link Tor} object. - * + * * @return a fresh instance of {@link Tor} * @throws IOException * @throws TorCtlException @@ -72,7 +67,7 @@ public TorMode(File torDir) { * service path literally. Hence, we set "torDir/hiddenservice" as * the hidden service directory. By doing so, we use the same * private_key file as in {@link NewTor} mode. - * + * * @return "" in {@link NewTor} Mode, * "torDir/externalTorHiddenService" in {@link RunningTor} * mode diff --git a/p2p/src/main/java/bisq/network/p2p/peers/PeerManager.java b/p2p/src/main/java/bisq/network/p2p/peers/PeerManager.java index f9c4fd8f04f..e8cf818336d 100644 --- a/p2p/src/main/java/bisq/network/p2p/peers/PeerManager.java +++ b/p2p/src/main/java/bisq/network/p2p/peers/PeerManager.java @@ -33,8 +33,8 @@ import bisq.common.UserThread; import bisq.common.app.Capabilities; import bisq.common.config.Config; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.persistable.PersistedDataHost; -import bisq.common.storage.Storage; import javax.inject.Inject; import javax.inject.Named; @@ -105,7 +105,8 @@ public interface Listener { private int maxConnections; private final Set seedNodeAddresses; - private final Storage storage; + private final PersistenceManager persistenceManager; + private final PeerList peerList = new PeerList(); private final HashSet persistedPeers = new HashSet<>(); private final Set reportedPeers = new HashSet<>(); private final ClockWatcher.Listener listener; @@ -131,12 +132,13 @@ public PeerManager(NetworkNode networkNode, SeedNodeRepository seedNodeRepository, ClockWatcher clockWatcher, @Named(Config.MAX_CONNECTIONS) int maxConnections, - Storage storage) { + PersistenceManager persistenceManager) { this.networkNode = networkNode; this.seedNodeAddresses = new HashSet<>(seedNodeRepository.getSeedNodeAddresses()); this.clockWatcher = clockWatcher; - this.storage = storage; + this.persistenceManager = persistenceManager; + this.persistenceManager.initialize(peerList, PersistenceManager.Source.PRIVATE_LOW_PRIO); this.networkNode.addConnectionListener(this); setConnectionLimits(maxConnections); @@ -174,19 +176,9 @@ public void shutDown() { @Override public void readPersisted() { - PeerList persistedPeerList = storage.initAndGetPersistedWithFileName("PeerList", 1000); - if (persistedPeerList != null) { - long peersWithNoCapabilitiesSet = persistedPeerList.getList().stream() - .filter(e -> e.getCapabilities().isEmpty()) - .mapToInt(e -> 1) - .count(); - if (peersWithNoCapabilitiesSet > 100) { - log.warn("peersWithNoCapabilitiesSet={}, persistedPeerList.size()={}", peersWithNoCapabilitiesSet, persistedPeerList.size()); - } else { - log.info("peersWithNoCapabilitiesSet={}, persistedPeerList.size()={}", peersWithNoCapabilitiesSet, persistedPeerList.size()); - } - - this.persistedPeers.addAll(persistedPeerList.getList()); + PeerList persisted = persistenceManager.getPersisted(); + if (persisted != null) { + this.persistedPeers.addAll(persisted.getList()); } } @@ -466,7 +458,8 @@ public void addToReportedPeers(Set reportedPeersToAdd, Connection connecti persistedPeers.addAll(reportedPeersToAdd); purgePersistedPeersIfExceeds(); - storage.queueUpForSave(new PeerList(new ArrayList<>(persistedPeers)), 2000); + peerList.setAll(persistedPeers); + persistenceManager.requestPersistence(); printReportedPeers(); } else { @@ -530,7 +523,8 @@ private void printNewReportedPeers(Set reportedPeers) { private boolean removePersistedPeer(Peer persistedPeer) { if (persistedPeers.contains(persistedPeer)) { persistedPeers.remove(persistedPeer); - storage.queueUpForSave(new PeerList(new ArrayList<>(persistedPeers)), 2000); + peerList.setAll(persistedPeers); + persistenceManager.requestPersistence(); return true; } else { return false; diff --git a/p2p/src/main/java/bisq/network/p2p/peers/peerexchange/PeerList.java b/p2p/src/main/java/bisq/network/p2p/peers/peerexchange/PeerList.java index b09c67df06f..d32a4952c03 100644 --- a/p2p/src/main/java/bisq/network/p2p/peers/peerexchange/PeerList.java +++ b/p2p/src/main/java/bisq/network/p2p/peers/peerexchange/PeerList.java @@ -22,6 +22,7 @@ import com.google.protobuf.Message; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.stream.Collectors; @@ -31,10 +32,13 @@ @EqualsAndHashCode public class PeerList implements PersistableEnvelope { @Getter - private final List list; + private final List list = new ArrayList<>(); + + public PeerList() { + } public PeerList(List list) { - this.list = list; + setAll(list); } public int size() { @@ -54,4 +58,9 @@ public static PeerList fromProto(protobuf.PeerList proto) { .map(Peer::fromProto) .collect(Collectors.toList()))); } + + public void setAll(Collection collection) { + this.list.clear(); + this.list.addAll(collection); + } } diff --git a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java index 6643c37ae7f..f412e5146a4 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java @@ -59,11 +59,11 @@ import bisq.common.crypto.CryptoException; import bisq.common.crypto.Hash; import bisq.common.crypto.Sig; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.network.NetworkEnvelope; import bisq.common.proto.network.NetworkPayload; import bisq.common.proto.persistable.PersistablePayload; import bisq.common.proto.persistable.PersistedDataHost; -import bisq.common.storage.Storage; import bisq.common.util.Hex; import bisq.common.util.Tuple2; import bisq.common.util.Utilities; @@ -131,7 +131,7 @@ public class P2PDataStorage implements MessageListener, ConnectionListener, Pers private final Set hashMapChangedListeners = new CopyOnWriteArraySet<>(); private Timer removeExpiredEntriesTimer; - private final Storage sequenceNumberMapStorage; + private final PersistenceManager persistenceManager; @VisibleForTesting final SequenceNumberMap sequenceNumberMap = new SequenceNumberMap(); @@ -153,29 +153,28 @@ public P2PDataStorage(NetworkNode networkNode, AppendOnlyDataStoreService appendOnlyDataStoreService, ProtectedDataStoreService protectedDataStoreService, ResourceDataStoreService resourceDataStoreService, - Storage sequenceNumberMapStorage, + PersistenceManager persistenceManager, Clock clock, @Named("MAX_SEQUENCE_NUMBER_MAP_SIZE_BEFORE_PURGE") int maxSequenceNumberBeforePurge) { this.broadcaster = broadcaster; this.appendOnlyDataStoreService = appendOnlyDataStoreService; this.protectedDataStoreService = protectedDataStoreService; this.resourceDataStoreService = resourceDataStoreService; + this.persistenceManager = persistenceManager; this.clock = clock; this.maxSequenceNumberMapSizeBeforePurge = maxSequenceNumberBeforePurge; - networkNode.addMessageListener(this); networkNode.addConnectionListener(this); - this.sequenceNumberMapStorage = sequenceNumberMapStorage; - sequenceNumberMapStorage.setNumMaxBackupFiles(5); + this.persistenceManager.initialize(sequenceNumberMap, PersistenceManager.Source.PRIVATE); } @Override public void readPersisted() { - SequenceNumberMap persistedSequenceNumberMap = sequenceNumberMapStorage.initAndGetPersisted(sequenceNumberMap, 300); - if (persistedSequenceNumberMap != null) - sequenceNumberMap.setMap(getPurgedSequenceNumberMap(persistedSequenceNumberMap.getMap())); + SequenceNumberMap persisted = persistenceManager.getPersisted(); + if (persisted != null) + sequenceNumberMap.setMap(getPurgedSequenceNumberMap(persisted.getMap())); } // This method is called at startup in a non-user thread. @@ -445,8 +444,10 @@ void removeExpiredEntries() { }); removeFromMapAndDataStore(toRemoveList); - if (sequenceNumberMap.size() > this.maxSequenceNumberMapSizeBeforePurge) + if (sequenceNumberMap.size() > this.maxSequenceNumberMapSizeBeforePurge) { sequenceNumberMap.setMap(getPurgedSequenceNumberMap(sequenceNumberMap.getMap())); + requestPersistence(); + } } public void onBootstrapComplete() { @@ -675,7 +676,7 @@ private boolean addProtectedStorageEntry(ProtectedStorageEntry protectedStorageE // Record the updated sequence number and persist it. Higher delay so we can batch more items. sequenceNumberMap.put(hashOfPayload, new MapValue(protectedStorageEntry.getSequenceNumber(), this.clock.millis())); - sequenceNumberMapStorage.queueUpForSave(SequenceNumberMap.clone(sequenceNumberMap), 2000); + requestPersistence(); // Optionally, broadcast the add/update depending on the calling environment if (allowBroadcast) @@ -729,7 +730,7 @@ public boolean refreshTTL(RefreshOfferMessage refreshTTLMessage, // Record the latest sequence number and persist it sequenceNumberMap.put(hashOfPayload, new MapValue(updatedEntry.getSequenceNumber(), this.clock.millis())); - sequenceNumberMapStorage.queueUpForSave(SequenceNumberMap.clone(sequenceNumberMap), 1000); + requestPersistence(); // Always broadcast refreshes broadcaster.broadcast(refreshTTLMessage, sender); @@ -765,7 +766,7 @@ public boolean remove(ProtectedStorageEntry protectedStorageEntry, // Record the latest sequence number and persist it sequenceNumberMap.put(hashOfPayload, new MapValue(protectedStorageEntry.getSequenceNumber(), this.clock.millis())); - sequenceNumberMapStorage.queueUpForSave(SequenceNumberMap.clone(sequenceNumberMap), 300); + requestPersistence(); // Update that we have seen this AddOncePayload so the next time it is seen it fails verification if (protectedStoragePayload instanceof AddOncePayload) @@ -879,7 +880,7 @@ private void removeFromMapAndDataStore( if (protectedStoragePayload instanceof PersistablePayload) { ProtectedStorageEntry previous = protectedDataStoreService.remove(hashOfPayload, protectedStorageEntry); if (previous == null) - log.error("We cannot remove the protectedStorageEntry from the persistedEntryMap as it does not exist."); + log.warn("We cannot remove the protectedStorageEntry from the persistedEntryMap as it does not exist."); } }); @@ -915,6 +916,10 @@ private boolean hasSequenceNrIncreased(int newSequenceNumber, ByteArray hashOfDa } } + private void requestPersistence() { + persistenceManager.requestPersistence(); + } + public static ByteArray get32ByteHashAsByteArray(NetworkPayload data) { return new ByteArray(P2PDataStorage.get32ByteHash(data)); } diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/AppendOnlyDataStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/AppendOnlyDataStoreService.java index 0a87288f53f..916523fcc60 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/AppendOnlyDataStoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/AppendOnlyDataStoreService.java @@ -20,8 +20,6 @@ import bisq.network.p2p.storage.P2PDataStorage; import bisq.network.p2p.storage.payload.PersistableNetworkPayload; -import bisq.common.proto.persistable.PersistableEnvelope; - import javax.inject.Inject; import java.util.ArrayList; @@ -38,7 +36,7 @@ @Slf4j public class AppendOnlyDataStoreService { @Getter - private final List> services = new ArrayList<>(); + private final List, PersistableNetworkPayload>> services = new ArrayList<>(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -49,7 +47,7 @@ public class AppendOnlyDataStoreService { public AppendOnlyDataStoreService() { } - public void addService(MapStoreService service) { + public void addService(MapStoreService, PersistableNetworkPayload> service) { services.add(service); } diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/HistoricalDataStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/HistoricalDataStoreService.java index 5f1346f20cf..3c92ca706fd 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/HistoricalDataStoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/HistoricalDataStoreService.java @@ -21,7 +21,7 @@ import bisq.network.p2p.storage.payload.PersistableNetworkPayload; import bisq.common.app.Version; -import bisq.common.storage.Storage; +import bisq.common.persistence.PersistenceManager; import com.google.common.collect.ImmutableMap; @@ -39,8 +39,8 @@ * request so the responding (seed)node can figure out if we miss any of the historical data. */ @Slf4j -public abstract class HistoricalDataStoreService extends MapStoreService { - private ImmutableMap storesByVersion; +public abstract class HistoricalDataStoreService> extends MapStoreService { + private ImmutableMap> storesByVersion; // Cache to avoid that we have to recreate the historical data at each request private ImmutableMap allHistoricalPayloads; @@ -49,8 +49,8 @@ public abstract class HistoricalDataStoreService storage) { - super(storageDir, storage); + public HistoricalDataStoreService(File storageDir, PersistenceManager persistenceManager) { + super(storageDir, persistenceManager); } @@ -126,7 +126,7 @@ protected void put(P2PDataStorage.ByteArray hash, PersistableNetworkPayload payl } getMapOfLiveData().put(hash, payload); - persist(); + requestPersistence(); } @Override @@ -139,7 +139,7 @@ protected PersistableNetworkPayload putIfAbsent(P2PDataStorage.ByteArray hash, P // So it will be always null. We still keep the return type as we override the method from MapStoreService which // follow the Map.putIfAbsent signature. getMapOfLiveData().put(hash, payload); - persist(); + requestPersistence(); return null; } @@ -152,7 +152,7 @@ protected void readFromResources(String postFix) { // Now we add our historical data stores. As they are immutable after created we use an ImmutableMap ImmutableMap.Builder allHistoricalPayloadsBuilder = ImmutableMap.builder(); - ImmutableMap.Builder storesByVersionBuilder = ImmutableMap.builder(); + ImmutableMap.Builder> storesByVersionBuilder = ImmutableMap.builder(); Version.HISTORY.forEach(version -> readHistoricalStoreFromResources(version, postFix, allHistoricalPayloadsBuilder, storesByVersionBuilder)); @@ -168,12 +168,12 @@ protected void readFromResources(String postFix) { private void readHistoricalStoreFromResources(String version, String postFix, ImmutableMap.Builder allHistoricalDataBuilder, - ImmutableMap.Builder storesByVersionBuilder) { + ImmutableMap.Builder> storesByVersionBuilder) { String fileName = getFileName() + "_" + version; boolean wasCreatedFromResources = makeFileFromResourceFile(fileName, postFix); // If resource file does not exist we return null. We do not create a new store as it would never get filled. - PersistableNetworkPayloadStore historicalStore = storage.getPersisted(fileName); + PersistableNetworkPayloadStore historicalStore = persistenceManager.getPersisted(fileName); if (historicalStore == null) { log.warn("Resource file with file name {} does not exits.", fileName); return; @@ -187,7 +187,8 @@ private void readHistoricalStoreFromResources(String version, } } - private void pruneStore(PersistableNetworkPayloadStore historicalStore, String version) { + private void pruneStore(PersistableNetworkPayloadStore historicalStore, + String version) { int preLive = getMapOfLiveData().size(); getMapOfLiveData().keySet().removeAll(historicalStore.getMap().keySet()); int postLive = getMapOfLiveData().size(); @@ -198,7 +199,7 @@ private void pruneStore(PersistableNetworkPayloadStore historicalStore, String v } else { log.info("No pruning from historical data store with version {} was applied", version); } - storage.queueUpForSave(store); + requestPersistence(); } private boolean anyMapContainsKey(P2PDataStorage.ByteArray hash) { diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/MapStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/MapStoreService.java index 67088799c4a..52f37bf44e5 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/MapStoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/MapStoreService.java @@ -19,9 +19,9 @@ import bisq.network.p2p.storage.P2PDataStorage; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.persistable.PersistableEnvelope; import bisq.common.proto.persistable.PersistablePayload; -import bisq.common.storage.Storage; import java.io.File; @@ -43,8 +43,8 @@ public abstract class MapStoreService storage) { - super(storageDir, storage); + public MapStoreService(File storageDir, PersistenceManager persistenceManager) { + super(storageDir, persistenceManager); } @@ -58,18 +58,18 @@ public MapStoreService(File storageDir, Storage storage) { void put(P2PDataStorage.ByteArray hash, R payload) { getMap().put(hash, payload); - persist(); + requestPersistence(); } R putIfAbsent(P2PDataStorage.ByteArray hash, R payload) { R previous = getMap().putIfAbsent(hash, payload); - persist(); + requestPersistence(); return previous; } R remove(P2PDataStorage.ByteArray hash) { - final R result = getMap().remove(hash); - persist(); + R result = getMap().remove(hash); + requestPersistence(); return result; } diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/PersistableNetworkPayloadStore.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/PersistableNetworkPayloadStore.java index a617faad083..802761a1884 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/PersistableNetworkPayloadStore.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/PersistableNetworkPayloadStore.java @@ -20,18 +20,32 @@ import bisq.network.p2p.storage.P2PDataStorage; import bisq.network.p2p.storage.payload.PersistableNetworkPayload; -import bisq.common.proto.persistable.ThreadedPersistableEnvelope; +import bisq.common.proto.persistable.PersistableEnvelope; +import java.util.Collection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + /** - * Base class for store implementations using a map with a PersistableNetworkPayload - * as the type of the map value. + * Store for PersistableNetworkPayload map entries with it's data hash as key. */ -public abstract class PersistableNetworkPayloadStore implements ThreadedPersistableEnvelope { +@Slf4j +public abstract class PersistableNetworkPayloadStore implements PersistableEnvelope { @Getter - public Map map = new ConcurrentHashMap<>(); + protected final Map map = new ConcurrentHashMap<>(); + + protected PersistableNetworkPayloadStore() { + } + + protected PersistableNetworkPayloadStore(Collection collection) { + collection.forEach(item -> map.put(new P2PDataStorage.ByteArray(item.getHash()), item)); + } + + public boolean containsKey(P2PDataStorage.ByteArray hash) { + return map.containsKey(hash); + } } diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/ProtectedDataStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/ProtectedDataStoreService.java index c671ad28658..07f07ad7323 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/ProtectedDataStoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/ProtectedDataStoreService.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; @@ -36,7 +37,7 @@ */ @Slf4j public class ProtectedDataStoreService { - private List> services = new ArrayList<>(); + private final List> services = new ArrayList<>(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -70,12 +71,10 @@ public void put(P2PDataStorage.ByteArray hash, ProtectedStorageEntry entry) { } public ProtectedStorageEntry remove(P2PDataStorage.ByteArray hash, ProtectedStorageEntry protectedStorageEntry) { - final ProtectedStorageEntry[] result = new ProtectedStorageEntry[1]; + AtomicReference result = new AtomicReference<>(); services.stream() .filter(service -> service.canHandle(protectedStorageEntry)) - .forEach(service -> { - result[0] = service.remove(hash); - }); - return result[0]; + .forEach(service -> result.set(service.remove(hash))); + return result.get(); } } diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/ResourceDataStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/ResourceDataStoreService.java index ae444a1ec25..b6002af580e 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/ResourceDataStoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/ResourceDataStoreService.java @@ -31,7 +31,7 @@ */ @Slf4j public class ResourceDataStoreService { - private List> services; + private final List> services; @Inject public ResourceDataStoreService() { diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SequenceNumberMap.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SequenceNumberMap.java index cbb4f77398a..f8a369d37af 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SequenceNumberMap.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SequenceNumberMap.java @@ -19,7 +19,7 @@ import bisq.network.p2p.storage.P2PDataStorage; -import bisq.common.proto.persistable.ThreadedPersistableEnvelope; +import bisq.common.proto.persistable.PersistableEnvelope; import java.util.HashMap; import java.util.Map; @@ -34,7 +34,7 @@ * in protobuffer the map construct can't be anything, so the straightforward mapping was not possible. * Hence this Persistable class. */ -public class SequenceNumberMap implements ThreadedPersistableEnvelope { +public class SequenceNumberMap implements PersistableEnvelope { @Getter @Setter private Map map = new ConcurrentHashMap<>(); @@ -42,10 +42,6 @@ public class SequenceNumberMap implements ThreadedPersistableEnvelope { public SequenceNumberMap() { } - public static SequenceNumberMap clone(SequenceNumberMap map) { - return new SequenceNumberMap(map.getMap()); - } - /////////////////////////////////////////////////////////////////////////////////////////// // PROTO BUFFER diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/StoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/StoreService.java index 3cbbb036538..adc1688593e 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/StoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/StoreService.java @@ -17,15 +17,14 @@ package bisq.network.p2p.storage.persistence; +import bisq.common.file.FileUtil; +import bisq.common.file.ResourceNotFoundException; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.persistable.PersistableEnvelope; -import bisq.common.storage.FileUtil; -import bisq.common.storage.ResourceNotFoundException; -import bisq.common.storage.Storage; import java.nio.file.Paths; import java.io.File; -import java.io.IOException; import lombok.extern.slf4j.Slf4j; @@ -43,7 +42,7 @@ @Slf4j public abstract class StoreService { - protected final Storage storage; + protected final PersistenceManager persistenceManager; protected final String absolutePathOfStorageDir; protected T store; @@ -52,12 +51,9 @@ public abstract class StoreService { // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - public StoreService(File storageDir, - Storage storage) { - this.storage = storage; + public StoreService(File storageDir, PersistenceManager persistenceManager) { + this.persistenceManager = persistenceManager; absolutePathOfStorageDir = storageDir.getAbsolutePath(); - - storage.setNumMaxBackupFiles(1); } @@ -65,8 +61,8 @@ public StoreService(File storageDir, // API /////////////////////////////////////////////////////////////////////////////////////////// - protected void persist() { - storage.queueUpForSave(store, 200); + protected void requestPersistence() { + persistenceManager.requestPersistence(); } protected T getStore() { @@ -86,11 +82,6 @@ protected void readFromResources(String postFix) { try { readStore(); } catch (Throwable t) { - try { - storage.removeAndBackupFile(fileName); - } catch (IOException e) { - log.error(e.toString()); - } makeFileFromResourceFile(fileName, postFix); readStore(); } @@ -122,11 +113,16 @@ protected boolean makeFileFromResourceFile(String fileName, String postFix) { } protected T getStore(String fileName) { - T store = storage.initAndGetPersistedWithFileName(fileName, 100); - if (store != null) { - log.info("{}: size of {}: {} MB", this.getClass().getSimpleName(), - storage.getClass().getSimpleName(), - store.toProtoMessage().toByteArray().length / 1_000_000D); + T store; + T persisted = persistenceManager.getPersisted(fileName); + if (persisted != null) { + store = persisted; + + int length = store.toProtoMessage().toByteArray().length; + double size = length > 1_000_000D ? length / 1_000_000D : length / 1_000D; + String unit = length > 1_000_000D ? "MB" : "KB"; + log.info("{}: size of {}: {} {}", this.getClass().getSimpleName(), + persisted.getClass().getSimpleName(), size, unit); } else { store = createStore(); } @@ -135,7 +131,10 @@ protected T getStore(String fileName) { protected void readStore() { store = getStore(getFileName()); + initializePersistenceManager(); } + protected abstract void initializePersistenceManager(); + protected abstract T createStore(); } diff --git a/p2p/src/test/java/bisq/network/crypto/EncryptionServiceTests.java b/p2p/src/test/java/bisq/network/crypto/EncryptionServiceTests.java index 000f4c0d375..c6571e1df20 100644 --- a/p2p/src/test/java/bisq/network/crypto/EncryptionServiceTests.java +++ b/p2p/src/test/java/bisq/network/crypto/EncryptionServiceTests.java @@ -21,8 +21,8 @@ import bisq.common.crypto.KeyRing; import bisq.common.crypto.KeyStorage; import bisq.common.crypto.PubKeyRing; +import bisq.common.file.FileUtil; import bisq.common.proto.network.NetworkEnvelope; -import bisq.common.storage.FileUtil; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; diff --git a/p2p/src/test/java/bisq/network/p2p/MockNode.java b/p2p/src/test/java/bisq/network/p2p/MockNode.java index b2a0c2afd08..9520cae156a 100644 --- a/p2p/src/test/java/bisq/network/p2p/MockNode.java +++ b/p2p/src/test/java/bisq/network/p2p/MockNode.java @@ -17,8 +17,6 @@ package bisq.network.p2p; -import bisq.network.p2p.seed.SeedNodeRepository; - import bisq.network.p2p.network.Connection; import bisq.network.p2p.network.InboundConnection; import bisq.network.p2p.network.NetworkNode; @@ -26,11 +24,12 @@ import bisq.network.p2p.network.Statistic; import bisq.network.p2p.peers.PeerManager; import bisq.network.p2p.peers.peerexchange.PeerList; +import bisq.network.p2p.seed.SeedNodeRepository; import bisq.common.ClockWatcher; +import bisq.common.file.CorruptedStorageFileHandler; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.persistable.PersistenceProtoResolver; -import bisq.common.storage.CorruptedDatabaseFilesHandler; -import bisq.common.storage.Storage; import java.nio.file.Files; @@ -60,8 +59,8 @@ public MockNode(int maxConnections) throws IOException { this.maxConnections = maxConnections; networkNode = mock(NetworkNode.class); File storageDir = Files.createTempDirectory("storage").toFile(); - Storage storage = new Storage<>(storageDir, mock(PersistenceProtoResolver.class), mock(CorruptedDatabaseFilesHandler.class)); - peerManager = new PeerManager(networkNode, mock(SeedNodeRepository.class), new ClockWatcher(), maxConnections, storage); + PersistenceManager persistenceManager = new PersistenceManager<>(storageDir, mock(PersistenceProtoResolver.class), mock(CorruptedStorageFileHandler.class)); + peerManager = new PeerManager(networkNode, mock(SeedNodeRepository.class), new ClockWatcher(), maxConnections, persistenceManager); connections = new HashSet<>(); when(networkNode.getAllConnections()).thenReturn(connections); } diff --git a/p2p/src/test/java/bisq/network/p2p/storage/TestState.java b/p2p/src/test/java/bisq/network/p2p/storage/TestState.java index e71246a0310..b04491ed403 100644 --- a/p2p/src/test/java/bisq/network/p2p/storage/TestState.java +++ b/p2p/src/test/java/bisq/network/p2p/storage/TestState.java @@ -39,8 +39,8 @@ import bisq.network.p2p.storage.persistence.SequenceNumberMap; import bisq.common.crypto.Sig; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.persistable.PersistablePayload; -import bisq.common.storage.Storage; import java.security.PublicKey; @@ -69,13 +69,13 @@ public class TestState { final AppendOnlyDataStoreListener appendOnlyDataStoreListener; private final HashMapChangedListener hashMapChangedListener; - private final Storage mockSeqNrStorage; + private final PersistenceManager mockSeqNrPersistenceManager; private final ProtectedDataStoreService protectedDataStoreService; final ClockFake clockFake; TestState() { this.mockBroadcaster = mock(Broadcaster.class); - this.mockSeqNrStorage = mock(Storage.class); + this.mockSeqNrPersistenceManager = mock(PersistenceManager.class); this.clockFake = new ClockFake(); this.protectedDataStoreService = new ProtectedDataStoreService(); @@ -83,7 +83,9 @@ public class TestState { this.mockBroadcaster, new AppendOnlyDataStoreServiceFake(), this.protectedDataStoreService, mock(ResourceDataStoreService.class), - this.mockSeqNrStorage, this.clockFake, MAX_SEQUENCE_NUMBER_MAP_SIZE_BEFORE_PURGE); + this.mockSeqNrPersistenceManager, + this.clockFake, + MAX_SEQUENCE_NUMBER_MAP_SIZE_BEFORE_PURGE); this.appendOnlyDataStoreListener = mock(AppendOnlyDataStoreListener.class); this.hashMapChangedListener = mock(HashMapChangedListener.class); @@ -92,10 +94,13 @@ this.protectedDataStoreService, mock(ResourceDataStoreService.class), this.mockedStorage = createP2PDataStorageForTest( this.mockBroadcaster, this.protectedDataStoreService, - this.mockSeqNrStorage, + this.mockSeqNrPersistenceManager, this.clockFake, this.hashMapChangedListener, this.appendOnlyDataStoreListener); + + when(this.mockSeqNrPersistenceManager.getPersisted()) + .thenReturn(this.mockedStorage.sequenceNumberMap); } @@ -105,22 +110,22 @@ this.protectedDataStoreService, mock(ResourceDataStoreService.class), * not running the entire storage code paths. */ void simulateRestart() { - when(this.mockSeqNrStorage.initAndGetPersisted(any(SequenceNumberMap.class), anyLong())) - .thenReturn(this.mockedStorage.sequenceNumberMap); - this.mockedStorage = createP2PDataStorageForTest( this.mockBroadcaster, this.protectedDataStoreService, - this.mockSeqNrStorage, + this.mockSeqNrPersistenceManager, this.clockFake, this.hashMapChangedListener, this.appendOnlyDataStoreListener); + + when(this.mockSeqNrPersistenceManager.getPersisted()) + .thenReturn(this.mockedStorage.sequenceNumberMap); } private static P2PDataStorage createP2PDataStorageForTest( Broadcaster broadcaster, ProtectedDataStoreService protectedDataStoreService, - Storage sequenceNrMapStorage, + PersistenceManager sequenceNrMapPersistenceManager, ClockFake clock, HashMapChangedListener hashMapChangedListener, AppendOnlyDataStoreListener appendOnlyDataStoreListener) { @@ -129,7 +134,7 @@ private static P2PDataStorage createP2PDataStorageForTest( broadcaster, new AppendOnlyDataStoreServiceFake(), protectedDataStoreService, mock(ResourceDataStoreService.class), - sequenceNrMapStorage, clock, MAX_SEQUENCE_NUMBER_MAP_SIZE_BEFORE_PURGE); + sequenceNrMapPersistenceManager, clock, MAX_SEQUENCE_NUMBER_MAP_SIZE_BEFORE_PURGE); // Currently TestState only supports reading ProtectedStorageEntries off disk. p2PDataStorage.readFromResources("unused"); @@ -145,7 +150,6 @@ private void resetState() { reset(this.mockBroadcaster); reset(this.appendOnlyDataStoreListener); reset(this.hashMapChangedListener); - reset(this.mockSeqNrStorage); } void incrementClock() { @@ -160,11 +164,7 @@ public static NodeAddress getTestNodeAddress() { * Common test helpers that verify the correct events were signaled based on the test expectation and before/after states. */ private void verifySequenceNumberMapWriteContains(P2PDataStorage.ByteArray payloadHash, int sequenceNumber) { - final ArgumentCaptor captor = ArgumentCaptor.forClass(SequenceNumberMap.class); - verify(this.mockSeqNrStorage).queueUpForSave(captor.capture(), anyLong()); - - SequenceNumberMap savedMap = captor.getValue(); - Assert.assertEquals(sequenceNumber, savedMap.get(payloadHash).sequenceNr); + Assert.assertEquals(sequenceNumber, mockSeqNrPersistenceManager.getPersisted().get(payloadHash).sequenceNr); } void verifyPersistableAdd(SavedTestState beforeState, @@ -231,8 +231,6 @@ void verifyProtectedStorageAdd(SavedTestState beforeState, if (expectedSequenceNrMapWrite) { this.verifySequenceNumberMapWriteContains(P2PDataStorage.get32ByteHashAsByteArray(protectedStorageEntry.getProtectedStoragePayload()), protectedStorageEntry.getSequenceNumber()); - } else { - verify(this.mockSeqNrStorage, never()).queueUpForSave(any(SequenceNumberMap.class), anyLong()); } } @@ -272,9 +270,6 @@ void verifyProtectedStorageRemove(SavedTestState beforeState, verify(this.hashMapChangedListener, never()).onRemoved(any()); } - if (!expectedSeqNrWrite) - verify(this.mockSeqNrStorage, never()).queueUpForSave(any(SequenceNumberMap.class), anyLong()); - if (!expectedBroadcast) verify(this.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), nullable(NodeAddress.class)); @@ -338,7 +333,6 @@ void verifyRefreshTTL(SavedTestState beforeState, } verify(this.mockBroadcaster, never()).broadcast(any(BroadcastMessage.class), nullable(NodeAddress.class)); - verify(this.mockSeqNrStorage, never()).queueUpForSave(any(SequenceNumberMap.class), anyLong()); } } diff --git a/p2p/src/test/java/bisq/network/p2p/storage/mocks/MapStoreServiceFake.java b/p2p/src/test/java/bisq/network/p2p/storage/mocks/MapStoreServiceFake.java index 9f282497d8a..0f6f661fda0 100644 --- a/p2p/src/test/java/bisq/network/p2p/storage/mocks/MapStoreServiceFake.java +++ b/p2p/src/test/java/bisq/network/p2p/storage/mocks/MapStoreServiceFake.java @@ -21,9 +21,9 @@ import bisq.network.p2p.storage.payload.ProtectedStorageEntry; import bisq.network.p2p.storage.persistence.MapStoreService; +import bisq.common.persistence.PersistenceManager; import bisq.common.proto.persistable.PersistableEnvelope; import bisq.common.proto.persistable.PersistablePayload; -import bisq.common.storage.Storage; import java.io.File; @@ -45,7 +45,7 @@ public class MapStoreServiceFake extends MapStoreService { private final Map map; public MapStoreServiceFake() { - super(mock(File.class), mock(Storage.class)); + super(mock(File.class), mock(PersistenceManager.class)); this.map = new HashMap<>(); } @@ -67,4 +67,8 @@ public boolean canHandle(PersistablePayload payload) { protected void readFromResources(String postFix) { // do nothing. This Fake only supports in-memory storage. } + + @Override + protected void initializePersistenceManager() { + } } diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 2dd32a983c5..e16746c09be 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -77,7 +77,7 @@ message NetworkEnvelope { PeerPublishedDelayedPayoutTxMessage peer_published_delayed_payout_tx_message = 49; RefreshTradeStateRequest refresh_trade_state_request = 50 [deprecated = true]; - TraderSignedWitnessMessage trader_signed_witness_message = 51; + TraderSignedWitnessMessage trader_signed_witness_message = 51 [deprecated = true]; } } @@ -1401,7 +1401,7 @@ message Trade { NodeAddress refund_agent_node_address = 33; PubKeyRing refund_agent_pub_key_ring = 34; RefundResultState refund_result_state = 35; - int64 last_refresh_request_date = 36; + int64 last_refresh_request_date = 36 [deprecated = true]; string counter_currency_extra_data = 37; string asset_tx_proof_result = 38; // name of AssetTxProofResult enum } diff --git a/seednode/src/main/resources/logback.xml b/seednode/src/main/resources/logback.xml index 0d084c25ca9..47fdc20cfa1 100644 --- a/seednode/src/main/resources/logback.xml +++ b/seednode/src/main/resources/logback.xml @@ -10,8 +10,6 @@ - - diff --git a/statsnode/src/main/resources/logback.xml b/statsnode/src/main/resources/logback.xml index a4685e4d705..4a14ad263d5 100644 --- a/statsnode/src/main/resources/logback.xml +++ b/statsnode/src/main/resources/logback.xml @@ -10,9 +10,6 @@ - - -