diff --git a/wallets/json-rpc/build.gradle b/wallets/json-rpc/build.gradle new file mode 100644 index 0000000000..f88f051e13 --- /dev/null +++ b/wallets/json-rpc/build.gradle @@ -0,0 +1,11 @@ +plugins { + id 'bisq.gradle.plugin.BisqPlugin' +} + +dependencies { + implementation 'com.squareup.okhttp3:okhttp:4.10.0' + implementation 'com.squareup.moshi:moshi:1.14.0' + + testImplementation libs.assertj.core + testImplementation 'com.squareup.okhttp3:mockwebserver:4.10.0' +} \ No newline at end of file diff --git a/wallets/json-rpc/src/main/java/bisq/wallets/json_rpc/JsonRpcCall.java b/wallets/json-rpc/src/main/java/bisq/wallets/json_rpc/JsonRpcCall.java new file mode 100644 index 0000000000..9fd4cbe2ad --- /dev/null +++ b/wallets/json-rpc/src/main/java/bisq/wallets/json_rpc/JsonRpcCall.java @@ -0,0 +1,33 @@ +/* + * 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.wallets.json_rpc; + +import java.util.UUID; + +public class JsonRpcCall { + private final String jsonrpc = "2.0"; + private final String id; + private final String method; + private final Object params; + + public JsonRpcCall(String method, Object params) { + this.id = UUID.randomUUID().toString(); + this.method = method; + this.params = params; + } +} diff --git a/wallets/json-rpc/src/main/java/bisq/wallets/json_rpc/JsonRpcClient.java b/wallets/json-rpc/src/main/java/bisq/wallets/json_rpc/JsonRpcClient.java new file mode 100644 index 0000000000..1c8669dc40 --- /dev/null +++ b/wallets/json-rpc/src/main/java/bisq/wallets/json_rpc/JsonRpcClient.java @@ -0,0 +1,66 @@ +/* + * 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.wallets.json_rpc; + +import com.squareup.moshi.JsonAdapter; +import com.squareup.moshi.Moshi; +import okhttp3.*; + +import java.io.IOException; +import java.util.Objects; + +public class JsonRpcClient { + + public static final String AUTHORIZATION_HEADER_NAME = "Authorization"; + + private final JsonRpcEndpointSpec rpcEndpointSpec; + + private final OkHttpClient client = new OkHttpClient(); + private final MediaType jsonMediaType = MediaType.parse("application/json"); + + private final Moshi moshi = new Moshi.Builder().build(); + private final JsonAdapter jsonRpcCallJsonAdapter = moshi.adapter(JsonRpcCall.class); + + public JsonRpcClient(JsonRpcEndpointSpec rpcEndpointSpec) { + this.rpcEndpointSpec = rpcEndpointSpec; + } + + public R call(RpcCall rpcCall) throws IOException { + JsonRpcCall jsonRpcCall = new JsonRpcCall(rpcCall.getRpcMethodName(), rpcCall.request); + String jsonRequest = jsonRpcCallJsonAdapter.toJson(jsonRpcCall); + Request request = buildRequest(jsonRequest); + + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); + + ResponseBody responseBody = response.body(); + Objects.requireNonNull(responseBody); + + JsonAdapter jsonAdapter = rpcCall.getJsonAdapter(); + return jsonAdapter.fromJson(responseBody.source()); + } + } + + private Request buildRequest(String body) { + return new Request.Builder() + .url(rpcEndpointSpec.getUrl()) + .addHeader(AUTHORIZATION_HEADER_NAME, rpcEndpointSpec.getAuthHeaderValue()) + .post(RequestBody.create(body, jsonMediaType)) + .build(); + } +} diff --git a/wallets/json-rpc/src/main/java/bisq/wallets/json_rpc/JsonRpcEndpointSpec.java b/wallets/json-rpc/src/main/java/bisq/wallets/json_rpc/JsonRpcEndpointSpec.java new file mode 100644 index 0000000000..42fb0b6e45 --- /dev/null +++ b/wallets/json-rpc/src/main/java/bisq/wallets/json_rpc/JsonRpcEndpointSpec.java @@ -0,0 +1,43 @@ +/* + * 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.wallets.json_rpc; + +import lombok.Getter; +import okhttp3.Credentials; +import okhttp3.HttpUrl; + +@Getter +public class JsonRpcEndpointSpec { + private final HttpUrl url; + private final String username; + private final String password; + + public JsonRpcEndpointSpec(String url, String username, String password) { + this(HttpUrl.parse(url), username, password); + } + + public JsonRpcEndpointSpec(HttpUrl url, String username, String password) { + this.url = url; + this.username = username; + this.password = password; + } + + public String getAuthHeaderValue() { + return Credentials.basic(username, password); + } +} diff --git a/wallets/json-rpc/src/main/java/bisq/wallets/json_rpc/RpcCall.java b/wallets/json-rpc/src/main/java/bisq/wallets/json_rpc/RpcCall.java new file mode 100644 index 0000000000..1b4f197587 --- /dev/null +++ b/wallets/json-rpc/src/main/java/bisq/wallets/json_rpc/RpcCall.java @@ -0,0 +1,44 @@ +/* + * 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.wallets.json_rpc; + +import com.squareup.moshi.JsonAdapter; +import com.squareup.moshi.Moshi; +import lombok.Getter; + +public abstract class RpcCall { + + @Getter + protected final T request; + + private final Moshi moshi = new Moshi.Builder().build(); + + public RpcCall(T request) { + this.request = request; + } + + public abstract String getRpcMethodName(); + + public abstract boolean isResponseValid(R response); + + public abstract Class getRpcResponseClass(); + + public JsonAdapter getJsonAdapter() { + return moshi.adapter(getRpcResponseClass()); + } +} diff --git a/wallets/json-rpc/src/test/java/bisq/wallets/json_rpc/DummyGetBlockChainInfoRpcCall.java b/wallets/json-rpc/src/test/java/bisq/wallets/json_rpc/DummyGetBlockChainInfoRpcCall.java new file mode 100644 index 0000000000..df1a2ee911 --- /dev/null +++ b/wallets/json-rpc/src/test/java/bisq/wallets/json_rpc/DummyGetBlockChainInfoRpcCall.java @@ -0,0 +1,42 @@ +/* + * 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.wallets.json_rpc; + +import java.util.HashMap; +import java.util.Map; + +public class DummyGetBlockChainInfoRpcCall extends RpcCall, DummyJsonRpcResponse> { + public DummyGetBlockChainInfoRpcCall() { + super(new HashMap<>()); + } + + @Override + public String getRpcMethodName() { + return "getblockchaininfo"; + } + + @Override + public boolean isResponseValid(DummyJsonRpcResponse response) { + return true; + } + + @Override + public Class getRpcResponseClass() { + return DummyJsonRpcResponse.class; + } +} diff --git a/wallets/json-rpc/src/test/java/bisq/wallets/json_rpc/DummyJsonRpcResponse.java b/wallets/json-rpc/src/test/java/bisq/wallets/json_rpc/DummyJsonRpcResponse.java new file mode 100644 index 0000000000..67a36790d9 --- /dev/null +++ b/wallets/json-rpc/src/test/java/bisq/wallets/json_rpc/DummyJsonRpcResponse.java @@ -0,0 +1,22 @@ +/* + * 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.wallets.json_rpc; + +public class DummyJsonRpcResponse { + GetBlockChainInfoDummyResponse result; +} diff --git a/wallets/json-rpc/src/test/java/bisq/wallets/json_rpc/GetBlockChainInfoDummyResponse.java b/wallets/json-rpc/src/test/java/bisq/wallets/json_rpc/GetBlockChainInfoDummyResponse.java new file mode 100644 index 0000000000..194fd7bdcd --- /dev/null +++ b/wallets/json-rpc/src/test/java/bisq/wallets/json_rpc/GetBlockChainInfoDummyResponse.java @@ -0,0 +1,22 @@ +/* + * 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.wallets.json_rpc; + +public class GetBlockChainInfoDummyResponse { + String chain; +} diff --git a/wallets/json-rpc/src/test/java/bisq/wallets/json_rpc/JsonRpcClientTest.java b/wallets/json-rpc/src/test/java/bisq/wallets/json_rpc/JsonRpcClientTest.java new file mode 100644 index 0000000000..75ce105c24 --- /dev/null +++ b/wallets/json-rpc/src/test/java/bisq/wallets/json_rpc/JsonRpcClientTest.java @@ -0,0 +1,66 @@ +/* + * 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.wallets.json_rpc; + +import okhttp3.HttpUrl; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class JsonRpcClientTest { + + private final MockWebServer server = new MockWebServer(); + + @BeforeEach + void setUp() throws IOException { + String mockResponse = "{\"result\":{\"chain\":\"regtest\",\"blocks\":102,\"headers\":102,\"bestblockhash\":\"5e5bcaaa2690bf99da1046334e4a2d783901608718a5b647846e67ffe0802bc8\",\"difficulty\":4.656542373906925e-10,\"time\":1663343392,\"mediantime\":1663060997,\"verificationprogress\":1,\"initialblockdownload\":true,\"chainwork\":\"00000000000000000000000000000000000000000000000000000000000000ce\",\"size_on_disk\":31048,\"pruned\":false,\"warnings\":\"\"},\"error\":null,\"id\":\"curltest\"}"; + server.enqueue(new MockResponse().setBody(mockResponse)); + server.start(); + } + + @AfterEach + void tearDown() throws IOException { + server.shutdown(); + } + + @Test + void dummyGetBlockchainInfoTest() throws Exception { + HttpUrl baseUrl = server.url("/"); + JsonRpcEndpointSpec endpointSpec = new JsonRpcEndpointSpec(baseUrl, "bisq", "bisq"); + JsonRpcClient jsonRpcClient = new JsonRpcClient(endpointSpec); + + var rpcCall = new DummyGetBlockChainInfoRpcCall(); + DummyJsonRpcResponse dummyJsonRpcResponse = jsonRpcClient.call(rpcCall); + + assertThat(dummyJsonRpcResponse).isNotNull(); + assertThat(dummyJsonRpcResponse.result.chain).isEqualTo("regtest"); + + RecordedRequest recordedRequest = server.takeRequest(); + assertThat("/").isEqualTo(recordedRequest.getPath()); + + String authHeader = recordedRequest.getHeader(JsonRpcClient.AUTHORIZATION_HEADER_NAME); + assertThat(authHeader).isNotNull(); + } +} diff --git a/wallets/settings.gradle b/wallets/settings.gradle index dabc636b0b..0d3a93086b 100644 --- a/wallets/settings.gradle +++ b/wallets/settings.gradle @@ -21,6 +21,7 @@ include 'bitcoind' include 'electrum' include 'elementsd' +include 'json-rpc' include 'process' include 'regtest'