diff --git a/build-logic/commons/src/main/groovy/bisq.java-conventions.gradle b/build-logic/commons/src/main/groovy/bisq.java-conventions.gradle
index af2524a9aa..db97cf74f2 100644
--- a/build-logic/commons/src/main/groovy/bisq.java-conventions.gradle
+++ b/build-logic/commons/src/main/groovy/bisq.java-conventions.gradle
@@ -29,7 +29,8 @@ dependencies {
testImplementation libs.hamcrest
testImplementation libs.junit.jupiter.api
testImplementation libs.junit.jupiter.params
- testImplementation libs.mockito
+ testImplementation libs.mockito.core
+ testImplementation libs.mockito.junit.jupiter
testRuntimeOnly libs.junit.jupiter.engine
}
diff --git a/common/src/main/java/bisq/common/config/Config.java b/common/src/main/java/bisq/common/config/Config.java
index d05be693f9..c1153f0467 100644
--- a/common/src/main/java/bisq/common/config/Config.java
+++ b/common/src/main/java/bisq/common/config/Config.java
@@ -28,6 +28,8 @@ import java.util.Optional;
import ch.qos.logback.classic.Level;
+import lombok.Getter;
+
import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.String.format;
import static java.util.stream.Collectors.toList;
@@ -157,7 +159,8 @@ public class Config {
// Options supported only at the command-line interface (cli)
public final boolean helpRequested;
- public final File configFile;
+ @Getter
+ private final File configFile;
// Options supported on cmd line and in the config file
public final String appName;
diff --git a/common/src/test/java/bisq/common/config/ConfigTests.java b/common/src/test/java/bisq/common/config/ConfigTests.java
index bd6b7f9c59..ab578d7018 100644
--- a/common/src/test/java/bisq/common/config/ConfigTests.java
+++ b/common/src/test/java/bisq/common/config/ConfigTests.java
@@ -53,7 +53,7 @@ public class ConfigTests {
assertThat(config.appName, equalTo(config.defaultAppName));
assertThat(config.userDataDir, equalTo(config.defaultUserDataDir));
assertThat(config.appDataDir, equalTo(config.defaultAppDataDir));
- assertThat(config.configFile, equalTo(config.defaultConfigFile));
+ assertThat(config.getConfigFile(), equalTo(config.defaultConfigFile));
}
@Test
@@ -62,7 +62,7 @@ public class ConfigTests {
assertThat(config.appName, equalTo("My-Bisq"));
assertThat(config.userDataDir, equalTo(config.defaultUserDataDir));
assertThat(config.appDataDir, equalTo(new File(config.userDataDir, "My-Bisq")));
- assertThat(config.configFile, equalTo(new File(config.appDataDir, DEFAULT_CONFIG_FILE_NAME)));
+ assertThat(config.getConfigFile(), equalTo(new File(config.appDataDir, DEFAULT_CONFIG_FILE_NAME)));
}
@Test
@@ -72,7 +72,7 @@ public class ConfigTests {
assertThat(config.appName, equalTo(config.defaultAppName));
assertThat(config.userDataDir, equalTo(config.defaultUserDataDir));
assertThat(config.appDataDir, equalTo(appDataDir));
- assertThat(config.configFile, equalTo(new File(config.appDataDir, DEFAULT_CONFIG_FILE_NAME)));
+ assertThat(config.getConfigFile(), equalTo(new File(config.appDataDir, DEFAULT_CONFIG_FILE_NAME)));
}
@Test
@@ -82,7 +82,7 @@ public class ConfigTests {
assertThat(config.appName, equalTo(config.defaultAppName));
assertThat(config.userDataDir, equalTo(userDataDir));
assertThat(config.appDataDir, equalTo(new File(userDataDir, config.defaultAppName)));
- assertThat(config.configFile, equalTo(new File(config.appDataDir, DEFAULT_CONFIG_FILE_NAME)));
+ assertThat(config.getConfigFile(), equalTo(new File(config.appDataDir, DEFAULT_CONFIG_FILE_NAME)));
}
@Test
@@ -92,7 +92,7 @@ public class ConfigTests {
assertThat(config.appName, equalTo("My-Bisq"));
assertThat(config.userDataDir, equalTo(config.defaultUserDataDir));
assertThat(config.appDataDir, equalTo(appDataDir));
- assertThat(config.configFile, equalTo(new File(config.appDataDir, DEFAULT_CONFIG_FILE_NAME)));
+ assertThat(config.getConfigFile(), equalTo(new File(config.appDataDir, DEFAULT_CONFIG_FILE_NAME)));
}
@Test
@@ -166,7 +166,7 @@ public class ConfigTests {
public void whenConfigFileOptionIsSetToExistingFile_thenConfigFilePropertyReflectsItsValue() throws IOException {
File configFile = File.createTempFile("bisq", "properties");
Config config = configWithOpts(opt(CONFIG_FILE, configFile.getAbsolutePath()));
- assertThat(config.configFile, equalTo(configFile));
+ assertThat(config.getConfigFile(), equalTo(configFile));
}
@Test
@@ -175,7 +175,7 @@ public class ConfigTests {
File appDataDir = configFile.getParentFile();
String relativeConfigFilePath = configFile.getName();
Config config = configWithOpts(opt(APP_DATA_DIR, appDataDir), opt(CONFIG_FILE, relativeConfigFilePath));
- assertThat(config.configFile, equalTo(configFile));
+ assertThat(config.getConfigFile(), equalTo(configFile));
}
@Test
@@ -188,7 +188,7 @@ public class ConfigTests {
assertThat(config.appName, equalTo("My-Bisq"));
assertThat(config.userDataDir, equalTo(config.defaultUserDataDir));
assertThat(config.appDataDir, equalTo(new File(config.userDataDir, config.appName)));
- assertThat(config.configFile, equalTo(configFile));
+ assertThat(config.getConfigFile(), equalTo(configFile));
}
@Test
diff --git a/core/src/main/java/bisq/core/filter/FilterManager.java b/core/src/main/java/bisq/core/filter/FilterManager.java
index 0fc5c7a950..68ddab6740 100644
--- a/core/src/main/java/bisq/core/filter/FilterManager.java
+++ b/core/src/main/java/bisq/core/filter/FilterManager.java
@@ -128,7 +128,7 @@ public class FilterManager {
this.keyRing = keyRing;
this.user = user;
this.preferences = preferences;
- this.configFileEditor = new ConfigFileEditor(config.configFile);
+ this.configFileEditor = new ConfigFileEditor(config.getConfigFile());
this.providersRepository = providersRepository;
this.ignoreDevMsg = ignoreDevMsg;
diff --git a/core/src/test/java/bisq/core/filter/FilterManagerAddFilterToNetworkTests.java b/core/src/test/java/bisq/core/filter/FilterManagerAddFilterToNetworkTests.java
new file mode 100644
index 0000000000..ef7f14788a
--- /dev/null
+++ b/core/src/test/java/bisq/core/filter/FilterManagerAddFilterToNetworkTests.java
@@ -0,0 +1,244 @@
+/*
+ * 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.core.filter;
+
+import bisq.core.provider.ProvidersRepository;
+import bisq.core.user.Preferences;
+import bisq.core.user.User;
+
+import bisq.network.p2p.P2PService;
+import bisq.network.p2p.network.BanFilter;
+import bisq.network.p2p.storage.P2PDataStorage;
+import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
+
+import bisq.common.app.DevEnv;
+import bisq.common.config.Config;
+import bisq.common.crypto.KeyRing;
+import bisq.common.crypto.Sig;
+
+import org.bitcoinj.core.ECKey;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PublicKey;
+import java.security.Security;
+
+import java.nio.file.Path;
+
+import java.io.File;
+
+import java.math.BigInteger;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.bitcoinj.core.Utils.HEX;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+@ExtendWith(MockitoExtension.class)
+public class FilterManagerAddFilterToNetworkTests {
+ static {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+
+ private Map p2pStorageMap;
+ private FilterManager filterManager;
+
+ private final PublicKey ownerPublicKey;
+ private final String privilegedDevPubKeyHex;
+ private final ECKey privilegedDevEcKey;
+
+ public FilterManagerAddFilterToNetworkTests() throws NoSuchAlgorithmException, NoSuchProviderException {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(Sig.KEY_ALGO, "BC");
+ KeyPair ownerKeyPair = keyPairGenerator.generateKeyPair();
+ ownerPublicKey = ownerKeyPair.getPublic();
+
+ privilegedDevEcKey = ECKey.fromPrivate(new BigInteger(1, HEX.decode(DevEnv.DEV_PRIVILEGE_PRIV_KEY)));
+ privilegedDevPubKeyHex = HEX.encode(privilegedDevEcKey.getPubKey());
+ }
+
+ @BeforeEach
+ void beforeEach(@TempDir Path tmpDir, @Mock P2PService p2PService, @Mock P2PDataStorage p2pDataStorage) {
+ doReturn(p2pDataStorage).when(p2PService).getP2PDataStorage();
+
+ p2pStorageMap = new HashMap<>();
+ doReturn(p2pStorageMap).when(p2pDataStorage).getMap();
+
+ Config config = mock(Config.class);
+ File configFile = tmpDir.resolve("configFile").toFile();
+ doReturn(configFile).when(config).getConfigFile();
+
+ filterManager = new FilterManager(
+ p2PService,
+ mock(KeyRing.class),
+ mock(User.class),
+ mock(Preferences.class),
+ config,
+ mock(ProvidersRepository.class),
+ mock(BanFilter.class),
+ false,
+ true
+ );
+ }
+
+ @Test
+ void addFilterWithInvalidPublicKey() {
+ // There should exist no filter before we add our filter
+ assertNull(filterManager.getFilter());
+
+ Filter filter = TestFilter.createFilter(ownerPublicKey, "invalidPubKeyAsHex");
+ p2pStorageMap.put(
+ new P2PDataStorage.ByteArray(new byte[100]),
+ TestFilter.createProtectedStorageEntryForFilter(filter)
+ );
+
+ filterManager.onAllServicesInitialized();
+
+ // FilterManager didn't add our filter
+ assertNull(filterManager.getFilter());
+ }
+
+ @Test
+ void addFilterWithInvalidSignature() {
+ // No filter before adding our filter
+ assertNull(filterManager.getFilter());
+
+ Filter filter = TestFilter.createFilter(ownerPublicKey, privilegedDevPubKeyHex);
+ p2pStorageMap.put(
+ new P2PDataStorage.ByteArray(new byte[100]),
+ TestFilter.createProtectedStorageEntryForFilter(filter)
+ );
+
+ filterManager.onAllServicesInitialized();
+
+ // FilterManager didn't add our filter
+ assertNull(filterManager.getFilter());
+ }
+
+ @Test
+ void publishValidFilter() {
+ // No filter before adding our filter
+ assertNull(filterManager.getFilter());
+
+ Filter filterWithSig = TestFilter.createSignedFilter(ownerPublicKey, privilegedDevEcKey);
+ p2pStorageMap.put(
+ new P2PDataStorage.ByteArray(new byte[100]),
+ TestFilter.createProtectedStorageEntryForFilter(filterWithSig)
+ );
+
+ filterManager.onAllServicesInitialized();
+
+ // Our filter got set
+ Filter currentFilter = filterManager.getFilter();
+ assertNotNull(currentFilter);
+ assertEquals(filterWithSig, currentFilter);
+ }
+
+ @Test
+ void addTooOldFilter() {
+ // No filter before adding our filter
+ assertNull(filterManager.getFilter());
+
+ long creationTime = System.currentTimeMillis();
+ Filter firstFilterWithSig = TestFilter.createSignedFilter(ownerPublicKey, privilegedDevEcKey, creationTime);
+ Filter secondFilterWithSig = TestFilter.createSignedFilter(ownerPublicKey, privilegedDevEcKey,
+ creationTime + 100);
+
+ assertNotEquals(firstFilterWithSig, secondFilterWithSig);
+
+ p2pStorageMap.put(
+ new P2PDataStorage.ByteArray(new byte[100]),
+ TestFilter.createProtectedStorageEntryForFilter(secondFilterWithSig)
+ );
+
+ filterManager.onAllServicesInitialized();
+
+ // Our filter got set
+ Filter currentFilter = filterManager.getFilter();
+ assertNotNull(currentFilter);
+ assertEquals(secondFilterWithSig, currentFilter);
+
+ p2pStorageMap.clear();
+ p2pStorageMap.put(
+ new P2PDataStorage.ByteArray(new byte[100]),
+ TestFilter.createProtectedStorageEntryForFilter(firstFilterWithSig)
+ );
+
+ filterManager.onAllServicesInitialized();
+
+ // Our filter got set
+ currentFilter = filterManager.getFilter();
+ assertNotNull(currentFilter);
+ assertEquals(secondFilterWithSig, currentFilter);
+ }
+
+ @Test
+ void addNewerFilter() {
+ // No filter before adding our filter
+ assertNull(filterManager.getFilter());
+
+ long creationTime = System.currentTimeMillis();
+ Filter firstFilterWithSig = TestFilter.createSignedFilter(ownerPublicKey, privilegedDevEcKey, creationTime);
+ Filter secondFilterWithSig = TestFilter.createSignedFilter(ownerPublicKey, privilegedDevEcKey,
+ creationTime + 100);
+
+ assertNotEquals(firstFilterWithSig, secondFilterWithSig);
+
+ p2pStorageMap.put(
+ new P2PDataStorage.ByteArray(new byte[100]),
+ TestFilter.createProtectedStorageEntryForFilter(firstFilterWithSig)
+ );
+
+ filterManager.onAllServicesInitialized();
+
+ // Our filter got set
+ Filter currentFilter = filterManager.getFilter();
+ assertNotNull(currentFilter);
+ assertEquals(firstFilterWithSig, currentFilter);
+
+ p2pStorageMap.clear();
+ p2pStorageMap.put(
+ new P2PDataStorage.ByteArray(new byte[100]),
+ TestFilter.createProtectedStorageEntryForFilter(secondFilterWithSig)
+ );
+
+ filterManager.onAllServicesInitialized();
+
+ // Our filter got set
+ currentFilter = filterManager.getFilter();
+ assertNotNull(currentFilter);
+ assertEquals(secondFilterWithSig, currentFilter);
+ }
+}
diff --git a/core/src/test/java/bisq/core/filter/TestFilter.java b/core/src/test/java/bisq/core/filter/TestFilter.java
new file mode 100644
index 0000000000..e7649acba5
--- /dev/null
+++ b/core/src/test/java/bisq/core/filter/TestFilter.java
@@ -0,0 +1,113 @@
+/*
+ * 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.core.filter;
+
+import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
+
+import org.bitcoinj.core.ECKey;
+import org.bitcoinj.core.Sha256Hash;
+
+import org.bouncycastle.util.encoders.Base64;
+
+import java.security.PublicKey;
+
+import java.time.Clock;
+
+import java.nio.charset.StandardCharsets;
+
+import java.util.Collections;
+
+import static org.bitcoinj.core.Utils.HEX;
+
+public class TestFilter {
+
+ public static ProtectedStorageEntry createProtectedStorageEntryForFilter(Filter filter) {
+ return new ProtectedStorageEntry(
+ filter,
+ filter.getOwnerPubKey(),
+ 1000,
+ new byte[100],
+ Clock.systemDefaultZone()
+ );
+ }
+
+ public static Filter createSignedFilter(PublicKey ownerPublicKey, ECKey signerKey) {
+ return createSignedFilter(ownerPublicKey, signerKey, System.currentTimeMillis());
+ }
+
+ public static Filter createSignedFilter(PublicKey ownerPublicKey, ECKey signerKey, long creationDate) {
+ Filter unsignedFilter = createFilter(ownerPublicKey, HEX.encode(signerKey.getPubKey()), creationDate);
+ return signFilter(unsignedFilter, signerKey);
+ }
+
+ public static Filter createFilter(PublicKey ownerPublicKey, String signerPubKeyAsHex) {
+ return createFilter(ownerPublicKey, signerPubKeyAsHex, System.currentTimeMillis());
+ }
+
+ public static Filter createFilter(PublicKey ownerPublicKey, String signerPubKeyAsHex, long creationDate) {
+ return new Filter(
+ Collections.emptyList(),
+ Collections.emptyList(),
+ Collections.emptyList(),
+ Collections.emptyList(),
+ Collections.emptyList(),
+ Collections.emptyList(),
+ Collections.emptyList(),
+ Collections.emptyList(),
+ false,
+ Collections.emptyList(),
+ false,
+ "",
+ "",
+ Collections.emptyList(),
+ Collections.emptyList(),
+ Collections.emptyList(),
+ Collections.emptyList(),
+ ownerPublicKey.getEncoded(),
+ creationDate,
+ null,
+ null,
+ signerPubKeyAsHex,
+ Collections.emptyList(),
+ false,
+ Collections.emptyList(),
+ Collections.emptySet(),
+ false,
+ false,
+ false,
+ 1,
+ Collections.emptyList(),
+ 1,
+ 1,
+ 1,
+ 1,
+ Collections.emptyList()
+ );
+ }
+
+ private static Filter signFilter(Filter unsignedFilter, ECKey signerKey) {
+ byte[] filterData = unsignedFilter.toProtoMessage().toByteArray();
+ Sha256Hash hash = Sha256Hash.of(filterData);
+
+ ECKey.ECDSASignature ecdsaSignature = signerKey.sign(hash);
+ byte[] encodeToDER = ecdsaSignature.encodeToDER();
+
+ String signatureAsBase64 = new String(Base64.encode(encodeToDER), StandardCharsets.UTF_8);
+ return Filter.cloneWithSig(unsignedFilter, signatureAsBase64);
+ }
+}
diff --git a/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.java b/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.java
index 80c1a710d0..19071fb841 100644
--- a/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.java
+++ b/desktop/src/main/java/bisq/desktop/main/settings/network/NetworkSettingsView.java
@@ -158,7 +158,7 @@ public class NetworkSettingsView extends ActivatableView {
this.localBitcoinNode = localBitcoinNode;
this.torNetworkSettingsWindow = torNetworkSettingsWindow;
this.clockWatcher = clockWatcher;
- this.configFileEditor = new ConfigFileEditor(config.configFile);
+ this.configFileEditor = new ConfigFileEditor(config.getConfigFile());
}
public void initialize() {
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 4ec055967a..c8e62518c5 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -98,7 +98,8 @@ logback-core = { module = "ch.qos.logback:logback-core", version.ref = "logback"
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" }
-mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" }
+mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" }
+mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter", version.ref = "mockito" }
natpryce-make-it-easy = { module = "com.natpryce:make-it-easy", version.ref = "natpryce-make-it-easy" }
netlayer-tor-external = { module = "com.github.bisq-network.netlayer:tor.external", version.ref = "netlayer" }
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 5331554b92..4da86e93b9 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -1739,6 +1739,14 @@
+
+
+
+
+
+
+
+