Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,29 @@ public interface ExonumClient {
*/
HashCode submitTransaction(TransactionMessage tx);

/**
* Returns a number of unconfirmed transactions those are currently located in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that/which are currently …

* the memory pool and are waiting for acceptance to a block.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: AFAIK, it is not a memory pool (they are persisted to avoid their loss in case a node goes down).

* @throws RuntimeException if the client is unable to complete a request
* (e.g., in case of connectivity problems)
*/
int getUnconfirmedTransactions();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will such a name work well when we add getTransaction/getTransactions, as it does not return transactions, but their number? getNumUnconfirmedTransactions — will be more accurate, but I am not quite happy with the name length :-)


/**
* Returns <b>true</b> if the node is connected to the other peers.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd not use bold for that, but no formatting or monospace.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall it be connected to all peers to be in the network? Or some peers? Or at least one peer?

* And <b>false</b> otherwise.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it required to write at each method that it works in a blocking way? 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd put that in a class level comment (unless we make a hybrid with both blocking and non-blocking APIs)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd consider simplifying to:

to the other peers; false — otherwise.

* @throws RuntimeException if the client is unable to complete a request
* (e.g., in case of connectivity problems)
*/
boolean healthCheck();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more proper name?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isNodeConnected? isNodeInNetwork?

BTW, is it OK to get an exception if the client is not connected? (probably, yes, as we need to communicate different problems differently; but the helthCheck is kind of an innocent name)


/**
* Returns string containing information about Exonum, Rust and OS version.
* @throws RuntimeException if the client is unable to complete a request
* (e.g., in case of connectivity problems)
*/
String getUserAgentInfo();

