diff --git a/apps/oracle-node-app/src/main/java/bisq/oracle_node/OracleNodeService.java b/apps/oracle-node-app/src/main/java/bisq/oracle_node/OracleNodeService.java index f3a92f1d16..cdeeb0fe33 100644 --- a/apps/oracle-node-app/src/main/java/bisq/oracle_node/OracleNodeService.java +++ b/apps/oracle-node-app/src/main/java/bisq/oracle_node/OracleNodeService.java @@ -108,7 +108,6 @@ public static OracleNodeService.Config from(com.typesafe.config.Config config) { private final String signatureBase64; private final String profileId; private final boolean staticPublicKeysProvided; - private AuthorizedOracleNode authorizedOracleNode; @Nullable private Scheduler startupScheduler, scheduler; @@ -185,7 +184,7 @@ public CompletableFuture initialize() { KeyPair keyPair = identity.getNetworkIdWithKeyPair().getKeyPair(); byte[] authorizedPublicKeyEncoded = authorizedPublicKey.getEncoded(); String authorizedPublicKeyAsHex = Hex.encode(authorizedPublicKeyEncoded); - authorizedOracleNode = new AuthorizedOracleNode(networkId, + AuthorizedOracleNode authorizedOracleNode = new AuthorizedOracleNode(networkId, profileId, authorizedPublicKeyAsHex, bondUserName, @@ -193,6 +192,16 @@ public CompletableFuture initialize() { staticPublicKeysProvided); bisq1BridgeService.setAuthorizedOracleNode(authorizedOracleNode); + // Can be removed once there are no pre 2.1.0 versions out there anymore + AuthorizedOracleNode oldVersion = new AuthorizedOracleNode(0, + networkId, + profileId, + authorizedPublicKeyAsHex, + bondUserName, + signatureBase64, + staticPublicKeysProvided); + bisq1BridgeService.setAuthorizedOracleNodeOldVersion(oldVersion); + // We only self-publish if we are a root oracle if (staticPublicKeysProvided) { AuthorizedBondedRole authorizedBondedRole = new AuthorizedBondedRole(profileId, @@ -206,6 +215,7 @@ public CompletableFuture initialize() { staticPublicKeysProvided); // TODO deactivate republishing until issues are resolved + // Repeat 3 times at startup to republish to ensure the data gets well distributed /* startupScheduler = Scheduler.run(() -> publishMyAuthorizedData(authorizedOracleNode, authorizedBondedRole, keyPair)) .repeated(1, 10, TimeUnit.SECONDS, 3); diff --git a/apps/oracle-node-app/src/main/java/bisq/oracle_node/bisq1_bridge/Bisq1BridgeService.java b/apps/oracle-node-app/src/main/java/bisq/oracle_node/bisq1_bridge/Bisq1BridgeService.java index ed26f60d13..622070605c 100644 --- a/apps/oracle-node-app/src/main/java/bisq/oracle_node/bisq1_bridge/Bisq1BridgeService.java +++ b/apps/oracle-node-app/src/main/java/bisq/oracle_node/bisq1_bridge/Bisq1BridgeService.java @@ -99,6 +99,8 @@ public static Bisq1BridgeService.Config from(com.typesafe.config.Config config) @Setter private AuthorizedOracleNode authorizedOracleNode; @Setter + private AuthorizedOracleNode authorizedOracleNodeOldVersion; + @Setter private Identity identity; @Nullable @@ -338,8 +340,17 @@ private void processAuthorizeAccountAgeRequest(AuthorizeAccountAgeRequest reques if (date == requestDate) { persistableStore.getAccountAgeRequests().add(request); persist(); - AuthorizedAccountAgeData data = new AuthorizedAccountAgeData(profileId, requestDate, staticPublicKeysProvided); + AuthorizedAccountAgeData data = new AuthorizedAccountAgeData(profileId, + requestDate, + staticPublicKeysProvided); publishAuthorizedData(data); + + // Can be removed once there are no pre 2.1.0 versions out there anymore + AuthorizedAccountAgeData oldVersion = new AuthorizedAccountAgeData(0, + profileId, + requestDate, + staticPublicKeysProvided); + publishAuthorizedData(oldVersion); } else { log.warn("Date of account age for {} is not matching the date from the users request. " + "Date from bridge service call: {}; Date from users request: {}", @@ -384,8 +395,17 @@ private void processAuthorizeSignedWitnessRequest(AuthorizeSignedWitnessRequest if (date == witnessSignDate) { persistableStore.getSignedWitnessRequests().add(request); persist(); - AuthorizedSignedWitnessData data = new AuthorizedSignedWitnessData(request.getProfileId(), request.getWitnessSignDate(), staticPublicKeysProvided); + AuthorizedSignedWitnessData data = new AuthorizedSignedWitnessData(request.getProfileId(), + request.getWitnessSignDate(), + staticPublicKeysProvided); publishAuthorizedData(data); + + // Can be removed once there are no pre 2.1.0 versions out there anymore + AuthorizedSignedWitnessData oldVersion = new AuthorizedSignedWitnessData(0, + request.getProfileId(), + request.getWitnessSignDate(), + staticPublicKeysProvided); + publishAuthorizedData(oldVersion); } else { log.warn("Date of signed witness for {} is not matching the date from the users request. " + "Date from bridge service call: {}; Date from users request: {}", @@ -439,6 +459,25 @@ private void processBondedRoleRegistrationRequest(BondedRoleRegistrationRequest } else { publishAuthorizedData(data); } + + // Can be removed once there are no pre 2.1.0 versions out there anymore + AuthorizedBondedRole oldVersion = new AuthorizedBondedRole(0, + profileId, + request.getAuthorizedPublicKey(), + bondedRoleType, + bondUserName, + signatureBase64, + request.getAddressByTransportTypeMap(), + request.getNetworkId(), + Optional.of(authorizedOracleNodeOldVersion), + false); + if (request.isCancellationRequest()) { + authorizedBondedRolesService.getAuthorizedBondedRoleStream() + .filter(authorizedBondedRole -> authorizedBondedRole.equals(oldVersion)) + .forEach(this::removeAuthorizedData); + } else { + publishAuthorizedData(oldVersion); + } } else { log.warn("RequestBondedRole failed. {}", bondedRoleVerificationDto.getErrorMessage()); } diff --git a/apps/oracle-node-app/src/main/java/bisq/oracle_node/market_price/MarketPricePropagationService.java b/apps/oracle-node-app/src/main/java/bisq/oracle_node/market_price/MarketPricePropagationService.java index c1a9f21e5e..5fe801377e 100644 --- a/apps/oracle-node-app/src/main/java/bisq/oracle_node/market_price/MarketPricePropagationService.java +++ b/apps/oracle-node-app/src/main/java/bisq/oracle_node/market_price/MarketPricePropagationService.java @@ -43,7 +43,13 @@ public CompletableFuture initialize() { log.info("initialize"); marketPriceByCurrencyMapPin = marketPriceRequestService.getMarketPriceByCurrencyMap().addObserver(() -> { if (!marketPriceRequestService.getMarketPriceByCurrencyMap().isEmpty()) { - publishAuthorizedData(new AuthorizedMarketPriceData(new TreeMap<>(marketPriceRequestService.getMarketPriceByCurrencyMap()), staticPublicKeysProvided)); + publishAuthorizedData(new AuthorizedMarketPriceData(new TreeMap<>(marketPriceRequestService.getMarketPriceByCurrencyMap()), + staticPublicKeysProvided)); + + // Can be removed once there are no pre 2.1.0 versions out there anymore + publishAuthorizedData(new AuthorizedMarketPriceData(0, + new TreeMap<>(marketPriceRequestService.getMarketPriceByCurrencyMap()), + staticPublicKeysProvided)); } }); diff --git a/apps/oracle-node-app/src/main/java/bisq/oracle_node/timestamp/TimestampService.java b/apps/oracle-node-app/src/main/java/bisq/oracle_node/timestamp/TimestampService.java index 8b6726ca7b..85525cdbcd 100644 --- a/apps/oracle-node-app/src/main/java/bisq/oracle_node/timestamp/TimestampService.java +++ b/apps/oracle-node-app/src/main/java/bisq/oracle_node/timestamp/TimestampService.java @@ -149,7 +149,12 @@ private void processAuthorizeTimestampRequest(AuthorizeTimestampRequest request) // to republish it. date = persistableStore.getTimestampsByProfileId().get(profileId); } - publishAuthorizedData(new AuthorizedTimestampData(profileId, date, staticPublicKeysProvided)); + AuthorizedTimestampData authorizedTimestampData = new AuthorizedTimestampData(profileId, date, staticPublicKeysProvided); + publishAuthorizedData(authorizedTimestampData); + + // Can be removed once there are no pre 2.1.0 versions out there anymore + AuthorizedTimestampData oldVersion = new AuthorizedTimestampData(0, profileId, date, staticPublicKeysProvided); + publishAuthorizedData(oldVersion); } private CompletableFuture publishAuthorizedData(AuthorizedDistributedData data) { diff --git a/apps/seed-node-app/src/main/java/bisq/seed_node/SeedNodeService.java b/apps/seed-node-app/src/main/java/bisq/seed_node/SeedNodeService.java index 35293c4df3..76b522602a 100644 --- a/apps/seed-node-app/src/main/java/bisq/seed_node/SeedNodeService.java +++ b/apps/seed-node-app/src/main/java/bisq/seed_node/SeedNodeService.java @@ -111,6 +111,7 @@ public CompletableFuture initialize() { KeyPair keyPair = keyBundleService.getOrCreateKeyBundle(defaultKeyId).getKeyPair(); // TODO deactivate republishing until issues are resolved + // Repeat 3 times at startup to republish to ensure the data gets well distributed /* startupScheduler = Scheduler.run(() -> publishMyBondedRole(authorizedBondedRole, keyPair, authorizedPrivateKey, authorizedPublicKey)) .repeated(1, 10, TimeUnit.SECONDS, 3); diff --git a/bonded-roles/src/main/java/bisq/bonded_roles/bonded_role/AuthorizedBondedRole.java b/bonded-roles/src/main/java/bisq/bonded_roles/bonded_role/AuthorizedBondedRole.java index 269afaab60..52f8ff0d50 100644 --- a/bonded-roles/src/main/java/bisq/bonded_roles/bonded_role/AuthorizedBondedRole.java +++ b/bonded-roles/src/main/java/bisq/bonded_roles/bonded_role/AuthorizedBondedRole.java @@ -20,6 +20,7 @@ import bisq.bonded_roles.AuthorizedPubKeys; import bisq.bonded_roles.BondedRoleType; import bisq.bonded_roles.oracle.AuthorizedOracleNode; +import bisq.common.annotation.ExcludeForHash; import bisq.common.application.DevMode; import bisq.common.proto.ProtoResolver; import bisq.common.proto.UnresolvableProtobufMessageException; @@ -43,8 +44,14 @@ @EqualsAndHashCode @Getter public final class AuthorizedBondedRole implements AuthorizedDistributedData { + private static final int VERSION = 1; + @EqualsAndHashCode.Exclude + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) private final MetaData metaData = new MetaData(TTL_100_DAYS, HIGHEST_PRIORITY, getClass().getSimpleName(), MAX_MAP_SIZE_100); + @EqualsAndHashCode.Exclude + @ExcludeForHash + private final int version; private final String profileId; private final String authorizedPublicKey; private final BondedRoleType bondedRoleType; @@ -54,6 +61,12 @@ public final class AuthorizedBondedRole implements AuthorizedDistributedData { private final NetworkId networkId; // The oracle node which did the validation and publishing private final Optional authorizingOracleNode; + + // ExcludeForHash from version 1 on to not treat data from different oracle nodes with different staticPublicKeysProvided value as duplicate data. + // We add version 2 and 3 for extra safety... + // Once no pre version 2.0.5 nodes are expected anymore in the network we can remove the parameter + // and use default `@ExcludeForHash` instead. + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) @EqualsAndHashCode.Exclude private final boolean staticPublicKeysProvided; @@ -66,6 +79,29 @@ public AuthorizedBondedRole(String profileId, NetworkId networkId, Optional authorizingOracleNode, boolean staticPublicKeysProvided) { + this(VERSION, + profileId, + authorizedPublicKey, + bondedRoleType, + bondUserName, + signatureBase64, + addressByTransportTypeMap, + networkId, + authorizingOracleNode, + staticPublicKeysProvided); + } + + public AuthorizedBondedRole(int version, + String profileId, + String authorizedPublicKey, + BondedRoleType bondedRoleType, + String bondUserName, + String signatureBase64, + Optional addressByTransportTypeMap, + NetworkId networkId, + Optional authorizingOracleNode, + boolean staticPublicKeysProvided) { + this.version = version; this.profileId = profileId; this.authorizedPublicKey = authorizedPublicKey; this.bondedRoleType = bondedRoleType; @@ -96,7 +132,8 @@ public bisq.bonded_roles.protobuf.AuthorizedBondedRole.Builder getBuilder(boolea .setBondUserName(bondUserName) .setSignatureBase64(signatureBase64) .setNetworkId(networkId.toProto(serializeForHash)) - .setStaticPublicKeysProvided(staticPublicKeysProvided); + .setStaticPublicKeysProvided(staticPublicKeysProvided) + .setVersion(version); addressByTransportTypeMap.ifPresent(e -> builder.setAddressByTransportTypeMap(e.toProto(serializeForHash))); authorizingOracleNode.ifPresent(oracleNode -> builder.setAuthorizingOracleNode(oracleNode.toProto(serializeForHash))); return builder; @@ -108,7 +145,9 @@ public bisq.bonded_roles.protobuf.AuthorizedBondedRole toProto(boolean serialize } public static AuthorizedBondedRole fromProto(bisq.bonded_roles.protobuf.AuthorizedBondedRole proto) { - return new AuthorizedBondedRole(proto.getProfileId(), + return new AuthorizedBondedRole( + proto.getVersion(), + proto.getProfileId(), proto.getAuthorizedPublicKey(), BondedRoleType.fromProto(proto.getBondedRoleType()), proto.getBondUserName(), @@ -120,7 +159,8 @@ public static AuthorizedBondedRole fromProto(bisq.bonded_roles.protobuf.Authoriz proto.hasAuthorizingOracleNode() ? Optional.of(AuthorizedOracleNode.fromProto(proto.getAuthorizingOracleNode())) : Optional.empty(), - proto.getStaticPublicKeysProvided()); + proto.getStaticPublicKeysProvided() + ); } public static ProtoResolver getResolver() { diff --git a/bonded-roles/src/main/java/bisq/bonded_roles/market_price/AuthorizedMarketPriceData.java b/bonded-roles/src/main/java/bisq/bonded_roles/market_price/AuthorizedMarketPriceData.java index 8879eeea23..d8a989bbc1 100644 --- a/bonded-roles/src/main/java/bisq/bonded_roles/market_price/AuthorizedMarketPriceData.java +++ b/bonded-roles/src/main/java/bisq/bonded_roles/market_price/AuthorizedMarketPriceData.java @@ -18,6 +18,7 @@ package bisq.bonded_roles.market_price; import bisq.bonded_roles.AuthorizedPubKeys; +import bisq.common.annotation.ExcludeForHash; import bisq.common.application.DevMode; import bisq.common.currency.Market; import bisq.common.currency.MarketRepository; @@ -44,16 +45,35 @@ @EqualsAndHashCode @Getter public final class AuthorizedMarketPriceData implements AuthorizedDistributedData { + private static final int VERSION = 1; public static final long TTL = TimeUnit.MINUTES.toMillis(10); @EqualsAndHashCode.Exclude + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) private final MetaData metaData = new MetaData(TTL, DEFAULT_PRIORITY, getClass().getSimpleName()); + @EqualsAndHashCode.Exclude + @ExcludeForHash + private final int version; // We need deterministic sorting or the map, so we use a treemap private final TreeMap marketPriceByCurrencyMap; + + // ExcludeForHash from version 1 on to not treat data from different oracle nodes with different staticPublicKeysProvided value as duplicate data. + // We add version 2 and 3 for extra safety... + // Once no pre version 2.0.5 nodes are expected anymore in the network we can remove the parameter + // and use default `@ExcludeForHash` instead. + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) @EqualsAndHashCode.Exclude private final boolean staticPublicKeysProvided; - public AuthorizedMarketPriceData(TreeMap marketPriceByCurrencyMap, boolean staticPublicKeysProvided) { + public AuthorizedMarketPriceData(TreeMap marketPriceByCurrencyMap, + boolean staticPublicKeysProvided) { + this(VERSION, marketPriceByCurrencyMap, staticPublicKeysProvided); + } + + public AuthorizedMarketPriceData(int version, + TreeMap marketPriceByCurrencyMap, + boolean staticPublicKeysProvided) { + this.version = version; this.marketPriceByCurrencyMap = marketPriceByCurrencyMap; this.staticPublicKeysProvided = staticPublicKeysProvided; @@ -72,7 +92,8 @@ public bisq.bonded_roles.protobuf.AuthorizedMarketPriceData.Builder getBuilder(b .putAllMarketPriceByCurrencyMap(marketPriceByCurrencyMap.entrySet().stream() .collect(Collectors.toMap(e -> e.getKey().getMarketCodes(), e -> e.getValue().toProto(serializeForHash)))) - .setStaticPublicKeysProvided(staticPublicKeysProvided); + .setStaticPublicKeysProvided(staticPublicKeysProvided) + .setVersion(version); } @Override @@ -85,7 +106,11 @@ public static AuthorizedMarketPriceData fromProto(bisq.bonded_roles.protobuf.Aut .filter(e -> MarketRepository.findAnyMarketByMarketCodes(e.getKey()).isPresent()) .collect(Collectors.toMap(e -> MarketRepository.findAnyMarketByMarketCodes(e.getKey()).orElseThrow(), e -> MarketPrice.fromProto(e.getValue()))); - return new AuthorizedMarketPriceData(new TreeMap<>(map), proto.getStaticPublicKeysProvided()); + return new AuthorizedMarketPriceData( + proto.getVersion(), + new TreeMap<>(map), + proto.getStaticPublicKeysProvided() + ); } public static ProtoResolver getResolver() { diff --git a/bonded-roles/src/main/java/bisq/bonded_roles/oracle/AuthorizedOracleNode.java b/bonded-roles/src/main/java/bisq/bonded_roles/oracle/AuthorizedOracleNode.java index 6026846469..c372c0e65d 100644 --- a/bonded-roles/src/main/java/bisq/bonded_roles/oracle/AuthorizedOracleNode.java +++ b/bonded-roles/src/main/java/bisq/bonded_roles/oracle/AuthorizedOracleNode.java @@ -18,6 +18,7 @@ package bisq.bonded_roles.oracle; import bisq.bonded_roles.AuthorizedPubKeys; +import bisq.common.annotation.ExcludeForHash; import bisq.common.application.DevMode; import bisq.common.proto.ProtoResolver; import bisq.common.proto.UnresolvableProtobufMessageException; @@ -40,13 +41,25 @@ @EqualsAndHashCode @Getter public final class AuthorizedOracleNode implements AuthorizedDistributedData { + private static final int VERSION = 1; + @EqualsAndHashCode.Exclude + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) private final MetaData metaData = new MetaData(TTL_100_DAYS, HIGHEST_PRIORITY, getClass().getSimpleName(), MAX_MAP_SIZE_100); + @EqualsAndHashCode.Exclude + @ExcludeForHash + private final int version; private final NetworkId networkId; private final String profileId; private final String authorizedPublicKey; private final String bondUserName; // username from DAO proposal private final String signatureBase64; // signature created by bond with username as message + + // ExcludeForHash from version 1 on to not treat data from different oracle nodes with different staticPublicKeysProvided value as duplicate data. + // We add version 2 and 3 for extra safety... + // Once no pre version 2.0.5 nodes are expected anymore in the network we can remove the parameter + // and use default `@ExcludeForHash` instead. + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) @EqualsAndHashCode.Exclude private final boolean staticPublicKeysProvided; @@ -56,6 +69,23 @@ public AuthorizedOracleNode(NetworkId networkId, String bondUserName, String signatureBase64, boolean staticPublicKeysProvided) { + this(VERSION, + networkId, + profileId, + authorizedPublicKey, + bondUserName, + signatureBase64, + staticPublicKeysProvided); + } + + public AuthorizedOracleNode(int version, + NetworkId networkId, + String profileId, + String authorizedPublicKey, + String bondUserName, + String signatureBase64, + boolean staticPublicKeysProvided) { + this.version = version; this.networkId = networkId; this.profileId = profileId; this.authorizedPublicKey = authorizedPublicKey; @@ -82,7 +112,8 @@ public bisq.bonded_roles.protobuf.AuthorizedOracleNode.Builder getBuilder(boolea .setAuthorizedPublicKey(authorizedPublicKey) .setBondUserName(bondUserName) .setSignatureBase64(signatureBase64) - .setStaticPublicKeysProvided(staticPublicKeysProvided); + .setStaticPublicKeysProvided(staticPublicKeysProvided) + .setVersion(version); } @Override @@ -91,12 +122,15 @@ public bisq.bonded_roles.protobuf.AuthorizedOracleNode toProto(boolean serialize } public static AuthorizedOracleNode fromProto(bisq.bonded_roles.protobuf.AuthorizedOracleNode proto) { - return new AuthorizedOracleNode(NetworkId.fromProto(proto.getNetworkId()), + return new AuthorizedOracleNode( + proto.getVersion(), + NetworkId.fromProto(proto.getNetworkId()), proto.getProfileId(), proto.getAuthorizedPublicKey(), proto.getBondUserName(), proto.getSignatureBase64(), - proto.getStaticPublicKeysProvided()); + proto.getStaticPublicKeysProvided() + ); } public static ProtoResolver getResolver() { diff --git a/bonded-roles/src/main/java/bisq/bonded_roles/release/ReleaseNotification.java b/bonded-roles/src/main/java/bisq/bonded_roles/release/ReleaseNotification.java index 84a7a3fc6a..2e0b27b783 100644 --- a/bonded-roles/src/main/java/bisq/bonded_roles/release/ReleaseNotification.java +++ b/bonded-roles/src/main/java/bisq/bonded_roles/release/ReleaseNotification.java @@ -18,6 +18,7 @@ package bisq.bonded_roles.release; import bisq.bonded_roles.AuthorizedPubKeys; +import bisq.common.annotation.ExcludeForHash; import bisq.common.application.DevMode; import bisq.common.proto.ProtoResolver; import bisq.common.proto.UnresolvableProtobufMessageException; @@ -42,10 +43,15 @@ @EqualsAndHashCode @Getter public final class ReleaseNotification implements AuthorizedDistributedData { + private static final int VERSION = 1; public final static int MAX_MESSAGE_LENGTH = 10_000; @EqualsAndHashCode.Exclude + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) private final MetaData metaData = new MetaData(TTL_100_DAYS, HIGH_PRIORITY, getClass().getSimpleName()); + @EqualsAndHashCode.Exclude + @ExcludeForHash + private final int version; private final String id; private final long date; private final boolean isPreRelease; @@ -53,9 +59,16 @@ public final class ReleaseNotification implements AuthorizedDistributedData { private final String releaseNotes; private final String versionString; private final String releaseManagerProfileId; + + // ExcludeForHash from version 1 on to not treat data from different oracle nodes with different staticPublicKeysProvided value as duplicate data. + // We add version 2 and 3 for extra safety... + // Once no pre version 2.0.5 nodes are expected anymore in the network we can remove the parameter + // and use default `@ExcludeForHash` instead. + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) @EqualsAndHashCode.Exclude private final boolean staticPublicKeysProvided; - @EqualsAndHashCode.Exclude // transient are excluded by default but let's make it more explicit + + // transient fields are excluded by default for EqualsAndHashCode private transient final Version releaseVersion; public ReleaseNotification(String id, @@ -66,6 +79,27 @@ public ReleaseNotification(String id, String versionString, String releaseManagerProfileId, boolean staticPublicKeysProvided) { + this(VERSION, + id, + date, + isPreRelease, + isLauncherUpdate, + releaseNotes, + versionString, + releaseManagerProfileId, + staticPublicKeysProvided); + } + + public ReleaseNotification(int version, + String id, + long date, + boolean isPreRelease, + boolean isLauncherUpdate, + String releaseNotes, + String versionString, + String releaseManagerProfileId, + boolean staticPublicKeysProvided) { + this.version = version; this.id = id; this.date = date; this.isPreRelease = isPreRelease; @@ -99,7 +133,8 @@ public bisq.bonded_roles.protobuf.ReleaseNotification.Builder getBuilder(boolean .setReleaseNotes(releaseNotes) .setVersionString(versionString) .setReleaseManagerProfileId(releaseManagerProfileId) - .setStaticPublicKeysProvided(staticPublicKeysProvided); + .setStaticPublicKeysProvided(staticPublicKeysProvided) + .setVersion(version); } @Override @@ -108,14 +143,17 @@ public bisq.bonded_roles.protobuf.ReleaseNotification toProto(boolean serializeF } public static ReleaseNotification fromProto(bisq.bonded_roles.protobuf.ReleaseNotification proto) { - return new ReleaseNotification(proto.getId(), + return new ReleaseNotification( + proto.getVersion(), + proto.getId(), proto.getDate(), proto.getIsPreRelease(), proto.getIsLauncherUpdate(), proto.getReleaseNotes(), proto.getVersionString(), proto.getReleaseManagerProfileId(), - proto.getStaticPublicKeysProvided()); + proto.getStaticPublicKeysProvided() + ); } public static ProtoResolver getResolver() { diff --git a/bonded-roles/src/main/java/bisq/bonded_roles/security_manager/alert/AuthorizedAlertData.java b/bonded-roles/src/main/java/bisq/bonded_roles/security_manager/alert/AuthorizedAlertData.java index 89719034fb..4b2f2dba1a 100644 --- a/bonded-roles/src/main/java/bisq/bonded_roles/security_manager/alert/AuthorizedAlertData.java +++ b/bonded-roles/src/main/java/bisq/bonded_roles/security_manager/alert/AuthorizedAlertData.java @@ -19,6 +19,7 @@ import bisq.bonded_roles.AuthorizedPubKeys; import bisq.bonded_roles.bonded_role.AuthorizedBondedRole; +import bisq.common.annotation.ExcludeForHash; import bisq.common.application.DevMode; import bisq.common.proto.ProtoResolver; import bisq.common.proto.UnresolvableProtobufMessageException; @@ -44,10 +45,15 @@ @EqualsAndHashCode @Getter public final class AuthorizedAlertData implements AuthorizedDistributedData { + private static final int VERSION = 1; public final static int MAX_MESSAGE_LENGTH = 1000; @EqualsAndHashCode.Exclude + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) private final MetaData metaData = new MetaData(TTL_30_DAYS, HIGH_PRIORITY, getClass().getSimpleName()); + @EqualsAndHashCode.Exclude + @ExcludeForHash + private final int version; private final String id; private final long date; private final AlertType alertType; @@ -58,6 +64,12 @@ public final class AuthorizedAlertData implements AuthorizedDistributedData { private final Optional minVersion; private final Optional bannedRole; private final String securityManagerProfileId; + + // ExcludeForHash from version 1 on to not treat data from different oracle nodes with different staticPublicKeysProvided value as duplicate data. + // We add version 2 and 3 for extra safety... + // Once no pre version 2.0.5 nodes are expected anymore in the network we can remove the parameter + // and use default `@ExcludeForHash` instead. + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) @EqualsAndHashCode.Exclude private final boolean staticPublicKeysProvided; @@ -72,6 +84,33 @@ public AuthorizedAlertData(String id, Optional bannedRole, String securityManagerProfileId, boolean staticPublicKeysProvided) { + this(VERSION, + id, + date, + alertType, + headline, + message, + haltTrading, + requireVersionForTrading, + minVersion, + bannedRole, + securityManagerProfileId, + staticPublicKeysProvided); + } + + public AuthorizedAlertData(int version, + String id, + long date, + AlertType alertType, + Optional headline, + Optional message, + boolean haltTrading, + boolean requireVersionForTrading, + Optional minVersion, + Optional bannedRole, + String securityManagerProfileId, + boolean staticPublicKeysProvided) { + this.version = version; this.id = id; this.date = date; this.alertType = alertType; @@ -105,7 +144,8 @@ public bisq.bonded_roles.protobuf.AuthorizedAlertData.Builder getBuilder(boolean .setHaltTrading(haltTrading) .setRequireVersionForTrading(requireVersionForTrading) .setSecurityManagerProfileId(securityManagerProfileId) - .setStaticPublicKeysProvided(staticPublicKeysProvided); + .setStaticPublicKeysProvided(staticPublicKeysProvided) + .setVersion(version); message.ifPresent(builder::setMessage); headline.ifPresent(headline -> { // We only set the headline if defaultHeadline is present (not AlertType.BAN) and @@ -129,7 +169,9 @@ public bisq.bonded_roles.protobuf.AuthorizedAlertData toProto(boolean serializeF public static AuthorizedAlertData fromProto(bisq.bonded_roles.protobuf.AuthorizedAlertData proto) { AlertType alertType = AlertType.fromProto(proto.getAlertType()); - return new AuthorizedAlertData(proto.getId(), + return new AuthorizedAlertData( + proto.getVersion(), + proto.getId(), proto.getDate(), alertType, proto.hasHeadline() ? Optional.of(proto.getHeadline()) : getDefaultHeadline(alertType), diff --git a/bonded-roles/src/main/java/bisq/bonded_roles/security_manager/difficulty_adjustment/AuthorizedDifficultyAdjustmentData.java b/bonded-roles/src/main/java/bisq/bonded_roles/security_manager/difficulty_adjustment/AuthorizedDifficultyAdjustmentData.java index 0c68c26448..328cbfdf48 100644 --- a/bonded-roles/src/main/java/bisq/bonded_roles/security_manager/difficulty_adjustment/AuthorizedDifficultyAdjustmentData.java +++ b/bonded-roles/src/main/java/bisq/bonded_roles/security_manager/difficulty_adjustment/AuthorizedDifficultyAdjustmentData.java @@ -18,6 +18,7 @@ package bisq.bonded_roles.security_manager.difficulty_adjustment; import bisq.bonded_roles.AuthorizedPubKeys; +import bisq.common.annotation.ExcludeForHash; import bisq.common.application.DevMode; import bisq.common.proto.ProtoResolver; import bisq.common.proto.UnresolvableProtobufMessageException; @@ -41,11 +42,23 @@ @EqualsAndHashCode @Getter public final class AuthorizedDifficultyAdjustmentData implements AuthorizedDistributedData { + private static final int VERSION = 1; + @EqualsAndHashCode.Exclude + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) private final MetaData metaData = new MetaData(TTL_100_DAYS, HIGH_PRIORITY, getClass().getSimpleName()); + @EqualsAndHashCode.Exclude + @ExcludeForHash + private final int version; private final long date; private final double difficultyAdjustmentFactor; private final String securityManagerProfileId; + + // ExcludeForHash from version 1 on to not treat data from different oracle nodes with different staticPublicKeysProvided value as duplicate data. + // We add version 2 and 3 for extra safety... + // Once no pre version 2.0.5 nodes are expected anymore in the network we can remove the parameter + // and use default `@ExcludeForHash` instead. + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) @EqualsAndHashCode.Exclude private final boolean staticPublicKeysProvided; @@ -53,6 +66,19 @@ public AuthorizedDifficultyAdjustmentData(long date, double difficultyAdjustmentFactor, String securityManagerProfileId, boolean staticPublicKeysProvided) { + this(VERSION, + date, + difficultyAdjustmentFactor, + securityManagerProfileId, + staticPublicKeysProvided); + } + + public AuthorizedDifficultyAdjustmentData(int version, + long date, + double difficultyAdjustmentFactor, + String securityManagerProfileId, + boolean staticPublicKeysProvided) { + this.version = version; this.date = date; this.difficultyAdjustmentFactor = difficultyAdjustmentFactor; this.securityManagerProfileId = securityManagerProfileId; @@ -73,7 +99,8 @@ public bisq.bonded_roles.protobuf.AuthorizedDifficultyAdjustmentData.Builder get .setDate(date) .setDifficultyAdjustmentFactor(difficultyAdjustmentFactor) .setSecurityManagerProfileId(securityManagerProfileId) - .setStaticPublicKeysProvided(staticPublicKeysProvided); + .setStaticPublicKeysProvided(staticPublicKeysProvided) + .setVersion(version); } @Override @@ -82,10 +109,13 @@ public bisq.bonded_roles.protobuf.AuthorizedDifficultyAdjustmentData toProto(boo } public static AuthorizedDifficultyAdjustmentData fromProto(bisq.bonded_roles.protobuf.AuthorizedDifficultyAdjustmentData proto) { - return new AuthorizedDifficultyAdjustmentData(proto.getDate(), + return new AuthorizedDifficultyAdjustmentData( + proto.getVersion(), + proto.getDate(), proto.getDifficultyAdjustmentFactor(), proto.getSecurityManagerProfileId(), - proto.getStaticPublicKeysProvided()); + proto.getStaticPublicKeysProvided() + ); } public static ProtoResolver getResolver() { diff --git a/bonded-roles/src/main/java/bisq/bonded_roles/security_manager/min_reputation_score/AuthorizedMinRequiredReputationScoreData.java b/bonded-roles/src/main/java/bisq/bonded_roles/security_manager/min_reputation_score/AuthorizedMinRequiredReputationScoreData.java index 99c6834be4..901a0fee74 100644 --- a/bonded-roles/src/main/java/bisq/bonded_roles/security_manager/min_reputation_score/AuthorizedMinRequiredReputationScoreData.java +++ b/bonded-roles/src/main/java/bisq/bonded_roles/security_manager/min_reputation_score/AuthorizedMinRequiredReputationScoreData.java @@ -18,6 +18,7 @@ package bisq.bonded_roles.security_manager.min_reputation_score; import bisq.bonded_roles.AuthorizedPubKeys; +import bisq.common.annotation.ExcludeForHash; import bisq.common.application.DevMode; import bisq.common.proto.ProtoResolver; import bisq.common.proto.UnresolvableProtobufMessageException; @@ -41,11 +42,23 @@ @EqualsAndHashCode @Getter public final class AuthorizedMinRequiredReputationScoreData implements AuthorizedDistributedData { + private static final int VERSION = 1; + @EqualsAndHashCode.Exclude + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) private final MetaData metaData = new MetaData(TTL_100_DAYS, HIGH_PRIORITY, getClass().getSimpleName()); + @EqualsAndHashCode.Exclude + @ExcludeForHash + private final int version; private final long date; private final long minRequiredReputationScore; private final String securityManagerProfileId; + + // ExcludeForHash from version 1 on to not treat data from different oracle nodes with different staticPublicKeysProvided value as duplicate data. + // We add version 2 and 3 for extra safety... + // Once no pre version 2.0.5 nodes are expected anymore in the network we can remove the parameter + // and use default `@ExcludeForHash` instead. + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) @EqualsAndHashCode.Exclude private final boolean staticPublicKeysProvided; @@ -53,6 +66,19 @@ public AuthorizedMinRequiredReputationScoreData(long date, long minRequiredReputationScore, String securityManagerProfileId, boolean staticPublicKeysProvided) { + this(VERSION, + date, + minRequiredReputationScore, + securityManagerProfileId, + staticPublicKeysProvided); + } + + public AuthorizedMinRequiredReputationScoreData(int version, + long date, + long minRequiredReputationScore, + String securityManagerProfileId, + boolean staticPublicKeysProvided) { + this.version = version; this.date = date; this.minRequiredReputationScore = minRequiredReputationScore; this.securityManagerProfileId = securityManagerProfileId; @@ -73,7 +99,8 @@ public bisq.bonded_roles.protobuf.AuthorizedMinRequiredReputationScoreData.Build .setDate(date) .setMinRequiredReputationScore(minRequiredReputationScore) .setSecurityManagerProfileId(securityManagerProfileId) - .setStaticPublicKeysProvided(staticPublicKeysProvided); + .setStaticPublicKeysProvided(staticPublicKeysProvided) + .setVersion(version); } @Override @@ -82,10 +109,13 @@ public bisq.bonded_roles.protobuf.AuthorizedMinRequiredReputationScoreData toPro } public static AuthorizedMinRequiredReputationScoreData fromProto(bisq.bonded_roles.protobuf.AuthorizedMinRequiredReputationScoreData proto) { - return new AuthorizedMinRequiredReputationScoreData(proto.getDate(), + return new AuthorizedMinRequiredReputationScoreData( + proto.getVersion(), + proto.getDate(), proto.getMinRequiredReputationScore(), proto.getSecurityManagerProfileId(), - proto.getStaticPublicKeysProvided()); + proto.getStaticPublicKeysProvided() + ); } public static ProtoResolver getResolver() { diff --git a/bonded-roles/src/main/proto/bonded_roles.proto b/bonded-roles/src/main/proto/bonded_roles.proto index 6d09f72623..267fe32917 100644 --- a/bonded-roles/src/main/proto/bonded_roles.proto +++ b/bonded-roles/src/main/proto/bonded_roles.proto @@ -25,6 +25,7 @@ message AuthorizedOracleNode { string bondUserName = 4; string signatureBase64 = 5; bool staticPublicKeysProvided = 6; + sint32 version = 7; } message BondedRoleRegistrationRequest { @@ -48,6 +49,7 @@ message AuthorizedBondedRole { network.identity.NetworkId networkId = 7; optional AuthorizedOracleNode authorizingOracleNode = 8; bool staticPublicKeysProvided = 9; + sint32 version = 10; } enum AlertType { @@ -70,6 +72,7 @@ message AuthorizedAlertData { string securityManagerProfileId = 9; bool staticPublicKeysProvided = 10; optional string headline = 11; + sint32 version = 12; } message ReleaseNotification { @@ -81,6 +84,7 @@ message ReleaseNotification { string versionString = 6; string releaseManagerProfileId = 7; bool staticPublicKeysProvided = 8; + sint32 version = 9; } message AuthorizedDifficultyAdjustmentData { @@ -88,6 +92,7 @@ message AuthorizedDifficultyAdjustmentData { double difficultyAdjustmentFactor = 2; string securityManagerProfileId = 3; bool staticPublicKeysProvided = 4; + sint32 version = 5; } message AuthorizedMinRequiredReputationScoreData { @@ -95,6 +100,7 @@ message AuthorizedMinRequiredReputationScoreData { sint64 minRequiredReputationScore = 2; string securityManagerProfileId = 3; bool staticPublicKeysProvided = 4; + sint32 version = 5; } enum MarketPriceProvider { @@ -115,6 +121,7 @@ message MarketPrice { message AuthorizedMarketPriceData { map marketPriceByCurrencyMap = 1; bool staticPublicKeysProvided = 2; + sint32 version = 3; } message MarketPriceStore { diff --git a/chat/src/main/java/bisq/chat/common/CommonPublicChatChannel.java b/chat/src/main/java/bisq/chat/common/CommonPublicChatChannel.java index a1d304c708..63d533f548 100644 --- a/chat/src/main/java/bisq/chat/common/CommonPublicChatChannel.java +++ b/chat/src/main/java/bisq/chat/common/CommonPublicChatChannel.java @@ -41,7 +41,7 @@ private static String createId(ChatChannelDomain chatChannelDomain, String chann private final Optional channelAdminId; private final List channelModeratorIds; private final String channelTitle; - @EqualsAndHashCode.Exclude // transient are excluded by default but let's make it more explicit + // transient fields are excluded by default for EqualsAndHashCode private transient final String description; public CommonPublicChatChannel(ChatChannelDomain chatChannelDomain, String channelTitle) { diff --git a/common/src/main/java/bisq/common/currency/FiatCurrency.java b/common/src/main/java/bisq/common/currency/FiatCurrency.java index 6af001ebe8..4f450b48a3 100644 --- a/common/src/main/java/bisq/common/currency/FiatCurrency.java +++ b/common/src/main/java/bisq/common/currency/FiatCurrency.java @@ -33,7 +33,7 @@ public final class FiatCurrency extends TradeCurrency { private final static String PREFIX = "★ "; @Getter - @EqualsAndHashCode.Exclude // transient are excluded by default but let's make it more explicit + // transient fields are excluded by default for EqualsAndHashCode private transient final Currency currency; public FiatCurrency(String code) { diff --git a/network/network/src/main/java/bisq/network/p2p/node/Capability.java b/network/network/src/main/java/bisq/network/p2p/node/Capability.java index 856d472acc..6b43608e9b 100644 --- a/network/network/src/main/java/bisq/network/p2p/node/Capability.java +++ b/network/network/src/main/java/bisq/network/p2p/node/Capability.java @@ -41,6 +41,7 @@ public final class Capability implements NetworkProto { public static final int VERSION = 1; + @EqualsAndHashCode.Exclude @ExcludeForHash private final int version; private final Address address; @@ -48,6 +49,7 @@ public final class Capability implements NetworkProto { // ExcludeForHash from version 1 on to not break hash for pow check or version 0. We add version 2 and 3 for extra safety... // Once no pre version 2.0.5 nodes are expected anymore in the network we can remove the parameter // and use default `@ExcludeForHash` instead. + @EqualsAndHashCode.Exclude @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) private final List features; @ExcludeForHash(excludeOnlyInVersions = {0}) diff --git a/network/network/src/main/java/bisq/network/p2p/node/authorization/token/hash_cash/HashCashTokenService.java b/network/network/src/main/java/bisq/network/p2p/node/authorization/token/hash_cash/HashCashTokenService.java index e8e0a91df8..992ee45193 100644 --- a/network/network/src/main/java/bisq/network/p2p/node/authorization/token/hash_cash/HashCashTokenService.java +++ b/network/network/src/main/java/bisq/network/p2p/node/authorization/token/hash_cash/HashCashTokenService.java @@ -119,19 +119,28 @@ public boolean isAuthorized(EnvelopePayloadMessage message, // Verify payload byte[] payload = getPayload(message); - if (!Arrays.equals(payload, proofOfWork.getPayload())) { - log.warn("Message payload not matching proof of work payload. " + - "getPayload(message)={};\n" + - "proofOfWork.getPayload()={};\n" + - "getPayload(message).length={};\n" + - "proofOfWork.getPayload().length={}\n" + - "message={}", - StringUtils.truncate(Hex.encode(payload), 200), - StringUtils.truncate(Hex.encode(proofOfWork.getPayload()), 200), - payload.length, - proofOfWork.getPayload().length, - StringUtils.truncate(message.toString(), 1000)); - return false; + byte[] proofOfWorkPayload = proofOfWork.getPayload(); + if (!Arrays.equals(payload, proofOfWorkPayload)) { + // We try again with ignoring ExcludeForHash annotations by using the serialize() method. + byte[] payloadWithoutUsingExcludeForHash = message.serialize(); + if (Arrays.equals(payloadWithoutUsingExcludeForHash, proofOfWorkPayload)) { + log.info("Proof of work payload not matching message.serializeForHash() but " + + "matching message.serialize(). This is expected for certain messages from " + + "nodes which do not run the latest version."); + } else { + log.warn("Message payload not matching proof of work payload. " + + "getPayload(message)={};\n" + + "proofOfWork.getPayload()={};\n" + + "getPayload(message).length={};\n" + + "proofOfWork.getPayload().length={}\n" + + "message={}", + StringUtils.truncate(Hex.encode(payload), 200), + StringUtils.truncate(Hex.encode(proofOfWorkPayload), 200), + payload.length, + proofOfWorkPayload.length, + StringUtils.truncate(message.toString(), 5000)); + return false; + } } // Verify challenge diff --git a/network/network/src/main/java/bisq/network/p2p/services/confidential/ConfidentialMessageService.java b/network/network/src/main/java/bisq/network/p2p/services/confidential/ConfidentialMessageService.java index 98eb2b9ece..a173ad14ed 100644 --- a/network/network/src/main/java/bisq/network/p2p/services/confidential/ConfidentialMessageService.java +++ b/network/network/src/main/java/bisq/network/p2p/services/confidential/ConfidentialMessageService.java @@ -261,13 +261,18 @@ private SendConfidentialMessageResult storeMailBoxMessage(MetaData metaData, return new SendConfidentialMessageResult(MessageDeliveryStatus.FAILED).setErrorMsg("We have not stored the mailboxMessage because the dataService is not present."); } - MailboxData mailboxData = new MailboxData(confidentialMessage, metaData); - // We do not wait for the broadcast result as that can take a while. We pack the future into our result, + MailboxData mailboxData = new MailboxData(metaData, confidentialMessage); + // We do not wait for the broadcast result as that can take a while. We pack the future into our result, // so clients can react on it as they wish. - BroadcastResult mailboxFuture = dataService.get().addMailboxData(mailboxData, - senderKeyPair, - receiverPubKey.getPublicKey()) - .join(); // TODO (refactor, low prio) async for creating the stores, could be made blocking + PublicKey publicKey = receiverPubKey.getPublicKey(); + + // TODO (refactor, low prio) async for creating the stores, could be made blocking + BroadcastResult mailboxFuture = dataService.get().addMailboxData(mailboxData, senderKeyPair, publicKey).join(); + + // Send also with version 0 for backward compatibility + MailboxData oldVersion = MailboxData.cloneWithVersion0(mailboxData); + dataService.get().addMailboxData(oldVersion, senderKeyPair, publicKey).join(); + return new SendConfidentialMessageResult(MessageDeliveryStatus.TRY_ADD_TO_MAILBOX).setMailboxFuture(mailboxFuture); } diff --git a/network/network/src/main/java/bisq/network/p2p/services/data/DataService.java b/network/network/src/main/java/bisq/network/p2p/services/data/DataService.java index 72efb6d06b..1f50dae4ab 100644 --- a/network/network/src/main/java/bisq/network/p2p/services/data/DataService.java +++ b/network/network/src/main/java/bisq/network/p2p/services/data/DataService.java @@ -228,6 +228,7 @@ public CompletableFuture addMailboxData(MailboxData mailboxData return storageService.getOrCreateMailboxDataStore(mailboxData.getClassName()) .thenApply(store -> { try { + // Send 2 versions AddMailboxRequest request = AddMailboxRequest.from(mailboxData, senderKeyPair, receiverPublicKey); DataStorageResult dataStorageResult = store.add(request); if (dataStorageResult.isSuccess()) { @@ -254,6 +255,14 @@ public CompletableFuture removeAuthenticatedData(AuthenticatedD try { RemoveAuthenticatedDataRequest request = RemoveAuthenticatedDataRequest.from(store, authenticatedData, keyPair); DataStorageResult dataStorageResult = store.remove(request); + + // Send also with version 0 for backward compatibility + RemoveAuthenticatedDataRequest oldVersion = RemoveAuthenticatedDataRequest.cloneWithVersion0(request); + DataStorageResult oldVersionDataStorageResult = store.remove(oldVersion); + if (dataStorageResult.isSuccess() || oldVersionDataStorageResult.isSuccess()) { + broadcasters.forEach(broadcaster -> broadcaster.broadcast(oldVersion)); + } + if (dataStorageResult.isSuccess()) { return new BroadcastResult(broadcasters.stream().map(broadcaster -> broadcaster.broadcast(request))); } else { @@ -276,6 +285,14 @@ public CompletableFuture removeMailboxData(MailboxData mailboxD try { RemoveMailboxRequest request = RemoveMailboxRequest.from(mailboxData, keyPair); DataStorageResult dataStorageResult = store.remove(request); + + // Send also with version 0 for backward compatibility + RemoveMailboxRequest oldVersion = RemoveMailboxRequest.cloneWithVersion0(request); + DataStorageResult oldVersionDataStorageResult = store.remove(oldVersion); + if (dataStorageResult.isSuccess() || oldVersionDataStorageResult.isSuccess()) { + broadcasters.forEach(broadcaster -> broadcaster.broadcast(oldVersion)); + } + if (dataStorageResult.isSuccess()) { return new BroadcastResult(broadcasters.stream().map(broadcaster -> broadcaster.broadcast(request))); } else { diff --git a/network/network/src/main/java/bisq/network/p2p/services/data/inventory/Inventory.java b/network/network/src/main/java/bisq/network/p2p/services/data/inventory/Inventory.java index 764e40ca2a..e9b267522e 100644 --- a/network/network/src/main/java/bisq/network/p2p/services/data/inventory/Inventory.java +++ b/network/network/src/main/java/bisq/network/p2p/services/data/inventory/Inventory.java @@ -41,7 +41,7 @@ public final class Inventory implements NetworkProto { private final List entries; private final boolean maxSizeReached; - @EqualsAndHashCode.Exclude // transient are excluded by default but let's make it more explicit + // transient fields are excluded by default for EqualsAndHashCode private transient final Optional cachedSerializedSize; public Inventory(Collection entries, boolean maxSizeReached) { diff --git a/network/network/src/main/java/bisq/network/p2p/services/data/inventory/InventoryRequest.java b/network/network/src/main/java/bisq/network/p2p/services/data/inventory/InventoryRequest.java index 532bf91c82..207a309964 100644 --- a/network/network/src/main/java/bisq/network/p2p/services/data/inventory/InventoryRequest.java +++ b/network/network/src/main/java/bisq/network/p2p/services/data/inventory/InventoryRequest.java @@ -33,6 +33,7 @@ public final class InventoryRequest implements BroadcastMessage, Request { private static final int VERSION = 1; + @EqualsAndHashCode.Exclude @ExcludeForHash private final int version; private final InventoryFilter inventoryFilter; diff --git a/network/network/src/main/java/bisq/network/p2p/services/data/inventory/InventoryResponse.java b/network/network/src/main/java/bisq/network/p2p/services/data/inventory/InventoryResponse.java index 0f1751be5a..8c0b03eb63 100644 --- a/network/network/src/main/java/bisq/network/p2p/services/data/inventory/InventoryResponse.java +++ b/network/network/src/main/java/bisq/network/p2p/services/data/inventory/InventoryResponse.java @@ -30,10 +30,12 @@ public final class InventoryResponse implements BroadcastMessage, Response { private static final int VERSION = 1; + @EqualsAndHashCode.Exclude @ExcludeForHash private final int version; // After v 2.0.6 version 0 should not be used anymore. Then we can remove the excludeOnlyInVersions param to // not need to maintain future versions. We add though hypothetical versions 2 and 3 for safety + @EqualsAndHashCode.Exclude @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) private final Inventory inventory; private final int requestNonce; diff --git a/network/network/src/main/java/bisq/network/p2p/services/data/inventory/InventoryResponseService.java b/network/network/src/main/java/bisq/network/p2p/services/data/inventory/InventoryResponseService.java index 706b3981d8..4f4f292a24 100644 --- a/network/network/src/main/java/bisq/network/p2p/services/data/inventory/InventoryResponseService.java +++ b/network/network/src/main/java/bisq/network/p2p/services/data/inventory/InventoryResponseService.java @@ -82,11 +82,11 @@ private void handleInventoryRequest(InventoryRequest request, Connection connect long ts = System.currentTimeMillis(); int requestersVersion = request.getVersion(); - // We filter out version 1 obejcts in AddAuthenticatedDataRequest objects which would break the hash when requested from old nodes (pre v.2.0.5) + // We filter out version 1 objects in Add/Remove DataRequest objects which would break the hash when requested from old nodes (pre v.2.0.5) // This code can be removed once there are no old nodes expected in the network anymore. - Predicate addAuthenticatedDataRequestPredicate = distributedDataVersion -> requestersVersion > 0 || distributedDataVersion == 0; + Predicate predicate = distributedDataVersion -> requestersVersion > 0 || distributedDataVersion == 0; - Inventory inventory = filterService.createInventory(inventoryFilter, addAuthenticatedDataRequestPredicate); + Inventory inventory = filterService.createInventory(inventoryFilter, predicate); // The requestersVersion param can be removed once there are no old nodes expected in the network anymore. InventoryResponse inventoryResponse = new InventoryResponse(requestersVersion, inventory, request.getNonce()); diff --git a/network/network/src/main/java/bisq/network/p2p/services/data/inventory/filter/FilterService.java b/network/network/src/main/java/bisq/network/p2p/services/data/inventory/filter/FilterService.java index 5eea1e3125..b6750f01ce 100644 --- a/network/network/src/main/java/bisq/network/p2p/services/data/inventory/filter/FilterService.java +++ b/network/network/src/main/java/bisq/network/p2p/services/data/inventory/filter/FilterService.java @@ -58,15 +58,15 @@ public FilterService(StorageService storageService, int maxSize) { abstract protected boolean isAddAppendOnlyDataRequestMissing(T filter, Map.Entry entry); - public Inventory createInventory(InventoryFilter inventoryFilter, Predicate addAuthenticatedDataRequestPredicate) { + public Inventory createInventory(InventoryFilter inventoryFilter, Predicate predicate) { final AtomicInteger accumulatedSize = new AtomicInteger(); final AtomicBoolean maxSizeReached = new AtomicBoolean(); // The type is not defined at compile time, thus we do a safe cast T filter = safeCast(inventoryFilter); - List dataRequests = getAuthenticatedDataRequests(filter, accumulatedSize, maxSizeReached, addAuthenticatedDataRequestPredicate); + List dataRequests = getAuthenticatedDataRequests(filter, accumulatedSize, maxSizeReached, predicate); if (!maxSizeReached.get()) { - dataRequests.addAll(getMailboxRequests(filter, accumulatedSize, maxSizeReached)); + dataRequests.addAll(getMailboxRequests(filter, accumulatedSize, maxSizeReached, predicate)); } if (!maxSizeReached.get()) { @@ -83,7 +83,7 @@ public Inventory createInventory(InventoryFilter inventoryFilter, Predicate getAuthenticatedDataRequests(T filter, AtomicInteger accumulatedSize, AtomicBoolean maxSizeReached, - Predicate addAuthenticatedDataRequestPredicate) { + Predicate predicate) { List addRequests = new ArrayList<>(); List removeRequests = new ArrayList<>(); storageService.getAuthenticatedDataStoreMaps().flatMap(map -> map.entrySet().stream()) @@ -93,11 +93,14 @@ private List getAuthenticatedDataRequests(T filter, if (dataRequest instanceof AddAuthenticatedDataRequest) { AddAuthenticatedDataRequest addAuthenticatedDataRequest = (AddAuthenticatedDataRequest) dataRequest; DistributedData distributedData = addAuthenticatedDataRequest.getAuthenticatedSequentialData().getAuthenticatedData().getDistributedData(); - if (addAuthenticatedDataRequestPredicate.test(distributedData.getVersion())) { + if (predicate.test(distributedData.getVersion())) { addRequests.add(addAuthenticatedDataRequest); } } else if (dataRequest instanceof RemoveAuthenticatedDataRequest) { - removeRequests.add((RemoveAuthenticatedDataRequest) dataRequest); + RemoveAuthenticatedDataRequest removeAuthenticatedDataRequest = (RemoveAuthenticatedDataRequest) dataRequest; + if (predicate.test(removeAuthenticatedDataRequest.getVersion())) { + removeRequests.add(removeAuthenticatedDataRequest); + } } // Refresh is ignored } @@ -132,7 +135,8 @@ private List getAuthenticatedDataRequests(T filter, private List getMailboxRequests(T filter, AtomicInteger accumulatedSize, - AtomicBoolean maxSizeReached) { + AtomicBoolean maxSizeReached, + Predicate predicate) { List addRequests = new ArrayList<>(); List removeRequests = new ArrayList<>(); storageService.getMailboxStoreMaps().flatMap(map -> map.entrySet().stream()) @@ -140,19 +144,17 @@ private List getMailboxRequests(T filter, if (isMailboxRequestMissing(filter, mapEntry)) { MailboxRequest dataRequest = mapEntry.getValue(); if (dataRequest instanceof AddMailboxRequest) { - addRequests.add((AddMailboxRequest) dataRequest); + AddMailboxRequest addMailboxRequest = (AddMailboxRequest) dataRequest; + if (predicate.test(addMailboxRequest.getMailboxSequentialData().getMailboxData().getVersion())) { + addRequests.add(addMailboxRequest); + } } else if (dataRequest instanceof RemoveMailboxRequest) { - removeRequests.add((RemoveMailboxRequest) dataRequest); + RemoveMailboxRequest removeMailboxRequest = (RemoveMailboxRequest) dataRequest; + if (predicate.test(removeMailboxRequest.getVersion())) { + removeRequests.add(removeMailboxRequest); + } } } - /* if (!hashSetFilter.getFilterEntries().contains(toFilterEntry(mapEntry))) { - MailboxRequest dataRequest = mapEntry.getValue(); - if (dataRequest instanceof AddMailboxRequest) { - addRequests.add((AddMailboxRequest) dataRequest); - } else if (dataRequest instanceof RemoveMailboxRequest) { - removeRequests.add((RemoveMailboxRequest) dataRequest); - } - }*/ }); List sortedAndFilteredRequests = addRequests.stream() .sorted((o1, o2) -> Integer.compare(o2.getMailboxSequentialData().getMailboxData().getMetaData().getPriority(), diff --git a/network/network/src/main/java/bisq/network/p2p/services/data/storage/MetaData.java b/network/network/src/main/java/bisq/network/p2p/services/data/storage/MetaData.java index e0053f819c..7c2d7b86f0 100644 --- a/network/network/src/main/java/bisq/network/p2p/services/data/storage/MetaData.java +++ b/network/network/src/main/java/bisq/network/p2p/services/data/storage/MetaData.java @@ -52,9 +52,13 @@ public final class MetaData implements NetworkProto { public static final int HIGH_PRIORITY = 1; public static final int HIGHEST_PRIORITY = 2; + // How long data are kept in the storage map private final long ttl; + // Used for inventory request priority of delivery if inventory size exceeds limit private final int priority; + // Used for name of storage file, for lookup of the store for a given distributedData object and for logging private final String className; + // Max file size of the storage file private final int maxMapSize; public MetaData(String className) { diff --git a/network/network/src/main/java/bisq/network/p2p/services/data/storage/auth/AddAuthenticatedDataRequest.java b/network/network/src/main/java/bisq/network/p2p/services/data/storage/auth/AddAuthenticatedDataRequest.java index b74c6774dd..ce6097ed4e 100644 --- a/network/network/src/main/java/bisq/network/p2p/services/data/storage/auth/AddAuthenticatedDataRequest.java +++ b/network/network/src/main/java/bisq/network/p2p/services/data/storage/auth/AddAuthenticatedDataRequest.java @@ -70,7 +70,7 @@ public static AddAuthenticatedDataRequest from(AuthenticatedDataStorageService s private final byte[] signature; @Getter private final byte[] ownerPublicKeyBytes; - @EqualsAndHashCode.Exclude // transient are excluded by default but let's make it more explicit + // transient fields are excluded by default for EqualsAndHashCode private transient final PublicKey ownerPublicKey; public AddAuthenticatedDataRequest(AuthenticatedSequentialData authenticatedSequentialData, byte[] signature, PublicKey ownerPublicKey) { diff --git a/network/network/src/main/java/bisq/network/p2p/services/data/storage/auth/AuthenticatedDataStorageService.java b/network/network/src/main/java/bisq/network/p2p/services/data/storage/auth/AuthenticatedDataStorageService.java index 70a2dd8e57..7740b988d6 100644 --- a/network/network/src/main/java/bisq/network/p2p/services/data/storage/auth/AuthenticatedDataStorageService.java +++ b/network/network/src/main/java/bisq/network/p2p/services/data/storage/auth/AuthenticatedDataStorageService.java @@ -24,6 +24,7 @@ import bisq.network.p2p.services.data.storage.DataStorageResult; import bisq.network.p2p.services.data.storage.DataStorageService; import bisq.network.p2p.services.data.storage.DataStore; +import bisq.network.p2p.services.data.storage.MetaData; import bisq.network.p2p.services.data.storage.auth.authorized.AuthorizedData; import bisq.persistence.PersistenceService; import bisq.security.DigestUtil; @@ -31,6 +32,7 @@ import lombok.extern.slf4j.Slf4j; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.TimeUnit; @@ -84,6 +86,7 @@ public DataStorageResult add(AddAuthenticatedDataRequest request) { if (isExceedingMapSize()) { return new DataStorageResult(false).maxMapSizeReached(); } + requestFromMap = map.get(byteArray); if (request.equals(requestFromMap)) { return new DataStorageResult(false).requestAlreadyReceived(); @@ -123,16 +126,17 @@ public DataStorageResult add(AddAuthenticatedDataRequest request) { return new DataStorageResult(false).signatureInvalid(); } map.put(byteArray, request); + + // If we had already the data (only updated seq nr) we return true to broadcast the message but do not + // notify listeners as data has not changed. + if (requestFromMap != null) { + log.warn("requestFromMap != null. request={}", request); + return new DataStorageResult(true).payloadAlreadyStored(); + } } persist(); - // If we had already the data (only updated seq nr) we return false as well and do not notify listeners. - /* if (requestFromMap != null) { - log.warn("requestFromMap != null. request={}", request); - return new Result(false).payloadAlreadyStored(); - }*/ - listeners.forEach(listener -> { try { listener.onAdded(authenticatedData); @@ -157,7 +161,7 @@ public DataStorageResult remove(RemoveAuthenticatedDataRequest request) { // track of the sequence number map.put(byteArray, request); persist(); - return new DataStorageResult(false).noEntry(); + return new DataStorageResult(true).noEntry(); } if (requestFromMap instanceof RemoveAuthenticatedDataRequest) { @@ -168,7 +172,7 @@ public DataStorageResult remove(RemoveAuthenticatedDataRequest request) { map.put(byteArray, request); persist(); } - return new DataStorageResult(false).alreadyRemoved(); + return new DataStorageResult(true).alreadyRemoved(); } // At that point we know requestFromMap is an AddProtectedDataRequest @@ -176,14 +180,6 @@ public DataStorageResult remove(RemoveAuthenticatedDataRequest request) { "requestFromMap expected be type of AddProtectedDataRequest"); AddAuthenticatedDataRequest addRequestFromMap = (AddAuthenticatedDataRequest) requestFromMap; - // The metaData provided in the RemoveAuthenticatedDataRequest must be the same as we had in the AddAuthenticatedDataRequest - // The AddAuthenticatedDataRequest does use the metaData from the code base, not one provided by the message, thus it is trusted. - if (!request.getMetaData().equals(addRequestFromMap.getAuthenticatedSequentialData().getAuthenticatedData().getMetaData())) { - log.warn("MetaData of remove request not matching the one from the addRequest from the map. {} vs. {}", - request.getMetaData(), - addRequestFromMap.getAuthenticatedSequentialData().getAuthenticatedData().getMetaData()); - } - // We have an entry, lets validate if we can remove it AuthenticatedSequentialData dataFromMap = addRequestFromMap.getAuthenticatedSequentialData(); authenticatedDataFromMap = dataFromMap.getAuthenticatedData(); @@ -201,9 +197,26 @@ public DataStorageResult remove(RemoveAuthenticatedDataRequest request) { log.warn("Signature is invalid at remove. request={}", request); return new DataStorageResult(false).signatureInvalid(); } + + // As metaData from distributedData is taken from the users current code base but the one from RemoveAuthenticatedDataRequest + // is from the senders version (taken from senders distributedData) it could be different if both users had + // different versions and metaData has changed between those versions. + // If we detect such a difference we use our metaData version. This also protects against malicious manipulation. + MetaData metaDataFromDistributedData = addRequestFromMap.getAuthenticatedSequentialData().getAuthenticatedData().getMetaData(); + if (!request.getMetaDataFromProto().equals(metaDataFromDistributedData)) { + request.setMetaDataFromDistributedData(Optional.of(metaDataFromDistributedData)); + log.warn("MetaData of remove request not matching the one from the addRequest from the map. We override " + + "metadata with the one we have from the associated distributed data." + + "{} vs. {}", + request.getMetaDataFromProto(), + metaDataFromDistributedData); + } + map.put(byteArray, request); } + persist(); + listeners.forEach(listener -> { try { listener.onRemoved(authenticatedDataFromMap); diff --git a/network/network/src/main/java/bisq/network/p2p/services/data/storage/auth/RefreshAuthenticatedDataRequest.java b/network/network/src/main/java/bisq/network/p2p/services/data/storage/auth/RefreshAuthenticatedDataRequest.java index baa640e84c..695c986b1d 100644 --- a/network/network/src/main/java/bisq/network/p2p/services/data/storage/auth/RefreshAuthenticatedDataRequest.java +++ b/network/network/src/main/java/bisq/network/p2p/services/data/storage/auth/RefreshAuthenticatedDataRequest.java @@ -17,6 +17,7 @@ package bisq.network.p2p.services.data.storage.auth; +import bisq.common.annotation.ExcludeForHash; import bisq.common.encoding.Hex; import bisq.common.util.MathUtils; import bisq.common.validation.NetworkDataValidation; @@ -39,6 +40,8 @@ @EqualsAndHashCode @Slf4j public final class RefreshAuthenticatedDataRequest implements DataRequest { + private static final int VERSION = 1; + public static RefreshAuthenticatedDataRequest from(AuthenticatedDataStorageService store, AuthenticatedData authenticatedData, KeyPair keyPair) @@ -46,39 +49,39 @@ public static RefreshAuthenticatedDataRequest from(AuthenticatedDataStorageServi byte[] hash = DigestUtil.hash(authenticatedData.serializeForHash()); byte[] signature = SignatureUtil.sign(hash, keyPair.getPrivate()); int newSequenceNumber = store.getSequenceNumber(hash) + 1; - return new RefreshAuthenticatedDataRequest(authenticatedData.getMetaData(), + PublicKey publicKey = keyPair.getPublic(); + return new RefreshAuthenticatedDataRequest(VERSION, + authenticatedData.getMetaData(), hash, - keyPair.getPublic(), + publicKey.getEncoded(), + publicKey, newSequenceNumber, signature); } + + @EqualsAndHashCode.Exclude + @ExcludeForHash private final MetaData metaData; + + @EqualsAndHashCode.Exclude + @ExcludeForHash + private final int version; + private final byte[] hash; private final byte[] ownerPublicKeyBytes; // 442 bytes transient private final PublicKey ownerPublicKey; private final int sequenceNumber; private final byte[] signature; // 47 bytes - public RefreshAuthenticatedDataRequest(MetaData metaData, - byte[] hash, - PublicKey ownerPublicKey, - int sequenceNumber, - byte[] signature) { - this(metaData, - hash, - ownerPublicKey.getEncoded(), - ownerPublicKey, - sequenceNumber, - signature); - } - - private RefreshAuthenticatedDataRequest(MetaData metaData, + private RefreshAuthenticatedDataRequest(int version, + MetaData metaData, byte[] hash, byte[] ownerPublicKeyBytes, PublicKey ownerPublicKey, int sequenceNumber, byte[] signature) { + this.version = version; this.metaData = metaData; this.hash = hash; this.ownerPublicKeyBytes = ownerPublicKeyBytes; @@ -109,6 +112,7 @@ public bisq.network.protobuf.RefreshAuthenticatedDataRequest toValueProto(boolea @Override public bisq.network.protobuf.RefreshAuthenticatedDataRequest.Builder getValueBuilder(boolean serializeForHash) { return bisq.network.protobuf.RefreshAuthenticatedDataRequest.newBuilder() + .setVersion(version) .setMetaData(metaData.toProto(serializeForHash)) .setHash(ByteString.copyFrom(hash)) .setOwnerPublicKeyBytes(ByteString.copyFrom(ownerPublicKeyBytes)) @@ -121,6 +125,7 @@ public static RefreshAuthenticatedDataRequest fromProto(bisq.network.protobuf.Re try { PublicKey ownerPublicKey = KeyGeneration.generatePublic(ownerPublicKeyBytes); return new RefreshAuthenticatedDataRequest( + proto.getVersion(), MetaData.fromProto(proto.getMetaData()), proto.getHash().toByteArray(), ownerPublicKeyBytes, @@ -184,7 +189,8 @@ public String getClassName() { @Override public String toString() { return "RefreshAuthenticatedDataRequest{" + - "\r\n metaData=" + metaData + + "\r\n version=" + version + + ",\r\n metaData=" + metaData + ",\r\n hash=" + Hex.encode(hash) + ",\r\n ownerPublicKeyBytes=" + Hex.encode(ownerPublicKeyBytes) + ",\r\n sequenceNumber=" + sequenceNumber + diff --git a/network/network/src/main/java/bisq/network/p2p/services/data/storage/auth/RemoveAuthenticatedDataRequest.java b/network/network/src/main/java/bisq/network/p2p/services/data/storage/auth/RemoveAuthenticatedDataRequest.java index be3d9d2a3d..b07bb70462 100644 --- a/network/network/src/main/java/bisq/network/p2p/services/data/storage/auth/RemoveAuthenticatedDataRequest.java +++ b/network/network/src/main/java/bisq/network/p2p/services/data/storage/auth/RemoveAuthenticatedDataRequest.java @@ -17,6 +17,7 @@ package bisq.network.p2p.services.data.storage.auth; +import bisq.common.annotation.ExcludeForHash; import bisq.common.encoding.Hex; import bisq.common.util.MathUtils; import bisq.common.validation.NetworkDataValidation; @@ -29,58 +30,73 @@ import com.google.protobuf.ByteString; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.PublicKey; import java.util.Arrays; +import java.util.Optional; @Getter @EqualsAndHashCode @Slf4j public final class RemoveAuthenticatedDataRequest implements AuthenticatedDataRequest, RemoveDataRequest { + private static final int VERSION = 1; public static RemoveAuthenticatedDataRequest from(AuthenticatedDataStorageService store, AuthenticatedData authenticatedData, KeyPair keyPair) throws GeneralSecurityException { byte[] hash = DigestUtil.hash(authenticatedData.serializeForHash()); byte[] signature = SignatureUtil.sign(hash, keyPair.getPrivate()); int newSequenceNumber = store.getSequenceNumber(hash) + 1; - return new RemoveAuthenticatedDataRequest(authenticatedData.getMetaData(), + PublicKey publicKey = keyPair.getPublic(); + return new RemoveAuthenticatedDataRequest(VERSION, + authenticatedData.getMetaData(), hash, - keyPair.getPublic(), + publicKey.getEncoded(), + publicKey, newSequenceNumber, signature); } + + public static RemoveAuthenticatedDataRequest cloneWithVersion0(RemoveAuthenticatedDataRequest request) { + return new RemoveAuthenticatedDataRequest(0, + request.getMetaData(), + request.getHash(), + request.getOwnerPublicKeyBytes(), + request.getOwnerPublicKey(), + request.getSequenceNumber(), + request.getSignature()); + } + + @EqualsAndHashCode.Exclude + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) private final MetaData metaData; + + @EqualsAndHashCode.Exclude + @ExcludeForHash + private final int version; + private final byte[] hash; private final byte[] ownerPublicKeyBytes; transient private PublicKey ownerPublicKey; private final int sequenceNumber; private final byte[] signature; private final long created; + @Setter + private transient Optional metaDataFromDistributedData = Optional.empty(); - public RemoveAuthenticatedDataRequest(MetaData metaData, - byte[] hash, - PublicKey ownerPublicKey, - int sequenceNumber, - byte[] signature) { - this(metaData, - hash, - ownerPublicKey.getEncoded(), - ownerPublicKey, - sequenceNumber, - signature); - } - - private RemoveAuthenticatedDataRequest(MetaData metaData, + private RemoveAuthenticatedDataRequest(int version, + MetaData metaData, byte[] hash, byte[] ownerPublicKeyBytes, PublicKey ownerPublicKey, int sequenceNumber, byte[] signature) { - this(metaData, + this(version, + metaData, hash, ownerPublicKeyBytes, ownerPublicKey, @@ -89,13 +105,15 @@ private RemoveAuthenticatedDataRequest(MetaData metaData, System.currentTimeMillis()); } - private RemoveAuthenticatedDataRequest(MetaData metaData, + private RemoveAuthenticatedDataRequest(int version, + MetaData metaData, byte[] hash, byte[] ownerPublicKeyBytes, PublicKey ownerPublicKey, int sequenceNumber, byte[] signature, long created) { + this.version = version; this.metaData = metaData; this.hash = hash; this.ownerPublicKeyBytes = ownerPublicKeyBytes; @@ -128,6 +146,7 @@ public bisq.network.protobuf.RemoveAuthenticatedDataRequest toValueProto(boolean @Override public bisq.network.protobuf.RemoveAuthenticatedDataRequest.Builder getValueBuilder(boolean serializeForHash) { return bisq.network.protobuf.RemoveAuthenticatedDataRequest.newBuilder() + .setVersion(version) .setMetaData(metaData.toProto(serializeForHash)) .setHash(ByteString.copyFrom(hash)) .setOwnerPublicKeyBytes(ByteString.copyFrom(ownerPublicKeyBytes)) @@ -141,6 +160,7 @@ public static RemoveAuthenticatedDataRequest fromProto(bisq.network.protobuf.Rem try { PublicKey ownerPublicKey = KeyGeneration.generatePublic(ownerPublicKeyBytes); return new RemoveAuthenticatedDataRequest( + proto.getVersion(), MetaData.fromProto(proto.getMetaData()), proto.getHash().toByteArray(), ownerPublicKeyBytes, @@ -155,9 +175,17 @@ public static RemoveAuthenticatedDataRequest fromProto(bisq.network.protobuf.Rem } } + public MetaData getMetaDataFromProto() { + return metaData; + } + + public MetaData getMetaData() { + return metaDataFromDistributedData.orElse(metaData); + } + @Override public double getCostFactor() { - return MathUtils.bounded(0.1, 0.3, metaData.getCostFactor()); + return MathUtils.bounded(0.1, 0.3, getMetaData().getCostFactor()); } public boolean isSignatureInvalid() { @@ -185,22 +213,24 @@ public boolean isSequenceNrInvalid(long seqNumberFromMap) { @Override public boolean isExpired() { - return (System.currentTimeMillis() - created) > metaData.getTtl(); + return (System.currentTimeMillis() - created) > getMetaData().getTtl(); } @Override public int getMaxMapSize() { - return metaData.getMaxMapSize(); + return getMetaData().getMaxMapSize(); } public String getClassName() { - return metaData.getClassName(); + return getMetaData().getClassName(); } @Override public String toString() { return "RemoveAuthenticatedDataRequest{" + - "\r\n metaData=" + metaData + + "\r\n version=" + version + + ",\r\n metaData=" + metaData + + ",\r\n metaDataFromDistributedData=" + metaDataFromDistributedData + ",\r\n hash=" + Hex.encode(hash) + ",\r\n ownerPublicKeyBytes=" + Hex.encode(ownerPublicKeyBytes) + ",\r\n ownerPublicKey=" + ownerPublicKey + diff --git a/network/network/src/main/java/bisq/network/p2p/services/data/storage/mailbox/MailboxData.java b/network/network/src/main/java/bisq/network/p2p/services/data/storage/mailbox/MailboxData.java index 9675a0eb87..e7e035f125 100644 --- a/network/network/src/main/java/bisq/network/p2p/services/data/storage/mailbox/MailboxData.java +++ b/network/network/src/main/java/bisq/network/p2p/services/data/storage/mailbox/MailboxData.java @@ -17,12 +17,12 @@ package bisq.network.p2p.services.data.storage.mailbox; +import bisq.common.annotation.ExcludeForHash; import bisq.network.p2p.services.confidential.ConfidentialMessage; import bisq.network.p2p.services.data.storage.MetaData; import bisq.network.p2p.services.data.storage.StorageData; import lombok.EqualsAndHashCode; import lombok.Getter; -import lombok.ToString; import java.util.concurrent.TimeUnit; @@ -35,17 +35,34 @@ * Holds the ConfidentialMessage and metaData providing information about the message type. */ @EqualsAndHashCode -@ToString @Getter public final class MailboxData implements StorageData { + private static final int VERSION = 1; + + public static MailboxData cloneWithVersion0(MailboxData mailboxData) { + return new MailboxData(0, mailboxData.getMetaData(), mailboxData.getConfidentialMessage()); + } + + @EqualsAndHashCode.Exclude + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) + private final MetaData metaData; + + @EqualsAndHashCode.Exclude + @ExcludeForHash + private final int version; + public final static long MAX_TLL = TimeUnit.DAYS.toMillis(15); private final ConfidentialMessage confidentialMessage; - private final MetaData metaData; - public MailboxData(ConfidentialMessage confidentialMessage, MetaData metaData) { - this.confidentialMessage = confidentialMessage; + public MailboxData(MetaData metaData, ConfidentialMessage confidentialMessage) { + this(VERSION, metaData, confidentialMessage); + } + + private MailboxData(int version, MetaData metaData, ConfidentialMessage confidentialMessage) { + this.version = version; this.metaData = metaData; + this.confidentialMessage = confidentialMessage; verify(); } @@ -62,13 +79,16 @@ public bisq.network.protobuf.MailboxData toProto(boolean serializeForHash) { @Override public bisq.network.protobuf.MailboxData.Builder getBuilder(boolean serializeForHash) { return bisq.network.protobuf.MailboxData.newBuilder() - .setConfidentialMessage(confidentialMessage.toProto(serializeForHash).getConfidentialMessage()) - .setMetaData(metaData.toProto(serializeForHash)); + .setVersion(version) + .setMetaData(metaData.toProto(serializeForHash)) + .setConfidentialMessage(confidentialMessage.toProto(serializeForHash).getConfidentialMessage()); } public static MailboxData fromProto(bisq.network.protobuf.MailboxData proto) { - return new MailboxData(ConfidentialMessage.fromProto(proto.getConfidentialMessage()), - MetaData.fromProto(proto.getMetaData())); + return new MailboxData( + proto.getVersion(), + MetaData.fromProto(proto.getMetaData()), + ConfidentialMessage.fromProto(proto.getConfidentialMessage())); } public String getClassName() { @@ -79,4 +99,13 @@ public String getClassName() { public boolean isDataInvalid(byte[] ownerPubKeyHash) { return confidentialMessage.isDataInvalid(ownerPubKeyHash); } + + @Override + public String toString() { + return "MailboxData{" + + "metaData=" + metaData + + ", version=" + version + + ", confidentialMessage=" + confidentialMessage + + '}'; + } } diff --git a/network/network/src/main/java/bisq/network/p2p/services/data/storage/mailbox/MailboxDataStorageService.java b/network/network/src/main/java/bisq/network/p2p/services/data/storage/mailbox/MailboxDataStorageService.java index cc5a70c5dd..f78e2ca0d9 100644 --- a/network/network/src/main/java/bisq/network/p2p/services/data/storage/mailbox/MailboxDataStorageService.java +++ b/network/network/src/main/java/bisq/network/p2p/services/data/storage/mailbox/MailboxDataStorageService.java @@ -23,11 +23,13 @@ import bisq.network.p2p.services.data.storage.DataStorageResult; import bisq.network.p2p.services.data.storage.DataStorageService; import bisq.network.p2p.services.data.storage.DataStore; +import bisq.network.p2p.services.data.storage.MetaData; import bisq.persistence.PersistenceService; import bisq.security.DigestUtil; import lombok.extern.slf4j.Slf4j; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.TimeUnit; @@ -75,13 +77,13 @@ public DataStorageResult add(AddMailboxRequest request) { if (isExceedingMapSize()) { return new DataStorageResult(false).maxMapSizeReached(); } - requestFromMap = map.get(byteArray); - int sequenceNumberFromMap = requestFromMap != null ? requestFromMap.getSequenceNumber() : 0; + requestFromMap = map.get(byteArray); if (request.equals(requestFromMap)) { return new DataStorageResult(false).requestAlreadyReceived(); } + int sequenceNumberFromMap = requestFromMap != null ? requestFromMap.getSequenceNumber() : 0; if (requestFromMap != null && mailboxSequentialData.isSequenceNrInvalid(sequenceNumberFromMap)) { return new DataStorageResult(false).sequenceNrInvalid(); } @@ -102,15 +104,16 @@ public DataStorageResult add(AddMailboxRequest request) { return new DataStorageResult(false).signatureInvalid(); } map.put(byteArray, request); - } - persist(); - // If we had already the data (only updated seq nr) we return false as well and do not notify listeners. - // This should only happen if client re-publishes mailbox data - if (requestFromMap != null) { - return new DataStorageResult(false).payloadAlreadyStored(); + // If we had already the data (only updated seq nr) we return true to broadcast the message but do not + // notify listeners as data has not changed. + if (requestFromMap != null) { + return new DataStorageResult(true).payloadAlreadyStored(); + } } + persist(); + listeners.forEach(listener -> { try { listener.onAdded(mailboxData); @@ -135,7 +138,7 @@ public DataStorageResult remove(RemoveMailboxRequest request) { // track of the sequence number map.put(byteArray, request); persist(); - return new DataStorageResult(false).noEntry(); + return new DataStorageResult(true).noEntry(); } if (requestFromMap instanceof RemoveMailboxRequest) { @@ -145,7 +148,7 @@ public DataStorageResult remove(RemoveMailboxRequest request) { map.put(byteArray, request); persist(); } - return new DataStorageResult(false).alreadyRemoved(); + return new DataStorageResult(true).alreadyRemoved(); } // At that point we know requestFromMap is an AddMailboxRequest @@ -166,6 +169,20 @@ public DataStorageResult remove(RemoveMailboxRequest request) { return new DataStorageResult(false).signatureInvalid(); } + // As metaData from mailboxData is taken from the users current code base but the one from RemoveMailboxRequest + // is from the senders version (taken from senders mailboxData) it could be different if both users had + // different versions and metaData has changed between those versions. + // If we detect such a difference we use our metaData version. This also protects against malicious manipulation. + MetaData metaDataFromMailboxData = sequentialDataFromMap.getMailboxData().getMetaData(); + if (!request.getMetaDataFromProto().equals(metaDataFromMailboxData)) { + request.setMetaDataFromDistributedData(Optional.of(metaDataFromMailboxData)); + log.warn("MetaData of remove request not matching the one from the addRequest from the map. We override " + + "metadata with the one we have from the associated mailbox data." + + "{} vs. {}", + request.getMetaDataFromProto(), + metaDataFromMailboxData); + } + map.put(byteArray, request); listeners.forEach(listener -> { try { diff --git a/network/network/src/main/java/bisq/network/p2p/services/data/storage/mailbox/MailboxSequentialData.java b/network/network/src/main/java/bisq/network/p2p/services/data/storage/mailbox/MailboxSequentialData.java index 307c8008d5..b396e36a57 100644 --- a/network/network/src/main/java/bisq/network/p2p/services/data/storage/mailbox/MailboxSequentialData.java +++ b/network/network/src/main/java/bisq/network/p2p/services/data/storage/mailbox/MailboxSequentialData.java @@ -38,7 +38,7 @@ public final class MailboxSequentialData implements NetworkProto { private final byte[] receiversPubKeyBytes; private final long created; private final int sequenceNumber; - @EqualsAndHashCode.Exclude // transient are excluded by default but let's make it more explicit + // transient fields are excluded by default for EqualsAndHashCode private transient final PublicKey receiversPubKey; public MailboxSequentialData(MailboxData mailboxData, diff --git a/network/network/src/main/java/bisq/network/p2p/services/data/storage/mailbox/RemoveMailboxRequest.java b/network/network/src/main/java/bisq/network/p2p/services/data/storage/mailbox/RemoveMailboxRequest.java index 65a37af41c..c9066d6fef 100644 --- a/network/network/src/main/java/bisq/network/p2p/services/data/storage/mailbox/RemoveMailboxRequest.java +++ b/network/network/src/main/java/bisq/network/p2p/services/data/storage/mailbox/RemoveMailboxRequest.java @@ -17,6 +17,8 @@ package bisq.network.p2p.services.data.storage.mailbox; +import bisq.common.annotation.ExcludeForHash; +import bisq.common.encoding.Hex; import bisq.common.util.MathUtils; import bisq.common.validation.NetworkDataValidation; import bisq.network.p2p.services.data.RemoveDataRequest; @@ -27,52 +29,71 @@ import com.google.protobuf.ByteString; import lombok.EqualsAndHashCode; import lombok.Getter; -import lombok.ToString; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.PublicKey; import java.util.Arrays; +import java.util.Optional; @Slf4j -@ToString @EqualsAndHashCode @Getter public final class RemoveMailboxRequest implements MailboxRequest, RemoveDataRequest { - private final MetaData metaData; - private final byte[] hash; - private final byte[] receiverPublicKeyBytes; - private final byte[] signature; - private final long created; - private transient PublicKey receiverPublicKey; + private static final int VERSION = 1; public static RemoveMailboxRequest from(MailboxData mailboxData, KeyPair receiverKeyPair) throws GeneralSecurityException { byte[] hash = DigestUtil.hash(mailboxData.serializeForHash()); byte[] signature = SignatureUtil.sign(hash, receiverKeyPair.getPrivate()); - return new RemoveMailboxRequest(mailboxData.getMetaData(), hash, receiverKeyPair.getPublic(), signature); - } - - // Receiver is owner for remove request - public RemoveMailboxRequest(MetaData metaData, - byte[] hash, - PublicKey receiverPublicKey, - byte[] signature) { - this(metaData, + PublicKey publicKey = receiverKeyPair.getPublic(); + long created = System.currentTimeMillis(); + return new RemoveMailboxRequest(VERSION, + mailboxData.getMetaData(), hash, - receiverPublicKey.getEncoded(), - receiverPublicKey, + publicKey.getEncoded(), + publicKey, signature, - System.currentTimeMillis()); + created); + } + + public static RemoveMailboxRequest cloneWithVersion0(RemoveMailboxRequest request) { + return new RemoveMailboxRequest(0, + request.getMetaData(), + request.getHash(), + request.getReceiverPublicKeyBytes(), + request.getReceiverPublicKey(), + request.getSignature(), + request.getCreated()); } - private RemoveMailboxRequest(MetaData metaData, + @EqualsAndHashCode.Exclude + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) + private final MetaData metaData; + + @EqualsAndHashCode.Exclude + @ExcludeForHash + private final int version; + + private final byte[] hash; + private final byte[] receiverPublicKeyBytes; + private final byte[] signature; + private final long created; + private transient PublicKey receiverPublicKey; + @Setter + private transient Optional metaDataFromDistributedData = Optional.empty(); + + // Receiver is owner for remove request + private RemoveMailboxRequest(int version, + MetaData metaData, byte[] hash, byte[] receiverPublicKeyBytes, PublicKey receiverPublicKey, byte[] signature, long created) { + this.version = version; this.metaData = metaData; this.hash = hash; this.receiverPublicKeyBytes = receiverPublicKeyBytes; @@ -104,6 +125,7 @@ public bisq.network.protobuf.RemoveMailboxRequest toValueProto(boolean serialize @Override public bisq.network.protobuf.RemoveMailboxRequest.Builder getValueBuilder(boolean serializeForHash) { return bisq.network.protobuf.RemoveMailboxRequest.newBuilder() + .setVersion(version) .setMetaData(metaData.toProto(serializeForHash)) .setHash(ByteString.copyFrom(hash)) .setReceiverPublicKeyBytes(ByteString.copyFrom(receiverPublicKeyBytes)) @@ -116,6 +138,7 @@ public static RemoveMailboxRequest fromProto(bisq.network.protobuf.RemoveMailbox try { PublicKey receiverPublicKey = KeyGeneration.generatePublic(receiverPublicKeyBytes); return new RemoveMailboxRequest( + proto.getVersion(), MetaData.fromProto(proto.getMetaData()), proto.getHash().toByteArray(), receiverPublicKeyBytes, @@ -129,9 +152,17 @@ public static RemoveMailboxRequest fromProto(bisq.network.protobuf.RemoveMailbox } } + public MetaData getMetaDataFromProto() { + return metaData; + } + + public MetaData getMetaData() { + return metaDataFromDistributedData.orElse(metaData); + } + @Override public double getCostFactor() { - return MathUtils.bounded(0.1, 0.3, metaData.getCostFactor()); + return MathUtils.bounded(0.1, 0.3, getMetaData().getCostFactor()); } public boolean isPublicKeyHashInvalid(MailboxSequentialData mailboxSequentialData) { @@ -160,7 +191,7 @@ public boolean isSequenceNrInvalid(long seqNumberFromMap) { } public String getClassName() { - return metaData.getClassName(); + return getMetaData().getClassName(); } @Override @@ -170,11 +201,26 @@ public int getSequenceNumber() { @Override public boolean isExpired() { - return (System.currentTimeMillis() - created) > Math.min(MailboxData.MAX_TLL, metaData.getTtl()); + return (System.currentTimeMillis() - created) > Math.min(MailboxData.MAX_TLL, getMetaData().getTtl()); } @Override public int getMaxMapSize() { - return metaData.getMaxMapSize(); + return getMetaData().getMaxMapSize(); + } + + + @Override + public String toString() { + return "RemoveMailboxRequest{" + + "metaData=" + metaData + + ", metaDataFromDistributedData=" + metaDataFromDistributedData + + ", version=" + version + + ", hash=" + Hex.encode(hash) + + ", receiverPublicKeyBytes=" + Hex.encode(receiverPublicKeyBytes) + + ", signature=" + Hex.encode(signature) + + ", created=" + created + + ", receiverPublicKey=" + receiverPublicKey + + '}'; } } diff --git a/network/network/src/main/java/bisq/network/p2p/services/peer_group/Peer.java b/network/network/src/main/java/bisq/network/p2p/services/peer_group/Peer.java index 619347d9c6..f46410cd07 100644 --- a/network/network/src/main/java/bisq/network/p2p/services/peer_group/Peer.java +++ b/network/network/src/main/java/bisq/network/p2p/services/peer_group/Peer.java @@ -39,7 +39,6 @@ public final class Peer implements NetworkProto, Comparable { @EqualsAndHashCode.Include private final Address address; - private final Capability capability; private final NetworkLoad networkLoad; private final boolean isOutboundConnection; diff --git a/network/network/src/main/java/bisq/network/p2p/services/peer_group/exchange/PeerExchangeRequest.java b/network/network/src/main/java/bisq/network/p2p/services/peer_group/exchange/PeerExchangeRequest.java index fac38d98cb..415df4211f 100644 --- a/network/network/src/main/java/bisq/network/p2p/services/peer_group/exchange/PeerExchangeRequest.java +++ b/network/network/src/main/java/bisq/network/p2p/services/peer_group/exchange/PeerExchangeRequest.java @@ -17,6 +17,7 @@ package bisq.network.p2p.services.peer_group.exchange; +import bisq.common.annotation.ExcludeForHash; import bisq.network.p2p.message.EnvelopePayloadMessage; import bisq.network.p2p.message.Request; import bisq.network.p2p.services.peer_group.Peer; @@ -38,6 +39,8 @@ public final class PeerExchangeRequest implements EnvelopePayloadMessage, Reques @Setter public static long maxNumPeers; private final int nonce; + @ExcludeForHash + @EqualsAndHashCode.Exclude private final List peers; public PeerExchangeRequest(int nonce, List peers) { diff --git a/network/network/src/main/java/bisq/network/p2p/services/peer_group/exchange/PeerExchangeResponse.java b/network/network/src/main/java/bisq/network/p2p/services/peer_group/exchange/PeerExchangeResponse.java index e08ee90547..fd29273f39 100644 --- a/network/network/src/main/java/bisq/network/p2p/services/peer_group/exchange/PeerExchangeResponse.java +++ b/network/network/src/main/java/bisq/network/p2p/services/peer_group/exchange/PeerExchangeResponse.java @@ -17,6 +17,7 @@ package bisq.network.p2p.services.peer_group.exchange; +import bisq.common.annotation.ExcludeForHash; import bisq.network.p2p.message.EnvelopePayloadMessage; import bisq.network.p2p.message.Response; import bisq.network.p2p.services.peer_group.Peer; @@ -38,6 +39,8 @@ public final class PeerExchangeResponse implements EnvelopePayloadMessage, Respo @Setter public static long maxNumPeers; private final int nonce; + @ExcludeForHash + @EqualsAndHashCode.Exclude private final List peers; public PeerExchangeResponse(int nonce, List peers) { diff --git a/network/network/src/main/proto/network.proto b/network/network/src/main/proto/network.proto index a3dfbbe6fd..87c29f1a11 100644 --- a/network/network/src/main/proto/network.proto +++ b/network/network/src/main/proto/network.proto @@ -243,6 +243,7 @@ message RemoveAuthenticatedDataRequest { sint32 sequenceNumber = 4; bytes signature = 5; sint64 created = 6; + sint32 version = 7; } message RefreshAuthenticatedDataRequest { MetaData metaData = 1; @@ -250,11 +251,13 @@ message RefreshAuthenticatedDataRequest { bytes ownerPublicKeyBytes = 3; sint32 sequenceNumber = 4; bytes signature = 5; + sint32 version = 6; } message MailboxData { ConfidentialMessage confidentialMessage = 1; MetaData metaData = 2; + sint32 version = 3; } message MailboxSequentialData { MailboxData mailboxData = 1; @@ -275,6 +278,7 @@ message RemoveMailboxRequest { bytes receiverPublicKeyBytes = 3; bytes signature = 4; sint64 created = 5; + sint32 version = 6; } message AddAppendOnlyDataRequest { diff --git a/security/src/main/java/bisq/security/keys/KeyBundle.java b/security/src/main/java/bisq/security/keys/KeyBundle.java index 97ab18a2f8..d74f92f8b0 100644 --- a/security/src/main/java/bisq/security/keys/KeyBundle.java +++ b/security/src/main/java/bisq/security/keys/KeyBundle.java @@ -17,10 +17,10 @@ public class KeyBundle implements PersistableProto { private final TorKeyPair torKeyPair; private final String keyId; @ToString.Exclude - @EqualsAndHashCode.Exclude // transient are excluded by default but let's make it more explicit + // transient fields are excluded by default for EqualsAndHashCode private transient final byte[] encodedPrivateKey; @ToString.Exclude - @EqualsAndHashCode.Exclude // transient are excluded by default but let's make it more explicit + // transient fields are excluded by default for EqualsAndHashCode private transient final byte[] encodedPublicKey; // private final I2pKeyPair i2PKeyPair; diff --git a/support/src/main/java/bisq/support/moderator/ModeratorService.java b/support/src/main/java/bisq/support/moderator/ModeratorService.java index c89c93505e..a437c676cd 100644 --- a/support/src/main/java/bisq/support/moderator/ModeratorService.java +++ b/support/src/main/java/bisq/support/moderator/ModeratorService.java @@ -166,6 +166,11 @@ public CompletableFuture banReportedUser(ReportToModeratorMessa UserIdentity selectedUserIdentity = userIdentityService.getSelectedUserIdentity(); KeyPair keyPair = selectedUserIdentity.getNetworkIdWithKeyPair().getKeyPair(); BannedUserProfileData data = new BannedUserProfileData(message.getAccusedUserProfile(), staticPublicKeysProvided); + + // Can be removed once there are no pre 2.1.0 versions out there anymore + BannedUserProfileData oldVersion = new BannedUserProfileData(0, message.getAccusedUserProfile(), staticPublicKeysProvided); + networkService.publishAuthorizedData(oldVersion, keyPair); + return networkService.publishAuthorizedData(data, keyPair); } diff --git a/support/src/main/java/bisq/support/release_manager/ReleaseManagerService.java b/support/src/main/java/bisq/support/release_manager/ReleaseManagerService.java index da6083eba8..782c2f47c2 100644 --- a/support/src/main/java/bisq/support/release_manager/ReleaseManagerService.java +++ b/support/src/main/java/bisq/support/release_manager/ReleaseManagerService.java @@ -101,6 +101,19 @@ public CompletableFuture publishReleaseNotification(boolean isPreReleas version, profileId, staticPublicKeysProvided); + + // Can be removed once there are no pre 2.1.0 versions out there anymore + ReleaseNotification oldVersion = new ReleaseNotification(0, + StringUtils.createUid(), + new Date().getTime(), + isPreRelease, + isLauncherUpdate, + releaseNotes, + version, + profileId, + staticPublicKeysProvided); + networkService.publishAuthorizedData(oldVersion, keyPair); + return networkService.publishAuthorizedData(releaseNotification, keyPair) .thenApply(broadCastDataResult -> true); } diff --git a/support/src/main/java/bisq/support/security_manager/SecurityManagerService.java b/support/src/main/java/bisq/support/security_manager/SecurityManagerService.java index 33e71ee859..cd32bc5040 100644 --- a/support/src/main/java/bisq/support/security_manager/SecurityManagerService.java +++ b/support/src/main/java/bisq/support/security_manager/SecurityManagerService.java @@ -112,6 +112,22 @@ public CompletableFuture publishAlert(AlertType alertType, bannedRole, profileId, staticPublicKeysProvided); + + // Can be removed once there are no pre 2.1.0 versions out there anymore + AuthorizedAlertData oldVersion = new AuthorizedAlertData(0, + StringUtils.createUid(), + new Date().getTime(), + alertType, + headline, + message, + haltTrading, + requireVersionForTrading, + minVersion, + bannedRole, + profileId, + staticPublicKeysProvided); + networkService.publishAuthorizedData(oldVersion, keyPair); + return networkService.publishAuthorizedData(authorizedAlertData, keyPair) .thenApply(broadCastDataResult -> true); } @@ -129,6 +145,15 @@ public CompletableFuture publishDifficultyAdjustment(double difficultyA difficultyAdjustmentFactor, profileId, staticPublicKeysProvided); + + // Can be removed once there are no pre 2.1.0 versions out there anymore + AuthorizedDifficultyAdjustmentData oldVersion = new AuthorizedDifficultyAdjustmentData(0, + new Date().getTime(), + difficultyAdjustmentFactor, + profileId, + staticPublicKeysProvided); + networkService.publishAuthorizedData(oldVersion, keyPair); + return networkService.publishAuthorizedData(data, keyPair) .thenApply(broadCastDataResult -> true); } @@ -141,6 +166,15 @@ public CompletableFuture publishMinRequiredReputationScore(long minRequ minRequiredReputationScore, profileId, staticPublicKeysProvided); + + // Can be removed once there are no pre 2.1.0 versions out there anymore + AuthorizedMinRequiredReputationScoreData oldVersion = new AuthorizedMinRequiredReputationScoreData(0, + new Date().getTime(), + minRequiredReputationScore, + profileId, + staticPublicKeysProvided); + networkService.publishAuthorizedData(oldVersion, keyPair); + return networkService.publishAuthorizedData(data, keyPair) .thenApply(broadCastDataResult -> true); } diff --git a/trade/src/main/java/bisq/trade/Trade.java b/trade/src/main/java/bisq/trade/Trade.java index 9268407229..bfa072e3f6 100644 --- a/trade/src/main/java/bisq/trade/Trade.java +++ b/trade/src/main/java/bisq/trade/Trade.java @@ -61,7 +61,7 @@ private static TradeRole createRole(boolean isBuyer, boolean isTaker) { private final Identity myIdentity; private final P taker; private final P maker; - @EqualsAndHashCode.Exclude // transient are excluded by default but let's make it more explicit + // transient fields are excluded by default for EqualsAndHashCode private transient final TradeRole tradeRole; private final Observable contract = new Observable<>(); private final Observable errorMessage = new Observable<>(); diff --git a/user/src/main/java/bisq/user/banned/BannedUserProfileData.java b/user/src/main/java/bisq/user/banned/BannedUserProfileData.java index 1418cc3e57..77ae3f0227 100644 --- a/user/src/main/java/bisq/user/banned/BannedUserProfileData.java +++ b/user/src/main/java/bisq/user/banned/BannedUserProfileData.java @@ -18,6 +18,7 @@ package bisq.user.banned; import bisq.bonded_roles.AuthorizedPubKeys; +import bisq.common.annotation.ExcludeForHash; import bisq.common.application.DevMode; import bisq.common.proto.ProtoResolver; import bisq.common.proto.UnresolvableProtobufMessageException; @@ -39,13 +40,35 @@ @EqualsAndHashCode @Getter public final class BannedUserProfileData implements AuthorizedDistributedData { + private static final int VERSION = 1; + @EqualsAndHashCode.Exclude + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) private final MetaData metaData = new MetaData(TTL_100_DAYS, HIGH_PRIORITY, getClass().getSimpleName()); + @EqualsAndHashCode.Exclude + @ExcludeForHash + private final int version; private final UserProfile userProfile; + + // ExcludeForHash from version 1 on to not treat data from different oracle nodes with different staticPublicKeysProvided value as duplicate data. + // We add version 2 and 3 for extra safety... + // Once no pre version 2.0.5 nodes are expected anymore in the network we can remove the parameter + // and use default `@ExcludeForHash` instead. + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) @EqualsAndHashCode.Exclude private final boolean staticPublicKeysProvided; - public BannedUserProfileData(UserProfile userProfile, boolean staticPublicKeysProvided) { + public BannedUserProfileData(UserProfile userProfile, + boolean staticPublicKeysProvided) { + this(VERSION, + userProfile, + staticPublicKeysProvided); + } + + public BannedUserProfileData(int version, + UserProfile userProfile, + boolean staticPublicKeysProvided) { + this.version = version; this.userProfile = userProfile; this.staticPublicKeysProvided = staticPublicKeysProvided; @@ -60,7 +83,8 @@ public void verify() { public bisq.user.protobuf.BannedUserProfileData.Builder getBuilder(boolean serializeForHash) { return bisq.user.protobuf.BannedUserProfileData.newBuilder() .setUserProfile(userProfile.toProto(serializeForHash)) - .setStaticPublicKeysProvided(staticPublicKeysProvided); + .setStaticPublicKeysProvided(staticPublicKeysProvided) + .setVersion(version); } @Override @@ -70,8 +94,10 @@ public bisq.user.protobuf.BannedUserProfileData toProto(boolean serializeForHash public static BannedUserProfileData fromProto(bisq.user.protobuf.BannedUserProfileData proto) { return new BannedUserProfileData( + proto.getVersion(), UserProfile.fromProto(proto.getUserProfile()), - proto.getStaticPublicKeysProvided()); + proto.getStaticPublicKeysProvided() + ); } public static ProtoResolver getResolver() { diff --git a/user/src/main/java/bisq/user/profile/UserProfile.java b/user/src/main/java/bisq/user/profile/UserProfile.java index 604eb88b91..aa14ab616e 100644 --- a/user/src/main/java/bisq/user/profile/UserProfile.java +++ b/user/src/main/java/bisq/user/profile/UserProfile.java @@ -62,11 +62,6 @@ public static UserProfile forEdit(UserProfile userProfile, String terms, String userProfile.getNetworkId(), terms, statement); } - public static UserProfile forRePublish(UserProfile userProfile) { - return new UserProfile(userProfile.getNickName(), userProfile.getProofOfWork(), userProfile.getAvatarVersion(), - userProfile.getNetworkId(), userProfile.getTerms(), userProfile.getStatement()); - } - public static UserProfile createNew(String nickName, ProofOfWork proofOfWork, int avatarVersion, diff --git a/user/src/main/java/bisq/user/reputation/data/AuthorizedAccountAgeData.java b/user/src/main/java/bisq/user/reputation/data/AuthorizedAccountAgeData.java index 29c9694c95..b5cd92c527 100644 --- a/user/src/main/java/bisq/user/reputation/data/AuthorizedAccountAgeData.java +++ b/user/src/main/java/bisq/user/reputation/data/AuthorizedAccountAgeData.java @@ -18,6 +18,7 @@ package bisq.user.reputation.data; import bisq.bonded_roles.AuthorizedPubKeys; +import bisq.common.annotation.ExcludeForHash; import bisq.common.application.DevMode; import bisq.common.proto.ProtoResolver; import bisq.common.proto.UnresolvableProtobufMessageException; @@ -40,16 +41,40 @@ @EqualsAndHashCode @Getter public final class AuthorizedAccountAgeData implements AuthorizedDistributedData { + private static final int VERSION = 1; public static final long TTL = TTL_100_DAYS; @EqualsAndHashCode.Exclude + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) private final MetaData metaData = new MetaData(TTL, HIGHEST_PRIORITY, getClass().getSimpleName()); + @EqualsAndHashCode.Exclude + @ExcludeForHash + private final int version; private final String profileId; private final long date; + + // ExcludeForHash from version 1 on to not treat data from different oracle nodes with different staticPublicKeysProvided value as duplicate data. + // We add version 2 and 3 for extra safety... + // Once no pre version 2.0.5 nodes are expected anymore in the network we can remove the parameter + // and use default `@ExcludeForHash` instead. + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) @EqualsAndHashCode.Exclude private final boolean staticPublicKeysProvided; - public AuthorizedAccountAgeData(String profileId, long date, boolean staticPublicKeysProvided) { + public AuthorizedAccountAgeData(String profileId, + long date, + boolean staticPublicKeysProvided) { + this(VERSION, + profileId, + date, + staticPublicKeysProvided); + } + + public AuthorizedAccountAgeData(int version, + String profileId, + long date, + boolean staticPublicKeysProvided) { + this.version = version; this.profileId = profileId; this.date = date; this.staticPublicKeysProvided = staticPublicKeysProvided; @@ -68,7 +93,8 @@ public bisq.user.protobuf.AuthorizedAccountAgeData.Builder getBuilder(boolean se return bisq.user.protobuf.AuthorizedAccountAgeData.newBuilder() .setProfileId(profileId) .setDate(date) - .setStaticPublicKeysProvided(staticPublicKeysProvided); + .setStaticPublicKeysProvided(staticPublicKeysProvided) + .setVersion(version); } @Override @@ -78,9 +104,11 @@ public bisq.user.protobuf.AuthorizedAccountAgeData toProto(boolean serializeForH public static AuthorizedAccountAgeData fromProto(bisq.user.protobuf.AuthorizedAccountAgeData proto) { return new AuthorizedAccountAgeData( + proto.getVersion(), proto.getProfileId(), proto.getDate(), - proto.getStaticPublicKeysProvided()); + proto.getStaticPublicKeysProvided() + ); } public static ProtoResolver getResolver() { diff --git a/user/src/main/java/bisq/user/reputation/data/AuthorizedBondedReputationData.java b/user/src/main/java/bisq/user/reputation/data/AuthorizedBondedReputationData.java index 0bbd1c42c3..a812124e4d 100644 --- a/user/src/main/java/bisq/user/reputation/data/AuthorizedBondedReputationData.java +++ b/user/src/main/java/bisq/user/reputation/data/AuthorizedBondedReputationData.java @@ -47,20 +47,28 @@ public final class AuthorizedBondedReputationData implements AuthorizedDistribut private static final int VERSION = 1; @EqualsAndHashCode.Exclude + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) private final MetaData metaData = new MetaData(TTL_100_DAYS, HIGH_PRIORITY, getClass().getSimpleName()); + @EqualsAndHashCode.Exclude + @ExcludeForHash + private final int version; private final long blockTime; private final long amount; private final byte[] hash; private final long lockTime; - @EqualsAndHashCode.Exclude - private final boolean staticPublicKeysProvided; - @ExcludeForHash - private final int version; @ExcludeForHash(excludeOnlyInVersions = {0}) private final int blockHeight; @ExcludeForHash(excludeOnlyInVersions = {0}) private final String txId; + // ExcludeForHash from version 1 on to not treat data from different oracle nodes with different staticPublicKeysProvided value as duplicate data. + // We add version 2 and 3 for extra safety... + // Once no pre version 2.0.5 nodes are expected anymore in the network we can remove the parameter + // and use default `@ExcludeForHash` instead. + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) + @EqualsAndHashCode.Exclude + private final boolean staticPublicKeysProvided; + public AuthorizedBondedReputationData(long blockTime, long amount, byte[] hash, diff --git a/user/src/main/java/bisq/user/reputation/data/AuthorizedProofOfBurnData.java b/user/src/main/java/bisq/user/reputation/data/AuthorizedProofOfBurnData.java index cce9186e49..def3d36d94 100644 --- a/user/src/main/java/bisq/user/reputation/data/AuthorizedProofOfBurnData.java +++ b/user/src/main/java/bisq/user/reputation/data/AuthorizedProofOfBurnData.java @@ -47,19 +47,27 @@ public final class AuthorizedProofOfBurnData implements AuthorizedDistributedDat private static final int VERSION = 1; @EqualsAndHashCode.Exclude + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) private final MetaData metaData = new MetaData(TTL_100_DAYS, HIGH_PRIORITY, getClass().getSimpleName()); - private final long blockTime; - private final long amount; - private final byte[] hash; @EqualsAndHashCode.Exclude - private final boolean staticPublicKeysProvided; @ExcludeForHash private final int version; + private final long blockTime; + private final long amount; + private final byte[] hash; @ExcludeForHash(excludeOnlyInVersions = {0}) private final int blockHeight; @ExcludeForHash(excludeOnlyInVersions = {0}) private final String txId; + // ExcludeForHash from version 1 on to not treat data from different oracle nodes with different staticPublicKeysProvided value as duplicate data. + // We add version 2 and 3 for extra safety... + // Once no pre version 2.0.5 nodes are expected anymore in the network we can remove the parameter + // and use default `@ExcludeForHash` instead. + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) + @EqualsAndHashCode.Exclude + private final boolean staticPublicKeysProvided; + public AuthorizedProofOfBurnData(long blockTime, long amount, byte[] hash, diff --git a/user/src/main/java/bisq/user/reputation/data/AuthorizedSignedWitnessData.java b/user/src/main/java/bisq/user/reputation/data/AuthorizedSignedWitnessData.java index e09a315326..529c6f0dfa 100644 --- a/user/src/main/java/bisq/user/reputation/data/AuthorizedSignedWitnessData.java +++ b/user/src/main/java/bisq/user/reputation/data/AuthorizedSignedWitnessData.java @@ -18,6 +18,7 @@ package bisq.user.reputation.data; import bisq.bonded_roles.AuthorizedPubKeys; +import bisq.common.annotation.ExcludeForHash; import bisq.common.application.DevMode; import bisq.common.proto.ProtoResolver; import bisq.common.proto.UnresolvableProtobufMessageException; @@ -40,16 +41,40 @@ @EqualsAndHashCode @Getter public final class AuthorizedSignedWitnessData implements AuthorizedDistributedData { + private static final int VERSION = 1; public static final long TTL = TTL_100_DAYS; @EqualsAndHashCode.Exclude + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) private final MetaData metaData = new MetaData(TTL, HIGH_PRIORITY, getClass().getSimpleName()); + @EqualsAndHashCode.Exclude + @ExcludeForHash + private final int version; private final String profileId; private final long witnessSignDate; + + // ExcludeForHash from version 1 on to not treat data from different oracle nodes with different staticPublicKeysProvided value as duplicate data. + // We add version 2 and 3 for extra safety... + // Once no pre version 2.0.5 nodes are expected anymore in the network we can remove the parameter + // and use default `@ExcludeForHash` instead. + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) @EqualsAndHashCode.Exclude private final boolean staticPublicKeysProvided; - public AuthorizedSignedWitnessData(String profileId, long witnessSignDate, boolean staticPublicKeysProvided) { + public AuthorizedSignedWitnessData(String profileId, + long witnessSignDate, + boolean staticPublicKeysProvided) { + this(VERSION, + profileId, + witnessSignDate, + staticPublicKeysProvided); + } + + public AuthorizedSignedWitnessData(int version, + String profileId, + long witnessSignDate, + boolean staticPublicKeysProvided) { + this.version = version; this.profileId = profileId; this.witnessSignDate = witnessSignDate; this.staticPublicKeysProvided = staticPublicKeysProvided; @@ -68,7 +93,8 @@ public bisq.user.protobuf.AuthorizedSignedWitnessData.Builder getBuilder(boolean return bisq.user.protobuf.AuthorizedSignedWitnessData.newBuilder() .setProfileId(profileId) .setWitnessSignDate(witnessSignDate) - .setStaticPublicKeysProvided(staticPublicKeysProvided); + .setStaticPublicKeysProvided(staticPublicKeysProvided) + .setVersion(version); } @Override @@ -78,9 +104,11 @@ public bisq.user.protobuf.AuthorizedSignedWitnessData toProto(boolean serializeF public static AuthorizedSignedWitnessData fromProto(bisq.user.protobuf.AuthorizedSignedWitnessData proto) { return new AuthorizedSignedWitnessData( + proto.getVersion(), proto.getProfileId(), proto.getWitnessSignDate(), - proto.getStaticPublicKeysProvided()); + proto.getStaticPublicKeysProvided() + ); } public static ProtoResolver getResolver() { diff --git a/user/src/main/java/bisq/user/reputation/data/AuthorizedTimestampData.java b/user/src/main/java/bisq/user/reputation/data/AuthorizedTimestampData.java index 157249005d..79986db95d 100644 --- a/user/src/main/java/bisq/user/reputation/data/AuthorizedTimestampData.java +++ b/user/src/main/java/bisq/user/reputation/data/AuthorizedTimestampData.java @@ -18,6 +18,7 @@ package bisq.user.reputation.data; import bisq.bonded_roles.AuthorizedPubKeys; +import bisq.common.annotation.ExcludeForHash; import bisq.common.application.DevMode; import bisq.common.proto.ProtoResolver; import bisq.common.proto.UnresolvableProtobufMessageException; @@ -39,16 +40,40 @@ @EqualsAndHashCode @Getter public final class AuthorizedTimestampData implements AuthorizedDistributedData { + private static final int VERSION = 1; public static final long TTL = TTL_30_DAYS; @EqualsAndHashCode.Exclude + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) private final MetaData metaData = new MetaData(TTL, getClass().getSimpleName()); + @EqualsAndHashCode.Exclude + @ExcludeForHash + private final int version; private final String profileId; private final long date; + + // ExcludeForHash from version 1 on to not treat data from different oracle nodes with different staticPublicKeysProvided value as duplicate data. + // We add version 2 and 3 for extra safety... + // Once no pre version 2.0.5 nodes are expected anymore in the network we can remove the parameter + // and use default `@ExcludeForHash` instead. + @ExcludeForHash(excludeOnlyInVersions = {1, 2, 3}) @EqualsAndHashCode.Exclude private final boolean staticPublicKeysProvided; - public AuthorizedTimestampData(String profileId, long date, boolean staticPublicKeysProvided) { + public AuthorizedTimestampData(String profileId, + long date, + boolean staticPublicKeysProvided) { + this(VERSION, + profileId, + date, + staticPublicKeysProvided); + } + + public AuthorizedTimestampData(int version, + String profileId, + long date, + boolean staticPublicKeysProvided) { + this.version = version; this.profileId = profileId; this.date = date; this.staticPublicKeysProvided = staticPublicKeysProvided; @@ -67,7 +92,8 @@ public bisq.user.protobuf.AuthorizedTimestampData.Builder getBuilder(boolean ser return bisq.user.protobuf.AuthorizedTimestampData.newBuilder() .setProfileId(profileId) .setDate(date) - .setStaticPublicKeysProvided(staticPublicKeysProvided); + .setStaticPublicKeysProvided(staticPublicKeysProvided) + .setVersion(version); } @Override @@ -77,9 +103,11 @@ public bisq.user.protobuf.AuthorizedTimestampData toProto(boolean serializeForHa public static AuthorizedTimestampData fromProto(bisq.user.protobuf.AuthorizedTimestampData proto) { return new AuthorizedTimestampData( + proto.getVersion(), proto.getProfileId(), proto.getDate(), - proto.getStaticPublicKeysProvided()); + proto.getStaticPublicKeysProvided() + ); } public static ProtoResolver getResolver() { diff --git a/user/src/main/proto/user.proto b/user/src/main/proto/user.proto index f6be2d58fd..9fbeb95f90 100644 --- a/user/src/main/proto/user.proto +++ b/user/src/main/proto/user.proto @@ -133,23 +133,27 @@ message AuthorizedAccountAgeData { string profileId = 1; sint64 date = 2; bool staticPublicKeysProvided = 3; + sint32 version = 4; } message AuthorizedSignedWitnessData { string profileId = 1; sint64 witnessSignDate = 2; bool staticPublicKeysProvided = 3; + sint32 version = 4; } message AuthorizedTimestampData { string profileId = 1; sint64 date = 2; bool staticPublicKeysProvided = 3; + sint32 version = 4; } message BannedUserProfileData { UserProfile userProfile = 1; bool staticPublicKeysProvided = 2; + sint32 version = 3; } message BannedUserStore { repeated BannedUserProfileData bannedUserProfileDataSet = 1;