diff --git a/common/src/main/java/bisq/common/ExcludeForHash.java b/common/src/main/java/bisq/common/ExcludeForHash.java new file mode 100644 index 00000000000..25834c158f4 --- /dev/null +++ b/common/src/main/java/bisq/common/ExcludeForHash.java @@ -0,0 +1,28 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.common; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExcludeForHash { +} diff --git a/common/src/main/java/bisq/common/ExcludeForHashAwareProto.java b/common/src/main/java/bisq/common/ExcludeForHashAwareProto.java new file mode 100644 index 00000000000..18977fb3b9f --- /dev/null +++ b/common/src/main/java/bisq/common/ExcludeForHashAwareProto.java @@ -0,0 +1,119 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.common; + + +import com.google.protobuf.Descriptors; +import com.google.protobuf.Message; + +import java.io.IOException; +import java.io.OutputStream; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +import java.lang.reflect.Field; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Borrowed from Bisq2, but as we want to reduce risk of breaking things, specially for the DAO we pack + * the support for ExcludeForHash annotations into a dedicated interface. + * Classes which use ExcludeForHash need to implement that interface. + */ +public interface ExcludeForHashAwareProto extends Proto { + + //////////////////////////////////////////////////////////////////////////////// + // Override Proto methods + //////////////////////////////////////////////////////////////////////////////// + + default Message toProtoMessage() { + return completeProto(); + } + + default byte[] serialize() { + return resolveProto(false).toByteArray(); + } + + default byte[] serializeForHash() { + return resolveProto(true).toByteArray(); + } + + + //////////////////////////////////////////////////////////////////////////////// + // API + //////////////////////////////////////////////////////////////////////////////// + + Message.Builder getBuilder(boolean serializeForHash); + + Message toProto(boolean serializeForHash); + + default Message completeProto() { + return toProto(false); + } + + default T resolveProto(boolean serializeForHash) { + //noinspection unchecked + return (T) resolveBuilder(getBuilder(serializeForHash), serializeForHash).build(); + } + + default B resolveBuilder(B builder, boolean serializeForHash) { + return serializeForHash ? clearAnnotatedFields(builder) : builder; + } + + default int getSerializedSize() { + return resolveProto(false).getSerializedSize(); + } + + default void writeDelimitedTo(OutputStream outputStream) throws IOException { + completeProto().writeDelimitedTo(outputStream); + } + + default Set getExcludedFields() { + return Arrays.stream(getClass().getDeclaredFields()) + .peek(field -> field.setAccessible(true)) + .filter(field -> field.isAnnotationPresent(ExcludeForHash.class)) + .map(Field::getName) + .collect(Collectors.toSet()); + } + + /** + * Requires that the name of the java fields is the same as the name of the proto definition. + * + * @param builder The builder we transform by clearing the ExcludeForHash annotated fields. + * @return Builder with the fields annotated with ExcludeForHash cleared. + */ + default B clearAnnotatedFields(B builder) { + Set excludedFields = getExcludedFields(); + if (!excludedFields.isEmpty()) { + getLogger().error("Clear fields in builder annotated with @ExcludeForHash: {}", excludedFields); + } + for (Descriptors.FieldDescriptor fieldDesc : builder.getAllFields().keySet()) { + if (excludedFields.contains(fieldDesc.getName())) { + builder.clearField(fieldDesc); + } + } + return builder; + } + + private Logger getLogger() { + return LoggerFactory.getLogger(getClass().getSimpleName()); + } +} diff --git a/common/src/main/java/bisq/common/Proto.java b/common/src/main/java/bisq/common/Proto.java index 1d5bf8dc590..cdc37073eae 100644 --- a/common/src/main/java/bisq/common/Proto.java +++ b/common/src/main/java/bisq/common/Proto.java @@ -29,6 +29,8 @@ default byte[] serialize() { return toProtoMessage().toByteArray(); } + // If the class implements ExcludedFieldsProto this method will be overwritten so that + // fields annotated with ExcludeForHash will be excluded. default byte[] serializeForHash() { return serialize(); }