/**
* Returns Exonum client builder.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
package com.exonum.client;

import static com.exonum.binding.common.serialization.json.JsonSerializer.json;
import static com.exonum.client.ExonumUrls.HEALTH_CHECK;
import static com.exonum.client.ExonumUrls.MEMORY_POOL;
import static com.exonum.client.ExonumUrls.SUBMIT_TRANSACTION;
import static com.exonum.client.ExonumUrls.USER_AGENT;

import com.exonum.binding.common.hash.HashCode;
import com.exonum.binding.common.message.TransactionMessage;
Expand Down Expand Up @@ -54,17 +57,50 @@ class ExonumHttpClient implements ExonumClient {
public HashCode submitTransaction(TransactionMessage transactionMessage) {
String msg = toHex(transactionMessage.toBytes());

Request request = createRequest(toFullUrl(SUBMIT_TRANSACTION), new SubmitTxRequest(msg));
Request request = postRequest(toFullUrl(SUBMIT_TRANSACTION), new SubmitTxRequest(msg));
SubmitTxResponse result = blockingExecuteWithResponse(request, SubmitTxResponse.class);

return result.hash;
}

@Override
public int getUnconfirmedTransactions() {
Request request = getRequest(toFullUrl(MEMORY_POOL));
MemoryPoolResponse result = blockingExecuteWithResponse(request,
MemoryPoolResponse.class);

return result.size;
}

@Override
public boolean healthCheck() {
Request request = getRequest(toFullUrl(HEALTH_CHECK));
HealthCheckResponse result = blockingExecuteWithResponse(request,
HealthCheckResponse.class);

return result.connectivity;
}

@Override
public String getUserAgentInfo() {
Request request = getRequest(toFullUrl(USER_AGENT));
String result = blockingExecutePlainText(request);

return result;
}

private static String toHex(byte[] array) {
return HEX_ENCODER.encode(array);
}

private static Request createRequest(URL url, Object requestBody) {
private static Request getRequest(URL url) {
return new Request.Builder()
.url(url)
.get()
.build();
}

private static Request postRequest(URL url, Object requestBody) {
String jsonBody = json().toJson(requestBody);

return new Request.Builder()
Expand All @@ -81,17 +117,20 @@ private URL toFullUrl(String relativeUrl) {
}
}

private <T> T blockingExecuteWithResponse(Request request, Class<T> responseObject) {
private String blockingExecutePlainText(Request request) {
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new RuntimeException("Execution wasn't success: " + response.toString());
}
String responseJson = response.body().string();

return json().fromJson(responseJson, responseObject);
return response.body().string();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

private <T> T blockingExecuteWithResponse(Request request, Class<T> type) {
String response = blockingExecutePlainText(request);
return json().fromJson(response, type);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,11 @@
* Contains Exonum API URLs.
*/
final class ExonumUrls {
static final String SUBMIT_TRANSACTION = "/api/explorer/v1/transactions";
private static final String EXPLORER_PATHS_PREFIX = "/api/explorer/v1";
private static final String SYS_PATHS_PREFIX = "/api/system/v1";
static final String SUBMIT_TRANSACTION = EXPLORER_PATHS_PREFIX + "/transactions";
static final String MEMORY_POOL = SYS_PATHS_PREFIX + "/mempool";
static final String HEALTH_CHECK = SYS_PATHS_PREFIX + "/healthcheck";
static final String USER_AGENT = SYS_PATHS_PREFIX + "/user_agent";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2019 The Exonum Team
*
* 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 com.exonum.client;

import com.google.common.base.MoreObjects;

/**
* Json object wrapper for health check response.
*/
class HealthCheckResponse {
boolean connectivity;

@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("connectivity", connectivity)
.toString();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2019 The Exonum Team
*
* 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 com.exonum.client;

import com.google.common.base.MoreObjects;

/**
* Json object wrapper for memory pool response.
*/
class MemoryPoolResponse {
int size;

@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("size", size)
.toString();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
import static com.exonum.binding.common.crypto.CryptoFunctions.ed25519;
import static com.exonum.binding.common.serialization.json.JsonSerializer.json;
import static com.exonum.client.ExonumHttpClient.HEX_ENCODER;
import static com.exonum.client.ExonumUrls.HEALTH_CHECK;
import static com.exonum.client.ExonumUrls.MEMORY_POOL;
import static com.exonum.client.ExonumUrls.SUBMIT_TRANSACTION;
import static com.exonum.client.ExonumUrls.USER_AGENT;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

Expand Down Expand Up @@ -89,4 +92,59 @@ void submitTransactionTest() throws InterruptedException {
assertThat(actualTxMessage, is(txMessage));
}

@Test
void getUnconfirmedTransactions() throws InterruptedException {
// Mock response
int mockCount = 10;
String mockResponse = "{\"size\": " + mockCount + " }";
server.enqueue(new MockResponse().setBody(mockResponse));

// Call
int actualCount = exonumClient.getUnconfirmedTransactions();

// Assert response
assertThat(actualCount, is(mockCount));

// Assert request params
RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getMethod(), is("GET"));
assertThat(recordedRequest.getPath(), is(MEMORY_POOL));
}

@Test
void healthCheck() throws InterruptedException {
// Mock response
boolean mockConnectivity = true;
String mockResponse = "{\"connectivity\": " + mockConnectivity + " }";
server.enqueue(new MockResponse().setBody(mockResponse));

// Call
boolean actualConnectivity = exonumClient.healthCheck();

// Assert response
assertThat(actualConnectivity, is(mockConnectivity));

// Assert request params
RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getMethod(), is("GET"));
assertThat(recordedRequest.getPath(), is(HEALTH_CHECK));
}

@Test
void getUserAgentInfo() throws InterruptedException {
// Mock response
String mockResponse = "exonum 0.6.0/rustc 1.26.0 (2789b067d 2018-03-06)\n\n/Mac OS10.13.3";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see — a String will do 🙃

server.enqueue(new MockResponse().setBody(mockResponse));

// Call
String actualResponse = exonumClient.getUserAgentInfo();

// Assert response
assertThat(actualResponse, is(mockResponse));

// Assert request params
RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getMethod(), is("GET"));
assertThat(recordedRequest.getPath(), is(USER_AGENT));
}
}