From 1e2aff65830a616f1bdfca0be7ffedc553696016 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Mon, 8 Apr 2019 08:40:15 +1000 Subject: [PATCH] custom deserializer for TopicsParameter --- .../internal/parameters/FilterParameter.java | 7 +- .../parameters/TopicsDeserializer.java | 53 +++++++++++++ .../request/SubscriptionRequestMapper.java | 3 +- .../internal/methods/EthNewFilterTest.java | 2 +- .../parameters/FilterParameterTest.java | 76 +++++++++++++++++++ .../SubscriptionRequestMapperTest.java | 17 +++-- 6 files changed, 146 insertions(+), 12 deletions(-) create mode 100644 ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/TopicsDeserializer.java diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/FilterParameter.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/FilterParameter.java index 1e88d8d720..803094fdd1 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/FilterParameter.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/FilterParameter.java @@ -22,6 +22,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.common.base.MoreObjects; public class FilterParameter { @@ -38,14 +39,14 @@ public FilterParameter( @JsonProperty("toBlock") final String toBlock, @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) @JsonProperty("address") final List address, - @JsonProperty("topics") final List> topics, + @JsonDeserialize(using = TopicsDeserializer.class) @JsonProperty("topics") + final TopicsParameter topics, @JsonProperty("blockhash") final String blockhash) { this.fromBlock = fromBlock != null ? new BlockParameter(fromBlock) : new BlockParameter("latest"); this.toBlock = toBlock != null ? new BlockParameter(toBlock) : new BlockParameter("latest"); this.addresses = address != null ? renderAddress(address) : Collections.emptyList(); - this.topics = - topics != null ? new TopicsParameter(topics) : new TopicsParameter(Collections.emptyList()); + this.topics = topics != null ? topics : new TopicsParameter(Collections.emptyList()); this.blockhash = blockhash != null ? Hash.fromHexString(blockhash) : null; } diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/TopicsDeserializer.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/TopicsDeserializer.java new file mode 100644 index 0000000000..802a2c3530 --- /dev/null +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/TopicsDeserializer.java @@ -0,0 +1,53 @@ +/* + * Copyright 2019 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; + +public class TopicsDeserializer extends StdDeserializer { + public TopicsDeserializer() { + this(null); + } + + public TopicsDeserializer(final Class vc) { + super(vc); + } + + @Override + public TopicsParameter deserialize( + final JsonParser jsonparser, final DeserializationContext context) throws IOException { + List topicsList = new ArrayList<>(); + + try { + // try standard method + return jsonparser.readValueAs(TopicsParameter.class); + } catch (MismatchedInputException mie) { + // is there a single string value instead of expected list of list + String topics = jsonparser.getText(); + if (topics == null) { + return new TopicsParameter(Collections.singletonList(topicsList)); + } else { + // make it list of list + return new TopicsParameter(Collections.singletonList(Collections.singletonList(topics))); + } + } + } +} diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/SubscriptionRequestMapper.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/SubscriptionRequestMapper.java index 4fddb0ded0..1e78450aa3 100644 --- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/SubscriptionRequestMapper.java +++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/SubscriptionRequestMapper.java @@ -15,6 +15,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.FilterParameter; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.TopicsParameter; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.UnsignedLongParameter; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.methods.WebSocketRpcRequest; @@ -81,7 +82,7 @@ private SubscribeRequest parseLogsRequest( private FilterParameter createFilterParameter(final LogsSubscriptionParam logFilterParams) { final List addresses = hasAddresses(logFilterParams); final List> topics = hasTopics(logFilterParams); - return new FilterParameter(null, null, addresses, topics, null); + return new FilterParameter(null, null, addresses, new TopicsParameter(topics), null); } private List hasAddresses(final LogsSubscriptionParam logFilterParams) { diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/EthNewFilterTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/EthNewFilterTest.java index aa969cfee4..c2833f13cf 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/EthNewFilterTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/EthNewFilterTest.java @@ -158,7 +158,7 @@ private List> topics() { private FilterParameter filterParamWithAddressAndTopics( final Address address, final List> topics) { final List addresses = address != null ? Arrays.asList(address.toString()) : null; - return new FilterParameter("latest", "latest", addresses, topics, null); + return new FilterParameter("latest", "latest", addresses, new TopicsParameter(topics), null); } private JsonRpcRequest ethNewFilter(final FilterParameter filterParameter) { diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/FilterParameterTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/FilterParameterTest.java index 4ea8a5c5ba..496d74ab92 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/FilterParameterTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/parameters/FilterParameterTest.java @@ -17,6 +17,8 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest; import java.util.Arrays; +import java.util.Collections; +import java.util.List; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Test; @@ -53,10 +55,84 @@ public void jsonWithSingleAddressShouldSerializeSuccessfully() throws Exception .isEqualToComparingFieldByFieldRecursively(expectedFilterParameter); } + @Test + public void jsonWithSingleAddressAndSingleTopicShouldSerializeSuccessfully() throws Exception { + final String jsonWithSingleAddress = + "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getLogs\",\"params\":[{\"address\":\"0x0\", \"topics\":\"0x0000000000000000000000000000000000000000000000000000000000000002\" }],\"id\":1}"; + + final JsonRpcRequest request = readJsonAsJsonRpcRequest(jsonWithSingleAddress); + final FilterParameter expectedFilterParameter = + filterParameterWithAddressAndSingleListOfTopics( + "0x0", "0x0000000000000000000000000000000000000000000000000000000000000002"); + + final FilterParameter parsedFilterParameter = + parameters.required(request.getParams(), 0, FilterParameter.class); + + assertThat(parsedFilterParameter) + .isEqualToComparingFieldByFieldRecursively(expectedFilterParameter); + } + + @Test + public void jsonWithSingleAddressAndMultipleTopicsShouldSerializeSuccessfully() throws Exception { + final String jsonWithSingleAddress = + "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getLogs\",\"params\":[{\"address\":\"0x0\", \"topics\":[[\"0x0000000000000000000000000000000000000000000000000000000000000002\",\"0x0000000000000000000000000000000000000000000000000000000000000003\"]]}],\"id\":1}"; + + final JsonRpcRequest request = readJsonAsJsonRpcRequest(jsonWithSingleAddress); + final FilterParameter expectedFilterParameter = + filterParameterWithAddressAndSingleListOfTopics( + "0x0", + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000000000000000000000000000003"); + + final FilterParameter parsedFilterParameter = + parameters.required(request.getParams(), 0, FilterParameter.class); + + assertThat(parsedFilterParameter) + .isEqualToComparingFieldByFieldRecursively(expectedFilterParameter); + } + + @Test + public void jsonWithSingleAddressAndMultipleListsOfTopicsShouldSerializeSuccessfully() + throws Exception { + final String jsonWithSingleAddress = + "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getLogs\",\"params\":[{\"address\":\"0x0\", \"topics\":[[\"0x0000000000000000000000000000000000000000000000000000000000000002\",\"0x0000000000000000000000000000000000000000000000000000000000000003\"],[\"0x0000000000000000000000000000000000000000000000000000000000000002\",\"0x0000000000000000000000000000000000000000000000000000000000000003\"]]}],\"id\":1}"; + + final JsonRpcRequest request = readJsonAsJsonRpcRequest(jsonWithSingleAddress); + final FilterParameter expectedFilterParameter = + filterParameterWithAddressAndMultipleListOfTopics( + "0x0", + "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000000000000000000000000000003"); + + final FilterParameter parsedFilterParameter = + parameters.required(request.getParams(), 0, FilterParameter.class); + + assertThat(parsedFilterParameter) + .isEqualToComparingFieldByFieldRecursively(expectedFilterParameter); + } + private FilterParameter filterParameterWithAddresses(final String... addresses) { return new FilterParameter("latest", "latest", Arrays.asList(addresses), null, null); } + private FilterParameter filterParameterWithAddressAndSingleListOfTopics( + final String address, final String... topics) { + return new FilterParameter( + "latest", + "latest", + Arrays.asList(address), + new TopicsParameter(Collections.singletonList(Arrays.asList(topics))), + null); + } + + private FilterParameter filterParameterWithAddressAndMultipleListOfTopics( + final String address, final String... topics) { + List topicsList = Arrays.asList(topics); + List> topicsListList = Arrays.asList(topicsList, topicsList); + return new FilterParameter( + "latest", "latest", Arrays.asList(address), new TopicsParameter(topicsListList), null); + } + private JsonRpcRequest readJsonAsJsonRpcRequest(final String jsonWithSingleAddress) throws java.io.IOException { return new ObjectMapper().readValue(jsonWithSingleAddress, JsonRpcRequest.class); diff --git a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/SubscriptionRequestMapperTest.java b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/SubscriptionRequestMapperTest.java index a205890b71..def01f07d0 100644 --- a/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/SubscriptionRequestMapperTest.java +++ b/ethereum/jsonrpc/src/test/java/tech/pegasys/pantheon/ethereum/jsonrpc/websocket/subscription/request/SubscriptionRequestMapperTest.java @@ -22,6 +22,7 @@ import tech.pegasys.pantheon.ethereum.jsonrpc.internal.exception.InvalidJsonRpcParameters; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.FilterParameter; import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter; +import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.TopicsParameter; import tech.pegasys.pantheon.ethereum.jsonrpc.websocket.methods.WebSocketRpcRequest; import java.util.Arrays; @@ -160,7 +161,7 @@ public void mapRequestWithSingleAddress() { null, null, Arrays.asList("0x8320fe7702b96808f7bbc0d4a888ed1468216cfd"), - Collections.emptyList(), + new TopicsParameter(Collections.emptyList()), null); final SubscribeRequest expectedSubscribeRequest = new SubscribeRequest(SubscriptionType.LOGS, expectedFilterParam, null, null); @@ -184,9 +185,10 @@ public void mapRequestWithMultipleAddresses() { Arrays.asList( "0x8320fe7702b96808f7bbc0d4a888ed1468216cfd", "0xf17f52151EbEF6C7334FAD080c5704D77216b732"), - Arrays.asList( + new TopicsParameter( Arrays.asList( - "0xd78a0cb8bb633d06981248b816e7bd33c2a35a6089241d099fa519e361cab902")), + Arrays.asList( + "0xd78a0cb8bb633d06981248b816e7bd33c2a35a6089241d099fa519e361cab902"))), null); final SubscribeRequest expectedSubscribeRequest = new SubscribeRequest(SubscriptionType.LOGS, expectedFilterParam, null, null); @@ -208,10 +210,11 @@ public void mapRequestWithMultipleTopics() { null, null, Arrays.asList("0x8320fe7702b96808f7bbc0d4a888ed1468216cfd"), - Arrays.asList( + new TopicsParameter( Arrays.asList( - "0xd78a0cb8bb633d06981248b816e7bd33c2a35a6089241d099fa519e361cab902", - "0xd78a0cb8bb633d06981248b816e7bd33c2a35a6089241d099fa519e361cab901")), + Arrays.asList( + "0xd78a0cb8bb633d06981248b816e7bd33c2a35a6089241d099fa519e361cab902", + "0xd78a0cb8bb633d06981248b816e7bd33c2a35a6089241d099fa519e361cab901"))), null); final SubscribeRequest expectedSubscribeRequest = new SubscribeRequest(SubscriptionType.LOGS, expectedFilterParam, null, null); @@ -233,7 +236,7 @@ public void mapRequestToLogsWithoutTopics() { null, null, Arrays.asList("0x8320fe7702b96808f7bbc0d4a888ed1468216cfd"), - Collections.emptyList(), + new TopicsParameter(Collections.emptyList()), null); final SubscribeRequest expectedSubscribeRequest = new SubscribeRequest(SubscriptionType.LOGS, expectedFilterParam, null, null);