added getBundle, traverseBundle, bundleFromAddresses - pls test & verify

This commit is contained in:
AZ
2016-12-27 19:23:36 +01:00
parent 1951ce76f8
commit 11bb41a889
8 changed files with 397 additions and 121 deletions
+301 -95
View File
@@ -2,13 +2,15 @@ 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.error.ArgumentException;
import jota.error.InvalidBundleException;
import jota.error.InvalidSignatureException;
import jota.model.*;
import jota.pow.Curl;
import jota.utils.Converter;
import jota.utils.InputValidator;
import jota.utils.IotaAPIUtils;
import jota.utils.Signing;
import okhttp3.OkHttpClient;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
@@ -26,13 +28,13 @@ import java.util.concurrent.TimeUnit;
/**
* IotaAPIProxy Builder. Usage:
*
* <p>
* IotaApiProxy api = IotaApiProxy.Builder
* .protocol("http")
* .nodeAddress("localhost")
* .port(12345)
* .build();
*
* <p>
* GetNodeInfoResponse response = api.getNodeInfo();
*
* @author davassi
@@ -79,8 +81,8 @@ public class IotaAPIProxy {
final String nodeUrl = protocol + "://" + host + ":" + port;
final OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(120, TimeUnit.SECONDS)
.connectTimeout(120, TimeUnit.SECONDS)
.readTimeout(5000, TimeUnit.SECONDS)
.connectTimeout(5000, TimeUnit.SECONDS)
.build();
final Retrofit retrofit = new Retrofit.Builder()
@@ -174,9 +176,9 @@ public class IotaAPIProxy {
final Call<GetBalancesResponse> res = service.getBalances(IotaGetBalancesRequest.createIotaGetBalancesRequest(threshold, addresses));
return wrapCheckedException(res).body();
}
public GetBalancesResponse getBalances(Integer threshold, List<String> addresses) {
return getBalances(threshold, addresses.toArray(new String[] {}));
return getBalances(threshold, addresses.toArray(new String[]{}));
}
public InterruptAttachingToTangleResponse interruptAttachingToTangle() {
@@ -215,7 +217,7 @@ public class IotaAPIProxy {
public GetNewAddressResponse getNewAddress(final String seed, final int index, final boolean checksum, final int total, final boolean returnAll) {
final List<String> allAddresses = new ArrayList<>();
// If total number of addresses to generate is supplied, simply generate
// and return the list of all addresses
if (total != 0) {
@@ -227,10 +229,10 @@ public class IotaAPIProxy {
// 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 String newAddress = IotaAPIUtils.newAddress(seed, i, checksum);
final FindTransactionResponse response = findTransactionsByAddresses(new String[]{newAddress});
allAddresses.add(newAddress);
if (response.getHashes().length == 0) {
break;
@@ -239,9 +241,9 @@ public class IotaAPIProxy {
// If !returnAll return only the last address that was generated
if (!returnAll) {
allAddresses.subList(0, allAddresses.size()-1).clear();
allAddresses.subList(0, allAddresses.size() - 1).clear();
}
return GetNewAddressResponse.create(allAddresses);
return GetNewAddressResponse.create(allAddresses);
}
/*
@@ -263,14 +265,101 @@ public class IotaAPIProxy {
broadcastBundle
getAccountData
*/
/**
* @method getTransfers
* @param {string} seed
* @param {object} options
* @property {int} start Starting key index
* @property {int} end Ending key index
* @property {bool} inclusionStates returns confirmation status of all transactions
* @param {function} callback
* @returns {object} success
**/
public Bundle[] getTransfers(String seed, Integer start, Integer end, Boolean inclusionStates) throws ArgumentException, InvalidBundleException, InvalidSignatureException {
start = start != null ? 0 : start;
end = end == null ? null : end;
inclusionStates = inclusionStates != null ? inclusionStates : null;
if (start > end || end > (start + 500)) {
throw new ArgumentException();
}
GetNewAddressResponse gnr = getNewAddress(seed, start, false, end == null ? end - start : end, true);
if (gnr != null && gnr.getAddresses() != null) {
return bundlesFromAddresses(gnr.getAddresses().toArray(new String[gnr.getAddresses().size()]), inclusionStates);
}
return null;
}
public Bundle[] bundlesFromAddresses(String[] addresses, Boolean inclusionStates) throws ArgumentException, InvalidBundleException, InvalidSignatureException{
List<Transaction> trxs = findTransactionObjects(addresses);
// set of tail transactions
List<String> tailTransactions = new ArrayList<>();
List<String> nonTailBundleHashes = new ArrayList<>();
for (Transaction trx : trxs) {
// Sort tail and nonTails
if (Long.parseLong(trx.getCurrentIndex()) == 0) {
tailTransactions.add(trx.getHash());
} else {
nonTailBundleHashes.add(trx.getBundle());
}
}
if (nonTailBundleHashes.isEmpty()) return null;
List<Transaction> bundleObjects = findTransactionObjects(addresses);
for (Transaction trx : bundleObjects) {
// Sort tail and nonTails
if (Long.parseLong(trx.getCurrentIndex()) == 0) {
tailTransactions.add(trx.getHash());
}
}
List<Bundle> finalBundles = new ArrayList<>();
String[] tailTxArray = tailTransactions.toArray(new String[tailTransactions.size()]);
// If inclusionStates, get the confirmation status
// of the tail transactions, and thus the bundles
if (inclusionStates) {
GetInclusionStateResponse gisr = getLatestInclusion(tailTxArray);
if (gisr == null || gisr.getStates() == null || gisr.getStates().length == 0) return null;
for (String trx : tailTxArray) {
Bundle gbr = getBundle(trx);
if (gbr != null && gbr.getTransactions() != null) {
if (inclusionStates) {
boolean thisInclusion = gisr.getStates()[Arrays.asList(tailTxArray).indexOf(trx)];
for (Transaction t : gbr.getTransactions()) {
t.setPersistence(thisInclusion);
}
}
finalBundles.add(gbr);
}
}
}
Collections.sort(finalBundles, new Comparator<Bundle>() {
public int compare(Bundle c1, Bundle c2) {
if (Long.parseLong(c1.getTransactions().get(0).getTimestamp()) > Long.parseLong(c2.getTransactions().get(0).getTimestamp()))
return -1;
if (Long.parseLong(c1.getTransactions().get(0).getTimestamp()) < Long.parseLong(c2.getTransactions().get(0).getTimestamp()))
return 1;
return 0;
}
});
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) {
public StoreTransactionsResponse broadcastAndStore(final String... trytes) {
try {
broadcastTransactions(trytes);
} catch (Exception e) {
@@ -279,30 +368,31 @@ public class IotaAPIProxy {
}
return storeTransactions(trytes);
}
/**
* Facade method: Gets transactions to approve, attaches to Tangle, broadcasts and stores
*
* @param {array} trytes
* @param {int} depth
* @param {int} minWeightMagnitude
* @param {int} depth
* @param {int} minWeightMagnitude
* @return
*/
public List<Transaction> 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<Transaction> trx = new ArrayList<>();
for (final String tx : Arrays.asList(res.getTrytes())) {
trx.add(Converter.transactionObject(tx));
}
@@ -310,15 +400,15 @@ public class IotaAPIProxy {
}
/**
* 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
**/
* 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<Transaction> getTransactionsObjects(String[] hashes) {
if (!InputValidator.isArrayOfHashes(hashes)) {
@@ -326,9 +416,9 @@ public class IotaAPIProxy {
}
final GetTrytesResponse trytesResponse = getTrytes(hashes);
final List<Transaction> trxs = new ArrayList<>();
for (final String tryte : trytesResponse.getTrytes()) {
trxs.add(Converter.transactionObject(tryte));
}
@@ -356,18 +446,18 @@ public class IotaAPIProxy {
}
/**
* 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
**/
* 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<String> prepareTransfers(final String seed, final List<Transfer> transfers, String remainder, List<Input> inputs) {
// Input validation of transfers object
@@ -378,7 +468,7 @@ public class IotaAPIProxy {
// Create a new bundle
final Bundle bundle = new Bundle();
final List<String> signatureFragments = new ArrayList<>();
int totalValue = 0;
String tag;
@@ -398,7 +488,7 @@ public class IotaAPIProxy {
// 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());
@@ -454,7 +544,8 @@ public class IotaAPIProxy {
String[] balances = resbalances.getBalances();
List<Input> confirmedInputs = new ArrayList<>();
int totalBalance = 0; int i = 0;
int totalBalance = 0;
int i = 0;
for (String balance : balances) {
long thisBalance = Integer.parseInt(balance);
totalBalance += thisBalance;
@@ -495,23 +586,23 @@ public class IotaAPIProxy {
List<String> bundleTrytes = new ArrayList<>();
for (Transaction tx : trxb) {
bundleTrytes.add(jota.utils.IotaAPIUtils.transactionTrytes(tx));
bundleTrytes.add(Converter.transactionTrytes(tx));
}
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
**/
* 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(final String seed, final List<String> balances, int start, int end, int threshold) {
// validate the seed
@@ -564,18 +655,19 @@ public class IotaAPIProxy {
// If threshold defined, keep track of whether reached or not
// else set default to true
boolean thresholdReached = threshold != 0 ? false : true; int i = -1;
boolean thresholdReached = threshold != 0 ? false : true;
int i = -1;
List<Input> 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);
final Input newEntry = new Input(address, balance, start + i);
inputs.add(newEntry);
// Increase totalBalance of all aggregated inputs
totalBalance += balance;
@@ -589,53 +681,121 @@ public class IotaAPIProxy {
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
* 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
* @param {string} transaction Hash of a tail transaction
* @method getBundle
* @returns {list} bundle Transaction objects
**/
public GetBundleResponse getBundle(String transaction) {
return null; //IotaAPIUtils.getBundle(transaction);
public Bundle getBundle(String transaction) throws ArgumentException, InvalidBundleException, InvalidSignatureException {
Bundle bundle = traverseBundle(transaction, null, null);
if (bundle == null) {
return null;
}
long totalSum = 0;
int lastIndex = 0;
String bundleHash = bundle.getTransactions().get(0).getBundle();
Curl curl = new Curl();
curl.reset();
List<Signature> 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 = Converter.transactionTrytes(trx).substring(2187, 2187 + 162);
// 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; i < bundle.getTransactions().size() - 1; i++) {
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) {
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 = curl.squeeze(new int[243]);
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
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 (int i = 0; i < signaturesToValidate.size(); i++) {
boolean isValidSignature = Signing.validateSignatures(signaturesToValidate.get(i).getAddress(), signaturesToValidate.get(i).getSignatureFragments().toArray(new String[signaturesToValidate.size()]), bundleHash);
if (!isValidSignature) throw new InvalidSignatureException();
}
return bundle;
}
/**
* Replays a transfer by doing Proof of Work again
* 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
* @param {string} tail
* @param {int} depth
* @param {int} minWeightMagnitude
* @param {function} callback
* @method replayBundle
* @returns {object} analyzed Transaction objects
**/
public List<Transaction> replayTransfer(String transaction, int depth, int minWeightMagnitude) {
public List<Transaction> replayTransfer(String transaction, int depth, int minWeightMagnitude) throws InvalidBundleException, InvalidSignatureException, ArgumentException {
List<String> bundleTrytes = new ArrayList<>();
GetBundleResponse bundle = getBundle(transaction);
Bundle bundle = getBundle(transaction);
for (Transaction element : bundle.getTransactions()) {
bundleTrytes.add(IotaAPIUtils.transactionTrytes(element));
bundleTrytes.add(Converter.transactionTrytes(element));
}
return sendTrytes(bundleTrytes.toArray(new String[bundleTrytes.size()]), minWeightMagnitude);
}
/**
* Wrapper function for getNodeInfo and getInclusionStates
* Wrapper function for getNodeInfo and getInclusionStates
*
* @method getLatestInclusion
* @param {array} hashes
* @returns {function} callback
* @returns {array} state
* @param {array} hashes
* @method getLatestInclusion
* @returns {function} callback
* @returns {array} state
**/
public GetInclusionStateResponse getLatestInclusion(String[] hashes) {
GetNodeInfoResponse getNodeInfoResponse = getNodeInfo();
@@ -648,11 +808,57 @@ public class IotaAPIProxy {
public Transaction[] sendTransfer(String seed, int depth, int minWeightMagnitude, Transfer[] transactions, Input[] inputs, String address) {
List<String> trytes = prepareTransfers(seed, Arrays.asList(transactions), address, Arrays.asList(inputs));
List<String> trytes = prepareTransfers(seed, Arrays.asList(transactions), address, inputs == null ? null : Arrays.asList(inputs));
List<Transaction> trxs = sendTrytes(trytes.toArray(new String[trytes.size()]), minWeightMagnitude);
return trxs.toArray(new Transaction[trxs.size()]);
}
/**
* 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 & gtr.getTrytes().length != 0) {
Transaction trx = Converter.transactionObject(gtr.getTrytes()[0]);
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 != trx.getBundle()) {
return bundle;
}
// If only one bundle element, return
if (Integer.parseInt(trx.getLastIndex()) == 0 && Integer.parseInt(trx.getCurrentIndex()) == 0) {
return new Bundle(Arrays.asList(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 {
return null;
}
}
public static class Builder {
String protocol, host, port;
@@ -7,6 +7,14 @@ import java.util.List;
public class GetBundleResponse extends AbstractResponse {
public GetBundleResponse(){
}
public GetBundleResponse(List<Transaction> trxs){
this.transactions = trxs;
}
private List<Transaction> transactions = new ArrayList<>();
public List<Transaction> getTransactions() {
@@ -10,4 +10,8 @@ public class ArgumentException extends BaseException {
public ArgumentException() {
super("Wrong arguments passed to function");
}
public ArgumentException(String msg) {
super(msg);
}
}
@@ -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);
}
}
@@ -0,0 +1,10 @@
package jota.error;
/**
* Created by Adrian on 27.12.2016.
*/
public class InvalidSignatureException extends BaseException {
public InvalidSignatureException() {
super("Invalid Signatures!");
}
}
+33
View File
@@ -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<String> signatureFragments;
public Signature() {
this.signatureFragments = new ArrayList<>();
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public List<String> getSignatureFragments() {
return signatureFragments;
}
public void setSignatureFragments(List<String> signatureFragments) {
this.signatureFragments = signatureFragments;
}
}
+25
View File
@@ -206,6 +206,31 @@ public class Converter {
}
}
}
public static String transactionTrytes(Transaction trx) {
int[] valueTrits = Converter.trits(trx.getValue(), 81);
int[] timestampTrits = Converter.trits(trx.getTimestamp(), 27);
int[] currentIndexTrits = Converter.trits(trx.getTimestamp(), 27);
int[] lastIndexTrits = Converter.trits(trx.getCurrentIndex(), 27);
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 Transaction transactionObject(final String trytes) {
+1 -26
View File
@@ -43,31 +43,6 @@ public class IotaAPIUtils {
return address;
}
public static String transactionTrytes(Transaction trx) {
int[] valueTrits = Converter.trits(trx.getValue(), 81);
int[] timestampTrits = Converter.trits(trx.getTimestamp(), 27);
int[] currentIndexTrits = Converter.trits(trx.getTimestamp(), 27);
int[] lastIndexTrits = Converter.trits(trx.getCurrentIndex(), 27);
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<String> signInputsAndReturn(final String seed,
final List<Input> inputs,
final Bundle bundle,
@@ -138,7 +113,7 @@ public class IotaAPIUtils {
// Convert all bundle entries into trytes
for (Transaction tx : bundle.getTransactions()) {
bundleTrytes.add(IotaAPIUtils.transactionTrytes(tx));
bundleTrytes.add(Converter.transactionTrytes(tx));
}
Collections.reverse(bundleTrytes);
return bundleTrytes;