diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..964491c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +language: java +jdk: +- oraclejdk8 +script: mvn package +deploy: + provider: releases + file_glob: true + file: "/home/travis/build/iotaledger/iota.lib.java/target/jota*.jar" + skip_cleanup: true + on: + tags: true + repo: iotaledger/iota.lib.java + all_branches: true diff --git a/README.md b/README.md index c0f5a0e..915be80 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ The JOTA library is a simple Java wrapper around [[IOTA]](http://www.iotatoken.c It allows to connect easily using java directly to a local or a remote [[IOTA node]](https://iota.readme.io/docs/syncing-to-the-network). -* **Latest release:** 1.0.0 Release -* **Compatibility:** in development to be fully compatible with IOTA IRI v1.1.0 +* **Latest release:** 0.9.0 RC1 +* **Compatibility:** fully compatible with IOTA IRI v1.2.4 * **API coverage:** 14 of 14 commands fully implemented * **License:** Apache License 2.0 -* **Readme updated:** 2016-11-12 21:05:02 (UTC) +* **Readme updated:** 2016-01-19 21:05:02 (UTC) A list of all *IOTA* JSON-REST API commands currently supported by jota wrapper can be found in the `Commands` enum (see [here](https://github.com/davassi/JOTA/blob/master/src/main/java/jota/IotaAPICommands.java) for more details). @@ -32,27 +32,33 @@ Other dependencies: Connect to your local node with the default settings is quite straightforward: it requires only 2 lines of code. For example, in order to fetch the Node Info: - IotaApiProxy api = new IotaApiProxy.Builder.build(); + IotaApi api = new IotaApi.Builder.build(); GetNodeInfoResponse response = api.getNodeInfo(); -of if you need to connect to a remote node on https: +of if you need to connect to a remote node: - IotaApiProxy api = new IotaApiProxy.Builder - .protocol("https") + IotaApi api = new IotaApi.Builder + .protocol("http") .nodeAddress("somewhere_over_the_rainbow") - .port(54321) + .port(14265) .build(); GetNodeInfoResponse response = api.getNodeInfo(); -Jota is still *not* in the central maven repository. It will be available when it will cover 100% iota's rest interface. +In order to communicate with *IOTA node*, JOTA needs to be aware of your node's exact configuration. If you dont want to use the builder the easiest way of providing this information is via a `node_config.properties` file, for example: -In order to communicate with *IOTA node*, JOTA needs to be aware of your node's exact configuration. The easiest way of providing this information is via a `node_config.properties` file, for example: - - iota.node.protocol=http + iota.node.protocol=http``****************`` iota.node.host=127.0.0.1 iota.node.port=14265 +Jota is still *not* in the central maven repository. It will be available when it will cover 100% iota's rest interface. + +##Warning + - This is pre-release software! + - There may be performance and stability issues. + - You may loose all your money :) + - Please report any issues using the Issue Tracker" + That's it! ##Examples @@ -63,7 +69,5 @@ There's an extensive list of test coverages on the src/test/java package of the If JOTA has been useful to you and you feel like contributing, consider posting a bug report or a pull request. Alternatively, donations are very welcome too! -* Bitcoin: `3FGCHqhG1SUpgn2eS1Agq2KnxJemWnQFbB` - - - +* Bitcoin (Gianni Davassi): `3FGCHqhG1SUpgn2eS1Agq2KnxJemWnQFbB` +* Bitcoin (Adrian Ziser): `3FGCHqhG1SUpgn2eS1Agq2KnxJemWnQFbB` \ No newline at end of file diff --git a/node_config.properties b/node_config.properties index e5e5a5a..fbd32d9 100644 --- a/node_config.properties +++ b/node_config.properties @@ -1,5 +1,7 @@ iota.node.protocol=http -#iota.node.host=138.68.126.141 iota.node.host=node.iotawallet.info +#iota.node.host=138.68.90.186 +#iota.node.host=192.168.11.2 +#iota.node.host=138.68.90.186 iota.node.port=14265 diff --git a/pom.xml b/pom.xml index 70b381e..31625ee 100644 --- a/pom.xml +++ b/pom.xml @@ -2,12 +2,20 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - com.iota + org.iota jota - 1.0.0-RELEASE + 0.9.0-RC1 JOTA JOTA library is a simple Java wrapper around IOTA Node's JSON-REST HTTP interface. + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + 1.7 UTF-8 @@ -63,6 +71,7 @@ 4.12 test + @@ -91,6 +100,13 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + true + + diff --git a/src/main/java/jota/IotaAPI.java b/src/main/java/jota/IotaAPI.java new file mode 100644 index 0000000..ad1353d --- /dev/null +++ b/src/main/java/jota/IotaAPI.java @@ -0,0 +1,836 @@ +package jota; + +import jota.dto.response.*; +import jota.error.*; +import jota.model.*; +import jota.pow.ICurl; +import jota.pow.JCurl; +import jota.utils.*; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + +/** + * IotaAPI Builder. Usage: + *

+ * IotaApiProxy api = IotaApiProxy.Builder + * .protocol("http") + * .nodeAddress("localhost") + * .port(12345) + * .build(); + *

+ * GetNodeInfoResponse response = api.getNodeInfo(); + * + * @author davassi + */ +public class IotaAPI extends IotaAPICore { + + private static final Logger log = LoggerFactory.getLogger(IotaAPI.class); + private ICurl customCurl; + private StopWatch stopWatch; + + protected IotaAPI(Builder builder) { + super(builder); + customCurl = builder.customCurl; + } + + /** + * Generates a new address from a seed and returns the remainderAddress. + * This is either done deterministically, or by providing the index of the new remainderAddress + * + * @param seed Tryte-encoded seed. It should be noted that this seed is not transferred + * @param index Optional (default null). Key index to start search from. If the index is provided, the generation of the address is not deterministic. + * @param checksum Optional (default false). Adds 9-tryte address checksum + * @param total Optional (default 1)Total number of addresses to generate + * @param returnAll If true, it returns all addresses which were deterministically generated (until findTransactions returns null) + * @return an array of strings with the specifed number of addresses + */ + public GetNewAddressResponse getNewAddress(final String seed, final int index, final boolean checksum, final int total, final boolean returnAll) { + StopWatch stopWatch = new StopWatch(); + + List allAddresses = new ArrayList<>(); + + // If total number of addresses to generate is supplied, simply generate + // and return the list of all addresses + if (total != 0) { + for (int i = index; i < index + total; i++) { + allAddresses.add(IotaAPIUtils.newAddress(seed, i, checksum, customCurl)); + } + return GetNewAddressResponse.create(allAddresses, stopWatch.getElapsedTimeMili()); + } + // No total provided: Continue calling findTransactions to see if address was + // already created if null, return list of addresses + for (int i = index; ; i++) { + + final String newAddress = IotaAPIUtils.newAddress(seed, i, checksum, customCurl); + final FindTransactionResponse response = findTransactionsByAddresses(newAddress); + + allAddresses.add(newAddress); + if (response.getHashes().length == 0) { + break; + } + } + + // If !returnAll return only the last address that was generated + if (!returnAll) { + + //allAddresses = allAddresses.subList(allAddresses.size() - 2, allAddresses.size() - 1); + allAddresses = allAddresses.subList(allAddresses.size() - 1, allAddresses.size()); + } + return GetNewAddressResponse.create(allAddresses, stopWatch.getElapsedTimeMili()); + } + + /** + * @param {string} seed + * @param {object} options + * @param {function} callback + * @method getTransfers + * @property {int} start Starting key index + * @property {int} end Ending key index + * @property {bool} inclusionStates returns confirmation status of all transactions + * @returns {object} success + **/ + public GetTransferResponse getTransfers(String seed, Integer start, Integer end, Boolean inclusionStates) throws ArgumentException, InvalidBundleException, InvalidSignatureException, NoNodeInfoException, NoInclusionStatesExcpection { + StopWatch stopWatch = new StopWatch(); + // validate & if needed pad seed + if ((seed = InputValidator.validateSeed(seed)) == null) { + throw new IllegalStateException("Invalid Seed"); + } + + start = start != null ? 0 : start; + + if (start > end || end > (start + 500)) { + throw new ArgumentException(); + } + StopWatch sw = new StopWatch(); + + System.out.println("GetTransfer started"); + GetNewAddressResponse gnr = getNewAddress(seed, start, false, end == null ? end - start : end, true); + if (gnr != null && gnr.getAddresses() != null) { + System.out.println("GetTransfers after getNewAddresses " + sw.getElapsedTimeMili() + " ms"); + Bundle[] bundles = bundlesFromAddresses(gnr.getAddresses().toArray(new String[gnr.getAddresses().size()]), inclusionStates); + System.out.println("GetTransfers after bundlesFromAddresses " + sw.getElapsedTimeMili() + " ms"); + return GetTransferResponse.create(bundles, stopWatch.getElapsedTimeMili()); + } + return GetTransferResponse.create(new Bundle[]{}, stopWatch.getElapsedTimeMili()); + } + + public Bundle[] bundlesFromAddresses(String[] addresses, final Boolean inclusionStates) throws ArgumentException, InvalidBundleException, InvalidSignatureException, NoNodeInfoException, NoInclusionStatesExcpection { + + List trxs = findTransactionObjects(addresses); + // set of tail transactions + List tailTransactions = new ArrayList<>(); + List nonTailBundleHashes = new ArrayList<>(); + + for (Transaction trx : trxs) { + // Sort tail and nonTails + if (Long.parseLong(trx.getCurrentIndex()) == 0) { + tailTransactions.add(trx.getHash()); + } else { + if (nonTailBundleHashes.indexOf(trx.getBundle()) == -1) { + nonTailBundleHashes.add(trx.getBundle()); + } + } + } + + List bundleObjects = findTransactionObjectsByBundle(nonTailBundleHashes.toArray(new String[nonTailBundleHashes.size()])); + for (Transaction trx : bundleObjects) { + // Sort tail and nonTails + if (Long.parseLong(trx.getCurrentIndex()) == 0) { + if (tailTransactions.indexOf(trx.getHash()) == -1) { + tailTransactions.add(trx.getHash()); + } + } + } + + final List finalBundles = new ArrayList<>(); + final String[] tailTxArray = tailTransactions.toArray(new String[tailTransactions.size()]); + + // If inclusionStates, get the confirmation status + // of the tail transactions, and thus the bundles + GetInclusionStateResponse gisr = null; + if (tailTxArray != null && tailTxArray.length != 0 && inclusionStates) { + try { + gisr = getLatestInclusion(tailTxArray); + } catch (IllegalAccessError ignored) { + throw new NoInclusionStatesExcpection(); + } + if (gisr == null || gisr.getStates() == null || gisr.getStates().length == 0) { + throw new NoInclusionStatesExcpection(); + } + } + final GetInclusionStateResponse finalInclusionStates = gisr; + Parallel.For(Arrays.asList(tailTxArray), + new Parallel.Operation() { + public void perform(String param) { + + try { + GetBundleResponse bundleResponse = getBundle(param); + Bundle gbr = new Bundle(bundleResponse.getTransactions(), bundleResponse.getTransactions().size()); + if (gbr != null && gbr.getTransactions() != null) { + if (inclusionStates) { + boolean thisInclusion = finalInclusionStates.getStates()[Arrays.asList(tailTxArray).indexOf(param)]; + for (Transaction t : gbr.getTransactions()) { + t.setPersistence(thisInclusion); + } + } + finalBundles.add(gbr); + } + // If error returned from getBundle, simply ignore it because the bundle was most likely incorrect + } catch (InvalidBundleException | ArgumentException | InvalidSignatureException e) { + log.warn("GetBundleError: ", e); + } + } + }); + + Collections.sort(finalBundles); + Bundle[] returnValue = new Bundle[finalBundles.size()]; + for (int i = 0; i < finalBundles.size(); i++) { + returnValue[i] = new Bundle(finalBundles.get(i).getTransactions(), finalBundles.get(i).getTransactions().size()); + } + return returnValue; + } + + /** + * @param trytes + * @return a StoreTransactionsResponse + */ + public StoreTransactionsResponse broadcastAndStore(final String... trytes) throws BroadcastAndStoreException { + + try { + broadcastTransactions(trytes); + } catch (Exception e) { + log.error("Impossible to broadcastAndStore, aborting.", e); + throw new BroadcastAndStoreException(); + } + return storeTransactions(trytes); + } + + /** + * Facade method: Gets transactions to approve, attaches to Tangle, broadcasts and stores + * + * @param {array} trytes + * @param {int} depth + * @param {int} minWeightMagnitude + * @return + */ + public List sendTrytes(final String[] trytes, final int depth, final int minWeightMagnitude) { + final GetTransactionsToApproveResponse txs = getTransactionsToApprove(depth); + + // attach to tangle - do pow + final GetAttachToTangleResponse res = attachToTangle(txs.getTrunkTransaction(), txs.getBranchTransaction(), minWeightMagnitude, trytes); + + try { + broadcastAndStore(res.getTrytes()); + } catch (BroadcastAndStoreException e) { + return new ArrayList<>(); + } + + final List trx = new ArrayList<>(); + + for (final String tryte : Arrays.asList(res.getTrytes())) { + trx.add(new Transaction(tryte, customCurl)); + } + return trx; + } + + /** + * Wrapper function for getTrytes and transactionObjects + * gets the trytes and transaction object from a list of transaction hashes + * + * @param {array} hashes + * @return + * @method getTransactionsObjects + * @returns {function} callback + * @returns {object} success + **/ + public List getTransactionsObjects(String[] hashes) { + + if (!InputValidator.isArrayOfHashes(hashes)) { + throw new IllegalStateException("Not an Array of Hashes: " + Arrays.toString(hashes)); + } + + final GetTrytesResponse trytesResponse = getTrytes(hashes); + + final List trxs = new ArrayList<>(); + + for (final String tryte : trytesResponse.getTrytes()) { + trxs.add(new Transaction(tryte, customCurl)); + } + return trxs; + } + + /** + * Wrapper function for findTransactions, getTrytes and transactionObjects + * Returns the transactionObject of a transaction hash. The input can be a valid + * findTransactions input + * + * @param {object} input + * @method getTransactionsObjects + * @returns {function} callback + * @returns {object} success + **/ + public List findTransactionObjects(String[] input) { + FindTransactionResponse ftr = findTransactions(input, null, null, null); + if (ftr == null || ftr.getHashes() == null) + return new ArrayList<>(); + // get the transaction objects of the transactions + return getTransactionsObjects(ftr.getHashes()); + } + + /** + * Wrapper function for findTransactions, getTrytes and transactionObjects + * Returns the transactionObject of a transaction hash. The input can be a valid + * findTransactions input + * + * @param {object} input + * @method getTransactionsObjects + * @returns {function} callback + * @returns {object} success + **/ + public List findTransactionObjectsByBundle(String[] input) { + FindTransactionResponse ftr = findTransactions(null, null, null, input); + if (ftr == null || ftr.getHashes() == null) + return new ArrayList<>(); + + // get the transaction objects of the transactions + return getTransactionsObjects(ftr.getHashes()); + } + + /** + * Prepares transfer by generating bundle, finding and signing inputs + * + * @param {string} seed + * @param {object} transfers + * @param {object} options + * @param {function} callback + * @return + * @method prepareTransfers + * @property {array} inputs Inputs used for signing. Needs to have correct keyIndex and address value + * @property {string} address Remainder address + * @returns {array} trytes Returns bundle trytes + **/ + public List prepareTransfers(String seed, final List transfers, String remainder, List inputs) throws NotEnoughBalanceException { + + // Input validation of transfers object + if (!InputValidator.isTransfersCollectionCorrect(transfers)) { + throw new IllegalStateException("Invalid Transfer"); + } + + // validate & if needed pad seed + if ((seed = InputValidator.validateSeed(seed)) == null) { + throw new IllegalStateException("Invalid Seed"); + } + + + // Create a new bundle + final Bundle bundle = new Bundle(); + final List signatureFragments = new ArrayList<>(); + + long totalValue = 0; + String tag = ""; + + // Iterate over all transfers, get totalValue + // and prepare the signatureFragments, message and tag + for (final Transfer transfer : transfers) { + + int signatureMessageLength = 1; + + // If message longer than 2187 trytes, increase signatureMessageLength (add 2nd transaction) + if (transfer.getMessage().length() > 2187) { + + // Get total length, message / maxLength (2187 trytes) + signatureMessageLength += Math.floor(transfer.getMessage().length() / 2187); + + String msgCopy = transfer.getMessage(); + + // While there is still a message, copy it + while (!msgCopy.isEmpty()) { + + String fragment = StringUtils.substring(msgCopy, 0, 2187); + msgCopy = StringUtils.substring(msgCopy, 2187, msgCopy.length()); + + // Pad remainder of fragment + + fragment = StringUtils.rightPad(fragment, 2187, '9'); + + signatureFragments.add(fragment); + } + } else { + // Else, get single fragment with 2187 of 9's trytes + String fragment = StringUtils.substring(transfer.getMessage(), 0, 2187); + + fragment = StringUtils.rightPad(fragment, 2187, '9'); + + signatureFragments.add(fragment); + } + + // get current timestamp in seconds + long timestamp = (long) Math.floor(Calendar.getInstance().getTimeInMillis() / 1000); + + // If no tag defined, get 27 tryte tag. + tag = transfer.getTag().isEmpty() ? "999999999999999999999999999" : transfer.getTag(); + + // Pad for required 27 tryte length + tag = StringUtils.rightPad(tag, 27, '9'); + + // Add first entry to the bundle + bundle.addEntry(signatureMessageLength, transfer.getAddress(), transfer.getValue(), tag, timestamp); + // Sum up total value + totalValue += transfer.getValue(); + } + + // Get inputs if we are sending tokens + if (totalValue != 0) { + + // Case 1: user provided inputs + // Validate the inputs by calling getBalances + if (inputs != null && !inputs.isEmpty()) { + + // Get list if addresses of the provided inputs + List inputsAddresses = new ArrayList<>(); + for (final Input i : inputs) { + inputsAddresses.add(i.getAddress()); + } + + GetBalancesResponse balancesResponse = getBalances(100, inputsAddresses); + String[] balances = balancesResponse.getBalances(); + + List confirmedInputs = new ArrayList<>(); + int totalBalance = 0; + int i = 0; + for (String balance : balances) { + long thisBalance = Integer.parseInt(balance); + totalBalance += thisBalance; + + // If input has balance, add it to confirmedInputs + if (thisBalance > 0) { + Input inputEl = inputs.get(i++); + inputEl.setBalance(thisBalance); + confirmedInputs.add(inputEl); + } + } + + // Return not enough balance error + if (totalValue > totalBalance) { + throw new IllegalStateException("Not enough balance"); + } + + return addRemainder(seed, confirmedInputs, bundle, tag, totalValue, null, signatureFragments); + } + + // Case 2: Get inputs deterministically + // + // If no inputs provided, derive the addresses from the seed and + // confirm that the inputs exceed the threshold + else { + + @SuppressWarnings("unchecked") GetBalancesAndFormatResponse newinputs = getInputs(seed, 0, 0, totalValue); + // If inputs with enough balance + return addRemainder(seed, newinputs.getInput(), bundle, tag, totalValue, null, signatureFragments); + } + } else { + + // If no input required, don't sign and simply finalize the bundle + bundle.finalize(customCurl); + bundle.addTrytes(signatureFragments); + + List trxb = bundle.getTransactions(); + List bundleTrytes = new ArrayList<>(); + + for (Transaction trx : trxb) { + bundleTrytes.add(trx.toTrytes()); + } + Collections.reverse(bundleTrytes); + return bundleTrytes; + } + } + + /** + * Gets the inputs of a seed + * + * @param {string} seed + * @param {object} options + * @param {function} callback + * @method getInputs + * @property {int} start Starting key index + * @property {int} end Ending key index + * @property {int} threshold Min balance required + **/ + public GetBalancesAndFormatResponse getInputs(String seed, int start, int end, long threshold) { + StopWatch stopWatch = new StopWatch(); + // validate the seed + if (!InputValidator.isTrytes(seed, 0)) { + throw new IllegalStateException("Invalid Seed"); + } + + // validate & if needed pad seed + if ((seed = InputValidator.validateSeed(seed)) == null) { + throw new IllegalStateException("Invalid Seed"); + } + + // If start value bigger than end, return error + // or if difference between end and start is bigger than 500 keys + if (start > end || end > (start + 500)) { + throw new IllegalStateException("Invalid inputs provided"); + } + + // Case 1: start and end + // + // If start and end is defined by the user, simply iterate through the keys + // and call getBalances + if (end != 0) { + + List allAddresses = new ArrayList<>(); + + for (int i = start; i < end; i++) { + + String address = IotaAPIUtils.newAddress(seed, i, false, customCurl); + allAddresses.add(address); + } + + return getBalanceAndFormat(allAddresses, threshold, start, end, stopWatch); + } + // Case 2: iterate till threshold || end + // + // Either start from index: 0 or start (if defined) until threshold is reached. + // Calls getNewAddress and deterministically generates and returns all addresses + // We then do getBalance, format the output and return it + else { + final GetNewAddressResponse res = getNewAddress(seed, start, false, 0, true); + return getBalanceAndFormat(res.getAddresses(), threshold, start, end, stopWatch); + } + } + + // Calls getBalances and formats the output + // returns the final inputsObject then + public GetBalancesAndFormatResponse getBalanceAndFormat(final List addresses, long threshold, int start, int end, StopWatch stopWatch) { + + GetBalancesResponse getBalancesResponse = getBalances(100, addresses); + List balances = Arrays.asList(getBalancesResponse.getBalances()); + + // If threshold defined, keep track of whether reached or not + // else set default to true + boolean thresholdReached = threshold == 0; + int i = -1; + + List inputs = new ArrayList<>(); + long totalBalance = 0; + + for (String address : addresses) { + + long balance = Long.parseLong(balances.get(++i)); + + if (balance > 0) { + final Input newEntry = new Input(address, balance, start + i); + + inputs.add(newEntry); + // Increase totalBalance of all aggregated inputs + totalBalance += balance; + + if (!thresholdReached && totalBalance >= threshold) { + thresholdReached = true; + break; + } + } + } + + if (thresholdReached) { + return GetBalancesAndFormatResponse.create(inputs, totalBalance, stopWatch.getElapsedTimeMili()); + } + throw new IllegalStateException("Not enough balance"); + } + + /** + * Gets the associated bundle transactions of a single transaction + * Does validation of signatures, total sum as well as bundle order + * + * @param {string} transaction Hash of a tail transaction + * @method getBundle + * @returns {list} bundle Transaction objects + **/ + public GetBundleResponse getBundle(String transaction) throws ArgumentException, InvalidBundleException, InvalidSignatureException { + StopWatch stopWatch = new StopWatch(); + + Bundle bundle = traverseBundle(transaction, null, new Bundle()); + if (bundle == null) { + throw new ArgumentException("Unknown Bundle"); + } + + long totalSum = 0; + String bundleHash = bundle.getTransactions().get(0).getBundle(); + + ICurl curl = new JCurl(); + curl.reset(); + + List signaturesToValidate = new ArrayList<>(); + + for (int i = 0; i < bundle.getTransactions().size(); i++) { + Transaction trx = bundle.getTransactions().get(i); + Long bundleValue = Long.parseLong(trx.getValue()); + totalSum += bundleValue; + + if (i != Integer.parseInt(bundle.getTransactions().get(i).getCurrentIndex())) { + throw new ArgumentException("Invalid Bundle"); + } + + String trxTrytes = trx.toTrytes().substring(2187, 2187 + 162); + //System.out.println("Bundlesize "+bundle.getTransactions().size()+" "+trxTrytes); + // Absorb bundle hash + value + timestamp + lastIndex + currentIndex trytes. + curl.absorb(Converter.trits(trxTrytes)); + // Check if input transaction + if (bundleValue < 0) { + String address = trx.getAddress(); + Signature sig = new Signature(); + sig.setAddress(address); + sig.getSignatureFragments().add(trx.getSignatureFragments()); + + // Find the subsequent txs with the remaining signature fragment + for (int y = i; y < bundle.getTransactions().size() - 1; y++) { + Transaction newBundleTx = bundle.getTransactions().get(i + 1); + + // Check if new tx is part of the signature fragment + if (newBundleTx.getAddress().equals(address) && Long.parseLong(newBundleTx.getValue()) == 0) { + if (sig.getSignatureFragments().indexOf(newBundleTx.getSignatureFragments()) == -1) + sig.getSignatureFragments().add(newBundleTx.getSignatureFragments()); + } + } + signaturesToValidate.add(sig); + } + } + + // Check for total sum, if not equal 0 return error + if (totalSum != 0) throw new InvalidBundleException("Invalid Bundle Sum"); + int[] bundleFromTrxs = new int[243]; + curl.squeeze(bundleFromTrxs); + String bundleFromTxString = Converter.trytes(bundleFromTrxs); + + // Check if bundle hash is the same as returned by tx object + if (!bundleFromTxString.equals(bundleHash)) throw new InvalidBundleException("Invalid Bundle Hash"); + // Last tx in the bundle should have currentIndex === lastIndex + bundle.setLength(bundle.getTransactions().size()); + if (!bundle.getTransactions().get(bundle.getLength() - 1).getCurrentIndex().equals(bundle.getTransactions().get(bundle.getLength() - 1).getLastIndex())) + throw new InvalidBundleException("Invalid Bundle"); + + // Validate the signatures + for (Signature aSignaturesToValidate : signaturesToValidate) { + String[] signatureFragments = aSignaturesToValidate.getSignatureFragments().toArray(new String[aSignaturesToValidate.getSignatureFragments().size()]); + String address = aSignaturesToValidate.getAddress(); + boolean isValidSignature = new Signing().validateSignatures(address, signatureFragments, bundleHash); + + if (!isValidSignature) throw new InvalidSignatureException(); + } + + return GetBundleResponse.create(bundle.getTransactions(), stopWatch.getElapsedTimeMili()); + } + + /** + * Replays a transfer by doing Proof of Work again + * + * @param {string} tail + * @param {int} depth + * @param {int} minWeightMagnitude + * @param {function} callback + * @method replayBundle + * @returns {object} analyzed Transaction objects + **/ + public ReplayBundleResponse replayBundle(String transaction, int depth, int minWeightMagnitude) throws InvalidBundleException, ArgumentException, InvalidSignatureException { + StopWatch stopWatch = new StopWatch(); + + List bundleTrytes = new ArrayList<>(); + + GetBundleResponse bundleResponse = getBundle(transaction); + Bundle bundle = new Bundle(bundleResponse.getTransactions(), bundleResponse.getTransactions().size()); + for (Transaction trx : bundle.getTransactions()) { + + bundleTrytes.add(trx.toTrytes()); + } + + List trxs = sendTrytes(bundleTrytes.toArray(new String[bundleTrytes.size()]), depth, minWeightMagnitude); + + Boolean[] successful = new Boolean[trxs.size()]; + + for (int i = 0; i < trxs.size(); i++) { + + final FindTransactionResponse response = findTransactionsByBundles(trxs.get(i).getBundle()); + + + successful[i] = response.getHashes().length != 0; + } + + return ReplayBundleResponse.create(successful, stopWatch.getElapsedTimeMili()); + } + + /** + * Wrapper function for getNodeInfo and getInclusionStates + * + * @param {array} hashes + * @method getLatestInclusion + * @returns {function} callback + * @returns {array} state + **/ + public GetInclusionStateResponse getLatestInclusion(String[] hashes) throws NoNodeInfoException { + GetNodeInfoResponse getNodeInfoResponse = getNodeInfo(); + if (getNodeInfoResponse == null) throw new NoNodeInfoException(); + + String[] latestMilestone = {getNodeInfoResponse.getLatestSolidSubtangleMilestone()}; + + return getInclusionStates(hashes, latestMilestone); + } + + public SendTransferResponse sendTransfer(String seed, int depth, int minWeightMagnitude, final List transfers, Input[] inputs, String address) throws NotEnoughBalanceException { + StopWatch stopWatch = new StopWatch(); + + List trytes = prepareTransfers(seed, transfers, address, inputs == null ? null : Arrays.asList(inputs)); + List trxs = sendTrytes(trytes.toArray(new String[trytes.size()]), depth, minWeightMagnitude); + + Boolean[] successful = new Boolean[trxs.size()]; + + for (int i = 0; i < trxs.size(); i++) { + + final FindTransactionResponse response = findTransactionsByBundles(trxs.get(i).getBundle()); + + successful[i] = response.getHashes().length != 0; + } + + return SendTransferResponse.create(successful, stopWatch.getElapsedTimeMili()); + } + + /** + * Basically traverse the Bundle by going down the trunkTransactions until + * the bundle hash of the transaction is no longer the same. In case the input + * transaction hash is not a tail, we return an error. + * + * @param {string} trunkTx Hash of a trunk or a tail transaction of a bundle + * @param {string} bundleHash + * @param {array} bundle List of bundles to be populated + * @method traverseBundle + * @returns {array} bundle Transaction objects + **/ + public Bundle traverseBundle(String trunkTx, String bundleHash, Bundle bundle) throws ArgumentException { + GetTrytesResponse gtr = getTrytes(trunkTx); + + if (gtr != null) { + + if (gtr.getTrytes().length == 0) { + throw new ArgumentException("Bundle transactions not visible"); + } + + Transaction trx = new Transaction(gtr.getTrytes()[0], customCurl); + if (trx == null || trx.getBundle() == null) { + throw new ArgumentException("Invalid trytes, could not create object"); + } + // If first transaction to search is not a tail, return error + if (bundleHash == null && Integer.parseInt(trx.getCurrentIndex()) != 0) { + throw new ArgumentException("Invalid tail transaction supplied."); + } + // If no bundle hash, define it + if (bundleHash == null) { + bundleHash = trx.getBundle(); + } + // If different bundle hash, return with bundle + if (!bundleHash.equals(trx.getBundle())) { + return bundle; + } + // If only one bundle element, return + if (Integer.parseInt(trx.getLastIndex()) == 0 && Integer.parseInt(trx.getCurrentIndex()) == 0) { + return new Bundle(Collections.singletonList(trx), 1); + } + // Define new trunkTransaction for search + trunkTx = trx.getTrunkTransaction(); + // Add transaction object to bundle + bundle.getTransactions().add(trx); + + // Continue traversing with new trunkTx + return traverseBundle(trunkTx, bundleHash, bundle); + } else { + throw new ArgumentException("Get Trytes Response was null"); + } + } + + public String findTailTransactionHash(String hash) throws ArgumentException { + GetTrytesResponse gtr = getTrytes(hash); + + if (gtr == null) throw new ArgumentException("Invalid hash"); + + if (gtr.getTrytes().length == 0) { + throw new ArgumentException("Bundle transactions not visible"); + } + + Transaction trx = new Transaction(gtr.getTrytes()[0], customCurl); + if (trx == null || trx.getBundle() == null) { + throw new ArgumentException("Invalid trytes, could not create object"); + } + if (Integer.parseInt(trx.getCurrentIndex()) == 0) return trx.getHash(); + else return findTailTransactionHash(trx.getBundle()); + } + + public List addRemainder(final String seed, + final List inputs, + final Bundle bundle, + final String tag, + final long totalValue, + final String remainderAddress, + final List signatureFragments) throws NotEnoughBalanceException { + + long totalTransferValue = totalValue; + for (int i = 0; i < inputs.size(); i++) { + long thisBalance = inputs.get(i).getBalance(); + long toSubtract = 0 - thisBalance; + long timestamp = (long) Math.floor(Calendar.getInstance().getTimeInMillis() / 1000); + + // Add input as bundle entry + bundle.addEntry(2, inputs.get(i).getAddress(), toSubtract, tag, timestamp); + // If there is a remainder value + // Add extra output to send remaining funds to + + if (thisBalance >= totalTransferValue) { + long remainder = thisBalance - totalTransferValue; + + // If user has provided remainder address + // Use it to send remaining funds to + if (remainder > 0 && remainderAddress != null) { + // Remainder bundle entry + bundle.addEntry(1, remainderAddress, remainder, tag, timestamp); + // Final function for signing inputs + return IotaAPIUtils.signInputsAndReturn(seed, inputs, bundle, signatureFragments, customCurl); + } else if (remainder > 0) { + // Generate a new Address by calling getNewAddress + + GetNewAddressResponse res = getNewAddress(seed, 0, false, 0, false); + // Remainder bundle entry + bundle.addEntry(1, res.getAddresses().get(0), remainder, tag, timestamp); + + // Final function for signing inputs + return IotaAPIUtils.signInputsAndReturn(seed, inputs, bundle, signatureFragments, customCurl); + } else { + // If there is no remainder, do not add transaction to bundle + // simply sign and return + return IotaAPIUtils.signInputsAndReturn(seed, inputs, bundle, signatureFragments, customCurl); + } + + // If multiple inputs provided, subtract the totalTransferValue by + // the inputs balance + } else { + totalTransferValue -= thisBalance; + } + } + throw new NotEnoughBalanceException(); + } + + public static class Builder extends IotaAPICore.Builder { + private ICurl customCurl; + + public Builder withCustomCurl(ICurl curl) { + customCurl = curl; + return this; + } + + public IotaAPI build() { + super.build(); + return new IotaAPI(this); + } + } +} diff --git a/src/main/java/jota/IotaAPICommands.java b/src/main/java/jota/IotaAPICommands.java index bcb9b58..ed4d6bc 100644 --- a/src/main/java/jota/IotaAPICommands.java +++ b/src/main/java/jota/IotaAPICommands.java @@ -2,7 +2,7 @@ package jota; /** * IOTA's node command list - * + *

* 'params' is not currently used. */ public enum IotaAPICommands { diff --git a/src/main/java/jota/IotaAPICore.java b/src/main/java/jota/IotaAPICore.java new file mode 100644 index 0000000..7e032ac --- /dev/null +++ b/src/main/java/jota/IotaAPICore.java @@ -0,0 +1,254 @@ +package jota; + +import jota.dto.request.*; +import jota.dto.response.*; +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; + +/** + * Created by Adrian on 15.01.2017. + */ +public class IotaAPICore { + + private static final Logger log = LoggerFactory.getLogger(IotaAPICore.class); + + private IotaAPIService service; + private String protocol, host, port; + + protected IotaAPICore(final Builder builder) { + protocol = builder.protocol; + host = builder.host; + port = builder.port; + postConstruct(); + } + + 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()); + } + } + + 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) { + 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); + } + + 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"); + } + + public T host(String host) { + this.host = host; + return (T) this; + } + + public T port(String port) { + this.port = port; + return (T) this; + } + + public T protocol(String protocol) { + this.protocol = protocol; + return (T) this; + } + + } +} diff --git a/src/main/java/jota/IotaAPIProxy.java b/src/main/java/jota/IotaAPIProxy.java deleted file mode 100644 index 173bda8..0000000 --- a/src/main/java/jota/IotaAPIProxy.java +++ /dev/null @@ -1,721 +0,0 @@ -package jota; - -import jota.dto.request.*; -import jota.dto.response.*; -import jota.model.Bundle; -import jota.model.Input; -import jota.model.Transaction; -import jota.model.Transfer; -import jota.utils.Converter; -import jota.utils.InputValidator; -import jota.utils.IotaAPIUtils; -import okhttp3.OkHttpClient; -import org.apache.commons.lang3.StringUtils; -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.*; -import java.util.concurrent.TimeUnit; - -/** - * IotaAPIProxy Builder. Usage: - * - * IotaApiProxy api = IotaApiProxy.Builder - * .protocol("http") - * .nodeAddress("localhost") - * .port(12345) - * .build(); - * - * GetNodeInfoResponse response = api.getNodeInfo(); - * - * @author davassi - */ -public class IotaAPIProxy { - - private static final Logger log = LoggerFactory.getLogger(IotaAPIProxy.class); - - private IotaAPIService service; - private String protocol, host, port; - - private IotaAPIProxy(final Builder builder) { - protocol = builder.protocol; - host = builder.host; - port = builder.port; - postConstruct(); - } - - protected static Response wrapCheckedException(final Call call) { - try { - final Response res = call.execute(); - if (res.code() == 400) { - throw new IllegalAccessError(res.errorBody().toString()); - } - return res; - } catch (IOException e) { - log.error("Execution of the API call raised exception. IOTA Node not reachable?", e); - throw new IllegalStateException(e.getMessage()); - } - } - - private static final 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(120, TimeUnit.SECONDS) - .connectTimeout(120, 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 GetInclusionStateResponse getInclusionStates(Collection transactions, Collection 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) { - 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(); - } - - // end of proxied calls. - - /** - * Generates a new address from a seed and returns the remainderAddress. - * This is either done deterministically, or by providing the index of the new remainderAddress - * - * @param seed Tryte-encoded seed. It should be noted that this seed is not transferred - * @param index Optional (default null). Key index to start search from. If the index is provided, the generation of the address is not deterministic. - * @param checksum Optional (default false). Adds 9-tryte address checksum - * @param total Optional (default 1)Total number of addresses to generate - * @param returnAll If true, it returns all addresses which were deterministically generated (until findTransactions returns null) - * @return an array of strings with the specifed number of addresses - */ - public GetNewAddressResponse getNewAddress(final String seed, final int index, final boolean checksum, final int total, final boolean returnAll) { - - final List allAddresses = new ArrayList<>(); - - // If total number of addresses to generate is supplied, simply generate - // and return the list of all addresses - if (total != 0) { - for (int i = index; i < index + total; i++) { - allAddresses.add(IotaAPIUtils.newAddress(seed, i, checksum)); - } - return GetNewAddressResponse.create(allAddresses); - } - // No total provided: Continue calling findTransactions to see if address was - // already created if null, return list of addresses - for (int i = index; ; i++) { - - final String newAddress = IotaAPIUtils.newAddress(seed, i, checksum); - final FindTransactionResponse response = findTransactionsByAddresses(new String[]{newAddress}); - - allAddresses.add(newAddress); - if (response.getHashes().length == 0) { - break; - } - } - - // If !returnAll return only the last address that was generated - if (!returnAll) { - allAddresses.subList(0, allAddresses.size()-1).clear(); - } - return GetNewAddressResponse.create(allAddresses); - } - - /* - * newAddress - * broadcastAndStore - * sendTrytes - * prepareTransfers - * getInputs - * getLatestInclusion - - getTransfers - sendTransfer - getBundle - - getTransactionsObjects - findTransactionObjects - - replayBundle - broadcastBundle - getAccountData - */ - - /** - * - * @param trytes - * @return a StoreTransactionsResponse - */ - public StoreTransactionsResponse broadcastAndStore(final String ... trytes) { - - try { - broadcastTransactions(trytes); - } catch (Exception e) { - log.error("Impossible to broadcastAndStore, aborting.", e); - throw new IllegalStateException("BroadcastAndStore Illegal state Exception"); - } - return storeTransactions(trytes); - } - - /** - * Facade method: Gets transactions to approve, attaches to Tangle, broadcasts and stores - * @param {array} trytes - * @param {int} depth - * @param {int} minWeightMagnitude - * @return - */ - public List sendTrytes(final String trytes, final int minWeightMagnitude) { - - final GetTransactionsToApproveResponse txs = getTransactionsToApprove(minWeightMagnitude); - - // attach to tangle - do pow - final GetAttachToTangleResponse res = attachToTangle(txs.getTrunkTransaction(), txs.getBranchTransaction(), minWeightMagnitude, trytes); - - try { - broadcastAndStore(res.getTrytes()); - } catch (Exception e) { - log.error("Impossible to sendTrytes, aborting.", e); - throw new IllegalStateException("sendTrytes Illegal state Exception"); - } - - //return Arrays.stream(res.getTrytes()).map(Converter::transactionObject).collect(Collectors.toList()); - final List trx = new ArrayList<>(); - - for (final String tx : Arrays.asList(res.getTrytes())) { - trx.add(Converter.transactionObject(tx)); - } - return trx; - } - - /** - * Wrapper function for getTrytes and transactionObjects - * gets the trytes and transaction object from a list of transaction hashes - * - * @method getTransactionsObjects - * @param {array} hashes - * @return - * @returns {function} callback - * @returns {object} success - **/ - public List getTransactionsObjects(String[] hashes) { - - if (!InputValidator.isArrayOfHashes(hashes)) { - throw new IllegalStateException("Not an Array of Hashes: " + Arrays.toString(hashes)); - } - - final GetTrytesResponse trytesResponse = getTrytes(hashes); - - final List trxs = new ArrayList<>(); - - for (final String tryte : trytesResponse.getTrytes()) { - trxs.add(Converter.transactionObject(tryte)); - } - return trxs; - } - - /** - * Wrapper function for findTransactions, getTrytes and transactionObjects - * Returns the transactionObject of a transaction hash. The input can be a valid - * findTransactions input - * - * @param {object} input - * @method getTransactionsObjects - * @returns {function} callback - * @returns {object} success - **/ - public List findTransactionObjects(String[] input) { - FindTransactionResponse ftr = findTransactions(input, null, null, null); - if (ftr == null || ftr.getHashes() == null) - - return null; - - // get the transaction objects of the transactions - return getTransactionsObjects(ftr.getHashes()); - } - - /** - * Prepares transfer by generating bundle, finding and signing inputs - * - * @method prepareTransfers - * @param {string} seed - * @param {object} transfers - * @param {object} options - * @property {array} inputs Inputs used for signing. Needs to have correct keyIndex and address value - * @property {string} address Remainder address - * @param {function} callback - * @return - * @returns {array} trytes Returns bundle trytes - **/ - public List prepareTransfers(final String seed, final List transfers, String remainder, List inputs) { - - // Input validation of transfers object - if (!InputValidator.isTransfersCollectionCorrect(transfers)) { - throw new IllegalStateException("Invalid Transfer"); - } - - // Create a new bundle - final Bundle bundle = new Bundle(); - final List signatureFragments = new ArrayList<>(); - - int totalValue = 0; - String tag; - - // Iterate over all transfers, get totalValue - // and prepare the signatureFragments, message and tag - for (final Transfer transfer : transfers) { - - int signatureMessageLength = 1; - - // If message longer than 2187 trytes, increase signatureMessageLength (add 2nd transaction) - if (transfer.getMessage().length() > 2187) { - - // Get total length, message / maxLength (2187 trytes) - signatureMessageLength += Math.floor(transfer.getMessage().length() / 2187); - - String msgCopy = transfer.getMessage(); - - // While there is still a message, copy it - while (!msgCopy.isEmpty()) { - - String fragment = StringUtils.substring(msgCopy, 0, 2187); - msgCopy = StringUtils.substring(msgCopy, 2187, msgCopy.length()); - - // Pad remainder of fragment - for (int j = 0; fragment.length() < 2187; j++) { - fragment += "9"; - } - - signatureFragments.add(fragment); - } - } else { - // Else, get single fragment with 2187 of 9's trytes - String fragment = StringUtils.substring(transfer.getMessage(), 0, 2187); - - for (int j = 0; fragment.length() < 2187; j++) { - fragment += '9'; - } - - signatureFragments.add(fragment); - } - - // get current timestamp in seconds - long timestamp = (long) Math.floor(Calendar.getInstance().getTimeInMillis() / 1000); - - // If no tag defined, get 27 tryte tag. - tag = transfer.getTag().isEmpty() ? "999999999999999999999999999" : transfer.getTag(); - - // Pad for required 27 tryte length - for (int j = 0; tag.length() < 27; j++) { - tag += '9'; - } - - // Add first entry to the bundle - bundle.addEntry(signatureMessageLength, transfer.getAddress(), transfer.getValue(), tag, timestamp); - // Sum up total value - totalValue += transfer.getValue(); - } - - // Get inputs if we are sending tokens - if (totalValue != 0) { - - // Case 1: user provided inputs - // Validate the inputs by calling getBalances - if (!inputs.isEmpty()) { - - // Get list if addresses of the provided inputs - List inputsAddresses = new ArrayList<>(); - for (final Input i : inputs) { - inputsAddresses.add(i.getAddress()); - } - - GetBalancesResponse resbalances = getBalances(100, inputsAddresses); - String[] balances = resbalances.getBalances(); - - - List confirmedInputs = new ArrayList<>(); - int totalBalance = 0; int i = 0; - for (String balance : balances) { - long thisBalance = Integer.parseInt(balance); - totalBalance += thisBalance; - - // If input has balance, add it to confirmedInputs - if (thisBalance > 0) { - Input inputEl = inputs.get(i++); - inputEl.setBalance(thisBalance); - confirmedInputs.add(inputEl); - } - } - - // Return not enough balance error - if (totalValue > totalBalance) { - throw new IllegalStateException("Not enough balance"); - } - - return IotaAPIUtils.signInputsAndReturn(seed, confirmedInputs, bundle, signatureFragments); - } - - // Case 2: Get inputs deterministically - // - // If no inputs provided, derive the addresses from the seed and - // confirm that the inputs exceed the threshold - else { - - GetBalancesAndFormatResponse newinputs = getInputs(seed, Collections.EMPTY_LIST, 0, 0, totalValue); - // If inputs with enough balance - return IotaAPIUtils.signInputsAndReturn(seed, newinputs.getInput(), bundle, signatureFragments); - } - } else { - - // If no input required, don't sign and simply finalize the bundle - bundle.finalize(); - bundle.addTrytes(signatureFragments); - - List trxb = bundle.getTransactions(); - List bundleTrytes = new ArrayList<>(); - for (Transaction tx : trxb) { - jota.utils.IotaAPIUtils.transactionTrytes(tx); - } - Collections.reverse(bundleTrytes); - return bundleTrytes; - } - } - - /** - * Gets the inputs of a seed - * - * @method getInputs - * @param {string} seed - * @param {object} options - * @property {int} start Starting key index - * @property {int} end Ending key index - * @property {int} threshold Min balance required - * @param {function} callback - **/ - public GetBalancesAndFormatResponse getInputs(final String seed, final List balances, int start, int end, int threshold) { - - // validate the seed - if (!InputValidator.isTrytes(seed, 0)) { - throw new IllegalStateException("Invalid Seed"); - } - - // If start value bigger than end, return error - // or if difference between end and start is bigger than 500 keys - if (start > end || end > (start + 500)) { - throw new IllegalStateException("Invalid inputs provided"); - } - - // Case 1: start and end - // - // If start and end is defined by the user, simply iterate through the keys - // and call getBalances - if (end != 0) { - - List allAddresses = new ArrayList<>(); - - for (int i = start; i < end; i++) { - - String address = IotaAPIUtils.newAddress(seed, i, false); - allAddresses.add(address); - } - - return getBalanceAndFormat(allAddresses, balances, threshold, start, end); - } - // Case 2: iterate till threshold || end - // - // Either start from index: 0 or start (if defined) until threshold is reached. - // Calls getNewAddress and deterministically generates and returns all addresses - // We then do getBalance, format the output and return it - else { - - final GetNewAddressResponse res = getNewAddress(seed, start, false, 0, true); - return getBalanceAndFormat(res.getAddresses(), balances, threshold, start, end); - } - } - - // Calls getBalances and formats the output - // returns the final inputsObject then - public GetBalancesAndFormatResponse getBalanceAndFormat(final List addresses, List balances, long threshold, int start, int end) { - - if (balances == null || balances.isEmpty()) { - GetBalancesResponse getBalancesResponse = getBalances(100, addresses); - balances = Arrays.asList(getBalancesResponse.getBalances()); - } - - // If threshold defined, keep track of whether reached or not - // else set default to true - boolean thresholdReached = threshold != 0 ? false : true; int i = -1; - - List inputs = new ArrayList<>(); - long totalBalance = 0; - - for (String address : addresses) { - - long balance = Long.parseLong(balances.get(++i)); - - if (balance > 0) { - final Input newEntry = new Input(address, balance, start+i); - - inputs.add(newEntry); - // Increase totalBalance of all aggregated inputs - totalBalance += balance; - - if (!thresholdReached && totalBalance >= threshold) { - thresholdReached = true; - break; - } - } - } - - if (thresholdReached) { - return GetBalancesAndFormatResponse.create(inputs, totalBalance); - } - throw new IllegalStateException("Not enough balance"); - } - - /** - * Gets the associated bundle transactions of a single transaction - * Does validation of signatures, total sum as well as bundle order - * - * @method getBundle - * @param {string} transaction Hash of a tail transaction - * @returns {list} bundle Transaction objects - **/ - public GetBundleResponse getBundle(String transaction) { - return null; //IotaAPIUtils.getBundle(transaction); - } - - /** - * Replays a transfer by doing Proof of Work again - * - * @method replayBundle - * @param {string} tail - * @param {int} depth - * @param {int} minWeightMagnitude - * @param {function} callback - * @returns {object} analyzed Transaction objects - **/ - public List replayTransfer(String transaction, int depth, int minWeightMagnitude) { - - List bundleTrytes = new ArrayList<>(); - - GetBundleResponse bundle = getBundle(transaction); - - for (Transaction element : bundle.getTransactions()) { - - bundleTrytes.add(IotaAPIUtils.transactionTrytes(element)); - } - - return sendTrytes(bundleTrytes, minWeightMagnitude); - } - - /** - * Wrapper function for getNodeInfo and getInclusionStates - * - * @method getLatestInclusion - * @param {array} hashes - * @returns {function} callback - * @returns {array} state - **/ - public GetInclusionStateResponse getLatestInclusion(String[] hashes) { - GetNodeInfoResponse getNodeInfoResponse = getNodeInfo(); - if (getNodeInfoResponse == null) return null; - - String[] latestMilestone = {getNodeInfoResponse.getLatestSolidSubtangleMilestone()}; - - return getInclusionStates(hashes, latestMilestone); - } - - public static class Builder { - - String protocol, host, port; - - public IotaAPIProxy 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 IotaAPIProxy(this); - } - - 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"); - } - - public Builder host(String host) { - this.host = host; - return this; - } - - public Builder port(String port) { - this.port = port; - return this; - } - - public Builder protocol(String protocol) { - this.protocol = protocol; - return this; - } - - } -} diff --git a/src/main/java/jota/IotaAPIService.java b/src/main/java/jota/IotaAPIService.java index a626209..2946307 100644 --- a/src/main/java/jota/IotaAPIService.java +++ b/src/main/java/jota/IotaAPIService.java @@ -19,7 +19,7 @@ public interface IotaAPIService { /** * Returns information about your node. - * + *

* curl http://localhost:14265 -X POST -H 'Content-Type: application/json' * -d '{"command": "getNodeInfo"}' * @@ -31,7 +31,7 @@ public interface IotaAPIService { /** * Get the list of latest tips (unconfirmed transactions). - * + *

* curl http://localhost:14265 -X POST -H 'Content-Type: application/json' * -d '{"command": "getNeighbors"}' */ @@ -41,7 +41,7 @@ public interface IotaAPIService { /** * Add a list of neighbors to your node. - * + *

* curl http://localhost:14265 -X POST -H 'Content-Type: application/json' * -d '{"command": "addNeighbors", "uris": ["udp://8.8.8.8:14265", "udp://8.8.8.5:14265"]}' */ @@ -51,7 +51,7 @@ public interface IotaAPIService { /** * Removes a list of neighbors to your node. - * + *

* curl http://localhost:14265 -X POST -H 'Content-Type: application/json' * -d '{"command": "removeNeighbors", "uris": ["udp://8.8.8.8:14265", "udp://8.8.8.5:14265"]}' */ @@ -61,7 +61,7 @@ public interface IotaAPIService { /** * Get the list of latest tips (unconfirmed transactions). - * + *

* curl http://localhost:14265 -X POST -H 'Content-Type: application/json' * -d '{"command": "getTips"}' */ @@ -71,7 +71,7 @@ public interface IotaAPIService { /** * Find the transactions which match the specified input and return - * + *

* curl http://localhost:14265 \ -X POST \ -H 'Content-Type: application/json' \ * -d '{"command": "findTransactions", "addresses": ["RVORZ9SIIP9RCYMREUIXXVPQIPHVCNPQ9HZWYKFWYWZRE9JQKG9REPKIASHUUECPSQO9JT9XNMVKWYGVAZETAIRPTM"]}' */ @@ -82,7 +82,7 @@ public interface IotaAPIService { /** * Get the inclusion states of a set of transactions. This is for determining if a transaction was accepted and confirmed by the network or not. You can search for multiple tips (and thus, milestones) to get past inclusion states of transactions. - * + *

* curl http://localhost:14265 -X POST -H 'Content-Type: application/json' * -d '{"command": "getInclusionStates", "transactions"Q9HZWYKFWYWZRE9JQKG9REPKIASHUUECPSQO9JT9XNMVKWYGVAZETAIRPTM"], "tips" : []}' */ @@ -92,7 +92,7 @@ public interface IotaAPIService { /** * Returns the raw trytes data of a transaction. - * + *

* curl http://localhost:14265 -X POST -H 'Content-Type: application/json' * -d '{"command": "getTrytes", "hashes": ["OAATQS9VQLSXCLDJVJJVYUGONXAXOFMJOZNSYWRZSWECMXAQQURHQBJNLD9IOFEPGZEPEMPXCIVRX9999"]}' */ @@ -102,7 +102,7 @@ public interface IotaAPIService { /** * Tip selection which returns trunkTransaction and branchTransaction. The input value is the latest coordinator milestone, as provided through the getNodeInfo API call. - * + *

* curl http://localhost:14265 -X POST -H 'Content-Type: application/json' * -d '{"command": "getTransactionsToApprove", "depth": 27}' */ @@ -112,7 +112,7 @@ public interface IotaAPIService { /** * It returns the confirmed balance which a list of addresses have at the latest confirmed milestone. - * + *

* curl http://localhost:14265 -X POST -H 'Content-Type: application/json' * -d '{"command": "getBalances", "addresses": ["HBBYKAKTILIPVUKFOTSLHGENPTXYBNKXZFQFR9VQFWNBMTQNRVOUKPVPRNBSZVVILMAFBKOTBLGLWLOHQ"], "threshold": 100}' */ @@ -122,7 +122,7 @@ public interface IotaAPIService { /** * Attaches the specified transactions (trytes) to the Tangle by doing Proof of Work. - * + *

* curl http://localhost:14265 -X POST -H 'Content-Type: application/json' * -d '{"command": "attachToTangle", "trunkTransaction": "JVMTDGDPDFYHMZPMWEKKANBQSLSDTIIHAYQUMZOKHXXXGJHJDQPOMDOMNRDKYCZRUFZROZDADTHZC9999", "branchTransaction": "P9KFSJVGSPLXAEBJSHWFZLGP9GGJTIO9YITDEHATDTGAFLPLBZ9FOFWWTKMAZXZHFGQHUOXLXUALY9999", "minWeightMagnitude": 18, "trytes": ["TRYTVALUEHERE"]}' */ @@ -132,7 +132,7 @@ public interface IotaAPIService { /** * Interrupts and completely aborts the attachToTangle process. - * + *

* curl http://localhost:14265 -X POST -H 'Content-Type: application/json' * -d '{"command": "interruptAttachingToTangle" } */ @@ -142,7 +142,7 @@ public interface IotaAPIService { /** * Broadcast a list of transactions to all neighbors. The input trytes for this call are provided by attachToTangle. - * + *

* curl http://localhost:14265 -X POST -H 'Content-Type: application/json' * -d '{"command": "broadcastTransactions", "trytes": ["BYSWEAUTWXHXZ9YBZISEK9LUHWGMHXCGEVNZHRLUWQFCUSDXZHOFHWHL9MQPVJXXZLIXPXPXF9KYEREFSKCPKYIIKPZVLHUTDFQKKVVBBN9ATTLPCNPJDWDEVIYYLGPZGCWXOBDXMLJC9VO9QXTTBLAXTTBFUAROYEGQIVB9MJWJKXJMCUPTWAUGFZBTZCSJVRBGMYXTVBDDS9MYUJCPZ9YDWWQNIPUAIJXXSNLKUBSCOIJPCLEFPOXFJREXQCUVUMKSDOVQGGHRNILCO9GNCLWFM9APMNMWYASHXQAYBEXF9QRIHIBHYEJOYHRQJAOKAQ9AJJFQ9WEIWIJOTZATIBOXQLBMIJU9PCGBLVDDVFP9CFFSXTDUXMEGOOFXWRTLFGV9XXMYWEMGQEEEDBTIJ9OJOXFAPFQXCDAXOUDMLVYRMRLUDBETOLRJQAEDDLNVIRQJUBZBO9CCFDHIX9MSQCWYAXJVWHCUPTRSXJDESISQPRKZAFKFRULCGVRSBLVFOPEYLEE99JD9SEBALQINPDAZHFAB9RNBH9AZWIJOTLBZVIEJIAYGMC9AZGNFWGRSWAXTYSXVROVNKCOQQIWGPNQZKHUNODGYADPYLZZZUQRTJRTODOUKAOITNOMWNGHJBBA99QUMBHRENGBHTH9KHUAOXBVIVDVYYZMSEYSJWIOGGXZVRGN999EEGQMCOYVJQRIRROMPCQBLDYIGQO9AMORPYFSSUGACOJXGAQSPDY9YWRRPESNXXBDQ9OZOXVIOMLGTSWAMKMTDRSPGJKGBXQIVNRJRFRYEZ9VJDLHIKPSKMYC9YEGHFDS9SGVDHRIXBEMLFIINOHVPXIFAZCJKBHVMQZEVWCOSNWQRDYWVAIBLSCBGESJUIBWZECPUCAYAWMTQKRMCHONIPKJYYTEGZCJYCT9ABRWTJLRQXKMWY9GWZMHYZNWPXULNZAPVQLPMYQZCYNEPOCGOHBJUZLZDPIXVHLDMQYJUUBEDXXPXFLNRGIPWBRNQQZJSGSJTTYHIGGFAWJVXWL9THTPWOOHTNQWCNYOYZXALHAZXVMIZE9WMQUDCHDJMIBWKTYH9AC9AFOT9DPCADCV9ZWUTE9QNOMSZPTZDJLJZCJGHXUNBJFUBJWQUEZDMHXGBPTNSPZBR9TGSKVOHMOQSWPGFLSWNESFKSAZY9HHERAXALZCABFYPOVLAHMIHVDBGKUMDXC9WHHTIRYHZVWNXSVQUWCR9M9RAGMFEZZKZ9XEOQGOSLFQCHHOKLDSA9QCMDGCGMRYJZLBVIFOLBIJPROKMHOYTBTJIWUZWJMCTKCJKKTR9LCVYPVJI9AHGI9JOWMIWZAGMLDFJA9WU9QAMEFGABIBEZNNAL9OXSBFLOEHKDGHWFQSHMPLYFCNXAAZYJLMQDEYRGL9QKCEUEJ9LLVUOINVSZZQHCIKPAGMT9CAYIIMTTBCPKWTYHOJIIY9GYNPAJNUJ9BKYYXSV9JSPEXYMCFAIKTGNRSQGUNIYZCRT9FOWENSZQPD9ALUPYYAVICHVYELYFPUYDTWUSWNIYFXPX9MICCCOOZIWRNJIDALWGWRATGLJXNAYTNIZWQ9YTVDBOFZRKO9CFWRPAQQRXTPACOWCPRLYRYSJARRKSQPR9TCFXDVIXLP9XVL99ERRDSOHBFJDJQQGGGCZNDQ9NYCTQJWVZIAELCRBJJFDMCNZU9FIZRPGNURTXOCDSQGXTQHKHUECGWFUUYS9J9NYQ9U9P9UUP9YMZHWWWCIASCFLCMSKTELZWUGCDE9YOKVOVKTAYPHDF9ZCCQAYPJIJNGSHUIHHCOSSOOBUDOKE9CJZGYSSGNCQJVBEFTZFJ9SQUHOASKRRGBSHWKBCBWBTJHOGQ9WOMQFHWJVEG9NYX9KWBTCAIXNXHEBDIOFO9ALYMFGRICLCKKLG9FOBOX9PDWNQRGHBKHGKKRLWTBEQMCWQRLHAVYYZDIIPKVQTHYTWQMTOACXZOQCDTJTBAAUWXSGJF9PNQIJ9AJRUMUVCPWYVYVARKR9RKGOUHHNKNVGGPDDLGKPQNOYHNKAVVKCXWXOQPZNSLATUJT9AUWRMPPSWHSTTYDFAQDXOCYTZHOYYGAIM9CELMZ9AZPWB9MJXGHOKDNNSZVUDAGXTJJSSZCPZVPZBYNNTUQABSXQWZCHDQSLGK9UOHCFKBIBNETK999999999999999999999999999999999999999999999999999999999999999999999999999999999NOXDXXKUDWLOFJLIPQIBRBMGDYCPGDNLQOLQS99EQYKBIU9VHCJVIPFUYCQDNY9APGEVYLCENJIOBLWNB999999999XKBRHUD99C99999999NKZKEKWLDKMJCI9N9XQOLWEPAYWSH9999999999999999999999999KDDTGZLIPBNZKMLTOLOXQVNGLASESDQVPTXALEKRMIOHQLUHD9ELQDBQETS9QFGTYOYWLNTSKKMVJAUXSIROUICDOXKSYZTDPEDKOQENTJOWJONDEWROCEJIEWFWLUAACVSJFTMCHHXJBJRKAAPUDXXVXFWP9X9999IROUICDOXKSYZTDPEDKOQENTJOWJONDEWROCEJIEWFWLUAACVSJFTMCHHXJBJRKAAPUDXXVXFWP9X9999"]} */ @@ -152,7 +152,7 @@ public interface IotaAPIService { /** * Store transactions into the local storage. The trytes to be used for this call are returned by attachToTangle. - * + *

* curl http://localhost:14265 -X POST -H 'Content-Type: application/json' * -d '{"command": "storeTransactions", "trytes": ["BYSWEAUTWXHXZ9YBZISEK9LUHWGMHXCGEVNZHRLUWQFCUSDXZHOFHWHL9MQPVJXXZLIXPXPXF9KYEREFSKCPKYIIKPZVLHUTDFQKKVVBBN9ATTLPCNPJDWDEVIYYLGPZGCWXOBDXMLJC9VO9QXTTBLAXTTBFUAROYEGQIVB9MJWJKXJMCUPTWAUGFZBTZCSJVRBGMYXTVBDDS9MYUJCPZ9YDWWQNIPUAIJXXSNLKUBSCOIJPCLEFPOXFJREXQCUVUMKSDOVQGGHRNILCO9GNCLWFM9APMNMWYASHXQAYBEXF9QRIHIBHYEJOYHRQJAOKAQ9AJJFQ9WEIWIJOTZATIBOXQLBMIJU9PCGBLVDDVFP9CFFSXTDUXMEGOOFXWRTLFGV9XXMYWEMGQEEEDBTIJ9OJOXFAPFQXCDAXOUDMLVYRMRLUDBETOLRJQAEDDLNVIRQJUBZBO9CCFDHIX9MSQCWYAXJVWHCUPTRSXJDESISQPRKZAFKFRULCGVRSBLVFOPEYLEE99JD9SEBALQINPDAZHFAB9RNBH9AZWIJOTLBZVIEJIAYGMC9AZGNFWGRSWAXTYSXVROVNKCOQQIWGPNQZKHUNODGYADPYLZZZUQRTJRTODOUKAOITNOMWNGHJBBA99QUMBHRENGBHTH9KHUAOXBVIVDVYYZMSEYSJWIOGGXZVRGN999EEGQMCOYVJQRIRROMPCQBLDYIGQO9AMORPYFSSUGACOJXGAQSPDY9YWRRPESNXXBDQ9OZOXVIOMLGTSWAMKMTDRSPGJKGBXQIVNRJRFRYEZ9VJDLHIKPSKMYC9YEGHFDS9SGVDHRIXBEMLFIINOHVPXIFAZCJKBHVMQZEVWCOSNWQRDYWVAIBLSCBGESJUIBWZECPUCAYAWMTQKRMCHONIPKJYYTEGZCJYCT9ABRWTJLRQXKMWY9GWZMHYZNWPXULNZAPVQLPMYQZCYNEPOCGOHBJUZLZDPIXVHLDMQYJUUBEDXXPXFLNRGIPWBRNQQZJSGSJTTYHIGGFAWJVXWL9THTPWOOHTNQWCNYOYZXALHAZXVMIZE9WMQUDCHDJMIBWKTYH9AC9AFOT9DPCADCV9ZWUTE9QNOMSZPTZDJLJZCJGHXUNBJFUBJWQUEZDMHXGBPTNSPZBR9TGSKVOHMOQSWPGFLSWNESFKSAZY9HHERAXALZCABFYPOVLAHMIHVDBGKUMDXC9WHHTIRYHZVWNXSVQUWCR9M9RAGMFEZZKZ9XEOQGOSLFQCHHOKLDSA9QCMDGCGMRYJZLBVIFOLBIJPROKMHOYTBTJIWUZWJMCTKCJKKTR9LCVYPVJI9AHGI9JOWMIWZAGMLDFJA9WU9QAMEFGABIBEZNNAL9OXSBFLOEHKDGHWFQSHMPLYFCNXAAZYJLMQDEYRGL9QKCEUEJ9LLVUOINVSZZQHCIKPAGMT9CAYIIMTTBCPKWTYHOJIIY9GYNPAJNUJ9BKYYXSV9JSPEXYMCFAIKTGNRSQGUNIYZCRT9FOWENSZQPD9ALUPYYAVICHVYELYFPUYDTWUSWNIYFXPX9MICCCOOZIWRNJIDALWGWRATGLJXNAYTNIZWQ9YTVDBOFZRKO9CFWRPAQQRXTPACOWCPRLYRYSJARRKSQPR9TCFXDVIXLP9XVL99ERRDSOHBFJDJQQGGGCZNDQ9NYCTQJWVZIAELCRBJJFDMCNZU9FIZRPGNURTXOCDSQGXTQHKHUECGWFUUYS9J9NYQ9U9P9UUP9YMZHWWWCIASCFLCMSKTELZWUGCDE9YOKVOVKTAYPHDF9ZCCQAYPJIJNGSHUIHHCOSSOOBUDOKE9CJZGYSSGNCQJVBEFTZFJ9SQUHOASKRRGBSHWKBCBWBTJHOGQ9WOMQFHWJVEG9NYX9KWBTCAIXNXHEBDIOFO9ALYMFGRICLCKKLG9FOBOX9PDWNQRGHBKHGKKRLWTBEQMCWQRLHAVYYZDIIPKVQTHYTWQMTOACXZOQCDTJTBAAUWXSGJF9PNQIJ9AJRUMUVCPWYVYVARKR9RKGOUHHNKNVGGPDDLGKPQNOYHNKAVVKCXWXOQPZNSLATUJT9AUWRMPPSWHSTTYDFAQDXOCYTZHOYYGAIM9CELMZ9AZPWB9MJXGHOKDNNSZVUDAGXTJJSSZCPZVPZBYNNTUQABSXQWZCHDQSLGK9UOHCFKBIBNETK999999999999999999999999999999999999999999999999999999999999999999999999999999999NOXDXXKUDWLOFJLIPQIBRBMGDYCPGDNLQOLQS99EQYKBIU9VHCJVIPFUYCQDNY9APGEVYLCENJIOBLWNB999999999XKBRHUD99C99999999NKZKEKWLDKMJCI9N9XQOLWEPAYWSH9999999999999999999999999KDDTGZLIPBNZKMLTOLOXQVNGLASESDQVPTXALEKRMIOHQLUHD9ELQDBQETS9QFGTYOYWLNTSKKMVJAUXSIROUICDOXKSYZTDPEDKOQENTJOWJONDEWROCEJIEWFWLUAACVSJFTMCHHXJBJRKAAPUDXXVXFWP9X9999IROUICDOXKSYZTDPEDKOQENTJOWJONDEWROCEJIEWFWLUAACVSJFTMCHHXJBJRKAAPUDXXVXFWP9X9999"]}' */ diff --git a/src/main/java/jota/dto/request/IotaAttachToTangleRequest.java b/src/main/java/jota/dto/request/IotaAttachToTangleRequest.java index bfef85b..8d50a54 100644 --- a/src/main/java/jota/dto/request/IotaAttachToTangleRequest.java +++ b/src/main/java/jota/dto/request/IotaAttachToTangleRequest.java @@ -20,4 +20,36 @@ public class IotaAttachToTangleRequest extends IotaCommandRequest { public static IotaAttachToTangleRequest createAttachToTangleRequest(final String trunkTransaction, final String branchTransaction, final Integer minWeightMagnitude, final String... trytes) { return new IotaAttachToTangleRequest(trunkTransaction, branchTransaction, minWeightMagnitude, trytes); } + + public String getTrunkTransaction() { + return trunkTransaction; + } + + public void setTrunkTransaction(String trunkTransaction) { + this.trunkTransaction = trunkTransaction; + } + + public String getBranchTransaction() { + return branchTransaction; + } + + public void setBranchTransaction(String branchTransaction) { + this.branchTransaction = branchTransaction; + } + + public Integer getMinWeightMagnitude() { + return minWeightMagnitude; + } + + public void setMinWeightMagnitude(Integer minWeightMagnitude) { + this.minWeightMagnitude = minWeightMagnitude; + } + + public String[] getTrytes() { + return trytes; + } + + public void setTrytes(String[] trytes) { + this.trytes = trytes; + } } diff --git a/src/main/java/jota/dto/request/IotaBroadcastTransactionRequest.java b/src/main/java/jota/dto/request/IotaBroadcastTransactionRequest.java index 42847d9..5a0a5d5 100644 --- a/src/main/java/jota/dto/request/IotaBroadcastTransactionRequest.java +++ b/src/main/java/jota/dto/request/IotaBroadcastTransactionRequest.java @@ -14,4 +14,12 @@ public class IotaBroadcastTransactionRequest extends IotaCommandRequest { public static IotaBroadcastTransactionRequest createBroadcastTransactionsRequest(final String... trytes) { return new IotaBroadcastTransactionRequest(trytes); } + + public String[] getTrytes() { + return trytes; + } + + public void setTrytes(String[] trytes) { + this.trytes = trytes; + } } \ No newline at end of file diff --git a/src/main/java/jota/dto/request/IotaFindTransactionsRequest.java b/src/main/java/jota/dto/request/IotaFindTransactionsRequest.java index b3a72df..04da886 100644 --- a/src/main/java/jota/dto/request/IotaFindTransactionsRequest.java +++ b/src/main/java/jota/dto/request/IotaFindTransactionsRequest.java @@ -17,6 +17,38 @@ public class IotaFindTransactionsRequest extends IotaCommandRequest { return new IotaFindTransactionsRequest(); } + public String[] getBundles() { + return bundles; + } + + public void setBundles(String[] bundles) { + this.bundles = bundles; + } + + public String[] getAddresses() { + return addresses; + } + + public void setAddresses(String[] addresses) { + this.addresses = addresses; + } + + public String[] getTags() { + return tags; + } + + public void setTags(String[] tags) { + this.tags = tags; + } + + public String[] getApprovees() { + return approvees; + } + + public void setApprovees(String[] approvees) { + this.approvees = approvees; + } + public IotaFindTransactionsRequest byBundles(String... bundles) { this.bundles = bundles; return this; diff --git a/src/main/java/jota/dto/request/IotaGetBalancesRequest.java b/src/main/java/jota/dto/request/IotaGetBalancesRequest.java index 807865d..33d760d 100644 --- a/src/main/java/jota/dto/request/IotaGetBalancesRequest.java +++ b/src/main/java/jota/dto/request/IotaGetBalancesRequest.java @@ -17,5 +17,20 @@ public class IotaGetBalancesRequest extends IotaCommandRequest { return new IotaGetBalancesRequest(threshold, addresses); } + public String[] getAddresses() { + return addresses; + } + + public void setAddresses(String[] addresses) { + this.addresses = addresses; + } + + public Integer getThreshold() { + return threshold; + } + + public void setThreshold(Integer threshold) { + this.threshold = threshold; + } } diff --git a/src/main/java/jota/dto/request/IotaGetInclusionStateRequest.java b/src/main/java/jota/dto/request/IotaGetInclusionStateRequest.java index d0d323c..704806e 100644 --- a/src/main/java/jota/dto/request/IotaGetInclusionStateRequest.java +++ b/src/main/java/jota/dto/request/IotaGetInclusionStateRequest.java @@ -25,4 +25,19 @@ public class IotaGetInclusionStateRequest extends IotaCommandRequest { tips.toArray(new String[]{})); } + public String[] getTransactions() { + return transactions; + } + + public void setTransactions(String[] transactions) { + this.transactions = transactions; + } + + public String[] getTips() { + return tips; + } + + public void setTips(String[] tips) { + this.tips = tips; + } } diff --git a/src/main/java/jota/dto/request/IotaGetTransactionsToApproveRequest.java b/src/main/java/jota/dto/request/IotaGetTransactionsToApproveRequest.java index f597448..fb1b367 100644 --- a/src/main/java/jota/dto/request/IotaGetTransactionsToApproveRequest.java +++ b/src/main/java/jota/dto/request/IotaGetTransactionsToApproveRequest.java @@ -14,4 +14,12 @@ public class IotaGetTransactionsToApproveRequest extends IotaCommandRequest { public static IotaGetTransactionsToApproveRequest createIotaGetTransactionsToApproveRequest(Integer depth) { return new IotaGetTransactionsToApproveRequest(depth); } + + public Integer getDepth() { + return depth; + } + + public void setDepth(Integer depth) { + this.depth = depth; + } } diff --git a/src/main/java/jota/dto/request/IotaGetTrytesRequest.java b/src/main/java/jota/dto/request/IotaGetTrytesRequest.java index b493451..eaa5888 100644 --- a/src/main/java/jota/dto/request/IotaGetTrytesRequest.java +++ b/src/main/java/jota/dto/request/IotaGetTrytesRequest.java @@ -14,4 +14,12 @@ public class IotaGetTrytesRequest extends IotaCommandRequest { public static IotaGetTrytesRequest createGetTrytesRequest(String... hashes) { return new IotaGetTrytesRequest(hashes); } + + public String[] getHashes() { + return hashes; + } + + public void setHashes(String[] hashes) { + this.hashes = hashes; + } } diff --git a/src/main/java/jota/dto/request/IotaNeighborsRequest.java b/src/main/java/jota/dto/request/IotaNeighborsRequest.java index 82c96b1..c81c491 100644 --- a/src/main/java/jota/dto/request/IotaNeighborsRequest.java +++ b/src/main/java/jota/dto/request/IotaNeighborsRequest.java @@ -18,5 +18,13 @@ public class IotaNeighborsRequest extends IotaCommandRequest { public static IotaNeighborsRequest createRemoveNeighborsRequest(String... uris) { return new IotaNeighborsRequest(IotaAPICommands.REMOVE_NEIGHBORS, uris); } + + public String[] getUris() { + return uris; + } + + public void setUris(String[] uris) { + this.uris = uris; + } } diff --git a/src/main/java/jota/dto/request/IotaStoreTransactionsRequest.java b/src/main/java/jota/dto/request/IotaStoreTransactionsRequest.java index 5247894..1b9c55b 100644 --- a/src/main/java/jota/dto/request/IotaStoreTransactionsRequest.java +++ b/src/main/java/jota/dto/request/IotaStoreTransactionsRequest.java @@ -14,4 +14,12 @@ public class IotaStoreTransactionsRequest extends IotaCommandRequest { public static IotaStoreTransactionsRequest createStoreTransactionsRequest(final String... trytes) { return new IotaStoreTransactionsRequest(trytes); } + + public String[] getTrytes() { + return trytes; + } + + public void setTrytes(String[] trytes) { + this.trytes = trytes; + } } diff --git a/src/main/java/jota/dto/response/AbstractResponse.java b/src/main/java/jota/dto/response/AbstractResponse.java index cab9488..e93ee07 100644 --- a/src/main/java/jota/dto/response/AbstractResponse.java +++ b/src/main/java/jota/dto/response/AbstractResponse.java @@ -7,12 +7,16 @@ import org.apache.commons.lang3.builder.ToStringStyle; public abstract class AbstractResponse { - private Integer duration; + private Long duration; - public Integer getDuration() { + public Long getDuration() { return duration; } + public void setDuration(Long duration) { + this.duration = duration; + } + @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); diff --git a/src/main/java/jota/dto/response/AnalyzeTransactionResponse.java b/src/main/java/jota/dto/response/AnalyzeTransactionResponse.java index 53081ff..08c56cf 100644 --- a/src/main/java/jota/dto/response/AnalyzeTransactionResponse.java +++ b/src/main/java/jota/dto/response/AnalyzeTransactionResponse.java @@ -7,7 +7,7 @@ import java.util.List; public class AnalyzeTransactionResponse extends AbstractResponse { - private List transactions = new ArrayList(); + private List transactions = new ArrayList<>(); public List getTransactions() { return transactions; diff --git a/src/main/java/jota/dto/response/GetAttachToTangleResponse.java b/src/main/java/jota/dto/response/GetAttachToTangleResponse.java index 53eb98b..acbbc42 100644 --- a/src/main/java/jota/dto/response/GetAttachToTangleResponse.java +++ b/src/main/java/jota/dto/response/GetAttachToTangleResponse.java @@ -4,6 +4,10 @@ public class GetAttachToTangleResponse extends AbstractResponse { private String[] trytes; + public GetAttachToTangleResponse(long duration) { + setDuration(duration); + } + public String[] getTrytes() { return trytes; } diff --git a/src/main/java/jota/dto/response/GetBalancesAndFormatResponse.java b/src/main/java/jota/dto/response/GetBalancesAndFormatResponse.java index 493d063..f06d27a 100644 --- a/src/main/java/jota/dto/response/GetBalancesAndFormatResponse.java +++ b/src/main/java/jota/dto/response/GetBalancesAndFormatResponse.java @@ -1,14 +1,22 @@ package jota.dto.response; -import java.util.List; - import jota.model.Input; +import java.util.List; + public class GetBalancesAndFormatResponse extends AbstractResponse { private List input; private long totalBalance; - + + public static GetBalancesAndFormatResponse create(List inputs, long totalBalance2, long duration) { + GetBalancesAndFormatResponse res = new GetBalancesAndFormatResponse(); + res.setInput(inputs); + res.setTotalBalance(totalBalance2); + res.setDuration(duration); + return res; + } + public List getInput() { return input; } @@ -16,19 +24,12 @@ public class GetBalancesAndFormatResponse extends AbstractResponse { public void setInput(List input) { this.input = input; } - + public long getTotalBalance() { return totalBalance; } - + public void setTotalBalance(long totalBalance) { this.totalBalance = totalBalance; } - - public static GetBalancesAndFormatResponse create(List inputs, long totalBalance2) { - GetBalancesAndFormatResponse res = new GetBalancesAndFormatResponse(); - res.setInput(inputs); - res.setTotalBalance(totalBalance2); - return res; - } } diff --git a/src/main/java/jota/dto/response/GetBundleResponse.java b/src/main/java/jota/dto/response/GetBundleResponse.java index 278631f..c3dfa1b 100644 --- a/src/main/java/jota/dto/response/GetBundleResponse.java +++ b/src/main/java/jota/dto/response/GetBundleResponse.java @@ -9,7 +9,15 @@ public class GetBundleResponse extends AbstractResponse { private List transactions = new ArrayList<>(); + public static GetBundleResponse create(List transactions, long duration) { + GetBundleResponse res = new GetBundleResponse(); + res.transactions = transactions; + res.setDuration(duration); + return res; + } + + public List getTransactions() { return transactions; } -} +} \ No newline at end of file diff --git a/src/main/java/jota/dto/response/GetNewAddressResponse.java b/src/main/java/jota/dto/response/GetNewAddressResponse.java index 484e17e..42d2288 100644 --- a/src/main/java/jota/dto/response/GetNewAddressResponse.java +++ b/src/main/java/jota/dto/response/GetNewAddressResponse.java @@ -6,9 +6,10 @@ public class GetNewAddressResponse extends AbstractResponse { private List addresses; - public static GetNewAddressResponse create(List addresses) { + public static GetNewAddressResponse create(List addresses, long duration) { GetNewAddressResponse res = new GetNewAddressResponse(); res.addresses = addresses; + res.setDuration(duration); return res; } diff --git a/src/main/java/jota/dto/response/GetTransferResponse.java b/src/main/java/jota/dto/response/GetTransferResponse.java new file mode 100644 index 0000000..a4fc8da --- /dev/null +++ b/src/main/java/jota/dto/response/GetTransferResponse.java @@ -0,0 +1,21 @@ +package jota.dto.response; + +import jota.model.Bundle; + +/** + * Created by pinpong on 28.12.16. + */ +public class GetTransferResponse extends AbstractResponse { + + private Bundle[] transferBundle; + + public static GetTransferResponse create(Bundle[] transferBundle, long duration) { + GetTransferResponse res = new GetTransferResponse(); + res.transferBundle = transferBundle; + return res; + } + + public Bundle[] getTransfers() { + return transferBundle; + } +} diff --git a/src/main/java/jota/dto/response/GetTransfersResponse.java b/src/main/java/jota/dto/response/GetTransfersResponse.java deleted file mode 100644 index 441ccea..0000000 --- a/src/main/java/jota/dto/response/GetTransfersResponse.java +++ /dev/null @@ -1,15 +0,0 @@ -package jota.dto.response; - -import jota.model.Transfer; - -import java.util.ArrayList; -import java.util.List; - -public class GetTransfersResponse extends AbstractResponse { - - private List transfers = new ArrayList<>(); - - public List getTransfers() { - return transfers; - } -} diff --git a/src/main/java/jota/dto/response/ReplayBundleResponse.java b/src/main/java/jota/dto/response/ReplayBundleResponse.java new file mode 100644 index 0000000..2c60c6c --- /dev/null +++ b/src/main/java/jota/dto/response/ReplayBundleResponse.java @@ -0,0 +1,25 @@ +package jota.dto.response; + +/** + * Created by pinpong on 01.01.17. + */ +public class ReplayBundleResponse extends AbstractResponse { + + private Boolean[] successfully; + + public static ReplayBundleResponse create(Boolean[] successfully, long duration) { + ReplayBundleResponse res = new ReplayBundleResponse(); + res.successfully = successfully; + res.setDuration(duration); + return res; + } + + public Boolean[] getSuccessfully() { + return successfully; + } + + public void setSuccessfully(Boolean[] successfully) { + this.successfully = successfully; + } + +} diff --git a/src/main/java/jota/dto/response/SendTransferResponse.java b/src/main/java/jota/dto/response/SendTransferResponse.java new file mode 100644 index 0000000..d31b295 --- /dev/null +++ b/src/main/java/jota/dto/response/SendTransferResponse.java @@ -0,0 +1,25 @@ +package jota.dto.response; + +/** + * Created by pinpong on 28.12.16. + */ +public class SendTransferResponse extends AbstractResponse { + + private Boolean[] successfully; + + public static SendTransferResponse create(Boolean[] successfully, long duration) { + SendTransferResponse res = new SendTransferResponse(); + res.successfully = successfully; + res.setDuration(duration); + return res; + } + + public Boolean[] getSuccessfully() { + return successfully; + } + + public void setSuccessfully(Boolean[] successfully) { + this.successfully = successfully; + } + +} diff --git a/src/main/java/jota/dto/response/StoreTransactionsResponse.java b/src/main/java/jota/dto/response/StoreTransactionsResponse.java index 04128d2..af6c773 100644 --- a/src/main/java/jota/dto/response/StoreTransactionsResponse.java +++ b/src/main/java/jota/dto/response/StoreTransactionsResponse.java @@ -1,6 +1,8 @@ package jota.dto.response; public class StoreTransactionsResponse extends AbstractResponse { - // empty response + public StoreTransactionsResponse(long duration) { + setDuration(duration); + } } diff --git a/src/main/java/jota/error/ArgumentException.java b/src/main/java/jota/error/ArgumentException.java index c3da421..075d90e 100644 --- a/src/main/java/jota/error/ArgumentException.java +++ b/src/main/java/jota/error/ArgumentException.java @@ -10,4 +10,8 @@ public class ArgumentException extends BaseException { public ArgumentException() { super("Wrong arguments passed to function"); } + + public ArgumentException(String msg) { + super(msg); + } } diff --git a/src/main/java/jota/error/BaseException.java b/src/main/java/jota/error/BaseException.java index 2931408..972a0ac 100644 --- a/src/main/java/jota/error/BaseException.java +++ b/src/main/java/jota/error/BaseException.java @@ -1,5 +1,6 @@ package jota.error; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -14,6 +15,10 @@ public class BaseException extends Exception { public BaseException(String msg) { super(msg); + if (messages == null) { + messages = new ArrayList<>(); + } + messages.add(msg); } public BaseException(String msg, Exception cause) { diff --git a/src/main/java/jota/error/BroadcastAndStoreException.java b/src/main/java/jota/error/BroadcastAndStoreException.java new file mode 100644 index 0000000..a5b58e6 --- /dev/null +++ b/src/main/java/jota/error/BroadcastAndStoreException.java @@ -0,0 +1,11 @@ +package jota.error; + +/** + * Created by Adrian on 22.01.2017. + */ +public class BroadcastAndStoreException extends BaseException { + + public BroadcastAndStoreException() { + super("Impossible to broadcastAndStore, aborting."); + } +} \ No newline at end of file diff --git a/src/main/java/jota/error/InvalidBundleException.java b/src/main/java/jota/error/InvalidBundleException.java new file mode 100644 index 0000000..658011b --- /dev/null +++ b/src/main/java/jota/error/InvalidBundleException.java @@ -0,0 +1,15 @@ +package jota.error; + +/** + * Created by Adrian on 27.12.2016. + */ +public class InvalidBundleException extends BaseException { + + public InvalidBundleException() { + super("Invalid Bundle"); + } + + public InvalidBundleException(String msg) { + super(msg); + } +} diff --git a/src/main/java/jota/error/InvalidSignatureException.java b/src/main/java/jota/error/InvalidSignatureException.java new file mode 100644 index 0000000..d8d5565 --- /dev/null +++ b/src/main/java/jota/error/InvalidSignatureException.java @@ -0,0 +1,10 @@ +package jota.error; + +/** + * Created by Adrian on 27.12.2016. + */ +public class InvalidSignatureException extends BaseException { + public InvalidSignatureException() { + super("Invalid Signatures!"); + } +} diff --git a/src/main/java/jota/error/NoInclusionStatesExcpection.java b/src/main/java/jota/error/NoInclusionStatesExcpection.java new file mode 100644 index 0000000..c79ee4b --- /dev/null +++ b/src/main/java/jota/error/NoInclusionStatesExcpection.java @@ -0,0 +1,11 @@ +package jota.error; + +/** + * Created by Adrian on 22.01.2017. + */ +public class NoInclusionStatesExcpection extends BaseException { + + public NoInclusionStatesExcpection() { + super("No inclusion states for the provided seed"); + } +} diff --git a/src/main/java/jota/error/NoNodeInfoException.java b/src/main/java/jota/error/NoNodeInfoException.java new file mode 100644 index 0000000..920c6f3 --- /dev/null +++ b/src/main/java/jota/error/NoNodeInfoException.java @@ -0,0 +1,11 @@ +package jota.error; + +/** + * Created by Adrian on 22.01.2017. + */ +public class NoNodeInfoException extends BaseException { + + public NoNodeInfoException() { + super("Node info could not be retrieved"); + } +} \ No newline at end of file diff --git a/src/main/java/jota/error/NotEnoughBalanceException.java b/src/main/java/jota/error/NotEnoughBalanceException.java index 64c516c..dfe44a1 100644 --- a/src/main/java/jota/error/NotEnoughBalanceException.java +++ b/src/main/java/jota/error/NotEnoughBalanceException.java @@ -4,7 +4,7 @@ package jota.error; * Created by Adrian on 09.12.2016. */ public class NotEnoughBalanceException extends BaseException { - + private static final long serialVersionUID = -3807270816402226476L; public NotEnoughBalanceException() { diff --git a/src/main/java/jota/model/Bundle.java b/src/main/java/jota/model/Bundle.java index 90d966d..7c02db5 100644 --- a/src/main/java/jota/model/Bundle.java +++ b/src/main/java/jota/model/Bundle.java @@ -1,20 +1,22 @@ package jota.model; -import jota.pow.Curl; +import jota.pow.ICurl; +import jota.pow.JCurl; import jota.utils.Converter; +import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.List; + /** * Created by pinpong on 09.12.16. */ -public class Bundle { - - private List transactions; - private int length; +public class Bundle implements Comparable { public static String EMPTY_HASH = "999999999999999999999999999999999999999999999999999999999999999999999999999999999"; + private List transactions; + private int length; public Bundle() { @@ -30,10 +32,6 @@ public class Bundle { return transactions; } - public void setTransactions(List transactions) { - this.transactions = transactions; - } - public int getLength() { return length; } @@ -43,48 +41,37 @@ public class Bundle { } public void addEntry(int signatureMessageLength, String address, long value, String tag, long timestamp) { + if (getTransactions() == null) { + this.transactions = new ArrayList<>(getTransactions()); + } + for (int i = 0; i < signatureMessageLength; i++) { - - List transactions = new ArrayList<>(getTransactions()); - transactions.add(new Transaction(address, String.valueOf(i == 0 ? value : 0), tag, String.valueOf(timestamp))); - - setTransactions(transactions); - + Transaction trx = new Transaction(address, String.valueOf(i == 0 ? value : 0), tag, String.valueOf(timestamp)); + getTransactions().add(trx); } } - public void finalize() { + public void finalize(ICurl customCurl) { - Curl curl = new Curl(); + ICurl curl = customCurl == null ? new JCurl() : customCurl; curl.reset(); for (int i = 0; i < this.getTransactions().size(); i++) { - int[] valueTrits = Converter.trits(this.getTransactions().get(i).getValue()); - while (valueTrits.length < 81) { - valueTrits[valueTrits.length] = 0; - } + int[] valueTrits = Converter.trits(this.getTransactions().get(i).getValue(), 81); - int[] timestampTrits = Converter.trits(this.getTransactions().get(i).getTimestamp()); - while (timestampTrits.length < 27) { - timestampTrits[timestampTrits.length] = 0; - } + int[] timestampTrits = Converter.trits(this.getTransactions().get(i).getTimestamp(), 27); - int[] currentIndexTrits = Converter.trits(this.getTransactions().get(i).setCurrentIndex("" + i)); - while (currentIndexTrits.length < 27) { - currentIndexTrits[currentIndexTrits.length] = 0; - } + int[] currentIndexTrits = Converter.trits(this.getTransactions().get(i).setCurrentIndex("" + i), 27); + + int[] lastIndexTrits = Converter.trits(this.getTransactions().get(i).setLastIndex("" + (this.getTransactions().size() - 1)), 27); - int[] lastIndexTrits = Converter.trits(this.getTransactions().get(i).setLastIndex("" + (this.getTransactions().size() - 1))); - while (lastIndexTrits.length < 27) { - lastIndexTrits[lastIndexTrits.length] = 0; - } int[] t = Converter.trits(this.getTransactions().get(i).getAddress() + Converter.trytes(valueTrits) + this.getTransactions().get(i).getTag() + Converter.trytes(timestampTrits) + Converter.trytes(currentIndexTrits) + Converter.trytes(lastIndexTrits)); curl.absorb(t, 0, t.length); } - int[] hash = new int[90]; + int[] hash = new int[243]; curl.squeeze(hash, 0, hash.length); String hashInTrytes = Converter.trytes(hash); @@ -98,14 +85,12 @@ public class Bundle { String emptySignatureFragment = ""; String emptyHash = EMPTY_HASH; - for (int j = 0; emptySignatureFragment.length() < 2187; j++) { - emptySignatureFragment += '9'; - } + emptySignatureFragment = StringUtils.rightPad(emptySignatureFragment, 2187, '9'); for (int i = 0; i < this.getTransactions().size(); i++) { // Fill empty signatureMessageFragment - this.getTransactions().get(i).setSignatureFragments(signatureFragments.get(i) == null ? signatureFragments.get(i) : emptySignatureFragment); + this.getTransactions().get(i).setSignatureFragments((signatureFragments.size() <= i || signatureFragments.get(i).isEmpty()) ? emptySignatureFragment : signatureFragments.get(i)); // Fill empty trunkTransaction this.getTransactions().get(i).setTrunkTransaction(emptyHash); @@ -118,14 +103,14 @@ public class Bundle { } public int[] normalizedBundle(String bundleHash) { - int[] normalizedBundle = new int[33 * 27 + 27]; + int[] normalizedBundle = new int[81]; for (int i = 0; i < 3; i++) { long sum = 0; for (int j = 0; j < 27; j++) { - sum += (normalizedBundle[i * 27 + j] = Converter.value(Converter.trits("" + bundleHash.charAt(i * 27 + j)))); + sum += (normalizedBundle[i * 27 + j] = Converter.value(Converter.tritsString("" + bundleHash.charAt(i * 27 + j)))); } if (sum >= 0) { @@ -155,4 +140,8 @@ public class Bundle { return normalizedBundle; } -} + @Override + public int compareTo(Bundle o) { + return Long.compare(Long.parseLong(this.getTransactions().get(0).getTimestamp()), Long.parseLong(o.getTransactions().get(0).getTimestamp())); + } +} \ No newline at end of file diff --git a/src/main/java/jota/model/Inputs.java b/src/main/java/jota/model/Inputs.java new file mode 100644 index 0000000..5d4b993 --- /dev/null +++ b/src/main/java/jota/model/Inputs.java @@ -0,0 +1,39 @@ +package jota.model; + +import com.google.gson.Gson; + +import java.util.List; + +/** + * Created by Adrian on 09.12.2016. + */ +public class Inputs { + private List inputsList; + private long totalBalance; + + public Inputs(List inputsList, long totalBalance) { + this.inputsList = inputsList; + this.totalBalance = totalBalance; + } + + @Override + public String toString() { + return new Gson().toJson(this); + } + + public List getInputsList() { + return inputsList; + } + + public void setInputsList(List inputsList) { + inputsList = inputsList; + } + + public long getTotalBalance() { + return totalBalance; + } + + public void setTotalBalance(long totalBalance) { + this.totalBalance = totalBalance; + } +} diff --git a/src/main/java/jota/model/Signature.java b/src/main/java/jota/model/Signature.java new file mode 100644 index 0000000..3e0a9ba --- /dev/null +++ b/src/main/java/jota/model/Signature.java @@ -0,0 +1,33 @@ +package jota.model; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Adrian on 27.12.2016. + */ +public class Signature { + + String address; + List signatureFragments; + + public Signature() { + this.signatureFragments = new ArrayList<>(); + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public List getSignatureFragments() { + return signatureFragments; + } + + public void setSignatureFragments(List signatureFragments) { + this.signatureFragments = signatureFragments; + } +} diff --git a/src/main/java/jota/model/Transaction.java b/src/main/java/jota/model/Transaction.java index 0f7f962..9b49bc8 100644 --- a/src/main/java/jota/model/Transaction.java +++ b/src/main/java/jota/model/Transaction.java @@ -1,12 +1,23 @@ package jota.model; +import jota.pow.ICurl; +import jota.pow.JCurl; +import jota.utils.Converter; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; /** * Created by pinpong on 02.12.16. */ public class Transaction { + private static final Logger log = LoggerFactory.getLogger(Transaction.class); + private ICurl customCurl; + private String hash; private String signatureFragments; private String address; @@ -21,8 +32,21 @@ public class Transaction { private String nonce; private Boolean persistence; - public Transaction() { + public Transaction(ICurl curl) { + customCurl = curl; + } + public Transaction() { + customCurl = null; + } + + public Transaction(String trytes) { + transactionObject(trytes); + } + + public Transaction(String trytes, ICurl customCurl) { + transactionObject(trytes); + this.customCurl = customCurl; } public Transaction(String signatureFragments, String currentIndex, String lastIndex, String nonce, String hash, String tag, String timestamp, String trunkTransaction, String branchTransaction, String address, String value, String bundle) { @@ -157,4 +181,72 @@ public class Transaction { public void setPersistence(Boolean persistence) { this.persistence = persistence; } + + public boolean equals(Object obj) { + return obj != null && ((Transaction) obj).getHash().equals(this.getHash()); + } + + public String toTrytes() { + int[] valueTrits = Converter.trits(this.getValue(), 81); + + int[] timestampTrits = Converter.trits(this.getTimestamp(), 27); + + + int[] currentIndexTrits = Converter.trits(this.getCurrentIndex(), 27); + + + int[] lastIndexTrits = Converter.trits(this.getLastIndex(), 27); + + + return this.getSignatureFragments() + + this.getAddress() + + Converter.trytes(valueTrits) + + this.getTag() + + Converter.trytes(timestampTrits) + + Converter.trytes(currentIndexTrits) + + Converter.trytes(lastIndexTrits) + + this.getBundle() + + this.getTrunkTransaction() + + this.getBranchTransaction() + + this.getNonce(); + } + + public void transactionObject(final String trytes) { + + if (StringUtils.isEmpty(trytes)) { + log.warn("Warning: empty trytes in input for transactionObject"); + return; + } + + // validity check + for (int i = 2279; i < 2295; i++) { + if (trytes.charAt(i) != '9') { + log.warn("Trytes {} does not seem a valid tryte", trytes); + return; + } + } + + int[] transactionTrits = Converter.trits(trytes); + int[] hash = new int[243]; + + final ICurl curl = customCurl == null ? new JCurl() : customCurl; // we need a fluent JCurl. + + // generate the correct transaction hash + curl.reset(); + curl.absorb(transactionTrits, 0, transactionTrits.length); + curl.squeeze(hash, 0, hash.length); + + this.setHash(Converter.trytes(hash)); + this.setSignatureFragments(trytes.substring(0, 2187)); + this.setAddress(trytes.substring(2187, 2268)); + this.setValue("" + Converter.longValue(Arrays.copyOfRange(transactionTrits, 6804, 6837))); + this.setTag(trytes.substring(2295, 2322)); + this.setTimestamp("" + Converter.longValue(Arrays.copyOfRange(transactionTrits, 6966, 6993))); + this.setCurrentIndex("" + Converter.longValue(Arrays.copyOfRange(transactionTrits, 6993, 7020))); + this.setLastIndex("" + Converter.longValue(Arrays.copyOfRange(transactionTrits, 7020, 7047))); + this.setBundle(trytes.substring(2349, 2430)); + this.setTrunkTransaction(trytes.substring(2430, 2511)); + this.setBranchTransaction(trytes.substring(2511, 2592)); + this.setNonce(trytes.substring(2592, 2673)); + } } \ No newline at end of file diff --git a/src/main/java/jota/model/Transfer.java b/src/main/java/jota/model/Transfer.java index a1b675e..65cb8de 100644 --- a/src/main/java/jota/model/Transfer.java +++ b/src/main/java/jota/model/Transfer.java @@ -16,7 +16,7 @@ public class Transfer { private String tag; public Transfer(String timestamp, String address, String hash, Boolean persistence, long value, String message, - String tag) { + String tag) { this.timestamp = timestamp; this.address = address; this.hash = hash; @@ -43,54 +43,54 @@ public class Transfer { return address; } - public String getHash() { - return hash; - } - - public Boolean getPersistence() { - return persistence; - } - - public String getTimestamp() { - return timestamp; - } - - public long getValue() { - return value; - } - - public String getMessage() { - return message; - } - - public String getTag() { - return tag; - } - - public void setTimestamp(String timestamp) { - this.timestamp = timestamp; - } - public void setAddress(String address) { this.address = address; } + public String getHash() { + return hash; + } + public void setHash(String hash) { this.hash = hash; } + public Boolean getPersistence() { + return persistence; + } + public void setPersistence(Boolean persistence) { this.persistence = persistence; } + public String getTimestamp() { + return timestamp; + } + + public void setTimestamp(String timestamp) { + this.timestamp = timestamp; + } + + public long getValue() { + return value; + } + public void setValue(long value) { this.value = value; } + public String getMessage() { + return message; + } + public void setMessage(String message) { this.message = message; } + public String getTag() { + return tag; + } + public void setTag(String tag) { this.tag = tag; } diff --git a/src/main/java/jota/pow/ICurl.java b/src/main/java/jota/pow/ICurl.java new file mode 100644 index 0000000..9534837 --- /dev/null +++ b/src/main/java/jota/pow/ICurl.java @@ -0,0 +1,22 @@ +package jota.pow; + +/** + * Created by Adrian on 07.01.2017. + */ +public interface ICurl { + JCurl absorb(final int[] trits, int offset, int length); + + JCurl absorb(final int[] trits); + + int[] squeeze(final int[] trits, int offset, int length); + + int[] squeeze(final int[] trits); + + JCurl transform(); + + JCurl reset(); + + int[] getState(); + + void setState(int[] state); +} diff --git a/src/main/java/jota/pow/Curl.java b/src/main/java/jota/pow/JCurl.java similarity index 84% rename from src/main/java/jota/pow/Curl.java rename to src/main/java/jota/pow/JCurl.java index b804250..8eeebf1 100644 --- a/src/main/java/jota/pow/Curl.java +++ b/src/main/java/jota/pow/JCurl.java @@ -3,9 +3,9 @@ package jota.pow; /** * (c) 2016 Come-from-Beyond *

- * Curl belongs to the sponge function family. + * JCurl belongs to the sponge function family. */ -public class Curl { +public class JCurl implements ICurl { public static final int HASH_LENGTH = 243; private static final int STATE_LENGTH = 3 * HASH_LENGTH; @@ -15,22 +15,23 @@ public class Curl { private int[] state = new int[STATE_LENGTH]; - public Curl absorb(final int[] trits, int offset, int length) { + public JCurl absorb(final int[] trits, int offset, int length) { do { System.arraycopy(trits, offset, state, 0, length < HASH_LENGTH ? length : HASH_LENGTH); transform(); offset += HASH_LENGTH; } while ((length -= HASH_LENGTH) > 0); - + return this; } - - public Curl absorb(final int[] trits) { + + + public JCurl absorb(final int[] trits) { return absorb(trits, 0, trits.length); } - public Curl transform() { + public JCurl transform() { final int[] scratchpad = new int[STATE_LENGTH]; int scratchpadIndex = 0; @@ -43,13 +44,13 @@ public class Curl { return this; } - public Curl reset() { + public JCurl reset() { for (int stateIndex = 0; stateIndex < STATE_LENGTH; stateIndex++) { state[stateIndex] = 0; } return this; } - + public int[] squeeze(final int[] trits, int offset, int length) { do { @@ -60,7 +61,7 @@ public class Curl { return state; } - + public int[] squeeze(final int[] trits) { return squeeze(trits, 0, trits.length); } @@ -68,7 +69,8 @@ public class Curl { public int[] getState() { return state; } - public void setState(int[] state) { - this.state = state; + + public void setState(int[] state) { + this.state = state; } } diff --git a/src/main/java/jota/utils/Checksum.java b/src/main/java/jota/utils/Checksum.java index 6bf83f5..79605e5 100644 --- a/src/main/java/jota/utils/Checksum.java +++ b/src/main/java/jota/utils/Checksum.java @@ -1,9 +1,8 @@ package jota.utils; +import jota.pow.JCurl; import org.apache.commons.lang3.StringUtils; -import jota.pow.Curl; - /** * Created by pinpong on 02.12.16. */ @@ -33,12 +32,16 @@ public class Checksum { return addressWithRecalculateChecksum.equals(addressWithChecksum); } - private static boolean isAddressWithChecksum(String addressWithChecksum) { - return InputValidator.checkAddress(addressWithChecksum) && addressWithChecksum.length() == Constants.ADDRESS_LENGTH_WITH_CHECKSUM; + public static boolean isAddressWithChecksum(String address) { + return InputValidator.checkAddress(address) && address.length() == Constants.ADDRESS_LENGTH_WITH_CHECKSUM; + } + + public static boolean isAddressWithoutChecksum(String address) { + return InputValidator.checkAddress(address) && address.length() == Constants.ADDRESS_LENGTH_WITHOUT_CHECKSUM; } public static String calculateChecksum(String address) { - Curl curl = new Curl(); + JCurl curl = new JCurl(); curl.reset(); curl.setState(Converter.copyTrits(address, curl.getState())); curl.transform(); diff --git a/src/main/java/jota/utils/Converter.java b/src/main/java/jota/utils/Converter.java index 4276b08..283c20b 100644 --- a/src/main/java/jota/utils/Converter.java +++ b/src/main/java/jota/utils/Converter.java @@ -1,16 +1,10 @@ package jota.utils; -import jota.model.Transaction; -import jota.pow.Curl; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; public class Converter { - - private static final Logger log = LoggerFactory.getLogger(Converter.class); private static final int RADIX = 3; private static final int MAX_TRIT_VALUE = (RADIX - 1) / 2, MIN_TRIT_VALUE = -MAX_TRIT_VALUE; @@ -66,15 +60,46 @@ public class Converter { } } - public static int[] trits(final String trytes) { - final int[] trits = new int[trytes.length() * NUMBER_OF_TRITS_IN_A_TRYTE]; + public static int[] convertToIntArray(List integers) { + int[] ret = new int[integers.size()]; + for (int i = 0; i < ret.length; i++) { + ret[i] = integers.get(i); + } + return ret; + } + public static int[] trits(final String trytes, int length) { + int[] trits = trits(trytes); + + List tritsList = new LinkedList<>(); + + for (int i : trits) + tritsList.add(i); + + while (tritsList.size() < length) + tritsList.add(0); + + return convertToIntArray(tritsList); + } + + public static int[] tritsString(final String trytes) { + int[] d = new int[3 * trytes.length()]; + for (int i = 0; i < trytes.length(); i++) { + System.arraycopy(TRYTE_TO_TRITS_MAPPINGS[Constants.TRYTE_ALPHABET.indexOf(trytes.charAt(i))], 0, d, i * NUMBER_OF_TRITS_IN_A_TRYTE, NUMBER_OF_TRITS_IN_A_TRYTE); + } + return d; + } + + public static int[] trits(final String trytes) { + final List trits = new LinkedList<>(); if (InputValidator.isValue(trytes)) { - int value = Integer.parseInt(trytes); + long value = Long.parseLong(trytes); long absoluteValue = value < 0 ? -value : value; + int position = 0; + while (absoluteValue > 0) { int remainder = (int) (absoluteValue % RADIX); @@ -85,22 +110,21 @@ public class Converter { absoluteValue++; } - trits[trits.length] = remainder; + trits.add(position++, remainder); } if (value < 0) { - - for (int i = 0; i < trits.length; i++) { - - trits[i] = -trits[i]; + for (int i = 0; i < trits.size(); i++) { + trits.set(i, -trits.get(i)); } } } else { - + int[] d = new int[3 * trytes.length()]; for (int i = 0; i < trytes.length(); i++) { - System.arraycopy(TRYTE_TO_TRITS_MAPPINGS[Constants.TRYTE_ALPHABET.indexOf(trytes.charAt(i))], 0, trits, i * NUMBER_OF_TRITS_IN_A_TRYTE, NUMBER_OF_TRITS_IN_A_TRYTE); + System.arraycopy(TRYTE_TO_TRITS_MAPPINGS[Constants.TRYTE_ALPHABET.indexOf(trytes.charAt(i))], 0, d, i * NUMBER_OF_TRITS_IN_A_TRYTE, NUMBER_OF_TRITS_IN_A_TRYTE); } + return d; } - return trits; + return convertToIntArray(trits); } public static void copyTrits(final long value, final int[] destination, final int offset, final int size) { @@ -168,6 +192,15 @@ public class Converter { return value; } + public static long longValue(final int[] trits) { + long value = 0; + + for (int i = trits.length; i-- > 0; ) { + value = value * 3 + trits[i]; + } + return value; + } + public static void increment(final int[] trits, final int size) { for (int i = 0; i < size; i++) { @@ -178,47 +211,5 @@ public class Converter { } } } - - public static Transaction transactionObject(final String trytes) { - - if (StringUtils.isEmpty(trytes)) { - log.warn("Warning: empty trytes in input for transactionObject"); - return null; - } - - // validity check - for (int i = 2279; i < 2295; i++) { - if (trytes.charAt(i) != '9') { - log.warn("Trytes {} does not seem a valid tryte", trytes); - return null; - } - } - - int[] transactionTrits = Converter.trits(trytes); - int[] hash = new int[90]; - final Curl curl = new Curl(); // we need a fluent Curl. - - // generate the correct transaction hash - curl.reset(); - curl.absorb(transactionTrits, 0, transactionTrits.length); - curl.squeeze(hash, 0, hash.length); - - Transaction trx = new Transaction(); - - trx.setHash(Converter.trytes(hash)); - trx.setSignatureFragments(trytes.substring(0, 2187)); - trx.setAddress(trytes.substring(2187, 2268)); - trx.setValue("" + Converter.value(Arrays.copyOfRange(transactionTrits, 6804, 6837))); - trx.setTag(trytes.substring(2295, 2322)); - trx.setTimestamp("" + Converter.value(Arrays.copyOfRange(transactionTrits, 6966, 6993))); - trx.setCurrentIndex("" + Converter.value(Arrays.copyOfRange(transactionTrits, 6993, 7020))); - trx.setLastIndex("" + Converter.value(Arrays.copyOfRange(transactionTrits, 7020, 7047))); - trx.setBundle(trytes.substring(2349, 2430)); - trx.setTrunkTransaction(trytes.substring(2430, 2511)); - trx.setBranchTransaction(trytes.substring(2511, 2592)); - trx.setNonce(trytes.substring(2592, 2673)); - - return trx; - } -} +} \ No newline at end of file diff --git a/src/main/java/jota/utils/InputValidator.java b/src/main/java/jota/utils/InputValidator.java index 6e85443..4e01cd4 100644 --- a/src/main/java/jota/utils/InputValidator.java +++ b/src/main/java/jota/utils/InputValidator.java @@ -1,11 +1,10 @@ package jota.utils; -import java.util.List; - -import org.apache.commons.lang3.StringUtils; - -import jota.model.Transaction; import jota.model.Transfer; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; + +import java.util.List; /** * Created by pinpong on 02.12.16. @@ -27,13 +26,14 @@ public class InputValidator { public static boolean isTrytes(final String trytes, final int length) { return trytes.matches("^[A-Z9]{" + (length == 0 ? "0," : length) + "}$"); } - + public static boolean isValue(final String value) { - return StringUtils.isNumeric(value); + return NumberUtils.isNumber(value); } public static boolean isArrayOfHashes(String[] hashes) { - if (hashes == null) return false; + if (hashes == null) + return false; for (String hash : hashes) { // Check if address with checksum @@ -49,16 +49,16 @@ public class InputValidator { } return true; } - + /** - * checks if input is correct hash collections - * - * @method isTransfersArray - * @param {array} hash - * @returns {boolean} - **/ + * checks if input is correct hash collections + * + * @param {array} hash + * @method isTransfersArray + * @returns {boolean} + **/ public static boolean isTransfersCollectionCorrect(final List transfers) { - + for (final Transfer transfer : transfers) { if (!isTransfersArray(transfer)) { return false; @@ -66,9 +66,9 @@ public class InputValidator { } return true; } - + public static boolean isTransfersArray(final Transfer transfer) { - + if (!isAddress(transfer.getAddress())) { return false; } @@ -79,10 +79,15 @@ public class InputValidator { } // Check if tag is correct trytes of {0,27} trytes - if (!isTrytes(transfer.getTag(), 27)) { - return false; - } + return isTrytes(transfer.getTag(), 27); + } - return true; + public static String validateSeed(String seed) { + if (seed.length() > 81) + return null; + + seed = StringUtils.rightPad(seed, 81, '9'); + + return seed; } } diff --git a/src/main/java/jota/utils/IotaAPIUtils.java b/src/main/java/jota/utils/IotaAPIUtils.java index 617f014..3d59c4c 100644 --- a/src/main/java/jota/utils/IotaAPIUtils.java +++ b/src/main/java/jota/utils/IotaAPIUtils.java @@ -1,17 +1,17 @@ package jota.utils; +import jota.model.Bundle; +import jota.model.Input; +import jota.model.Transaction; +import jota.pow.ICurl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import jota.model.Bundle; -import jota.model.Input; -import jota.model.Transaction; - /** * Client Side computation service * @@ -29,11 +29,11 @@ public class IotaAPIUtils { * @param checksum * @return an String with address */ - public static String newAddress(String seed, int index, boolean checksum) { - - final int[] key = Signing.key(Converter.trits(seed), index, 2); - final int[] digests = Signing.digests(key); - final int[] addressTrits = Signing.address(digests); + public static String newAddress(String seed, int index, boolean checksum, ICurl curl) { + Signing signing = new Signing(curl); + final int[] key = signing.key(Converter.trits(seed), index, 2); + final int[] digests = signing.digests(key); + final int[] addressTrits = signing.address(digests); String address = Converter.trytes(addressTrits); @@ -43,45 +43,11 @@ public class IotaAPIUtils { return address; } - public static String transactionTrytes(Transaction trx) { - int[] valueTrits = Converter.trits(trx.getValue()); - while (valueTrits.length < 81) { - valueTrits[valueTrits.length] = 0; - } - - int[] timestampTrits = Converter.trits(trx.getTimestamp()); - while (timestampTrits.length < 27) { - timestampTrits[timestampTrits.length] = 0; - } - - int[] currentIndexTrits = Converter.trits(trx.getTimestamp()); - while (currentIndexTrits.length < 27) { - currentIndexTrits[currentIndexTrits.length] = 0; - } - - int[] lastIndexTrits = Converter.trits(trx.getCurrentIndex()); - while (lastIndexTrits.length < 27) { - lastIndexTrits[lastIndexTrits.length] = 0; - } - - return trx.getSignatureFragments() - + trx.getAddress() - + Converter.trytes(valueTrits) - + trx.getTag() - + Converter.trytes(timestampTrits) - + Converter.trytes(currentIndexTrits) - + Converter.trytes(lastIndexTrits) - + trx.getBundle() - + trx.getTrunkTransaction() - + trx.getBranchTransaction() - + trx.getNonce(); - } - - public static List signInputsAndReturn(final String seed, - final List inputs, + public static List signInputsAndReturn(final String seed, + final List inputs, final Bundle bundle, - final List signatureFragments) { - bundle.finalize(); + final List signatureFragments, ICurl curl) { + bundle.finalize(curl); bundle.addTrytes(signatureFragments); // SIGNING OF INPUTS @@ -95,9 +61,9 @@ public class IotaAPIUtils { // Get the corresponding keyIndex of the address int keyIndex = 0; - for (int k = 0; k < inputs.size(); k++) { - if (inputs.get(k).getAddress().equals(thisAddress)) { - keyIndex = inputs.get(k).getKeyIndex(); + for (Input input : inputs) { + if (input.getAddress().equals(thisAddress)) { + keyIndex = input.getKeyIndex(); break; } } @@ -105,7 +71,7 @@ public class IotaAPIUtils { String bundleHash = bundle.getTransactions().get(i).getBundle(); // Get corresponding private key of address - int[] key = Signing.key(Converter.trits(seed), keyIndex, 2); + int[] key = new Signing(curl).key(Converter.trits(seed), keyIndex, 2); // First 6561 trits for the firstFragment int[] firstFragment = Arrays.copyOfRange(key, 0, 6561); @@ -117,7 +83,7 @@ public class IotaAPIUtils { int[] firstBundleFragment = Arrays.copyOfRange(normalizedBundleHash, 0, 27); // Calculate the new signatureFragment with the first bundle fragment - int[] firstSignedFragment = Signing.signatureFragment(firstBundleFragment, firstFragment); + int[] firstSignedFragment = new Signing(curl).signatureFragment(firstBundleFragment, firstFragment); // Convert signature to trytes and assign the new signatureFragment bundle.getTransactions().get(i).setSignatureFragments(Converter.trytes(firstSignedFragment)); @@ -126,7 +92,7 @@ public class IotaAPIUtils { // find the second transaction to add the remainder of the signature for (int j = 0; j < bundle.getTransactions().size(); j++) { // Same address as well as value = 0 (as we already spent the input) - if (bundle.getTransactions().get(j).getAddress() == thisAddress && Long.parseLong(bundle.getTransactions().get(j).getValue()) == 0) { + if (bundle.getTransactions().get(j).getAddress().equals(thisAddress) && Long.parseLong(bundle.getTransactions().get(j).getValue()) == 0) { // Use the second 6562 trits int[] secondFragment = Arrays.copyOfRange(key, 6561, 6561 * 2); @@ -134,7 +100,7 @@ public class IotaAPIUtils { int[] secondBundleFragment = Arrays.copyOfRange(normalizedBundleHash, 27, 27 * 2); // Calculate the new signature - int[] secondSignedFragment = Signing.signatureFragment(secondBundleFragment, secondFragment); + int[] secondSignedFragment = new Signing(curl).signatureFragment(secondBundleFragment, secondFragment); // Convert signature to trytes and assign it again to this bundle entry bundle.getTransactions().get(j).setSignatureFragments(Converter.trytes(secondSignedFragment)); @@ -147,7 +113,7 @@ public class IotaAPIUtils { // Convert all bundle entries into trytes for (Transaction tx : bundle.getTransactions()) { - bundleTrytes.add(IotaAPIUtils.transactionTrytes(tx)); + bundleTrytes.add(tx.toTrytes()); } Collections.reverse(bundleTrytes); return bundleTrytes; diff --git a/src/main/java/jota/utils/IotaUnitConverter.java b/src/main/java/jota/utils/IotaUnitConverter.java index d25a39e..def86e0 100644 --- a/src/main/java/jota/utils/IotaUnitConverter.java +++ b/src/main/java/jota/utils/IotaUnitConverter.java @@ -16,24 +16,28 @@ public class IotaUnitConverter { return (long) (amount / Math.pow(10, toUnit.getValue())); } - public static String convertRawIotaAmountToDisplayText(long amount) { + public static String convertRawIotaAmountToDisplayText(long amount, boolean extended) { IotaUnits unit = findOptimalIotaUnitToDisplay(amount); double amountInDisplayUnit = convertAmountTo(amount, unit); - return createAmountWithUnitDisplayText(amountInDisplayUnit, unit); + return createAmountWithUnitDisplayText(amountInDisplayUnit, unit, extended); } public static double convertAmountTo(long amount, IotaUnits target) { return amount / Math.pow(10, target.getValue()); } - private static String createAmountWithUnitDisplayText(double amountInUnit, IotaUnits unit) { - String result = createAmountDisplayText(amountInUnit, unit); + private static String createAmountWithUnitDisplayText(double amountInUnit, IotaUnits unit, boolean extended) { + String result = createAmountDisplayText(amountInUnit, unit, extended); result += " " + unit.getUnit(); return result; } - public static String createAmountDisplayText(double amountInUnit, IotaUnits unit) { - DecimalFormat df = new DecimalFormat("##0.##################"); + public static String createAmountDisplayText(double amountInUnit, IotaUnits unit, boolean extended) { + DecimalFormat df; + if (extended) df = new DecimalFormat("##0.##################"); + else + df = new DecimalFormat("##0.##"); + String result = ""; // display unit as integer if value is between 1-999 or in decimal format result += unit == IotaUnits.IOTA ? (long) amountInUnit : df.format(amountInUnit); @@ -43,7 +47,7 @@ public class IotaUnitConverter { public static IotaUnits findOptimalIotaUnitToDisplay(long amount) { int length = String.valueOf(amount).length(); - if (amount < 0) {// do not count "-" sign + if (amount < 0) {// do not count "-" sign length -= 1; } diff --git a/src/main/java/jota/utils/IotaUnits.java b/src/main/java/jota/utils/IotaUnits.java index 779dde0..e799913 100644 --- a/src/main/java/jota/utils/IotaUnits.java +++ b/src/main/java/jota/utils/IotaUnits.java @@ -1,14 +1,10 @@ package jota.utils; -/** - * Created by pinpong on 30.11.16. - */ - /** * Table of IOTA units based off of the standard system of Units **/ public enum IotaUnits { - + IOTA("i", 0), KILO_IOTA("Ki", 3), MEGA_IOTA("Mi", 6), diff --git a/src/main/java/jota/utils/NamedThreadFactory.java b/src/main/java/jota/utils/NamedThreadFactory.java new file mode 100644 index 0000000..a6e3dab --- /dev/null +++ b/src/main/java/jota/utils/NamedThreadFactory.java @@ -0,0 +1,26 @@ +package jota.utils; + +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Created by Adrian on 15.01.2017. + */ +public class NamedThreadFactory implements ThreadFactory { + private final String baseName; + private final AtomicInteger threadNum = new AtomicInteger(0); + + public NamedThreadFactory(String baseName) { + this.baseName = baseName; + } + + @Override + public synchronized Thread newThread(Runnable r) { + Thread t = Executors.defaultThreadFactory().newThread(r); + + t.setName(baseName + "-" + threadNum.getAndIncrement()); + + return t; + } +} \ No newline at end of file diff --git a/src/main/java/jota/utils/Parallel.java b/src/main/java/jota/utils/Parallel.java new file mode 100644 index 0000000..f5cdeb7 --- /dev/null +++ b/src/main/java/jota/utils/Parallel.java @@ -0,0 +1,45 @@ +package jota.utils; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Created by Adrian on 15.01.2017. + */ +public class Parallel { + private static final int NUM_CORES = Runtime.getRuntime().availableProcessors(); + + private static final ExecutorService forPool = Executors.newFixedThreadPool(NUM_CORES * 2, new NamedThreadFactory("Parallel.For")); + + public static void For(final Iterable elements, final Operation operation) { + try { + // invokeAll blocks for us until all submitted tasks in the call complete + forPool.invokeAll(createCallables(elements, operation)); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public static Collection> createCallables(final Iterable elements, final Operation operation) { + List> callables = new LinkedList<>(); + for (final T elem : elements) { + callables.add(new Callable() { + @Override + public Void call() { + operation.perform(elem); + return null; + } + }); + } + + return callables; + } + + public interface Operation { + void perform(T pParameter); + } +} \ No newline at end of file diff --git a/src/main/java/jota/utils/Signing.java b/src/main/java/jota/utils/Signing.java index e0f2e84..30b55e8 100644 --- a/src/main/java/jota/utils/Signing.java +++ b/src/main/java/jota/utils/Signing.java @@ -1,15 +1,25 @@ package jota.utils; +import jota.model.Bundle; +import jota.pow.ICurl; +import jota.pow.JCurl; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import jota.model.Bundle; -import jota.pow.Curl; - public class Signing { + private ICurl curl; - static int[] key(int[] seed, int index, int length) { + public Signing() { + this(null); + } + + public Signing(ICurl curl) { + this.curl = curl == null ? new JCurl() : curl; + } + + public int[] key(int[] seed, int index, int length) { for (int i = 0; i < index; i++) { for (int j = 0; j < 243; j++) { @@ -21,7 +31,6 @@ public class Signing { } } - final Curl curl = new Curl(); curl.reset(); curl.absorb(seed, 0, seed.length); curl.squeeze(seed, 0, seed.length); @@ -44,7 +53,7 @@ public class Signing { return to(key); } - private static int[] to(List key) { + private int[] to(List key) { int a[] = new int[key.size()]; int i = 0; for (Integer v : key) { @@ -53,42 +62,37 @@ public class Signing { return a; } - public static int[] signatureFragment(int[] normalizedBundleFragment, int[] keyFragment) { + public int[] signatureFragment(int[] normalizedBundleFragment, int[] keyFragment) { - int[] signatureFragment = keyFragment; int[] hash; - Curl curl = new Curl(); - for (int i = 0; i < 27; i++) { - hash = Arrays.copyOfRange(signatureFragment, i * 243, (i + 1) * 243); + hash = Arrays.copyOfRange(keyFragment, i * 243, (i + 1) * 243); for (int j = 0; j < 13 - normalizedBundleFragment[i]; j++) { curl.reset() - .absorb(hash, 0, hash.length) - .squeeze(hash, 0, hash.length); + .absorb(hash, 0, hash.length) + .squeeze(hash, 0, hash.length); } for (int j = 0; j < 243; j++) { - signatureFragment[i * 243 + j] = hash[j]; + System.arraycopy(hash, j, keyFragment, i * 243 + j, 1); } } - return signatureFragment; + return keyFragment; } - public static int[] address(int[] digests) { - final Curl curl = new Curl(); + public int[] address(int[] digests) { int[] address = new int[243]; curl.reset() - .absorb(digests) - .squeeze(address); + .absorb(digests) + .squeeze(address); return address; } - - public static int[] digests(int[] key) { - final Curl curl = new Curl(); + + public int[] digests(int[] key) { int[] digests = new int[(int) Math.floor(key.length / 6561) * 243]; int[] buffer = new int[243]; @@ -101,8 +105,8 @@ public class Signing { buffer = Arrays.copyOfRange(keyFragment, j * 243, (j + 1) * 243); for (int k = 0; k < 26; k++) { curl.reset() - .absorb(buffer) - .squeeze(buffer); + .absorb(buffer) + .squeeze(buffer); } System.arraycopy(buffer, 0, keyFragment, j * 243, 243); } @@ -116,20 +120,19 @@ public class Signing { return digests; } - public static int[] digest(int[] normalizedBundleFragment, int[] signatureFragment) { - + public int[] digest(int[] normalizedBundleFragment, int[] signatureFragment) { + curl.reset(); int[] buffer = new int[243]; - Curl curl = new Curl().reset(); - for (int i = 0; i < 27; i++) { buffer = Arrays.copyOfRange(signatureFragment, i * 243, (i + 1) * 243); for (int j = normalizedBundleFragment[i] + 13; j-- > 0; ) { - new Curl().reset() - .absorb(buffer) - .squeeze(buffer); + ICurl jCurl = new JCurl(); + jCurl.reset(); + jCurl.absorb(buffer); + jCurl.squeeze(buffer); } curl.absorb(buffer); } @@ -138,7 +141,7 @@ public class Signing { return buffer; } - public static Boolean validateSignatures(String expectedAddress, String[] signatureFragments, String bundleHash) { + public Boolean validateSignatures(String expectedAddress, String[] signatureFragments, String bundleHash) { Bundle bundle = new Bundle(); @@ -151,18 +154,16 @@ public class Signing { } // Get digests - int[] digests = new int[signatureFragments.length * 243 + 243]; + int[] digests = new int[signatureFragments.length * 243]; for (int i = 0; i < signatureFragments.length; i++) { int[] digestBuffer = digest(normalizedBundleFragments[i % 3], Converter.trits(signatureFragments[i])); for (int j = 0; j < 243; j++) { - - digests[i * 243 + j] = digestBuffer[j]; + System.arraycopy(digestBuffer, j, digests, i * 243 + j, 1); } } - String address = Converter.trytes(address(digests)); return (expectedAddress.equals(address)); diff --git a/src/main/java/jota/utils/StopWatch.java b/src/main/java/jota/utils/StopWatch.java new file mode 100644 index 0000000..699de6f --- /dev/null +++ b/src/main/java/jota/utils/StopWatch.java @@ -0,0 +1,71 @@ +package jota.utils; + +/** + * Created by Adrian on 15.01.2017. + */ +public class StopWatch { + private long startTime = 0; + private boolean running = false; + private long currentTime = 0; + + public StopWatch() { + this.startTime = System.currentTimeMillis(); + this.running = true; + } + + public void reStart() { + this.startTime = System.currentTimeMillis(); + this.running = true; + } + + public StopWatch stop() { + this.running = false; + return this; + } + + public void pause() { + this.running = false; + currentTime = System.currentTimeMillis() - startTime; + } + + public void resume() { + this.running = true; + this.startTime = System.currentTimeMillis() - currentTime; + } + + //elaspsed time in milliseconds + public long getElapsedTimeMili() { + long elapsed = 0; + if (running) { + elapsed = (System.currentTimeMillis() - startTime); + } + return elapsed; + } + + //elaspsed time in seconds + public long getElapsedTimeSecs() { + long elapsed = 0; + if (running) { + elapsed = (System.currentTimeMillis() - startTime) / 1000; + } + return elapsed; + } + + //elaspsed time in minutes + public long getElapsedTimeMin() { + long elapsed = 0; + if (running) { + elapsed = (System.currentTimeMillis() - startTime) / 1000 / 60; + } + return elapsed; + } + + //elaspsed time in hours + public long getElapsedTimeHour() { + long elapsed = 0; + if (running) { + elapsed = ((System.currentTimeMillis() - startTime) / 1000 / 3600); + } + return elapsed; + } +} diff --git a/src/main/java/jota/utils/TrytesConverter.java b/src/main/java/jota/utils/TrytesConverter.java index ab9d02a..81f585a 100644 --- a/src/main/java/jota/utils/TrytesConverter.java +++ b/src/main/java/jota/utils/TrytesConverter.java @@ -4,7 +4,7 @@ package jota.utils; * Created by pinpong on 01.12.16. */ public class TrytesConverter { - + /** * Conversion of ascii encoded bytes to trytes. * Input is a string (can be stringified JSON object), return value is Trytes @@ -19,9 +19,9 @@ public class TrytesConverter { * b. The second value is the remainder (decimal value - first value), divided by 27 * 3. The two values returned from Step 2. are then input as indices into the available values list ('9ABCDEFGHIJKLMNOPQRSTUVWXYZ') to get the correct tryte value *

- * + *

* EXAMPLE - * + *

* Lets say we want to convert the ASCII character "Z". * 1. 'Z' has a decimal value of 90. * 2. 90 can be represented as 9 + 3 * 27. To make it simpler: @@ -32,10 +32,9 @@ public class TrytesConverter { * b. The second tryte value is '9ABCDEFGHIJKLMNOPQRSTUVWXYZ'[3] === "C" * Our tryte pair is "IC" *

- * - * @param inputString - * @return - * The ASCII char "Z" is represented as "IC" in trytes. + * + * @param inputString + * @return The ASCII char "Z" is represented as "IC" in trytes. */ public static String toTrytes(String inputString) { diff --git a/src/test/java/jota/InputValidatorTest.java b/src/test/java/jota/InputValidatorTest.java index b898baf..c41b4bd 100644 --- a/src/test/java/jota/InputValidatorTest.java +++ b/src/test/java/jota/InputValidatorTest.java @@ -20,6 +20,7 @@ public class InputValidatorTest { private static final String TEST_HASH = "OAATQS9VQLSXCLDJVJJVYUGONXAXOFMJOZNSYWRZSWECMXAQQURHQBJNLD9IOFEPGZEPEMPXCIVRX9999"; private static final String TEST_MESSAGE = "JOTA"; private static final String TEST_TAG = "JOTASPAM9999999999999999999"; + @Test public void shouldIsAddress() { assertEquals(InputValidator.isAddress(TEST_ADDRESS_WITHOUT_CHECKSUM), true); diff --git a/src/test/java/jota/IotaAPIProxyTest.java b/src/test/java/jota/IotaAPIProxyTest.java deleted file mode 100644 index ffb52ac..0000000 --- a/src/test/java/jota/IotaAPIProxyTest.java +++ /dev/null @@ -1,183 +0,0 @@ -package jota; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import jota.dto.response.*; -import jota.model.Transfer; -import org.hamcrest.core.Is; -import org.hamcrest.core.IsNull; -import org.junit.Before; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import static org.junit.Assert.assertThat; - -/** - * Let's do some integration test coverage against a default local real node. - * - * @author davassi - */ -public class IotaAPIProxyTest { - - private static Gson gson = new GsonBuilder().create(); - - private static final String TEST_SEED = "AAA999999999999999999999999999999999999999999999999999999999999999999999999999999"; - private static final String TEST_ADDRESS_WITHOUT_CHECKSUM = "PNGMCSNRCTRHCHPXYTPKEJYPCOWKOMRXZFHH9N9VDIKMNVAZCMIYRHVJIAZARZTUETJVFDMBEBIQE9QTH"; - private static final String TEST_ADDRESS_WITH_CHECKSUM = "PNGMCSNRCTRHCHPXYTPKEJYPCOWKOMRXZFHH9N9VDIKMNVAZCMIYRHVJIAZARZTUETJVFDMBEBIQE9QTHBFWDAOEFA"; - private static final String TEST_HASH = "OAATQS9VQLSXCLDJVJJVYUGONXAXOFMJOZNSYWRZSWECMXAQQURHQBJNLD9IOFEPGZEPEMPXCIVRX9999"; - private static final String TEST_TRYTES = "BYSWEAUTWXHXZ9YBZISEK9LUHWGMHXCGEVNZHRLUWQFCUSDXZHOFHWHL9MQPVJXXZLIXPXPXF9KYEREFSKCPKYIIKPZVLHUTDFQKKVVBBN9ATTLPCNPJDWDEVIYYLGPZGCWXOBDXMLJC9VO9QXTTBLAXTTBFUAROYEGQIVB9MJWJKXJMCUPTWAUGFZBTZCSJVRBGMYXTVBDDS9MYUJCPZ9YDWWQNIPUAIJXXSNLKUBSCOIJPCLEFPOXFJREXQCUVUMKSDOVQGGHRNILCO9GNCLWFM9APMNMWYASHXQAYBEXF9QRIHIBHYEJOYHRQJAOKAQ9AJJFQ9WEIWIJOTZATIBOXQLBMIJU9PCGBLVDDVFP9CFFSXTDUXMEGOOFXWRTLFGV9XXMYWEMGQEEEDBTIJ9OJOXFAPFQXCDAXOUDMLVYRMRLUDBETOLRJQAEDDLNVIRQJUBZBO9CCFDHIX9MSQCWYAXJVWHCUPTRSXJDESISQPRKZAFKFRULCGVRSBLVFOPEYLEE99JD9SEBALQINPDAZHFAB9RNBH9AZWIJOTLBZVIEJIAYGMC9AZGNFWGRSWAXTYSXVROVNKCOQQIWGPNQZKHUNODGYADPYLZZZUQRTJRTODOUKAOITNOMWNGHJBBA99QUMBHRENGBHTH9KHUAOXBVIVDVYYZMSEYSJWIOGGXZVRGN999EEGQMCOYVJQRIRROMPCQBLDYIGQO9AMORPYFSSUGACOJXGAQSPDY9YWRRPESNXXBDQ9OZOXVIOMLGTSWAMKMTDRSPGJKGBXQIVNRJRFRYEZ9VJDLHIKPSKMYC9YEGHFDS9SGVDHRIXBEMLFIINOHVPXIFAZCJKBHVMQZEVWCOSNWQRDYWVAIBLSCBGESJUIBWZECPUCAYAWMTQKRMCHONIPKJYYTEGZCJYCT9ABRWTJLRQXKMWY9GWZMHYZNWPXULNZAPVQLPMYQZCYNEPOCGOHBJUZLZDPIXVHLDMQYJUUBEDXXPXFLNRGIPWBRNQQZJSGSJTTYHIGGFAWJVXWL9THTPWOOHTNQWCNYOYZXALHAZXVMIZE9WMQUDCHDJMIBWKTYH9AC9AFOT9DPCADCV9ZWUTE9QNOMSZPTZDJLJZCJGHXUNBJFUBJWQUEZDMHXGBPTNSPZBR9TGSKVOHMOQSWPGFLSWNESFKSAZY9HHERAXALZCABFYPOVLAHMIHVDBGKUMDXC9WHHTIRYHZVWNXSVQUWCR9M9RAGMFEZZKZ9XEOQGOSLFQCHHOKLDSA9QCMDGCGMRYJZLBVIFOLBIJPROKMHOYTBTJIWUZWJMCTKCJKKTR9LCVYPVJI9AHGI9JOWMIWZAGMLDFJA9WU9QAMEFGABIBEZNNAL9OXSBFLOEHKDGHWFQSHMPLYFCNXAAZYJLMQDEYRGL9QKCEUEJ9LLVUOINVSZZQHCIKPAGMT9CAYIIMTTBCPKWTYHOJIIY9GYNPAJNUJ9BKYYXSV9JSPEXYMCFAIKTGNRSQGUNIYZCRT9FOWENSZQPD9ALUPYYAVICHVYELYFPUYDTWUSWNIYFXPX9MICCCOOZIWRNJIDALWGWRATGLJXNAYTNIZWQ9YTVDBOFZRKO9CFWRPAQQRXTPACOWCPRLYRYSJARRKSQPR9TCFXDVIXLP9XVL99ERRDSOHBFJDJQQGGGCZNDQ9NYCTQJWVZIAELCRBJJFDMCNZU9FIZRPGNURTXOCDSQGXTQHKHUECGWFUUYS9J9NYQ9U9P9UUP9YMZHWWWCIASCFLCMSKTELZWUGCDE9YOKVOVKTAYPHDF9ZCCQAYPJIJNGSHUIHHCOSSOOBUDOKE9CJZGYSSGNCQJVBEFTZFJ9SQUHOASKRRGBSHWKBCBWBTJHOGQ9WOMQFHWJVEG9NYX9KWBTCAIXNXHEBDIOFO9ALYMFGRICLCKKLG9FOBOX9PDWNQRGHBKHGKKRLWTBEQMCWQRLHAVYYZDIIPKVQTHYTWQMTOACXZOQCDTJTBAAUWXSGJF9PNQIJ9AJRUMUVCPWYVYVARKR9RKGOUHHNKNVGGPDDLGKPQNOYHNKAVVKCXWXOQPZNSLATUJT9AUWRMPPSWHSTTYDFAQDXOCYTZHOYYGAIM9CELMZ9AZPWB9MJXGHOKDNNSZVUDAGXTJJSSZCPZVPZBYNNTUQABSXQWZCHDQSLGK9UOHCFKBIBNETK999999999999999999999999999999999999999999999999999999999999999999999999999999999NOXDXXKUDWLOFJLIPQIBRBMGDYCPGDNLQOLQS99EQYKBIU9VHCJVIPFUYCQDNY9APGEVYLCENJIOBLWNB999999999XKBRHUD99C99999999NKZKEKWLDKMJCI9N9XQOLWEPAYWSH9999999999999999999999999KDDTGZLIPBNZKMLTOLOXQVNGLASESDQVPTXALEKRMIOHQLUHD9ELQDBQETS9QFGTYOYWLNTSKKMVJAUXSIROUICDOXKSYZTDPEDKOQENTJOWJONDEWROCEJIEWFWLUAACVSJFTMCHHXJBJRKAAPUDXXVXFWP9X9999IROUICDOXKSYZTDPEDKOQENTJOWJONDEWROCEJIEWFWLUAACVSJFTMCHHXJBJRKAAPUDXXVXFWP9X9999"; - private static final String TEST_MILESTONE = "SMYMAKKPSUKCKDRUEYCGZJTYCZ9HHDMDUWBAPXARGURPQRHTAJDASRWMIDTPTBNDKDEFBUTBGGAFX9999"; - private static final Integer TEST_MILESTONE_INDEX = 8059; - private static final String TEST_MESSAGE = "JOTA"; - private static final String TEST_TAG = "JOTASPAM9999999999999999999"; - - - private IotaAPIProxy proxy; - - @Before - public void createProxyInstance() { - proxy = new IotaAPIProxy.Builder().build(); - } - - @Test - public void shouldGetNodeInfo() { - GetNodeInfoResponse nodeInfo = proxy.getNodeInfo(); - assertThat(nodeInfo.getAppVersion(), IsNull.notNullValue()); - assertThat(nodeInfo.getAppName(), IsNull.notNullValue()); - //assertThat(nodeInfo.getJreVersion(), IsNull.notNullValue()); - assertThat(nodeInfo.getJreAvailableProcessors(), IsNull.notNullValue()); - assertThat(nodeInfo.getJreFreeMemory(), IsNull.notNullValue()); - assertThat(nodeInfo.getJreMaxMemory(), IsNull.notNullValue()); - assertThat(nodeInfo.getJreTotalMemory(), IsNull.notNullValue()); - assertThat(nodeInfo.getLatestMilestone(), IsNull.notNullValue()); - assertThat(nodeInfo.getLatestMilestoneIndex(), IsNull.notNullValue()); - assertThat(nodeInfo.getLatestSolidSubtangleMilestone(), IsNull.notNullValue()); - assertThat(nodeInfo.getLatestSolidSubtangleMilestoneIndex(), IsNull.notNullValue()); - assertThat(nodeInfo.getNeighbors(), IsNull.notNullValue()); - assertThat(nodeInfo.getPacketsQueueSize(), IsNull.notNullValue()); - assertThat(nodeInfo.getTime(), IsNull.notNullValue()); - assertThat(nodeInfo.getTips(), IsNull.notNullValue()); - assertThat(nodeInfo.getTransactionsToRequest(), IsNull.notNullValue()); - } - - @Test - public void shouldGetNeighbors() { - GetNeighborsResponse neighbors = proxy.getNeighbors(); - assertThat(neighbors.getNeighbors(), IsNull.notNullValue()); - } - - @Test - public void shouldAddNeighbors() { - AddNeighborsResponse res = proxy.addNeighbors("udp://8.8.8.8:14265"); - assertThat(res, IsNull.notNullValue()); - } - - @Test - public void shouldRemoveNeighbors() { - RemoveNeighborsResponse res = proxy.removeNeighbors("udp://8.8.8.8:14265"); - assertThat(res, IsNull.notNullValue()); - } - - @Test - public void shouldGetTips() { - GetTipsResponse tips = proxy.getTips(); - assertThat(tips, IsNull.notNullValue()); - } - - @Test - public void shouldFindTransactionsByAddresses() { - FindTransactionResponse trans = proxy.findTransactionsByAddresses(TEST_ADDRESS_WITH_CHECKSUM); - System.err.println(gson.toJson(trans)); - assertThat(trans.getHashes(), IsNull.notNullValue()); - } - - @Test - public void shouldFindTransactionsByApprovees() { - FindTransactionResponse trans = proxy.findTransactionsByApprovees(new String[]{TEST_HASH}); - assertThat(trans.getHashes(), IsNull.notNullValue()); - } - - @Test - public void shouldFindTransactionsByBundles() { - FindTransactionResponse trans = proxy.findTransactionsByBundles(TEST_HASH); - assertThat(trans.getHashes(), IsNull.notNullValue()); - } - - @Test - public void shouldFindTransactionsByDigests() { - FindTransactionResponse trans = proxy.findTransactionsByDigests(TEST_HASH); - assertThat(trans.getHashes(), IsNull.notNullValue()); - } - - - // ### - - @Test - public void shouldGetTrytes() { - GetTrytesResponse res = proxy.getTrytes(TEST_HASH); - assertThat(res.getTrytes(), IsNull.notNullValue()); - - } - - @Test - public void shouldGetInclusionStates() { - GetInclusionStateResponse res = proxy.getInclusionStates(new String[]{TEST_ADDRESS_WITH_CHECKSUM}, new String[]{"DNSBRJWNOVUCQPILOQIFDKBFJMVOTGHLIMLLRXOHFTJZGRHJUEDAOWXQRYGDI9KHYFGYDWQJZKX999999"}); - assertThat(res.getStates(), IsNull.notNullValue()); - } - - @Test // very long execution - public void shouldGetTransactionsToApprove() { - GetTransactionsToApproveResponse res = proxy.getTransactionsToApprove(27); - assertThat(res.getTrunkTransaction(), IsNull.notNullValue()); - assertThat(res.getBranchTransaction(), IsNull.notNullValue()); - - } - - @Test - public void shouldGetBalances() { - GetBalancesResponse res = proxy.getBalances(100, new String[]{TEST_ADDRESS_WITH_CHECKSUM}); - System.err.println(res); - assertThat(res.getBalances(), IsNull.notNullValue()); - assertThat(res.getMilestone(), IsNull.notNullValue()); - assertThat(res.getMilestoneIndex(), IsNull.notNullValue()); - - } - - @Test - public void shouldCreateIotaApiProxyInstanceWithDefaultValues() { - IotaAPIProxy proxy = new IotaAPIProxy.Builder().build(); - assertThat(proxy, IsNull.notNullValue()); - } - - @Test - public void shouldCreateANewAddress() { - final GetNewAddressResponse res = proxy.getNewAddress(TEST_SEED, 0, false, 1, false); - assertThat(res.getAddresses(), Is.is(Collections.singletonList(TEST_ADDRESS_WITHOUT_CHECKSUM))); - } - - @Test - public void shouldPrepareTransfer() { - List transfers = new ArrayList<>(); - transfers.add(new jota.model.Transfer(TEST_ADDRESS_WITH_CHECKSUM, 0, TEST_MESSAGE, TEST_TAG)); - proxy.prepareTransfers(TEST_SEED, transfers, null, null); - } - - @Test - public void shouldSendTrytes() { - proxy.sendTrytes(TEST_TRYTES, 18); - } - - @Test - public void shouldGetLastInclusionState() { - GetInclusionStateResponse res = proxy.getLatestInclusion(new String[]{TEST_HASH}); - assertThat(res.getStates(), IsNull.notNullValue()); - } - - @Test - public void shouldFindTransactionObjects() { - assertThat(proxy.findTransactionObjects(new String[]{TEST_ADDRESS_WITH_CHECKSUM}), IsNull.notNullValue()); - } -} \ No newline at end of file diff --git a/src/test/java/jota/IotaAPITest.java b/src/test/java/jota/IotaAPITest.java new file mode 100644 index 0000000..7fc438c --- /dev/null +++ b/src/test/java/jota/IotaAPITest.java @@ -0,0 +1,160 @@ +package jota; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import jota.dto.response.*; +import jota.error.*; +import jota.model.Bundle; +import jota.model.Transaction; +import jota.model.Transfer; +import org.hamcrest.core.Is; +import org.hamcrest.core.IsNull; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertThat; + +/** + * Let's do some integration test coverage against a default local real node. + * + * @author davassi + */ +public class IotaAPITest { + + private static final String TEST_SEED1 = "AAA999999999999999999999999999999999999999999999999999999999999999999999999999999"; + private static final String TEST_SEED2 = "IHDEENZYITYVYSPKAURUZAQKGVJEREFDJMYTANNXXGPZ9GJWTEOJJ9IPMXOGZNQLSNMFDSQOTZAEETUEA"; + private static final String TEST_ADDRESS_WITHOUT_CHECKSUM = "PNGMCSNRCTRHCHPXYTPKEJYPCOWKOMRXZFHH9N9VDIKMNVAZCMIYRHVJIAZARZTUETJVFDMBEBIQE9QTH"; + private static final String TEST_ADDRESS_WITH_CHECKSUM = "PNGMCSNRCTRHCHPXYTPKEJYPCOWKOMRXZFHH9N9VDIKMNVAZCMIYRHVJIAZARZTUETJVFDMBEBIQE9QTHBFWDAOEFA"; + private static final String TEST_HASH = "CKZ9TYPLUWH9FUSYJMPIZBVHWFZXTZMVOJLC9KOICSTBBQWXYTOTMCVPSPMYNDONTXHRULRFAWD999999"; + private static final String TEST_TRYTES = "BYSWEAUTWXHXZ9YBZISEK9LUHWGMHXCGEVNZHRLUWQFCUSDXZHOFHWHL9MQPVJXXZLIXPXPXF9KYEREFSKCPKYIIKPZVLHUTDFQKKVVBBN9ATTLPCNPJDWDEVIYYLGPZGCWXOBDXMLJC9VO9QXTTBLAXTTBFUAROYEGQIVB9MJWJKXJMCUPTWAUGFZBTZCSJVRBGMYXTVBDDS9MYUJCPZ9YDWWQNIPUAIJXXSNLKUBSCOIJPCLEFPOXFJREXQCUVUMKSDOVQGGHRNILCO9GNCLWFM9APMNMWYASHXQAYBEXF9QRIHIBHYEJOYHRQJAOKAQ9AJJFQ9WEIWIJOTZATIBOXQLBMIJU9PCGBLVDDVFP9CFFSXTDUXMEGOOFXWRTLFGV9XXMYWEMGQEEEDBTIJ9OJOXFAPFQXCDAXOUDMLVYRMRLUDBETOLRJQAEDDLNVIRQJUBZBO9CCFDHIX9MSQCWYAXJVWHCUPTRSXJDESISQPRKZAFKFRULCGVRSBLVFOPEYLEE99JD9SEBALQINPDAZHFAB9RNBH9AZWIJOTLBZVIEJIAYGMC9AZGNFWGRSWAXTYSXVROVNKCOQQIWGPNQZKHUNODGYADPYLZZZUQRTJRTODOUKAOITNOMWNGHJBBA99QUMBHRENGBHTH9KHUAOXBVIVDVYYZMSEYSJWIOGGXZVRGN999EEGQMCOYVJQRIRROMPCQBLDYIGQO9AMORPYFSSUGACOJXGAQSPDY9YWRRPESNXXBDQ9OZOXVIOMLGTSWAMKMTDRSPGJKGBXQIVNRJRFRYEZ9VJDLHIKPSKMYC9YEGHFDS9SGVDHRIXBEMLFIINOHVPXIFAZCJKBHVMQZEVWCOSNWQRDYWVAIBLSCBGESJUIBWZECPUCAYAWMTQKRMCHONIPKJYYTEGZCJYCT9ABRWTJLRQXKMWY9GWZMHYZNWPXULNZAPVQLPMYQZCYNEPOCGOHBJUZLZDPIXVHLDMQYJUUBEDXXPXFLNRGIPWBRNQQZJSGSJTTYHIGGFAWJVXWL9THTPWOOHTNQWCNYOYZXALHAZXVMIZE9WMQUDCHDJMIBWKTYH9AC9AFOT9DPCADCV9ZWUTE9QNOMSZPTZDJLJZCJGHXUNBJFUBJWQUEZDMHXGBPTNSPZBR9TGSKVOHMOQSWPGFLSWNESFKSAZY9HHERAXALZCABFYPOVLAHMIHVDBGKUMDXC9WHHTIRYHZVWNXSVQUWCR9M9RAGMFEZZKZ9XEOQGOSLFQCHHOKLDSA9QCMDGCGMRYJZLBVIFOLBIJPROKMHOYTBTJIWUZWJMCTKCJKKTR9LCVYPVJI9AHGI9JOWMIWZAGMLDFJA9WU9QAMEFGABIBEZNNAL9OXSBFLOEHKDGHWFQSHMPLYFCNXAAZYJLMQDEYRGL9QKCEUEJ9LLVUOINVSZZQHCIKPAGMT9CAYIIMTTBCPKWTYHOJIIY9GYNPAJNUJ9BKYYXSV9JSPEXYMCFAIKTGNRSQGUNIYZCRT9FOWENSZQPD9ALUPYYAVICHVYELYFPUYDTWUSWNIYFXPX9MICCCOOZIWRNJIDALWGWRATGLJXNAYTNIZWQ9YTVDBOFZRKO9CFWRPAQQRXTPACOWCPRLYRYSJARRKSQPR9TCFXDVIXLP9XVL99ERRDSOHBFJDJQQGGGCZNDQ9NYCTQJWVZIAELCRBJJFDMCNZU9FIZRPGNURTXOCDSQGXTQHKHUECGWFUUYS9J9NYQ9U9P9UUP9YMZHWWWCIASCFLCMSKTELZWUGCDE9YOKVOVKTAYPHDF9ZCCQAYPJIJNGSHUIHHCOSSOOBUDOKE9CJZGYSSGNCQJVBEFTZFJ9SQUHOASKRRGBSHWKBCBWBTJHOGQ9WOMQFHWJVEG9NYX9KWBTCAIXNXHEBDIOFO9ALYMFGRICLCKKLG9FOBOX9PDWNQRGHBKHGKKRLWTBEQMCWQRLHAVYYZDIIPKVQTHYTWQMTOACXZOQCDTJTBAAUWXSGJF9PNQIJ9AJRUMUVCPWYVYVARKR9RKGOUHHNKNVGGPDDLGKPQNOYHNKAVVKCXWXOQPZNSLATUJT9AUWRMPPSWHSTTYDFAQDXOCYTZHOYYGAIM9CELMZ9AZPWB9MJXGHOKDNNSZVUDAGXTJJSSZCPZVPZBYNNTUQABSXQWZCHDQSLGK9UOHCFKBIBNETK999999999999999999999999999999999999999999999999999999999999999999999999999999999NOXDXXKUDWLOFJLIPQIBRBMGDYCPGDNLQOLQS99EQYKBIU9VHCJVIPFUYCQDNY9APGEVYLCENJIOBLWNB999999999XKBRHUD99C99999999NKZKEKWLDKMJCI9N9XQOLWEPAYWSH9999999999999999999999999KDDTGZLIPBNZKMLTOLOXQVNGLASESDQVPTXALEKRMIOHQLUHD9ELQDBQETS9QFGTYOYWLNTSKKMVJAUXSIROUICDOXKSYZTDPEDKOQENTJOWJONDEWROCEJIEWFWLUAACVSJFTMCHHXJBJRKAAPUDXXVXFWP9X9999IROUICDOXKSYZTDPEDKOQENTJOWJONDEWROCEJIEWFWLUAACVSJFTMCHHXJBJRKAAPUDXXVXFWP9X9999"; + private static final String TEST_MILESTONE = "SMYMAKKPSUKCKDRUEYCGZJTYCZ9HHDMDUWBAPXARGURPQRHTAJDASRWMIDTPTBNDKDEFBUTBGGAFX9999"; + private static final Integer TEST_MILESTONE_INDEX = 8059; + private static final String TEST_MESSAGE = "JOTA"; + private static final String TEST_TAG = "JOTASPAM9999999999999999999"; + private static final String[] TEST_ADDRESSES = new String[]{"KHJXD9XKXPIVQRGREUIPVJTMEY9L9MXZAAKBBRYNINTIOXWBRMNLLW9MLGAXMGQWDBZLCOGFCBNKTDLDC" + , "MQSAAEDPMKIAJPPGNLIHPQIVFGLHNEGG9JNMSHOQSVQHBQBMLNHY9WVRTCOYUOWOIJHBQXIQVFPDF9YRW" + , "RGYOHMECRNVPYYPIAKEWHSOLBYOQPRFRPOJGHUMEGLICCUIPTZEXLWDLLPBNRXONUTQGLSAJSLHRXFVQD" + , "FOJHXRVJRFMJTFDUWJYYZXCZIJXKQALLXMLKHZFDMHWTIBBXUKSNSUYJLKYRQBNXKRSUXZHDTPWXYD9YF" + , "B9YNPQO9EXID9RDEEGLCBJBYKBLWHTOQOZKTLJDFPJZOPKJJTNUYUVVTDJPBCBYIWGPSCMNRZFGFHFSXH" + , "NQEFOAFIYKZOUXDFQ9X9PHCNSDETRTJZINZ9EYGKU99QJLDSTSC9VTBAA9FHLNLNYQXWLTNPRJDWCGIPP" + , "CEGLBSXDJVXGKGOUHRGMAQDRVYXCQLXBKUDWKFFSIABCUYRATFPTEEDIFYGAASKFZYREHLBIXBTKP9KLC" + , "QLOXU9GIQXPPE9UUT9DSIDSIESRIXMTGZJMKLSJTNBCRELAVLWVJLUOLKGFCWAEPEQWZWPBV9YZJJEHUS" + , "XIRMYJSGQXMM9YPHJVVLAVGBBLEEMOOKHHBFWKEAXJFONZLNSLBCGPQEVDMMOGHFVRDSYTETIFOIVNCR9" + , "PDVVBYBXMHZKADPAYOKQNDPHRSWTHAWQ9GRVIBOIMZQTYCWEPCDWDVRSOUNASVBDLBOAMVLYEVVCMAM9N" + , "U9GAIAPUUQWJGISAZWPLHUELTZ9WSHWXS9JLPKOWHRRIVUKGWCTJMBULVMKTETTUNHZ9HWHBALUCJIROU" + , "VFPMKZLLMDUOEKNBEKQZPTNZJZF9UHRWSTHXLWQQ9OAXTZQHTZPAWNJNXKAZFSDFWKFQEKZIGJTLWQFLO" + , "IGHK9XIWOAYBZUEZHQLEXBPTXSWVANIOUZZCPNKUIJIJOJNAQCJWUJHYKCZOIKVAAHDGAWJZKLTPVQL9G" + , "LXQPWMNXSUZTEYNC9ZBBFHY9YWCCOVKBNIIOUSVXZJZMJKJFDUWGUVXYCHGKUHEEIDHSGEWFAHVJPRIJT" + , "AKFDX9PGGQLZUWRMZ9YBDF9CG9TWXCNALCSXSAWHFIMGXCSYCJLSWIQDGGVDRMNEKKECQEYAITGNLNJFQ" + , "YX9QSPYMSFVOW9UVZRDVOCPYYMUTDHCCPKHMXQSJQJYIXVCHILKW9GBYJTYGLIKBTRQMDCYBMLLNGSSIK" + , "DSYCJKNG9TAGJHSKZQ9XLKAKNSKJFZIPVEDGJFXRTFGENHZFQGXHWDBNXLLDABDMOYELPG9DIXSNJFWAR" + , "9ANNACZYLDDPZILLQBQG9YMG9XJUMTAENDFQ9HMSSEFWYOAXPJTUXBFTSAXDJPAO9FKTWBBSCSFMOUR9I" + , "WDTFFXHBHMFQQVXQLBFJFVVHVIIAVYM9PFAZCHMKET9ESMHIRHSMVDJBZTXPTAFVIASMSXRDCIYVWVQNO" + , "XCCPS9GMTSUB9DXPVKLTBDHOFX9PJMBYZQYQEXMRQDPGQPLWRGZGXODYJKGVFOHHYUJRCSXAIDGYSAWRB" + , "KVEBCGMEOPDPRCQBPIEMZTTXYBURGZVNH9PLHKPMM9D9FUKWIGLKZROGNSYIFHULLWQWXCNAW9HKKVIDC"}; + private static Gson gson = new GsonBuilder().create(); + private IotaAPI iotaClient; + + @Before + public void createApiClientInstance() { + iotaClient = new IotaAPI.Builder().build(); + } + + @Test + public void shouldCreateIotaApiProxyInstanceWithDefaultValues() { + IotaAPI proxy = new IotaAPI.Builder().build(); + assertThat(proxy, IsNull.notNullValue()); + } + + + @Test + public void shouldGetInputs() { + GetBalancesAndFormatResponse res = iotaClient.getInputs(TEST_SEED1, 0, 0, 0); + System.out.println(res); + assertThat(res, IsNull.notNullValue()); + assertThat(res.getTotalBalance(), IsNull.notNullValue()); + assertThat(res.getInput(), IsNull.notNullValue()); + + } + + + @Test + public void shouldCreateANewAddress() { + final GetNewAddressResponse res = iotaClient.getNewAddress(TEST_SEED1, 0, false, 100, false); + assertThat(res.getAddresses().get(0), Is.is(TEST_ADDRESS_WITHOUT_CHECKSUM)); + System.out.println(new Gson().toJson(res)); + } + + @Test + public void shouldPrepareTransfer() { + List transfers = new ArrayList<>(); + transfers.add(new jota.model.Transfer(TEST_ADDRESS_WITH_CHECKSUM, 0, TEST_MESSAGE, TEST_TAG)); + transfers.add(new jota.model.Transfer(TEST_ADDRESS_WITH_CHECKSUM, 1, TEST_MESSAGE, TEST_TAG)); + List trytes = null; + try { + trytes = iotaClient.prepareTransfers(TEST_SEED1, transfers, null, null); + } catch (NotEnoughBalanceException e) { + e.printStackTrace(); + } + Assert.assertNotNull(trytes); + assertThat(trytes.isEmpty(), Is.is(false)); + } + + @Test + public void shouldGetLastInclusionState() throws NoNodeInfoException { + GetInclusionStateResponse res = iotaClient.getLatestInclusion(new String[]{TEST_HASH}); + assertThat(res.getStates(), IsNull.notNullValue()); + } + + @Test + public void shouldFindTransactionObjects() { + List ftr = iotaClient.findTransactionObjects(TEST_ADDRESSES); + assertThat(ftr, IsNull.notNullValue()); + } + + @Test + public void shouldGetBundle() throws InvalidBundleException, ArgumentException, InvalidSignatureException { + GetBundleResponse gbr = iotaClient.getBundle(TEST_HASH); + assertThat(gbr, IsNull.notNullValue()); + } + + @Test + public void shouldGetTransfers() throws InvalidBundleException, ArgumentException, InvalidSignatureException, NoInclusionStatesExcpection, NoNodeInfoException { + GetTransferResponse gtr = iotaClient.getTransfers(TEST_SEED1, 0, 0, false); + assertThat(gtr.getTransfers(), IsNull.notNullValue()); + + for (Bundle test : gtr.getTransfers()) { + for (Transaction trx : test.getTransactions()) { + System.out.println(new Gson().toJson(trx)); + } + } + } + + @Ignore + @Test + public void shouldSendTrytes() { + iotaClient.sendTrytes(new String[]{TEST_TRYTES}, 9, 18); + } + + @Test(expected = IllegalStateException.class) + public void shouldNotSendTransfer() throws ArgumentException, InvalidSignatureException, InvalidBundleException, NotEnoughBalanceException { + List transfers = new ArrayList<>(); + transfers.add(new jota.model.Transfer(TEST_ADDRESS_WITHOUT_CHECKSUM, 10000990, "JUSTANOTHERTEST", TEST_TAG)); + SendTransferResponse str = iotaClient.sendTransfer(TEST_SEED2, 9, 18, transfers, null, null); + assertThat(str.getSuccessfully(), IsNull.notNullValue()); + } + + @Ignore + @Test + public void shouldSendTransfer() throws ArgumentException, InvalidSignatureException, InvalidBundleException, NotEnoughBalanceException { + List transfers = new ArrayList<>(); + transfers.add(new jota.model.Transfer(TEST_ADDRESS_WITHOUT_CHECKSUM, 0, "JUSTANOTHERTEST", TEST_TAG)); + SendTransferResponse str = iotaClient.sendTransfer(TEST_SEED2, 9, 18, transfers, null, null); + assertThat(str.getSuccessfully(), IsNull.notNullValue()); + } +} \ No newline at end of file diff --git a/src/test/java/jota/IotaCoreApiTest.java b/src/test/java/jota/IotaCoreApiTest.java new file mode 100644 index 0000000..8913816 --- /dev/null +++ b/src/test/java/jota/IotaCoreApiTest.java @@ -0,0 +1,138 @@ +package jota; + +import com.google.gson.Gson; +import jota.dto.response.*; +import org.hamcrest.core.IsNull; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertThat; + +/** + * Created by Adrian on 15.01.2017. + */ +public class IotaCoreApiTest { + + private static final String TEST_BUNDLE = "XZKJUUMQOYUQFKMWQZNTFMSS9FKJLOEV9DXXXWPMQRTNCOUSUQNTBIJTVORLOQPLYZOTMLFRHYKMTGZZU"; + private static final String TEST_ADDRESS_WITH_CHECKSUM = "PNGMCSNRCTRHCHPXYTPKEJYPCOWKOMRXZFHH9N9VDIKMNVAZCMIYRHVJIAZARZTUETJVFDMBEBIQE9QTHBFWDAOEFA"; + private static final String TEST_HASH = "OAATQS9VQLSXCLDJVJJVYUGONXAXOFMJOZNSYWRZSWECMXAQQURHQBJNLD9IOFEPGZEPEMPXCIVRX9999"; + private static IotaAPICore proxy; + + @Before + public void createProxyInstance() { + proxy = new IotaAPICore.Builder().build(); + } + + @Test + public void shouldGetNodeInfo() { + GetNodeInfoResponse nodeInfo = proxy.getNodeInfo(); + System.out.println(new Gson().toJson(nodeInfo)); + assertThat(nodeInfo.getAppVersion(), IsNull.notNullValue()); + assertThat(nodeInfo.getAppName(), IsNull.notNullValue()); + assertThat(nodeInfo.getJreVersion(), IsNull.notNullValue()); + assertThat(nodeInfo.getJreAvailableProcessors(), IsNull.notNullValue()); + assertThat(nodeInfo.getJreFreeMemory(), IsNull.notNullValue()); + assertThat(nodeInfo.getJreMaxMemory(), IsNull.notNullValue()); + assertThat(nodeInfo.getJreTotalMemory(), IsNull.notNullValue()); + assertThat(nodeInfo.getLatestMilestone(), IsNull.notNullValue()); + assertThat(nodeInfo.getLatestMilestoneIndex(), IsNull.notNullValue()); + assertThat(nodeInfo.getLatestSolidSubtangleMilestone(), IsNull.notNullValue()); + assertThat(nodeInfo.getLatestSolidSubtangleMilestoneIndex(), IsNull.notNullValue()); + assertThat(nodeInfo.getNeighbors(), IsNull.notNullValue()); + assertThat(nodeInfo.getPacketsQueueSize(), IsNull.notNullValue()); + assertThat(nodeInfo.getTime(), IsNull.notNullValue()); + assertThat(nodeInfo.getTips(), IsNull.notNullValue()); + assertThat(nodeInfo.getTransactionsToRequest(), IsNull.notNullValue()); + } + + @Test + public void shouldGetNeighbors() { + GetNeighborsResponse neighbors = proxy.getNeighbors(); + assertThat(neighbors.getNeighbors(), IsNull.notNullValue()); + } + + @Test + public void shouldAddNeighbors() { + AddNeighborsResponse res = proxy.addNeighbors("udp://8.8.8.8:14265"); + assertThat(res, IsNull.notNullValue()); + } + + @Test + public void shouldRemoveNeighbors() { + RemoveNeighborsResponse res = proxy.removeNeighbors("udp://8.8.8.8:14265"); + assertThat(res, IsNull.notNullValue()); + } + + @Test + public void shouldGetTips() { + GetTipsResponse tips = proxy.getTips(); + assertThat(tips, IsNull.notNullValue()); + } + + @Test + public void shouldFindTransactionsByAddresses() { + FindTransactionResponse trans = proxy.findTransactionsByAddresses(TEST_ADDRESS_WITH_CHECKSUM); + assertThat(trans.getHashes(), IsNull.notNullValue()); + } + + @Test + public void shouldFindTransactionsByApprovees() { + FindTransactionResponse trans = proxy.findTransactionsByApprovees(new String[]{TEST_HASH}); + assertThat(trans.getHashes(), IsNull.notNullValue()); + } + + @Test + public void shouldFindTransactionsByBundles() { + FindTransactionResponse trans = proxy.findTransactionsByBundles(TEST_HASH); + assertThat(trans.getHashes(), IsNull.notNullValue()); + } + + @Test + public void shouldFindTransactionsByDigests() { + FindTransactionResponse trans = proxy.findTransactionsByDigests(TEST_HASH); + assertThat(trans.getHashes(), IsNull.notNullValue()); + } + + @Test + public void shouldGetTrytes() { + GetTrytesResponse res = proxy.getTrytes(TEST_HASH); + assertThat(res.getTrytes(), IsNull.notNullValue()); + + } + + @Test + public void shouldGetInclusionStates() { + GetInclusionStateResponse res = proxy.getInclusionStates(new String[]{"DBPECSH9YLSSTQDGERUHJBBJTKVUDBMTJLG9WPHBINGHIFOSJMDJLARTVOXXWEFQJLLBINOHCZGYFSMUEXWPPMTOFW"}, new String[]{"EJDQOQHMLJGBMFWB9WJSPRCYIGNPO9WRHDCEQXIMPVPIJ9JV9RJGVHNX9EPGXFOOKBABCVMMAAX999999"}); + assertThat(res.getStates(), IsNull.notNullValue()); + } + + @Test(expected = IllegalAccessError.class) + public void shouldNotGetInclusionStates() { + GetInclusionStateResponse res = proxy.getInclusionStates(new String[]{TEST_ADDRESS_WITH_CHECKSUM}, new String[]{"DNSBRJWNOVUCQPILOQIFDKBFJMVOTGHLIMLLRXOHFTJZGRHJUEDAOWXQRYGDI9KHYFGYDWQJZKX999999"}); + assertThat(res.getStates(), IsNull.notNullValue()); + } + + @Test // very long execution + public void shouldGetTransactionsToApprove() { + GetTransactionsToApproveResponse res = proxy.getTransactionsToApprove(27); + assertThat(res.getTrunkTransaction(), IsNull.notNullValue()); + assertThat(res.getBranchTransaction(), IsNull.notNullValue()); + } + + @Test + public void shouldFindTransactions() { + String test = TEST_BUNDLE; + FindTransactionResponse resp = proxy.findTransactions(new String[]{test}, new String[]{test}, new String[]{test}, new String[]{test}); + System.out.println(new Gson().toJson(resp)); + } + + @Test + public void shouldGetBalances() { + GetBalancesResponse res = proxy.getBalances(100, new String[]{TEST_ADDRESS_WITH_CHECKSUM}); + System.err.println(res); + assertThat(res.getBalances(), IsNull.notNullValue()); + assertThat(res.getMilestone(), IsNull.notNullValue()); + assertThat(res.getMilestoneIndex(), IsNull.notNullValue()); + + } +} diff --git a/src/test/java/jota/IotaUnitConverterTest.java b/src/test/java/jota/IotaUnitConverterTest.java index 81da852..624555a 100644 --- a/src/test/java/jota/IotaUnitConverterTest.java +++ b/src/test/java/jota/IotaUnitConverterTest.java @@ -48,11 +48,11 @@ public class IotaUnitConverterTest { @Test public void shouldConvertRawIotaAmountToDisplayText() { - assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1), "1 i"); - assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1000), "1 Ki"); - assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1000000), "1 Mi" ); - assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1000000000), "1 Gi" ); - assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1000000000000L), "1 Ti"); - assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1000000000000000L), "1 Pi"); + assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1, false), "1 i"); + assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1000, false), "1 Ki"); + assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1000000, false), "1 Mi"); + assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1000000000, false), "1 Gi"); + assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1000000000000L, false), "1 Ti"); + assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1000000000000000L, false), "1 Pi"); } } diff --git a/src/test/java/jota/SendMessageTest.java b/src/test/java/jota/SendMessageTest.java index 9a62c04..19d7d67 100644 --- a/src/test/java/jota/SendMessageTest.java +++ b/src/test/java/jota/SendMessageTest.java @@ -3,10 +3,10 @@ package jota; import org.junit.Test; public class SendMessageTest { - - @Test - public void sendMessage() { - - } + + @Test + public void sendMessage() { + + } } diff --git a/src/test/java/jota/TrytesConverterTest.java b/src/test/java/jota/TrytesConverterTest.java index 28b54a6..f6403e5 100644 --- a/src/test/java/jota/TrytesConverterTest.java +++ b/src/test/java/jota/TrytesConverterTest.java @@ -1,12 +1,12 @@ package jota; import jota.utils.TrytesConverter; +import org.apache.commons.lang3.RandomStringUtils; import org.junit.Test; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import org.apache.commons.lang3.RandomStringUtils; - /** * Created by pinpong on 01.12.16. */ @@ -15,19 +15,21 @@ public class TrytesConverterTest { @Test public void shouldConvertStringToTrytes() { assertEquals(TrytesConverter.toTrytes("Z"), "IC"); + assertEquals(TrytesConverter.toTrytes("JOTA JOTA"), "TBYBCCKBEATBYBCCKB"); } + @Test public void shouldConvertTrytesToString() { assertEquals(TrytesConverter.toString("IC"), "Z"); + assertEquals(TrytesConverter.toString("TBYBCCKBEATBYBCCKB"), "JOTA JOTA"); } - + @Test public void shouldConvertBackAndForth() { String str = RandomStringUtils.randomAlphabetic(1000).toUpperCase(); System.err.println(str); String back = TrytesConverter.toString(TrytesConverter.toTrytes(str)); - + assertTrue(str.equals(back)); } - -} +} \ No newline at end of file