package jota; import jota.dto.request.*; import jota.dto.response.*; import jota.error.InvalidTrytesException; import jota.utils.InputValidator; import okhttp3.OkHttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import retrofit2.Call; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.List; import java.util.Properties; import java.util.concurrent.TimeUnit; /** * This class provides access to the Iota core API * * @author Adrian */ public class IotaAPICore { private static final Logger log = LoggerFactory.getLogger(IotaAPICore.class); private IotaAPIService service; private String protocol, host, port; /** * Build the API core. * * @param builder The builder. */ protected IotaAPICore(final Builder builder) { protocol = builder.protocol; host = builder.host; port = builder.port; postConstruct(); } /** * @param call * @param * @return */ protected static Response wrapCheckedException(final Call call) { try { final Response res = call.execute(); if (res.code() == 400) { throw new IllegalAccessError("400 " + res.errorBody().string()); } else if (res.code() == 401) { throw new IllegalAccessError("401 " + res.errorBody().string()); } else if (res.code() == 500) { throw new IllegalAccessError("500 " + res.errorBody().string()); } return res; } catch (IOException e) { log.error("Execution of the API call raised exception. IOTA Node not reachable?", e); throw new IllegalStateException(e.getMessage()); } } /** * @param env * @param def * @return */ private static String env(String env, String def) { final String value = System.getenv(env); if (value == null) { log.warn("Environment variable '{}' is not defined, and actual value has not been specified. " + "Rolling back to default value: '{}'", env, def); return def; } return value; } /** * */ private void postConstruct() { final String nodeUrl = protocol + "://" + host + ":" + port; final OkHttpClient client = new OkHttpClient.Builder() .readTimeout(5000, TimeUnit.SECONDS) .connectTimeout(5000, TimeUnit.SECONDS) .build(); final Retrofit retrofit = new Retrofit.Builder() .baseUrl(nodeUrl) .addConverterFactory(GsonConverterFactory.create()) .client(client) .build(); service = retrofit.create(IotaAPIService.class); log.debug("Jota-API Java proxy pointing to node url: '{}'", nodeUrl); } public GetNodeInfoResponse getNodeInfo() { final Call res = service.getNodeInfo(IotaCommandRequest.createNodeInfoRequest()); return wrapCheckedException(res).body(); } public GetNeighborsResponse getNeighbors() { final Call res = service.getNeighbors(IotaCommandRequest.createGetNeighborsRequest()); return wrapCheckedException(res).body(); } public AddNeighborsResponse addNeighbors(String... uris) { final Call res = service.addNeighbors(IotaNeighborsRequest.createAddNeighborsRequest(uris)); return wrapCheckedException(res).body(); } public RemoveNeighborsResponse removeNeighbors(String... uris) { final Call res = service.removeNeighbors(IotaNeighborsRequest.createRemoveNeighborsRequest(uris)); return wrapCheckedException(res).body(); } public GetTipsResponse getTips() { final Call res = service.getTips(IotaCommandRequest.createGetTipsRequest()); return wrapCheckedException(res).body(); } public FindTransactionResponse findTransactions(String[] addresses, String[] tags, String[] approvees, String[] bundles) { final IotaFindTransactionsRequest findTransRequest = IotaFindTransactionsRequest .createFindTransactionRequest() .byAddresses(addresses) .byTags(tags) .byApprovees(approvees) .byBundles(bundles); final Call res = service.findTransactions(findTransRequest); return wrapCheckedException(res).body(); } public FindTransactionResponse findTransactionsByAddresses(final String... addresses) { return findTransactions(addresses, null, null, null); } public FindTransactionResponse findTransactionsByBundles(final String... bundles) { return findTransactions(null, null, null, bundles); } public FindTransactionResponse findTransactionsByApprovees(final String... approvees) { return findTransactions(null, null, approvees, null); } public FindTransactionResponse findTransactionsByDigests(final String... digests) { return findTransactions(null, digests, null, null); } public GetInclusionStateResponse getInclusionStates(String[] transactions, String[] tips) { final Call res = service.getInclusionStates(IotaGetInclusionStateRequest .createGetInclusionStateRequest(transactions, tips)); return wrapCheckedException(res).body(); } public GetTrytesResponse getTrytes(String... hashes) { final Call res = service.getTrytes(IotaGetTrytesRequest.createGetTrytesRequest(hashes)); return wrapCheckedException(res).body(); } public GetTransactionsToApproveResponse getTransactionsToApprove(Integer depth) { final Call res = service.getTransactionsToApprove(IotaGetTransactionsToApproveRequest.createIotaGetTransactionsToApproveRequest(depth)); return wrapCheckedException(res).body(); } public GetBalancesResponse getBalances(Integer threshold, String[] addresses) { final Call res = service.getBalances(IotaGetBalancesRequest.createIotaGetBalancesRequest(threshold, addresses)); return wrapCheckedException(res).body(); } public GetBalancesResponse getBalances(Integer threshold, List addresses) { return getBalances(threshold, addresses.toArray(new String[]{})); } public InterruptAttachingToTangleResponse interruptAttachingToTangle() { final Call res = service.interruptAttachingToTangle(IotaCommandRequest.createInterruptAttachToTangleRequest()); return wrapCheckedException(res).body(); } public GetAttachToTangleResponse attachToTangle(String trunkTransaction, String branchTransaction, Integer minWeightMagnitude, String... trytes) throws InvalidTrytesException { if (!InputValidator.isArrayOfTrytes(trytes)) { throw new InvalidTrytesException(); } final Call res = service.attachToTangle(IotaAttachToTangleRequest.createAttachToTangleRequest(trunkTransaction, branchTransaction, minWeightMagnitude, trytes)); return wrapCheckedException(res).body(); } public StoreTransactionsResponse storeTransactions(String... trytes) { final Call res = service.storeTransactions(IotaStoreTransactionsRequest.createStoreTransactionsRequest(trytes)); return wrapCheckedException(res).body(); } public BroadcastTransactionsResponse broadcastTransactions(String... trytes) { final Call res = service.broadcastTransactions(IotaBroadcastTransactionRequest.createBroadcastTransactionsRequest(trytes)); return wrapCheckedException(res).body(); } @SuppressWarnings("unchecked") public static class Builder> { String protocol, host, port; public IotaAPICore build() { if (protocol == null || host == null || port == null) { // check properties files. if (!checkPropertiesFiles()) { // last resort: best effort on enviroment variable, // before assigning default values. checkEnviromentVariables(); } } return new IotaAPICore(this); } /** * @return */ private boolean checkPropertiesFiles() { try { FileReader fileReader = new FileReader("node_config.properties"); BufferedReader bufferedReader = new BufferedReader(fileReader); final Properties nodeConfig = new Properties(); nodeConfig.load(bufferedReader); if (nodeConfig.getProperty("iota.node.protocol") != null) { protocol = nodeConfig.getProperty("iota.node.protocol"); } if (nodeConfig.getProperty("iota.node.host") != null) { host = nodeConfig.getProperty("iota.node.host"); } if (nodeConfig.getProperty("iota.node.port") != null) { port = nodeConfig.getProperty("iota.node.port"); } } catch (IOException e1) { log.debug("node_config.properties not found. Rolling back for another solution..."); } return (port != null && protocol != null && host != null); } /** * */ private void checkEnviromentVariables() { protocol = env("IOTA_NODE_PROTOCOL", "http"); host = env("IOTA_NODE_HOST", "localhost"); port = env("IOTA_NODE_PORT", "14265"); } /** * @param host * @return */ public T host(String host) { this.host = host; return (T) this; } /** * @param port * @return */ public T port(String port) { this.port = port; return (T) this; } /** * @param protocol * @return */ public T protocol(String protocol) { this.protocol = protocol; return (T) this; } } }