From a8e2fb46c27fb5038145d2345d96e9c4f66e46e2 Mon Sep 17 00:00:00 2001 From: Alexander Kalankhodzhaev Date: Sun, 27 Mar 2022 08:48:37 +0300 Subject: [PATCH] fix NPE when response is `null` --- build.gradle | 2 +- crypto/build.gradle | 2 +- rpc/rpc-codegen/build.gradle | 2 +- rpc/rpc-core/build.gradle | 2 +- rpc/rpc-sections/build.gradle | 6 +- .../rpc/sections/StateTests.java | 25 +++++++-- transport/build.gradle | 6 +- .../transport/coder/RpcCoder.java | 2 +- .../coder/RpcObjectDeserializer.java | 55 ------------------- .../transport/coder/RpcObjectTypeAdapter.java | 51 +++++++++++++++++ .../transport/coder/RpcCoderTest.java | 18 ++++++ 11 files changed, 101 insertions(+), 70 deletions(-) delete mode 100644 transport/src/main/java/com/strategyobject/substrateclient/transport/coder/RpcObjectDeserializer.java create mode 100644 transport/src/main/java/com/strategyobject/substrateclient/transport/coder/RpcObjectTypeAdapter.java diff --git a/build.gradle b/build.gradle index 282c0800..9248963f 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { allprojects { group = 'com.strategyobject.substrateclient' - version = '0.0.3-SNAPSHOT' + version = '0.0.4-SNAPSHOT' repositories { mavenLocal() diff --git a/crypto/build.gradle b/crypto/build.gradle index 70f01dc7..46706d93 100644 --- a/crypto/build.gradle +++ b/crypto/build.gradle @@ -1,6 +1,6 @@ dependencies { implementation 'com.github.multiformats:java-multibase:1.1.0' - implementation 'org.bouncycastle:bcprov-jdk15on:1.69' + implementation 'org.bouncycastle:bcprov-jdk15on:1.70' implementation project(":common") implementation project(":types") } diff --git a/rpc/rpc-codegen/build.gradle b/rpc/rpc-codegen/build.gradle index 30ab8855..2f6ad931 100644 --- a/rpc/rpc-codegen/build.gradle +++ b/rpc/rpc-codegen/build.gradle @@ -10,7 +10,7 @@ dependencies { implementation 'com.squareup:javapoet:1.13.0' testImplementation 'com.google.testing.compile:compile-testing:0.19' - testImplementation 'com.google.code.gson:gson:2.8.9' + testImplementation 'com.google.code.gson:gson:2.9.0' testCompileOnly project(':rpc:rpc-codegen') testAnnotationProcessor project(':rpc:rpc-codegen') diff --git a/rpc/rpc-core/build.gradle b/rpc/rpc-core/build.gradle index 658861ac..f6298418 100644 --- a/rpc/rpc-core/build.gradle +++ b/rpc/rpc-core/build.gradle @@ -3,5 +3,5 @@ dependencies { implementation project(':scale') implementation project(':transport') - testImplementation 'com.google.code.gson:gson:2.8.9' + testImplementation 'com.google.code.gson:gson:2.9.0' } \ No newline at end of file diff --git a/rpc/rpc-sections/build.gradle b/rpc/rpc-sections/build.gradle index 9b27b11b..09c912bb 100644 --- a/rpc/rpc-sections/build.gradle +++ b/rpc/rpc-sections/build.gradle @@ -17,7 +17,7 @@ dependencies { testImplementation 'org.testcontainers:testcontainers:1.16.3' testImplementation 'org.testcontainers:junit-jupiter:1.16.3' - testImplementation 'ch.qos.logback:logback-classic:1.2.10' - testImplementation 'org.awaitility:awaitility:4.1.1' - testImplementation 'org.bouncycastle:bcprov-jdk15on:1.69' + testImplementation 'ch.qos.logback:logback-classic:1.2.11' + testImplementation 'org.awaitility:awaitility:4.2.0' + testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70' } \ No newline at end of file diff --git a/rpc/rpc-sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/StateTests.java b/rpc/rpc-sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/StateTests.java index be61ffb5..d58dc705 100644 --- a/rpc/rpc-sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/StateTests.java +++ b/rpc/rpc-sections/src/test/java/com/strategyobject/substrateclient/rpc/sections/StateTests.java @@ -18,8 +18,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; @Testcontainers public class StateTests { @@ -100,11 +99,29 @@ void getStorage() throws ExecutionException, InterruptedException, TimeoutExcept val key = "0xc2261276cc9d1f8598ea4b6a74b15c2f308ce9615de0775a82f8a94dc3d285a1"; // TODO implement and use `xxhash` val storageData = rpcSection.getStorage(StorageKey.valueOf(HexConverter.toBytes(key))).get(WAIT_TIMEOUT, TimeUnit.SECONDS); - assertTrue(storageData != null); + assertNotNull(storageData); assertTrue(storageData.getData().length > 0); } } + @Test + void getStorageHandlesNullResponse() throws ExecutionException, InterruptedException, TimeoutException, RpcInterfaceInitializationException { + try (WsProvider wsProvider = WsProvider.builder() + .setEndpoint(substrate.getWsAddress()) + .disableAutoConnect() + .build()) { + wsProvider.connect().get(WAIT_TIMEOUT, TimeUnit.SECONDS); + + val sectionFactory = new RpcGeneratedSectionFactory(); + State rpcSection = sectionFactory.create(State.class, wsProvider); + + val emptyKey = new byte[32]; + val storageData = rpcSection.getStorage(StorageKey.valueOf(emptyKey)).get(WAIT_TIMEOUT, TimeUnit.SECONDS); + + assertNull(storageData); + } + } + @Test void getStorageAt() throws ExecutionException, InterruptedException, TimeoutException, RpcInterfaceInitializationException { try (WsProvider wsProvider = WsProvider.builder() @@ -123,7 +140,7 @@ void getStorageAt() throws ExecutionException, InterruptedException, TimeoutExce assertTrue(changes.size() > 0); assertTrue(changes.get(0).getChanges().size() > 0); - assertTrue(changes.get(0).getChanges().get(0).getValue0().getData() != null); + assertNotNull(changes.get(0).getChanges().get(0).getValue0().getData()); assertTrue(changes.get(0).getChanges().get(0).getValue0().getData().length > 0); } } diff --git a/transport/build.gradle b/transport/build.gradle index 62f70bd3..0e4d40b9 100644 --- a/transport/build.gradle +++ b/transport/build.gradle @@ -2,12 +2,12 @@ dependencies { implementation project(':common') implementation 'org.java-websocket:Java-WebSocket:1.5.2' - implementation 'com.google.code.gson:gson:2.8.9' + implementation 'com.google.code.gson:gson:2.9.0' testImplementation project(':tests') - testImplementation 'ch.qos.logback:logback-classic:1.2.10' + testImplementation 'ch.qos.logback:logback-classic:1.2.11' testImplementation 'org.testcontainers:testcontainers:1.16.3' testImplementation 'org.testcontainers:junit-jupiter:1.16.3' testImplementation "org.testcontainers:toxiproxy:1.16.3" - testImplementation 'org.awaitility:awaitility:4.1.1' + testImplementation 'org.awaitility:awaitility:4.2.0' } \ No newline at end of file diff --git a/transport/src/main/java/com/strategyobject/substrateclient/transport/coder/RpcCoder.java b/transport/src/main/java/com/strategyobject/substrateclient/transport/coder/RpcCoder.java index 3e053035..d766506d 100644 --- a/transport/src/main/java/com/strategyobject/substrateclient/transport/coder/RpcCoder.java +++ b/transport/src/main/java/com/strategyobject/substrateclient/transport/coder/RpcCoder.java @@ -9,7 +9,7 @@ public class RpcCoder { private static final Gson GSON = new GsonBuilder() - .registerTypeAdapter(RpcObject.class, new RpcObjectDeserializer()) + .registerTypeAdapter(RpcObject.class, new RpcObjectTypeAdapter()) .create(); private final AtomicInteger id = new AtomicInteger(0); diff --git a/transport/src/main/java/com/strategyobject/substrateclient/transport/coder/RpcObjectDeserializer.java b/transport/src/main/java/com/strategyobject/substrateclient/transport/coder/RpcObjectDeserializer.java deleted file mode 100644 index 322ef091..00000000 --- a/transport/src/main/java/com/strategyobject/substrateclient/transport/coder/RpcObjectDeserializer.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.strategyobject.substrateclient.transport.coder; - -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; -import com.strategyobject.substrateclient.transport.*; -import lombok.val; - -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.HashMap; - -public class RpcObjectDeserializer implements JsonDeserializer { - @Override - public RpcObject deserialize(JsonElement json, - Type typeOfT, - JsonDeserializationContext context) throws JsonParseException { - if (json.isJsonNull()) { - return new RpcNull(); - } - - if (json.isJsonPrimitive()) { - val primitive = json.getAsJsonPrimitive(); - - if (primitive.isBoolean()) { - return new RpcBoolean(primitive.getAsBoolean()); - } - - if (primitive.isNumber()) { - return new RpcNumber(primitive.getAsNumber()); - } - - return new RpcString(primitive.getAsString()); - } - - if (json.isJsonArray()) { - val jsonArray = json.getAsJsonArray(); - val list = new ArrayList(jsonArray.size()); - for (val item : jsonArray) { - list.add(context.deserialize(item, RpcObject.class)); - } - - return new RpcList(list); - } - - val jsonObject = json.getAsJsonObject(); - val map = new HashMap(jsonObject.size()); - for (val item : jsonObject.entrySet()) { - map.put(item.getKey(), context.deserialize(item.getValue(), RpcObject.class)); - } - - return new RpcMap(map); - } -} diff --git a/transport/src/main/java/com/strategyobject/substrateclient/transport/coder/RpcObjectTypeAdapter.java b/transport/src/main/java/com/strategyobject/substrateclient/transport/coder/RpcObjectTypeAdapter.java new file mode 100644 index 00000000..eef97e8a --- /dev/null +++ b/transport/src/main/java/com/strategyobject/substrateclient/transport/coder/RpcObjectTypeAdapter.java @@ -0,0 +1,51 @@ +package com.strategyobject.substrateclient.transport.coder; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import com.strategyobject.substrateclient.transport.*; +import lombok.val; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; + +public class RpcObjectTypeAdapter extends TypeAdapter { + @Override + public void write(JsonWriter out, RpcObject value) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public RpcObject read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return new RpcNull(); + case BOOLEAN: + return new RpcBoolean(in.nextBoolean()); + case NUMBER: + return new RpcNumber(in.nextDouble()); + case STRING: + return new RpcString(in.nextString()); + case BEGIN_ARRAY: + in.beginArray(); + val list = new ArrayList(); + while (in.hasNext()) { + list.add(this.read(in)); + } + + in.endArray(); + return new RpcList(list); + default: + in.beginObject(); + val map = new HashMap(); + while (in.hasNext()) { + map.put(in.nextName(), this.read(in)); + } + + in.endObject(); + return new RpcMap(map); + } + } +} diff --git a/transport/src/test/java/com/strategyobject/substrateclient/transport/coder/RpcCoderTest.java b/transport/src/test/java/com/strategyobject/substrateclient/transport/coder/RpcCoderTest.java index 06127663..38ef8171 100644 --- a/transport/src/test/java/com/strategyobject/substrateclient/transport/coder/RpcCoderTest.java +++ b/transport/src/test/java/com/strategyobject/substrateclient/transport/coder/RpcCoderTest.java @@ -12,6 +12,24 @@ class RpcCoderTest { + @Test + void decodeNullResult() { + val json = "{\n" + + " \"result\": null,\n" + + " \"id\": 0,\n" + + " \"jsonrpc\": \"3.0\"\n" + + "}"; + val actual = RpcCoder.decodeJson(json); + + val expected = new JsonRpcResponse(); + expected.jsonrpc = "3.0"; + expected.id = 0; + expected.result = RpcObject.ofNull(); + assertThat(actual) + .usingRecursiveComparison() + .isEqualTo(expected); + } + @Test void decodeJson() { val json = "{\n" +