diff --git a/src/main/java/com/luminiasoft/bitshares/AccountOptions.java b/src/main/java/com/luminiasoft/bitshares/AccountOptions.java new file mode 100644 index 0000000..972a133 --- /dev/null +++ b/src/main/java/com/luminiasoft/bitshares/AccountOptions.java @@ -0,0 +1,134 @@ +package com.luminiasoft.bitshares; + +import com.google.common.primitives.Bytes; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.luminiasoft.bitshares.interfaces.GrapheneSerializable; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by nelson on 12/5/16. + */ +public class AccountOptions implements GrapheneSerializable { + public static final String KEY_MEMO_KEY = "memo_key"; + public static final String KEY_NUM_COMMITTEE = "num_committee"; + public static final String KEY_NUM_WITNESS = "num_witness"; + public static final String KEY_VOTES = "votes"; + public static final String KEY_VOTING_ACCOUNT = "voting_account"; + public static final String KEY_EXTENSIONS = Extensions.KEY_EXTENSIONS; + + private PublicKey memo_key; + private UserAccount voting_account; + private int num_witness; + private int num_comittee; + private Vote[] votes; + private Extensions extensions; + + public AccountOptions(){ + voting_account = new UserAccount(UserAccount.PROXY_TO_SELF); + this.votes = new Vote[0]; + this.extensions = new Extensions(); + } + + public AccountOptions(PublicKey memoKey){ + this(); + this.memo_key = memoKey; + } + + public PublicKey getMemoKey() { + return memo_key; + } + + public void setMemoKey(PublicKey memo_key) { + this.memo_key = memo_key; + } + + public UserAccount getVotingAccount() { + return voting_account; + } + + public void setVotingAccount(UserAccount voting_account) { + this.voting_account = voting_account; + } + + public int getNumWitness() { + return num_witness; + } + + public void setNumWitness(int num_witness) { + this.num_witness = num_witness; + } + + public int getNumComittee() { + return num_comittee; + } + + public void setNum_comittee(int num_comittee) { + this.num_comittee = num_comittee; + } + + public Vote[] getVotes() { + return votes; + } + + public void setVotes(Vote[] votes) { + this.votes = votes; + } + + @Override + public byte[] toBytes() { + List byteArray = new ArrayList(); + + if(memo_key != null){ + // Adding byte to indicate that there is memo data + byteArray.add((byte) 1); + + // Adding memo key + byteArray.addAll(Bytes.asList(memo_key.toBytes())); + + // Adding voting account + byteArray.addAll(Bytes.asList(voting_account.toBytes())); + + // Adding num_witness + byteArray.addAll(Bytes.asList(Util.revertShort(Short.valueOf((short) num_witness)))); + + // Adding num_committee + byteArray.addAll(Bytes.asList(Util.revertShort(Short.valueOf((short) num_comittee)))); + + // Vote's array length + byteArray.add((byte) votes.length); + + for(Vote vote : votes){ + //TODO: Check this serialization + byteArray.addAll(Bytes.asList(vote.toBytes())); + } + }else{ + byteArray.add((byte) 0); + } + return Bytes.toArray(byteArray); + } + + @Override + public String toJsonString() { + return null; + } + + @Override + public JsonElement toJsonObject() { + JsonObject options = new JsonObject(); + options.addProperty(KEY_MEMO_KEY, new Address(memo_key.getKey()).toString()); + options.addProperty(KEY_NUM_COMMITTEE, num_comittee); + options.addProperty(KEY_NUM_WITNESS, num_witness); + options.addProperty(KEY_VOTING_ACCOUNT, voting_account.getObjectId()); + JsonArray votesArray = new JsonArray(); + for(Vote vote : votes){ + //TODO: Add votes representation + } + options.add(KEY_VOTES, votesArray); + options.add(KEY_EXTENSIONS, extensions.toJsonObject()); + return options; + } +} diff --git a/src/main/java/com/luminiasoft/bitshares/AccountUpdateOperation.java b/src/main/java/com/luminiasoft/bitshares/AccountUpdateOperation.java new file mode 100644 index 0000000..c716e1c --- /dev/null +++ b/src/main/java/com/luminiasoft/bitshares/AccountUpdateOperation.java @@ -0,0 +1,101 @@ +package com.luminiasoft.bitshares; + +import com.google.common.primitives.Bytes; +import com.google.common.primitives.UnsignedLong; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.sun.istack.internal.NotNull; +import com.sun.istack.internal.Nullable; + +/** + * Class used to encapsulate operations related to the account_update_operation. + */ +public class AccountUpdateOperation extends BaseOperation { + public static final String KEY_ACCOUNT = "account"; + public static final String KEY_OWNER = "owner"; + public static final String KEY_ACTIVE = "active"; + public static final String KEY_FEE = "fee"; + public static final String KEY_NEW_OPTIONS = "new_options"; + public static final String KEY_EXTENSIONS = "extensions"; + + private AssetAmount fee; + private UserAccount account; + private Optional owner; + private Optional active; + private Optional new_options; + private Extensions extensions; + + /** + * Account update operation constructor. + * @param account User account to update. Can't be null. + * @param owner Owner authority to set. Can be null. + * @param active Active authority to set. Can be null. + * @param options Active authority to set. Can be null. + * @param fee The fee to pay. Can be null. + */ + public AccountUpdateOperation(UserAccount account, Authority owner, Authority active, AccountOptions options, AssetAmount fee){ + super(OperationType.account_update_operation); + this.fee = fee; + this.account = account; + this.owner = new Optional<>(owner); + this.active = new Optional<>(active); + this.new_options = new Optional<>(options); + extensions = new Extensions(); + } + + public AccountUpdateOperation(UserAccount account, Authority owner, Authority active, AccountOptions options){ + this(account, owner, active, options, new AssetAmount(UnsignedLong.valueOf(0), new Asset("1.3.0"))); + } + + @Override + public void setFee(AssetAmount fee){ + this.fee = fee; + } + + public void setOwner(Authority owner){ + this.owner = new Optional<>(owner); + } + + public void setActive(Authority active){ + this.active = new Optional<>(active); + } + + public void setAccountOptions(AccountOptions options){ + this.new_options = new Optional<>(options); + } + + @Override + public String toJsonString() { + Gson gson = new Gson(); + return gson.toJson(this); + } + + @Override + public JsonElement toJsonObject() { + JsonArray array = new JsonArray(); + array.add(this.getId()); + + JsonObject accountUpdate = new JsonObject(); + accountUpdate.add(KEY_FEE, fee.toJsonObject()); + accountUpdate.addProperty(KEY_ACCOUNT, account.toJsonString()); + accountUpdate.add(KEY_OWNER, owner.toJsonObject()); + accountUpdate.add(KEY_ACTIVE, active.toJsonObject()); + accountUpdate.add(KEY_NEW_OPTIONS, new_options.toJsonObject()); + accountUpdate.add(KEY_EXTENSIONS, extensions.toJsonObject()); + array.add(accountUpdate); + return array; + } + + @Override + public byte[] toBytes() { + byte[] feeBytes = fee.toBytes(); + byte[] accountBytes = account.toBytes(); + byte[] ownerBytes = owner.toBytes(); + byte[] activeBytes = active.toBytes(); + byte[] newOptionsBytes = new_options.toBytes(); + byte[] extensionBytes = extensions.toBytes(); + return Bytes.concat(feeBytes, accountBytes, ownerBytes, activeBytes, newOptionsBytes, extensionBytes); + } +} diff --git a/src/main/java/com/luminiasoft/bitshares/AccountUpdateTransactionBuilder.java b/src/main/java/com/luminiasoft/bitshares/AccountUpdateTransactionBuilder.java new file mode 100644 index 0000000..435f8a3 --- /dev/null +++ b/src/main/java/com/luminiasoft/bitshares/AccountUpdateTransactionBuilder.java @@ -0,0 +1,66 @@ +package com.luminiasoft.bitshares; + +import com.luminiasoft.bitshares.errors.MalformedTransactionException; +import org.bitcoinj.core.ECKey; + +import java.util.ArrayList; +import java.util.List; + +/** + * Class used to build a transaction containing an account update operation. + */ +public class AccountUpdateTransactionBuilder extends TransactionBuilder { + private List operations; + private AssetAmount fee; + private UserAccount account; + private Authority owner; + private Authority active; + private AccountOptions new_options; + + public AccountUpdateTransactionBuilder(ECKey privKey) { + super(privKey); + } + + + public AccountUpdateTransactionBuilder setAccont(UserAccount account){ + this.account = account; + return this; + } + + public AccountUpdateTransactionBuilder setOwner(Authority owner){ + this.owner = owner; + return this; + } + + public AccountUpdateTransactionBuilder setActive(Authority active){ + this.active = active; + return this; + } + + public AccountUpdateTransactionBuilder setOptions(AccountOptions options){ + this.new_options = options; + return this; + } + + public AccountUpdateTransactionBuilder setFee(AssetAmount fee){ + this.fee = fee; + return this; + } + + @Override + public Transaction build() throws MalformedTransactionException { + if(account == null){ + throw new MalformedTransactionException("Missing required account information"); + }else{ + operations = new ArrayList<>(); + AccountUpdateOperation operation; + if(fee == null){ + operation = new AccountUpdateOperation(account, owner, active, new_options); + }else{ + operation = new AccountUpdateOperation(account, owner, active, new_options, fee); + } + operations.add(operation); + } + return new Transaction(privateKey, blockData, operations); + } +} diff --git a/src/main/java/com/luminiasoft/bitshares/Address.java b/src/main/java/com/luminiasoft/bitshares/Address.java index f2e8703..d363500 100644 --- a/src/main/java/com/luminiasoft/bitshares/Address.java +++ b/src/main/java/com/luminiasoft/bitshares/Address.java @@ -1,57 +1,66 @@ package com.luminiasoft.bitshares; import com.google.common.primitives.Bytes; -import com.google.gson.internal.LinkedTreeMap; -import static com.luminiasoft.bitshares.Test.OPENLEDGER_WITNESS_URL; -import com.luminiasoft.bitshares.interfaces.WitnessResponseListener; -import com.luminiasoft.bitshares.models.BaseResponse; -import com.luminiasoft.bitshares.models.WitnessResponse; -import com.luminiasoft.bitshares.test.NaiveSSLContext; -import com.luminiasoft.bitshares.ws.GetAccountNameById; -import com.luminiasoft.bitshares.ws.GetAccountsByAddress; -import com.neovisionaries.ws.client.WebSocket; -import com.neovisionaries.ws.client.WebSocketException; -import com.neovisionaries.ws.client.WebSocketFactory; -import java.io.IOException; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; +import com.luminiasoft.bitshares.errors.MalformedAddressException; import org.bitcoinj.core.Base58; import org.bitcoinj.core.ECKey; import org.spongycastle.crypto.digests.RIPEMD160Digest; +import org.spongycastle.math.ec.ECPoint; import java.util.Arrays; -import java.util.List; -import javafx.util.Pair; -import javax.net.ssl.SSLContext; +import java.util.IllegalFormatException; /** * Class used to encapsulate address-related operations. */ public class Address { - public final static String DEFAULT_PREFIX = "BTS"; + public final static String BITSHARES_PREFIX = "BTS"; - private ECKey key; + private PublicKey publicKey; private String prefix; public Address(ECKey key) { - this.key = key; - this.prefix = DEFAULT_PREFIX; + this.publicKey = new PublicKey(key); + this.prefix = BITSHARES_PREFIX; } public Address(ECKey key, String prefix) { - this.key = key; + this.publicKey = new PublicKey(key); this.prefix = prefix; } + public Address(String address) throws MalformedAddressException { + this.prefix = address.substring(0, 3); + byte[] decoded = Base58.decode(address.substring(3, address.length())); + byte[] pubKey = Arrays.copyOfRange(decoded, 0, decoded.length - 4); + byte[] checksum = Arrays.copyOfRange(decoded, decoded.length - 4, decoded.length); + publicKey = new PublicKey(ECKey.fromPublicOnly(pubKey)); + byte[] calculatedChecksum = calculateChecksum(pubKey); + for(int i = 0; i < calculatedChecksum.length; i++){ + if(checksum[i] != calculatedChecksum[i]){ + throw new MalformedAddressException("Checksum error"); + } + } + } + + public PublicKey getPublicKey(){ + return this.publicKey; + } + @Override public String toString() { - byte[] pubKey = key.getPubKey(); - byte[] checksum = new byte[160 / 8]; - RIPEMD160Digest ripemd160Digest = new RIPEMD160Digest(); - ripemd160Digest.update(pubKey, 0, pubKey.length); - ripemd160Digest.doFinal(checksum, 0); - byte[] pubKeyChecksummed = Bytes.concat(pubKey, Arrays.copyOfRange(checksum, 0, 4)); + byte[] pubKey = this.publicKey.toBytes(); + byte[] checksum = calculateChecksum(pubKey); + byte[] pubKeyChecksummed = Bytes.concat(pubKey, checksum); return this.prefix + Base58.encode(pubKeyChecksummed); } + + private byte[] calculateChecksum(byte[] data){ + byte[] checksum = new byte[160 / 8]; + RIPEMD160Digest ripemd160Digest = new RIPEMD160Digest(); + ripemd160Digest.update(data, 0, data.length); + ripemd160Digest.doFinal(checksum, 0); + return Arrays.copyOfRange(checksum, 0, 4); + } } diff --git a/src/main/java/com/luminiasoft/bitshares/Authority.java b/src/main/java/com/luminiasoft/bitshares/Authority.java new file mode 100644 index 0000000..a3637af --- /dev/null +++ b/src/main/java/com/luminiasoft/bitshares/Authority.java @@ -0,0 +1,129 @@ +package com.luminiasoft.bitshares; + +import com.google.common.primitives.Bytes; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.luminiasoft.bitshares.errors.MalformedAddressException; +import com.luminiasoft.bitshares.interfaces.GrapheneSerializable; + +import java.util.*; + +/** + * Created by nelson on 11/30/16. + */ +public class Authority implements GrapheneSerializable { + public static final String KEY_ACCOUNT_AUTHS = "account_auths"; + public static final String KEY_KEY_AUTHS = "key_auths"; + public static final String KEY_WEIGHT_THRESHOLD = "weight_threshold"; + public static final String KEY_EXTENSIONS = "extensions"; + + private long weight_threshold; + private HashMap account_auths; + private HashMap key_auths; + private Extensions extensions; + + public Authority(){ + this.weight_threshold = 1; + this.account_auths = new HashMap(); + this.key_auths = new HashMap(); + extensions = new Extensions(); + } + + /** + * Constructor for the authority class that takes every possible detail. + * @param weight_threshold: The total weight threshold + * @param keyAuths: Map of key to weights relationships. Can be null. + * @param accountAuths: Map of account to weights relationships. Can be null. + * @throws MalformedAddressException + */ + public Authority(long weight_threshold, HashMap keyAuths, HashMap accountAuths) { + this(); + this.weight_threshold = weight_threshold; + if(keyAuths != null) + this.key_auths = keyAuths; + if(accountAuths != null) + this.account_auths = accountAuths; + } + + public void setKeyAuthorities(HashMap keyAuths){ + if(keyAuths != null){ + for(Address address : keyAuths.keySet()){ + key_auths.put(address.getPublicKey(), keyAuths.get(address)); + } + } + } + + public void setAccountAuthorities(HashMap accountAuthorities){ + this.account_auths = accountAuthorities; + } + + @Override + public String toJsonString() { + return null; + } + + @Override + public JsonElement toJsonObject() { + JsonObject authority = new JsonObject(); + authority.addProperty(KEY_WEIGHT_THRESHOLD, weight_threshold); + JsonArray keyAuthArray = new JsonArray(); + JsonArray accountAuthArray = new JsonArray(); + + for(PublicKey publicKey : key_auths.keySet()){ + JsonArray subArray = new JsonArray(); + Address address = new Address(publicKey.getKey()); + subArray.add(address.toString()); + subArray.add(key_auths.get(publicKey)); + keyAuthArray.add(subArray); + } + + for(UserAccount key : account_auths.keySet()){ + JsonArray subArray = new JsonArray(); + subArray.add(key.toString()); + subArray.add(key_auths.get(key)); + accountAuthArray.add(subArray); + } + authority.add(KEY_KEY_AUTHS, keyAuthArray); + authority.add(KEY_ACCOUNT_AUTHS, accountAuthArray); + authority.add(KEY_EXTENSIONS, extensions.toJsonObject()); + return authority; + } + + @Override + public byte[] toBytes() { + List byteArray = new ArrayList(); + // Adding number of authorities + byteArray.add(Byte.valueOf((byte) (account_auths.size() + key_auths.size()))); + + // If the authority is not empty of references, we serialize its contents + // otherwise its only contribution will be a zero byte + if(account_auths.size() + key_auths.size() > 0){ + // Weight threshold + byteArray.addAll(Bytes.asList(Util.revertInteger(new Integer((int) weight_threshold)))); + + // Number of account authorities + byteArray.add((byte) account_auths.size()); + + //TODO: Check the account authorities serialization + // Serializing individual accounts and their corresponding weights + for(UserAccount account : account_auths.keySet()){ + byteArray.addAll(Bytes.asList(account.toBytes())); + byteArray.addAll(Bytes.asList(Util.revertShort(account_auths.get(account).shortValue()))); + } + + // Number of key authorities + byteArray.add((byte) key_auths.size()); + + // Serializing individual keys and their corresponding weights + for(PublicKey publicKey : key_auths.keySet()){ + byteArray.addAll(Bytes.asList(publicKey.toBytes())); + byteArray.addAll(Bytes.asList(Util.revertShort(key_auths.get(publicKey).shortValue()))); + } + + // Adding number of extensions + byteArray.add((byte) extensions.size()); + } + return Bytes.toArray(byteArray); + } +} \ No newline at end of file diff --git a/src/main/java/com/luminiasoft/bitshares/BaseOperation.java b/src/main/java/com/luminiasoft/bitshares/BaseOperation.java index eaef155..89616f2 100644 --- a/src/main/java/com/luminiasoft/bitshares/BaseOperation.java +++ b/src/main/java/com/luminiasoft/bitshares/BaseOperation.java @@ -14,7 +14,11 @@ public abstract class BaseOperation implements ByteSerializable, JsonSerializabl this.type = type; } - public abstract byte getId(); + public byte getId() { + return (byte) this.type.ordinal(); + } + + public abstract void setFee(AssetAmount assetAmount); public abstract byte[] toBytes(); } diff --git a/src/main/java/com/luminiasoft/bitshares/Extension.java b/src/main/java/com/luminiasoft/bitshares/Extension.java deleted file mode 100644 index f406bea..0000000 --- a/src/main/java/com/luminiasoft/bitshares/Extension.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.luminiasoft.bitshares; - -/** - * Created by nelson on 11/9/16. - */ -public class Extension { - //TODO: Give this class a proper implementation -} diff --git a/src/main/java/com/luminiasoft/bitshares/Extensions.java b/src/main/java/com/luminiasoft/bitshares/Extensions.java new file mode 100644 index 0000000..18c9a6d --- /dev/null +++ b/src/main/java/com/luminiasoft/bitshares/Extensions.java @@ -0,0 +1,43 @@ +package com.luminiasoft.bitshares; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.luminiasoft.bitshares.interfaces.ByteSerializable; +import com.luminiasoft.bitshares.interfaces.JsonSerializable; + +import java.util.ArrayList; + +/** + * Created by nelson on 11/9/16. + */ +public class Extensions implements JsonSerializable, ByteSerializable { + public static final String KEY_EXTENSIONS = "extensions"; + + private ArrayList extensions; + + public Extensions(){ + extensions = new ArrayList<>(); + } + + @Override + public String toJsonString() { + return null; + } + + @Override + public JsonElement toJsonObject() { + JsonArray array = new JsonArray(); + for(JsonSerializable o : extensions) + array.add(o.toJsonObject()); + return array; + } + + @Override + public byte[] toBytes() { + return new byte[1]; + } + + public int size(){ + return extensions.size(); + } +} diff --git a/src/main/java/com/luminiasoft/bitshares/Main.java b/src/main/java/com/luminiasoft/bitshares/Main.java index 82324dc..e46780b 100644 --- a/src/main/java/com/luminiasoft/bitshares/Main.java +++ b/src/main/java/com/luminiasoft/bitshares/Main.java @@ -7,17 +7,21 @@ import java.io.IOException; public class Main { // Brain key from Nelson's app referencing the bilthon-83 account - public static final String BRAIN_KEY = "PUMPER ISOTOME SERE STAINER CLINGER MOONLIT CHAETA UPBRIM AEDILIC BERTHER NIT SHAP SAID SHADING JUNCOUS CHOUGH"; + public static final String BILTHON_83_BRAIN_KEY = "PUMPER ISOTOME SERE STAINER CLINGER MOONLIT CHAETA UPBRIM AEDILIC BERTHER NIT SHAP SAID SHADING JUNCOUS CHOUGH"; - //public static final String BRAIN_KEY = "TWIXT SERMO TRILLI AUDIO PARDED PLUMET BIWA REHUNG MAUDLE VALVULA OUTBURN FEWNESS ALIENER UNTRACE PRICH TROKER"; - //public static final String BRAIN_KEY = "SIVER TIKKER FOGO HOMINAL PRAYER LUTEIN SMALLY ACARID MEROPIA TRANCE BOGONG IDDAT HICKORY SOUTANE MOOD DOWSER"; + public static final String BILTHON_5_BRAIN_KEY = "UNMATE AURIGAL NAVET WAVICLE REWOVE ABBOTCY COWHERB OUTKICK STOPPER JUSSORY BEAMLET WIRY"; + + public static final String BILTHON_7_BRAIN_KEY = "VENIN QUOTHA OBESELY TORIC OSMATIC SPOKEN DIACOPE CUBICA TABULA REDDING APONIA TARTAR"; + + //public static final String BILTHON_83_BRAIN_KEY = "TWIXT SERMO TRILLI AUDIO PARDED PLUMET BIWA REHUNG MAUDLE VALVULA OUTBURN FEWNESS ALIENER UNTRACE PRICH TROKER"; + //public static final String BILTHON_83_BRAIN_KEY = "SIVER TIKKER FOGO HOMINAL PRAYER LUTEIN SMALLY ACARID MEROPIA TRANCE BOGONG IDDAT HICKORY SOUTANE MOOD DOWSER"; public static final String BIP39_KEY = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; // WIF from Nelson's app referencing the bilthon-83 account // public static final String WIF = "5J96pne45qWM1WpektoeazN6k9Mt93jQ7LyueRxFfEMTiy6yxjM"; // Brain key from an empty account created by the cli_wallet -// public static final String BRAIN_KEY = "TWIXT SERMO TRILLI AUDIO PARDED PLUMET BIWA REHUNG MAUDLE VALVULA OUTBURN FEWNESS ALIENER UNTRACE PRICH TROKER"; - // WIF from an emty account created by the cli_wallet +// public static final String BILTHON_83_BRAIN_KEY = "TWIXT SERMO TRILLI AUDIO PARDED PLUMET BIWA REHUNG MAUDLE VALVULA OUTBURN FEWNESS ALIENER UNTRACE PRICH TROKER"; + // WIF from an empty account created by the cli_wallet public static final String WIF = "5KMzB2GqGhnh7ufhgddmz1eKPHS72uTLeL9hHjSvPb1UywWknF5"; public static final String EXTERNAL_SIGNATURE = "1f36c41acb774fcbc9c231b5895ec9701d6872729098d8ea56d78dda72a6b54252694db85d7591de5751b7aea06871da15d63a1028758421607ffc143e53ef3306"; @@ -38,7 +42,7 @@ public class Main { // e.printStackTrace(); // } // test.testCustomSerializer(); - //test.testUserAccountSerialization(); +// test.testUserAccountSerialization(); // test.testTransactionSerialization(); // test.testLoginSerialization(); // test.testNetworkBroadcastSerialization(); @@ -46,9 +50,10 @@ public class Main { // test.testGetDynamicParams(); // test.testGetRequiredFeesSerialization(); // test.testRequiredFeesResponse(); -// test.testTransactionBroadcastSequence(); + test.testTransactionBroadcastSequence(); // test.testAccountLookupDeserialization(); // test.testPrivateKeyManipulations(); +// test.testPublicKeyManipulations(); // test.testGetAccountByName(); // test.testGetRequiredFees(); // test.testRandomNumberGeneration(); @@ -59,8 +64,10 @@ public class Main { // test.testRelativeAccountHistory(); // test.testingInvoiceGeneration(); // test.testCompression(); - //test.testCreateBinFile(); - //test.testImportBinFile(); - test.testLookout(); +// test.testAccountUpdateSerialization(); +// test.testAccountUpdateOperationBroadcast(); +// test.testCreateBinFile(); +// test.testImportBinFile(); + //test.testLookupAccounts(); } } diff --git a/src/main/java/com/luminiasoft/bitshares/Optional.java b/src/main/java/com/luminiasoft/bitshares/Optional.java new file mode 100644 index 0000000..f7cc88f --- /dev/null +++ b/src/main/java/com/luminiasoft/bitshares/Optional.java @@ -0,0 +1,33 @@ +package com.luminiasoft.bitshares; + +import com.google.gson.JsonElement; +import com.luminiasoft.bitshares.interfaces.GrapheneSerializable; + +/** + * Used whenever we have an optional field. + */ +public class Optional implements GrapheneSerializable { + private T optionalField; + + public Optional(T field){ + optionalField = field; + } + + @Override + public byte[] toBytes() { + if(optionalField == null) + return new byte[] { (byte) 0 }; + else + return optionalField.toBytes(); + } + + @Override + public String toJsonString() { + return optionalField.toJsonString(); + } + + @Override + public JsonElement toJsonObject() { + return optionalField.toJsonObject(); + } +} diff --git a/src/main/java/com/luminiasoft/bitshares/PublicKey.java b/src/main/java/com/luminiasoft/bitshares/PublicKey.java new file mode 100644 index 0000000..14dd927 --- /dev/null +++ b/src/main/java/com/luminiasoft/bitshares/PublicKey.java @@ -0,0 +1,24 @@ +package com.luminiasoft.bitshares; + +import com.luminiasoft.bitshares.interfaces.ByteSerializable; +import org.bitcoinj.core.ECKey; + +/** + * Created by nelson on 11/30/16. + */ +public class PublicKey implements ByteSerializable { + private ECKey publicKey; + + public PublicKey(ECKey key) { + this.publicKey = key; + } + + public ECKey getKey(){ + return publicKey; + } + + @Override + public byte[] toBytes() { + return publicKey.getPubKey(); + } +} diff --git a/src/main/java/com/luminiasoft/bitshares/RPC.java b/src/main/java/com/luminiasoft/bitshares/RPC.java index 1632e43..f7db64b 100644 --- a/src/main/java/com/luminiasoft/bitshares/RPC.java +++ b/src/main/java/com/luminiasoft/bitshares/RPC.java @@ -15,5 +15,5 @@ public class RPC { public static final String CALL_GET_ACCOUNTS = "get_accounts"; public static final String CALL_GET_KEY_REFERENCES = "get_key_references"; public static final String CALL_GET_RELATIVE_ACCOUNT_HISTORY = "get_relative_account_history"; - public static final String CALL_GET_ID_BY_NAME = "lookup_accounts"; + public static final String CALL_LOOKUP_ACCOUNTS = "lookup_accounts"; } diff --git a/src/main/java/com/luminiasoft/bitshares/Test.java b/src/main/java/com/luminiasoft/bitshares/Test.java index efd0412..39157fd 100644 --- a/src/main/java/com/luminiasoft/bitshares/Test.java +++ b/src/main/java/com/luminiasoft/bitshares/Test.java @@ -5,6 +5,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.reflect.TypeToken; +import com.luminiasoft.bitshares.errors.MalformedAddressException; import com.luminiasoft.bitshares.errors.MalformedTransactionException; import com.luminiasoft.bitshares.interfaces.WitnessResponseListener; import com.luminiasoft.bitshares.models.*; @@ -12,17 +13,12 @@ import com.luminiasoft.bitshares.test.NaiveSSLContext; import com.luminiasoft.bitshares.ws.*; import com.neovisionaries.ws.client.*; import org.bitcoinj.core.*; -import org.spongycastle.crypto.Digest; import org.spongycastle.crypto.digests.RIPEMD160Digest; -import org.spongycastle.crypto.digests.SHA512Digest; -import org.spongycastle.crypto.prng.DigestRandomGenerator; import javax.net.ssl.SSLContext; import java.io.*; import java.lang.reflect.Type; -import java.math.BigInteger; import java.security.NoSuchAlgorithmException; -import java.text.SimpleDateFormat; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; @@ -186,144 +182,14 @@ public class Test { UserAccount to = new UserAccount("1.2.129848"); AssetAmount amount = new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120")); AssetAmount fee = new AssetAmount(UnsignedLong.valueOf(264174), new Asset("1.3.0")); - operations.add(new Transfer(from, to, amount, fee)); - this.transaction = new Transaction(Main.WIF, blockData, operations); + operations.add(new TransferOperation(from, to, amount, fee)); + BrainKey brainKey = new BrainKey(Main.BILTHON_83_BRAIN_KEY, 0); + this.transaction = new Transaction(brainKey.getWalletImportFormat(), blockData, operations); byte[] serializedTransaction = this.transaction.toBytes(); System.out.println("Serialized transaction"); System.out.println(Util.bytesToHex(serializedTransaction)); } - public void testWebSocketTransfer() throws IOException { - String login = "{\"id\":%d,\"method\":\"call\",\"params\":[1,\"login\",[\"\",\"\"]]}"; - String getDatabaseId = "{\"method\": \"call\", \"params\": [1, \"database\", []], \"jsonrpc\": \"2.0\", \"id\": %d}"; - String getHistoryId = "{\"method\": \"call\", \"params\": [1, \"history\", []], \"jsonrpc\": \"2.0\", \"id\": %d}"; - String getNetworkBroadcastId = "{\"method\": \"call\", \"params\": [1, \"network_broadcast\", []], \"jsonrpc\": \"2.0\", \"id\": %d}"; - String getDynamicParameters = "{\"method\": \"call\", \"params\": [0, \"get_dynamic_global_properties\", []], \"jsonrpc\": \"2.0\", \"id\": %d}"; - String rawPayload = "{\"method\": \"call\", \"params\": [%d, \"broadcast_transaction\", [{\"expiration\": \"%s\", \"signatures\": [\"%s\"], \"operations\": [[0, {\"fee\": {\"amount\": 264174, \"asset_id\": \"1.3.0\"}, \"amount\": {\"amount\": 100, \"asset_id\": \"1.3.120\"}, \"to\": \"1.2.129848\", \"extensions\": [], \"from\": \"1.2.138632\"}]], \"ref_block_num\": %d, \"extensions\": [], \"ref_block_prefix\": %d}]], \"jsonrpc\": \"2.0\", \"id\": %d}"; - -// String url = "wss://bitshares.openledger.info/ws"; - String url = "ws://api.devling.xyz:8088"; - WebSocketFactory factory = new WebSocketFactory().setConnectionTimeout(5000); - - // Create a WebSocket. The timeout value set above is used. - WebSocket ws = factory.createSocket(url); - - ws.addListener(new WebSocketAdapter() { - - private DynamicGlobalProperties dynProperties; - private int networkBroadcastApiId; - - @Override - public void onConnected(WebSocket websocket, Map> headers) throws Exception { - System.out.println("onConnected"); - String payload = String.format(login, 1); - System.out.println(">>"); - System.out.println(payload); - websocket.sendText(payload); - } - - @Override - public void onDisconnected(WebSocket websocket, WebSocketFrame serverCloseFrame, WebSocketFrame clientCloseFrame, boolean closedByServer) throws Exception { - System.out.println("onDisconnected"); - } - - @Override - public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception { - System.out.println("<<"); - String response = frame.getPayloadText(); - System.out.println(response); - Gson gson = new Gson(); - BaseResponse baseResponse = gson.fromJson(response, BaseResponse.class); -// if(baseResponse.id.equals("1")){ -// String payload = String.format(getDatabaseId, 2); -// System.out.println(">>"); -// System.out.println(payload); -// websocket.sendText(payload); -// }else if(baseResponse.id.equals("2")){ -// String payload = String.format(getHistoryId, 3); -// System.out.println(">>"); -// System.out.println(payload); -// websocket.sendText(payload); -// }else if(baseResponse.id.equals("3")){ - if (baseResponse.id == 1) { - String payload = String.format(getNetworkBroadcastId, 2); - System.out.println(">>"); - System.out.println(payload); - websocket.sendText(payload); -// }else if(baseResponse.id.equals("4")){ - } - if (baseResponse.id == 2) { - String payload = String.format(getDynamicParameters, 3); - Type ApiIdResponse = new TypeToken>() { - }.getType(); - WitnessResponse witnessResponse = gson.fromJson(response, ApiIdResponse); - networkBroadcastApiId = witnessResponse.result.intValue(); - System.out.println(">>"); - System.out.println(payload); - websocket.sendText(payload); - } else if (baseResponse.id == 3) { - // Got dynamic properties - Type DynamicGlobalPropertiesResponse = new TypeToken>() { - }.getType(); - WitnessResponse witnessResponse = gson.fromJson(response, DynamicGlobalPropertiesResponse); - dynProperties = witnessResponse.result; - - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); - Date date = dateFormat.parse(dynProperties.time); - long expirationTime = (date.getTime() / 1000) + 30; - testTransactionSerialization(dynProperties.head_block_number, dynProperties.head_block_id, expirationTime); - - BlockData blockData = new BlockData(dynProperties.head_block_number, dynProperties.head_block_id, expirationTime); - byte[] signatureBytes = signMessage(); - - String payload = String.format( - rawPayload, - networkBroadcastApiId, - dateFormat.format(new Date(expirationTime * 1000)), - Util.bytesToHex(signatureBytes), - blockData.getRefBlockNum(), - blockData.getRefBlockPrefix(), - 4); - System.out.println(">>"); - System.out.println(payload); - websocket.sendText(payload); - } - } - - @Override - public void onError(WebSocket websocket, WebSocketException cause) throws Exception { - System.out.println("onError"); - } - - @Override - public void onUnexpectedError(WebSocket websocket, WebSocketException cause) throws Exception { - System.out.println("onUnexpectedError"); - } - - @Override - public void handleCallbackError(WebSocket websocket, Throwable cause) throws Exception { - System.out.println("handleCallbackError. Msg: " + cause.getMessage()); - StackTraceElement[] stackTrace = cause.getStackTrace(); - for (StackTraceElement line : stackTrace) { - System.out.println(line.toString()); - } - } - }); - try { - // Connect to the server and perform an opening handshake. - // This method blocks until the opening handshake is finished. - ws.connect(); - } catch (OpeningHandshakeException e) { - // A violation against the WebSocket protocol was detected - // during the opening handshake. - System.out.println("OpeningHandshakeException"); - } catch (WebSocketException e) { - // Failed to establish a WebSocket connection. - System.out.println("WebSocketException. Msg: " + e.getMessage()); - } - } - public void testCustomSerializer() { AssetAmount amount = new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120")); String jsonAmount = amount.toJsonString(); @@ -343,8 +209,8 @@ public class Test { .setDestination(new UserAccount("1.2.129848")) .setAmount(new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120"))) .setFee(new AssetAmount(UnsignedLong.valueOf(264174), new Asset("1.3.0"))) - .setBlockData(new BlockData(Main.REF_BLOCK_NUM, Main.REF_BLOCK_PREFIX, Main.RELATIVE_EXPIRATION)) .setPrivateKey(DumpedPrivateKey.fromBase58(null, Main.WIF).getKey()) + .setBlockData(new BlockData(Main.REF_BLOCK_NUM, Main.REF_BLOCK_PREFIX, Main.RELATIVE_EXPIRATION)) .build(); ArrayList transactionList = new ArrayList<>(); @@ -429,14 +295,12 @@ public class Test { .setAmount(new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120"))) .setFee(new AssetAmount(UnsignedLong.valueOf(264174), new Asset("1.3.0"))) .setBlockData(new BlockData(43408, 1430521623, 1479231969)) - .setPrivateKey(DumpedPrivateKey.fromBase58(null, Main.WIF).getKey()) + .setPrivateKey(DumpedPrivateKey.fromBase58(null, Main.BILTHON_5_BRAIN_KEY).getKey()) .build(); ArrayList transactionList = new ArrayList<>(); transactionList.add(transaction); - transactionList.add(transaction); - SSLContext context = null; context = NaiveSSLContext.getInstance("TLS"); WebSocketFactory factory = new WebSocketFactory(); @@ -446,7 +310,7 @@ public class Test { WebSocket mWebSocket = factory.createSocket(OPENLEDGER_WITNESS_URL); - mWebSocket.addListener(new TransactionBroadcastSequence(transaction, listener)); + mWebSocket.addListener(new TransactionBroadcastSequence(transaction, new Asset("1.3.0"), listener)); mWebSocket.connect(); } catch (MalformedTransactionException e) { @@ -472,14 +336,29 @@ public class Test { } public void testPrivateKeyManipulations() { - ECKey privateKey = DumpedPrivateKey.fromBase58(null, Main.WIF).getKey(); + String brainKeyWords = "PUMPER ISOTOME SERE STAINER CLINGER MOONLIT CHAETA UPBRIM AEDILIC BERTHER NIT SHAP SAID SHADING JUNCOUS CHOUGH"; + BrainKey brainKey = new BrainKey(brainKeyWords, 0); + + ECKey privateKey = DumpedPrivateKey.fromBase58(null, brainKey.getWalletImportFormat()).getKey(); System.out.println("private key..............: " + Util.bytesToHex(privateKey.getSecretBytes())); System.out.println("public key uncompressed..: " + Util.bytesToHex(privateKey.getPubKey())); System.out.println("public key compressed....: " + Util.bytesToHex(privateKey.getPubKeyPoint().getEncoded(true))); System.out.println("base58...................: " + Base58.encode(privateKey.getPubKeyPoint().getEncoded(true))); System.out.println("base58...................: " + Base58.encode(privateKey.getPubKey())); - String brainKeyWords = "PUMPER ISOTOME SERE STAINER CLINGER MOONLIT CHAETA UPBRIM AEDILIC BERTHER NIT SHAP SAID SHADING JUNCOUS CHOUGH"; - BrainKey brainKey = new BrainKey(brainKeyWords, 0); + } + + public void testPublicKeyManipulations(){ +// PublicKey publicKey = new PublicKey("BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY"); +// System.out.println("Public key bytes"); +// System.out.println(Util.bytesToHex(publicKey.toBytes())); + Address address = null; + try { + address = new Address("BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY"); + System.out.println("Public key"); + System.out.println(Util.bytesToHex(address.getPublicKey().toBytes())); + } catch (MalformedAddressException e) { + e.printStackTrace(); + } } public void testGetAccountByName() { @@ -502,7 +381,7 @@ public class Test { UserAccount to = new UserAccount("1.2.129848"); AssetAmount amount = new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120")); AssetAmount fee = new AssetAmount(UnsignedLong.valueOf(264174), new Asset("1.3.0")); - Transfer transfer = new Transfer(from, to, amount, fee); + TransferOperation transfer = new TransferOperation(from, to, amount, fee); ArrayList operations = new ArrayList<>(); operations.add(transfer); @@ -521,52 +400,9 @@ public class Test { } } - public void testRandomNumberGeneration() { - byte[] seed = new byte[]{new Long(System.nanoTime()).byteValue()}; - doCountTest(new SHA512Digest(), seed); - } - - private void doCountTest(Digest digest, byte[] seed)//, byte[] expectedXors) - { - DigestRandomGenerator generator = new DigestRandomGenerator(digest); - byte[] output = new byte[digest.getDigestSize()]; - int[] averages = new int[digest.getDigestSize()]; - byte[] ands = new byte[digest.getDigestSize()]; - byte[] xors = new byte[digest.getDigestSize()]; - byte[] ors = new byte[digest.getDigestSize()]; - - generator.addSeedMaterial(seed); - - for (int i = 0; i != 1000000; i++) { - generator.nextBytes(output); - for (int j = 0; j != output.length; j++) { - averages[j] += output[j] & 0xff; - ands[j] &= output[j]; - xors[j] ^= output[j]; - ors[j] |= output[j]; - } - } - - for (int i = 0; i != output.length; i++) { - if ((averages[i] / 1000000) != 127) { - System.out.println("average test failed for " + digest.getAlgorithmName()); - } - System.out.println("averages[" + i + "] / 1000000: " + averages[i] / 1000000); - if (ands[i] != 0) { - System.out.println("and test failed for " + digest.getAlgorithmName()); - } - if ((ors[i] & 0xff) != 0xff) { - System.out.println("or test failed for " + digest.getAlgorithmName()); - } -// if (xors[i] != expectedXors[i]) { -// System.out.println("xor test failed for " + digest.getAlgorithmName()); -// } - } - } - /** * The final purpose of this test is to convert the plain brainkey at - * Main.BRAIN_KEY into the WIF at Main.WIF + * Main.BILTHON_83_BRAIN_KEY into the WIF at Main.WIF */ public void testBrainKeyOperations(boolean random) { try { @@ -581,32 +417,27 @@ public class Test { String suggestion = BrainKey.suggest(words); brainKey = new BrainKey(suggestion, 0); } else { - brainKey = new BrainKey(Main.BRAIN_KEY, 0); + System.out.println("Using brain key: "+Main.BILTHON_5_BRAIN_KEY); +// brainKey = new BrainKey(Main.BILTHON_83_BRAIN_KEY, 0); + brainKey = new BrainKey("CYNEBOT LUFBERY DAUNTER TOO SALOOP HOPOFF DIAULOS REV AES TORPOR RECTRIX DEVILRY", 0); } ECKey key = brainKey.getPrivateKey(); - System.out.println("Private key"); - System.out.println(Util.bytesToHex(key.getSecretBytes())); + System.out.println("Private key..................: "+Util.bytesToHex(key.getSecretBytes())); String wif = key.getPrivateKeyAsWiF(NetworkParameters.fromID(NetworkParameters.ID_MAINNET)); - System.out.println("wif compressed: " + wif); + System.out.println("Wif Compressed...............: " + wif); String wif2 = key.decompress().getPrivateKeyAsWiF(NetworkParameters.fromID(NetworkParameters.ID_MAINNET)); - System.out.println("wif decompressed: " + wif2); + System.out.println("Wif Decompressed.............: " + wif2); + System.out.println("Wif from BrainKey............: "+ brainKey.getWalletImportFormat()); - byte[] pubKey1 = key.decompress().getPubKey(); - System.out.println("decompressed public key: " + Base58.encode(pubKey1)); - byte[] pubKey2 = key.getPubKey(); - System.out.println("compressed public key: " + Base58.encode(pubKey2)); + byte[] uncompressedPubKey = key.decompress().getPubKey(); + byte[] compressedPubKey = key.getPubKey(); - System.out.println("pub key compressed : " + Util.bytesToHex(pubKey1)); - System.out.println("pub key uncompressed : " + Util.bytesToHex(pubKey2)); - - byte[] pubKey3 = key.getPubKeyPoint().getEncoded(true); - System.out.println("pub key compressed : " + Base58.encode(pubKey3)); + System.out.println("Public Key Decompressed......: " + Util.bytesToHex(uncompressedPubKey)); + System.out.println("Public Key Compressed........: " + Util.bytesToHex(compressedPubKey)); // Address generation test - Address address = new Address(key); - System.out.println("Block explorer's address: " + address); - - System.out.println("Wif: : " + brainKey.getWalletImportFormat()); + Address address = new Address(ECKey.fromPublicOnly(key.getPubKey())); + System.out.println("Block explorer's address.....: " + address); } catch (FileNotFoundException e) { System.out.println("FileNotFoundException. Msg: " + e.getMessage()); } catch (IOException e) { @@ -641,7 +472,7 @@ public class Test { } }; - BrainKey brainKey = new BrainKey(Main.BRAIN_KEY, 0); + BrainKey brainKey = new BrainKey(Main.BILTHON_83_BRAIN_KEY, 0); Address address = new Address(brainKey.getPrivateKey()); try { // Create a custom SSL context. @@ -729,7 +560,7 @@ public class Test { } public void testCreateBinFile() { - byte[] fileOutput = FileBin.getBytesFromBrainKey(Main.BRAIN_KEY, "123456", "bithon-83"); + byte[] fileOutput = FileBin.getBytesFromBrainKey(Main.BILTHON_83_BRAIN_KEY, "123456", "bithon-83"); ///String stringFile = "02f9f3eb0f61a0a96134975d86048bf92e114d6a1ce286140cad3a96c33e697282bc0a8a24d1ad0c7bc084a79816ce38e36bd2d624aa8bf686f53fb4c7e25e3974da9b40e0b17e9d0b5b82793a04b19646169c49c58cd67f4950aee7d275141dd24f52baaaee772995a9bd6a6562a7a38aae08951236d3f612aecef7aedd720a91eacbab3a792ca3ebe0105838fe11f6e9d0e83e5d77eb82f17c7ba85c670e69294a8bcf8365cfeca487a60093498496bbec394c729e3fda9f32fdccdea56288b36fb14a26aa309b548a6dd9c1d616d22167348f8d580f9dc7361b4457d2dc6d75ec985d8e2d3dcdff89cd425d9f14037ac961eb10ac5f92bab356ccecd8cf018ec05ab40d915b628a75ae32cfa4005634f08b24c0dc8c5a7636ed70cbd86a7f0c4f6236d74310470fafe3af8b5346c8cb61957f7292b468d276498f9e806399588b0afd5777e6ee5fe7cd3a6691d9b5486cb5c7adbd5ad0b17588dd32d82b01d49ecf0f2bf24ee54a490ee620e8ab049047ffa416b5efa8f1f0155d8f1be866a10d0d62ae44a3a8ecc0121c08837c2ee1a25f8b6dd7266273c41f4b9a5e3d600e3fb4de870f99ab1a7196d93f222595f92e97a2480f58b61b62639154a374b987664fd317622aaad156f831b03f2d9606537b65b3b1fcfb1fb6be39560ad2c301dd1fc25cee755e61b49ebfe42ca7e64b4b0fc4aa347b48a85c0b585a3499fe278e25cb2141f8009b9afc875fa2a2c439bf6cdec4b5190a6deb7f9390f072beb24749a8a2114cc1870c07be079abb3ee0ebc827f9b53e158a529bc6552eba280f05edf5f7ae1911de7acb4888150a509d029ec7c9da6de8adabbca6773a0a293a0a42de8278c82e88b9390b42b56f58bd8633fb97130e799a47a744e2e8958fd5"; //fileOutput = new BigInteger(stringFile, 16).toByteArray(); System.out.println(FileBin.getBrainkeyFromByte(fileOutput, "123456")); @@ -742,9 +573,63 @@ public class Test { } - void testLookout() { + public void testAccountUpdateSerialization() { + String newAddress = "BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY"; try { - GetAccountIdByName query = new GetAccountIdByName("henrutest-125", mListener); + Address address = new Address(newAddress); + HashMap authMap = new HashMap<>(); + authMap.put(address.getPublicKey(), 1); + Authority authority = new Authority(1, authMap, null); + AccountOptions options = new AccountOptions(address.getPublicKey()); + BrainKey brainKey = new BrainKey(Main.BILTHON_7_BRAIN_KEY, 0); + Transaction transaction = new AccountUpdateTransactionBuilder(brainKey.getPrivateKey()) + .setAccont(new UserAccount("1.2.140994")) + .setOwner(authority) + .setActive(authority) + .setOptions(options) + .setBlockData(new BlockData(Main.REF_BLOCK_NUM, Main.REF_BLOCK_PREFIX, Main.RELATIVE_EXPIRATION)) + .build(); + + System.out.println("Json object"); + System.out.println(transaction.toJsonString()); + System.out.println("Serialized transaction"); + System.out.println(Util.bytesToHex(transaction.toBytes())); + } catch(MalformedAddressException e){ + System.out.println("MalformedAddressException. Msg: "+e.getMessage()); + } catch (MalformedTransactionException e) { + System.out.println("MalformedTransactionException. Msg: "+e.getMessage()); + } + } + + public void testAccountUpdateOperationBroadcast(){ + + WitnessResponseListener listener = new WitnessResponseListener() { + @Override + public void onSuccess(WitnessResponse response) { + System.out.println("onSuccess"); + } + + @Override + public void onError(BaseResponse.Error error) { + System.out.println("onError"); + } + }; + + String newAddress = "BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY"; + try { + Address address = new Address(newAddress); + HashMap authMap = new HashMap<>(); + authMap.put(address.getPublicKey(), 1); + Authority authority = new Authority(1, authMap, null); + AccountOptions options = new AccountOptions(address.getPublicKey()); + BrainKey brainKey = new BrainKey(Main.BILTHON_7_BRAIN_KEY, 0); + Transaction transaction = new AccountUpdateTransactionBuilder(brainKey.getPrivateKey()) + .setAccont(new UserAccount("1.2.140994")) + .setOwner(authority) + .setActive(authority) + .setOptions(options) + .build(); + SSLContext context = null; context = NaiveSSLContext.getInstance("TLS"); WebSocketFactory factory = new WebSocketFactory(); @@ -753,15 +638,56 @@ public class Test { factory.setSSLContext(context); WebSocket mWebSocket = factory.createSocket(OPENLEDGER_WITNESS_URL); - mWebSocket.addListener(query); + + mWebSocket.addListener(new TransactionBroadcastSequence(transaction, new Asset("1.3.0"), listener)); mWebSocket.connect(); - } catch (WebSocketException ex) { - Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); - } catch (IOException ex) { - Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); - } catch (NoSuchAlgorithmException ex) { - Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); + + } catch (MalformedAddressException e) { + System.out.println("MalformedAddressException. Msg: "+e.getMessage()); + } catch (MalformedTransactionException e) { + System.out.println("MalformedTransactionException. Msg: "+e.getMessage()); + } catch (NoSuchAlgorithmException e) { + System.out.println("NoSuchAlgorithmException. Msg: "+e.getMessage()); + } catch (IOException e) { + System.out.println("IOException. Msg: "+e.getMessage()); + } catch (WebSocketException e) { + System.out.println("WebSocketException. Msg: "+e.getMessage()); } } + public void testLookupAccounts(){ + WitnessResponseListener listener = new WitnessResponseListener() { + @Override + public void onSuccess(WitnessResponse response) { + System.out.println("onSuccess"); + } + + @Override + public void onError(BaseResponse.Error error) { + System.out.println("onError"); + } + }; + + SSLContext context = null; + try { + context = NaiveSSLContext.getInstance("TLS"); + WebSocketFactory factory = new WebSocketFactory(); + + // Set the custom SSL context. + factory.setSSLContext(context); + + WebSocket mWebSocket = factory.createSocket(OPENLEDGER_WITNESS_URL); + + mWebSocket.addListener(new LookupAccounts("bilthon", listener)); + mWebSocket.connect(); + + } catch (NoSuchAlgorithmException e) { + System.out.println("NoSuchAlgorithmException. Msg: "+e.getMessage()); + } catch (WebSocketException e) { + System.out.println("WebSocketException. Msg: "+e.getMessage()); + } catch (IOException e) { + System.out.println("IOException. Msg: "+e.getMessage()); + } + } + } diff --git a/src/main/java/com/luminiasoft/bitshares/Transaction.java b/src/main/java/com/luminiasoft/bitshares/Transaction.java index 58ad0b8..29a4620 100644 --- a/src/main/java/com/luminiasoft/bitshares/Transaction.java +++ b/src/main/java/com/luminiasoft/bitshares/Transaction.java @@ -7,6 +7,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; +import com.luminiasoft.bitshares.errors.MalformedTransactionException; import com.luminiasoft.bitshares.interfaces.ByteSerializable; import com.luminiasoft.bitshares.interfaces.JsonSerializable; @@ -23,11 +24,12 @@ import java.util.List; import java.util.TimeZone; /** - * Class used to represent a generic graphene transaction. + * Class used to represent a generic Graphene transaction. */ public class Transaction implements ByteSerializable, JsonSerializable { private final String TAG = this.getClass().getName(); + public static final int DEFAULT_EXPIRATION_TIME = 30; public static final String KEY_EXPIRATION = "expiration"; public static final String KEY_SIGNATURES = "signatures"; public static final String KEY_OPERATIONS = "operations"; @@ -38,20 +40,7 @@ public class Transaction implements ByteSerializable, JsonSerializable { private ECKey privateKey; private BlockData blockData; private List operations; - private List extensions; - - /** - * Transaction constructor. - * @param wif: The user's private key in the base58 format. - * @param block_data: Block data containing important information used to sign a transaction. - * @param operation_list: List of operations to include in the transaction. - */ - public Transaction(String wif, BlockData block_data, List operation_list){ - this.privateKey = DumpedPrivateKey.fromBase58(null, wif).getKey(); - this.blockData = block_data; - this.operations = operation_list; - this.extensions = new ArrayList(); - } + private List extensions; /** * Transaction constructor. @@ -63,7 +52,34 @@ public class Transaction implements ByteSerializable, JsonSerializable { this.privateKey = privateKey; this.blockData = blockData; this.operations = operationList; - this.extensions = new ArrayList(); + this.extensions = new ArrayList(); + } + + /** + * Transaction constructor. + * @param wif: The user's private key in the base58 format. + * @param block_data: Block data containing important information used to sign a transaction. + * @param operation_list: List of operations to include in the transaction. + */ + public Transaction(String wif, BlockData block_data, List operation_list){ + this(DumpedPrivateKey.fromBase58(null, wif).getKey(), block_data, operation_list); + } + + /** + * Updates the block data + * @param blockData: New block data + */ + public void setBlockData(BlockData blockData){ + this.blockData = blockData; + } + + /** + * Updates the fees for all operations in this transaction. + * @param fees: New fees to apply + */ + public void setFees(List fees){ + for(int i = 0; i < operations.size(); i++) + operations.get(i).setFee(fees.get(i)); } public ECKey getPrivateKey(){ @@ -117,6 +133,7 @@ public class Transaction implements ByteSerializable, JsonSerializable { } return sigData; } + /** * Method that creates a serialized byte array with compact information about this transaction * that is needed for the creation of a signature. @@ -142,8 +159,8 @@ public class Transaction implements ByteSerializable, JsonSerializable { //Adding the number of extensions byteArray.add((byte) this.extensions.size()); - for(Extension extension : extensions){ - //TODO: Implement the extension serialization + for(Extensions extensions : this.extensions){ + //TODO: Implement the extensions serialization } // Adding a last zero byte to match the result obtained by the python-graphenelib code // I'm not exactly sure what's the meaning of this last zero byte, but for now I'll just diff --git a/src/main/java/com/luminiasoft/bitshares/TransactionBuilder.java b/src/main/java/com/luminiasoft/bitshares/TransactionBuilder.java index 05249a8..068a23c 100644 --- a/src/main/java/com/luminiasoft/bitshares/TransactionBuilder.java +++ b/src/main/java/com/luminiasoft/bitshares/TransactionBuilder.java @@ -11,5 +11,16 @@ public abstract class TransactionBuilder { protected ECKey privateKey; protected BlockData blockData; + public TransactionBuilder(){} + + public TransactionBuilder(ECKey privKey){ + this.privateKey = privKey; + } + + public TransactionBuilder setBlockData(BlockData blockData){ + this.blockData = blockData; + return this; + } + public abstract Transaction build() throws MalformedTransactionException; } diff --git a/src/main/java/com/luminiasoft/bitshares/Transfer.java b/src/main/java/com/luminiasoft/bitshares/TransferOperation.java similarity index 81% rename from src/main/java/com/luminiasoft/bitshares/Transfer.java rename to src/main/java/com/luminiasoft/bitshares/TransferOperation.java index d2b2bc0..4f12f70 100644 --- a/src/main/java/com/luminiasoft/bitshares/Transfer.java +++ b/src/main/java/com/luminiasoft/bitshares/TransferOperation.java @@ -1,16 +1,15 @@ package com.luminiasoft.bitshares; import com.google.common.primitives.Bytes; -import com.google.common.primitives.UnsignedLong; import com.google.gson.*; import java.lang.reflect.Type; /** - * Class used to encapsulate the Transfer operation related functionalities. + * Class used to encapsulate the TransferOperation operation related functionalities. * TODO: Add extensions support */ -public class Transfer extends BaseOperation { +public class TransferOperation extends BaseOperation { public static final String KEY_FEE = "fee"; public static final String KEY_AMOUNT = "amount"; public static final String KEY_EXTENSIONS = "extensions"; @@ -24,7 +23,7 @@ public class Transfer extends BaseOperation { private Memo memo; private String[] extensions; - public Transfer(UserAccount from, UserAccount to, AssetAmount transferAmount, AssetAmount fee){ + public TransferOperation(UserAccount from, UserAccount to, AssetAmount transferAmount, AssetAmount fee){ super(OperationType.transfer_operation); this.from = from; this.to = to; @@ -33,7 +32,7 @@ public class Transfer extends BaseOperation { this.memo = new Memo(); } - public Transfer(UserAccount from, UserAccount to, AssetAmount transferAmount){ + public TransferOperation(UserAccount from, UserAccount to, AssetAmount transferAmount){ super(OperationType.transfer_operation); this.from = from; this.to = to; @@ -41,15 +40,11 @@ public class Transfer extends BaseOperation { this.memo = new Memo(); } + @Override public void setFee(AssetAmount newFee){ this.fee = newFee; } - @Override - public byte getId() { - return (byte) this.type.ordinal(); - } - public UserAccount getFrom(){ return this.from; } @@ -78,8 +73,9 @@ public class Transfer extends BaseOperation { @Override public String toJsonString() { + //TODO: Evaluate using simple Gson class to return a simple string representation and drop the TransferSerializer class GsonBuilder gsonBuilder = new GsonBuilder(); - gsonBuilder.registerTypeAdapter(Transfer.class, new TransferSerializer()); + gsonBuilder.registerTypeAdapter(TransferOperation.class, new TransferSerializer()); return gsonBuilder.create().toJson(this); } @@ -97,10 +93,10 @@ public class Transfer extends BaseOperation { return array; } - public static class TransferSerializer implements JsonSerializer { + public static class TransferSerializer implements JsonSerializer { @Override - public JsonElement serialize(Transfer transfer, Type type, JsonSerializationContext jsonSerializationContext) { + public JsonElement serialize(TransferOperation transfer, Type type, JsonSerializationContext jsonSerializationContext) { JsonArray arrayRep = new JsonArray(); arrayRep.add(transfer.getId()); arrayRep.add(transfer.toJsonObject()); @@ -131,12 +127,12 @@ public class Transfer extends BaseOperation { * } * ] * - * It will convert this data into a nice Transfer object. + * It will convert this data into a nice TransferOperation object. */ - public static class TransferDeserializer implements JsonDeserializer { + public static class TransferDeserializer implements JsonDeserializer { @Override - public Transfer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + public TransferOperation deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { if(json.isJsonArray()){ // This block is used just to check if we are in the first step of the deserialization // when we are dealing with an array. @@ -146,7 +142,7 @@ public class Transfer extends BaseOperation { return null; }else{ // Calling itself recursively, this is only done once, so there will be no problems. - return context.deserialize(serializedTransfer.get(1), Transfer.class); + return context.deserialize(serializedTransfer.get(1), TransferOperation.class); } }else{ // This block is called in the second recursion and takes care of deserializing the @@ -160,7 +156,7 @@ public class Transfer extends BaseOperation { // Deserializing UserAccount objects UserAccount from = new UserAccount(jsonObject.get("from").getAsString()); UserAccount to = new UserAccount(jsonObject.get("to").getAsString()); - Transfer transfer = new Transfer(from, to, amount, fee); + TransferOperation transfer = new TransferOperation(from, to, amount, fee); return transfer; } } diff --git a/src/main/java/com/luminiasoft/bitshares/TransferTransactionBuilder.java b/src/main/java/com/luminiasoft/bitshares/TransferTransactionBuilder.java index 61f046c..c3ada97 100644 --- a/src/main/java/com/luminiasoft/bitshares/TransferTransactionBuilder.java +++ b/src/main/java/com/luminiasoft/bitshares/TransferTransactionBuilder.java @@ -16,6 +16,12 @@ public class TransferTransactionBuilder extends TransactionBuilder { private AssetAmount transferAmount; private AssetAmount feeAmount; + public TransferTransactionBuilder(){} + + public TransferTransactionBuilder(ECKey privKey) { + super(privKey); + } + public TransferTransactionBuilder setPrivateKey(ECKey key){ this.privateKey = key; return this; @@ -46,7 +52,8 @@ public class TransferTransactionBuilder extends TransactionBuilder { return this; } - public TransferTransactionBuilder addOperation(Transfer transferOperation){ + //TODO: Add support for multiple transfer operations in a single transaction + public TransferTransactionBuilder addOperation(TransferOperation transferOperation){ if(operations == null){ operations = new ArrayList(); } @@ -57,8 +64,6 @@ public class TransferTransactionBuilder extends TransactionBuilder { public Transaction build() throws MalformedTransactionException { if(privateKey == null){ throw new MalformedTransactionException("Missing private key information"); - }else if(blockData == null){ - throw new MalformedTransactionException("Missing block data information"); }else if(operations == null){ // If the operations list has not been set, we might be able to build one with the // previously provided data. But in order for this to work we have to have all @@ -73,11 +78,11 @@ public class TransferTransactionBuilder extends TransactionBuilder { if(transferAmount == null){ throw new MalformedTransactionException("Missing transfer amount information"); } - Transfer transferOperation; + TransferOperation transferOperation; if(feeAmount == null){ - transferOperation = new Transfer(sourceAccount, destinationAccount, transferAmount); + transferOperation = new TransferOperation(sourceAccount, destinationAccount, transferAmount); }else{ - transferOperation = new Transfer(sourceAccount, destinationAccount, transferAmount, feeAmount); + transferOperation = new TransferOperation(sourceAccount, destinationAccount, transferAmount, feeAmount); } operations.add(transferOperation); } diff --git a/src/main/java/com/luminiasoft/bitshares/UserAccount.java b/src/main/java/com/luminiasoft/bitshares/UserAccount.java index a99eb62..8ed3743 100644 --- a/src/main/java/com/luminiasoft/bitshares/UserAccount.java +++ b/src/main/java/com/luminiasoft/bitshares/UserAccount.java @@ -1,6 +1,6 @@ package com.luminiasoft.bitshares; -import com.google.gson.JsonObject; +import com.google.gson.*; import com.luminiasoft.bitshares.interfaces.ByteSerializable; import com.luminiasoft.bitshares.interfaces.JsonSerializable; @@ -8,6 +8,7 @@ import java.io.ByteArrayOutputStream; import java.io.DataOutput; import java.io.DataOutputStream; import java.io.IOException; +import java.lang.reflect.Type; /** * Class tha represents a graphene user account. @@ -15,15 +16,37 @@ import java.io.IOException; */ public class UserAccount extends GrapheneObject implements ByteSerializable, JsonSerializable { + public static final String PROXY_TO_SELF = "1.2.5"; + + private String accountName; + /** * Constructor that expects a user account in the string representation. * That is in the 1.2.x format. - * @param id: The string representing the account apiId. + * @param id: The string representing the user account. */ public UserAccount(String id) { super(id); } + /** + * Constructor that expects a user account withe the proper graphene object id and an account name. + * @param id: The string representing the user account. + * @param name: The name of this user account. + */ + public UserAccount(String id, String name){ + super(id); + this.accountName = name; + } + + /** + * Getter for the account name field. + * @return: The name of this account. + */ + public String getAccountName() { + return accountName; + } + @Override public byte[] toBytes() { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); @@ -45,4 +68,29 @@ public class UserAccount extends GrapheneObject implements ByteSerializable, Jso public JsonObject toJsonObject() { return null; } + + @Override + public String toString() { + return this.toJsonString(); + } + + /** + * Custom deserializer used to deserialize user accounts provided as response from the 'lookup_accounts' api call. + * This response contains serialized user accounts in the form [[{id1},{name1}][{id1},{name1}]]. + * + * For instance: + * [["bilthon-1","1.2.139205"],["bilthon-2","1.2.139207"],["bilthon-2016","1.2.139262"]] + * + * So this class will pick up this data and turn it into a UserAccount object. + */ + public static class UserAccountDeserializer implements JsonDeserializer { + + @Override + public UserAccount deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonArray array = json.getAsJsonArray(); + String name = array.get(0).getAsString(); + String id = array.get(1).getAsString(); + return new UserAccount(id, name); + } + } } diff --git a/src/main/java/com/luminiasoft/bitshares/Util.java b/src/main/java/com/luminiasoft/bitshares/Util.java index 03d00e8..5925ef4 100644 --- a/src/main/java/com/luminiasoft/bitshares/Util.java +++ b/src/main/java/com/luminiasoft/bitshares/Util.java @@ -1,6 +1,7 @@ package com.luminiasoft.bitshares; import org.tukaani.xz.FinishableOutputStream; +import com.google.common.primitives.Bytes; import org.tukaani.xz.LZMA2Options; import org.tukaani.xz.LZMAInputStream; import org.tukaani.xz.LZMAOutputStream; @@ -11,6 +12,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; import java.util.logging.Level; import java.util.logging.Logger; @@ -114,4 +116,33 @@ public class Util { } return null; } + + /** + * Returns an array of bytes with the underlying data used to represent an integer in the reverse form. + * This is useful for endianess switches, meaning that if you give this function a big-endian integer + * it will return it's little-endian bytes. + * @param input An Integer value. + * @return The array of bytes that represent this value in the reverse format. + */ + public static byte[] revertInteger(Integer input){ + return ByteBuffer.allocate(Integer.SIZE / 8).putInt(Integer.reverseBytes(input)).array(); + } + + /** + * Same operation as in the revertInteger function, but in this case for a short (2 bytes) value. + * @param input A Short value + * @return The array of bytes that represent this value in the reverse format. + */ + public static byte[] revertShort(Short input){ + return ByteBuffer.allocate(Short.SIZE / 8).putShort(Short.reverseBytes(input)).array(); + } + + /** + * Same operation as in the revertInteger function, but in this case for a long (8 bytes) value. + * @param input A Long value + * @return The array of bytes that represent this value in the reverse format. + */ + public static byte[] revertLong(Long input){ + return ByteBuffer.allocate(Long.SIZE / 8).putLong(Long.reverseBytes(input)).array(); + } } diff --git a/src/main/java/com/luminiasoft/bitshares/Vote.java b/src/main/java/com/luminiasoft/bitshares/Vote.java new file mode 100644 index 0000000..d8f674f --- /dev/null +++ b/src/main/java/com/luminiasoft/bitshares/Vote.java @@ -0,0 +1,33 @@ +package com.luminiasoft.bitshares; + +import com.luminiasoft.bitshares.interfaces.ByteSerializable; + +/** + * Created by nelson on 12/5/16. + */ +public class Vote implements ByteSerializable { + private int type; + private int instance; + + public Vote(String vote){ + String[] parts = vote.split(":"); + assert(parts.length == 2); + this.type = Integer.valueOf(parts[0]); + this.instance = Integer.valueOf(parts[1]); + } + + public Vote(int type, int instance){ + this.type = type; + this.instance = instance; + } + + @Override + public String toString() { + return String.format("%d:%d", this.type, this.instance); + } + + @Override + public byte[] toBytes() { + return new byte[] { (byte) this.instance, (byte) this.type }; + } +} diff --git a/src/main/java/com/luminiasoft/bitshares/errors/MalformedAddressException.java b/src/main/java/com/luminiasoft/bitshares/errors/MalformedAddressException.java new file mode 100644 index 0000000..a1c673d --- /dev/null +++ b/src/main/java/com/luminiasoft/bitshares/errors/MalformedAddressException.java @@ -0,0 +1,10 @@ +package com.luminiasoft.bitshares.errors; + +/** + * Created by nelson on 12/1/16. + */ +public class MalformedAddressException extends Exception { + public MalformedAddressException(String message){ + super(message); + } +} diff --git a/src/main/java/com/luminiasoft/bitshares/interfaces/GrapheneSerializable.java b/src/main/java/com/luminiasoft/bitshares/interfaces/GrapheneSerializable.java new file mode 100644 index 0000000..68ed06f --- /dev/null +++ b/src/main/java/com/luminiasoft/bitshares/interfaces/GrapheneSerializable.java @@ -0,0 +1,7 @@ +package com.luminiasoft.bitshares.interfaces; + +/** + * Interface used to group both ByteSerializable and JsonSerializable interfaces. + */ +public interface GrapheneSerializable extends ByteSerializable, JsonSerializable { +} diff --git a/src/main/java/com/luminiasoft/bitshares/interfaces/JsonSerializable.java b/src/main/java/com/luminiasoft/bitshares/interfaces/JsonSerializable.java index 78f8d29..e1f7377 100644 --- a/src/main/java/com/luminiasoft/bitshares/interfaces/JsonSerializable.java +++ b/src/main/java/com/luminiasoft/bitshares/interfaces/JsonSerializable.java @@ -5,7 +5,8 @@ import com.google.gson.JsonElement; import java.io.Serializable; /** - * Interface to be implemented by any entity for which makes sense to have a JSON-formatted string representation. + * Interface to be implemented by any entity for which makes sense to + * have a JSON-formatted string and object representation. */ public interface JsonSerializable extends Serializable { diff --git a/src/main/java/com/luminiasoft/bitshares/models/ApiCall.java b/src/main/java/com/luminiasoft/bitshares/models/ApiCall.java index 974c5aa..6d29147 100644 --- a/src/main/java/com/luminiasoft/bitshares/models/ApiCall.java +++ b/src/main/java/com/luminiasoft/bitshares/models/ApiCall.java @@ -88,6 +88,8 @@ public class ApiCall implements JsonSerializable { } } methodParams.add(array); + }else{ + System.out.println("Skipping parameter of type: "+this.params.get(i).getClass()); } } paramsArray.add(methodParams); diff --git a/src/main/java/com/luminiasoft/bitshares/models/HistoricalTransfer.java b/src/main/java/com/luminiasoft/bitshares/models/HistoricalTransfer.java index 763fbed..1e27519 100644 --- a/src/main/java/com/luminiasoft/bitshares/models/HistoricalTransfer.java +++ b/src/main/java/com/luminiasoft/bitshares/models/HistoricalTransfer.java @@ -1,8 +1,6 @@ package com.luminiasoft.bitshares.models; -import com.luminiasoft.bitshares.BaseOperation; -import com.luminiasoft.bitshares.GrapheneObject; -import com.luminiasoft.bitshares.Transfer; +import com.luminiasoft.bitshares.TransferOperation; /** * This class offers support to deserialization of transfer operations received by the API @@ -13,7 +11,7 @@ import com.luminiasoft.bitshares.Transfer; */ public class HistoricalTransfer { public String id; - public Transfer op; + public TransferOperation op; public Object[] result; public long block_num; public long trx_in_block; diff --git a/src/main/java/com/luminiasoft/bitshares/ws/GetRelativeAccountHistory.java b/src/main/java/com/luminiasoft/bitshares/ws/GetRelativeAccountHistory.java index 08af08e..d32806e 100644 --- a/src/main/java/com/luminiasoft/bitshares/ws/GetRelativeAccountHistory.java +++ b/src/main/java/com/luminiasoft/bitshares/ws/GetRelativeAccountHistory.java @@ -5,7 +5,7 @@ import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import com.luminiasoft.bitshares.AssetAmount; import com.luminiasoft.bitshares.RPC; -import com.luminiasoft.bitshares.Transfer; +import com.luminiasoft.bitshares.TransferOperation; import com.luminiasoft.bitshares.UserAccount; import com.luminiasoft.bitshares.interfaces.WitnessResponseListener; import com.luminiasoft.bitshares.models.ApiCall; @@ -119,7 +119,7 @@ public class GetRelativeAccountHistory extends WebSocketAdapter { System.out.println(frame.getPayloadText()); Type RelativeAccountHistoryResponse = new TypeToken>>(){}.getType(); GsonBuilder gsonBuilder = new GsonBuilder(); - gsonBuilder.registerTypeAdapter(Transfer.class, new Transfer.TransferDeserializer()); + gsonBuilder.registerTypeAdapter(TransferOperation.class, new TransferOperation.TransferDeserializer()); gsonBuilder.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetDeserializer()); WitnessResponse> transfersResponse = gsonBuilder.create().fromJson(response, RelativeAccountHistoryResponse); mListener.onSuccess(transfersResponse); diff --git a/src/main/java/com/luminiasoft/bitshares/ws/GetAccountIdByName.java b/src/main/java/com/luminiasoft/bitshares/ws/LookupAccounts.java similarity index 59% rename from src/main/java/com/luminiasoft/bitshares/ws/GetAccountIdByName.java rename to src/main/java/com/luminiasoft/bitshares/ws/LookupAccounts.java index 474b836..e6e44d7 100644 --- a/src/main/java/com/luminiasoft/bitshares/ws/GetAccountIdByName.java +++ b/src/main/java/com/luminiasoft/bitshares/ws/LookupAccounts.java @@ -1,9 +1,9 @@ package com.luminiasoft.bitshares.ws; -import com.google.gson.Gson; -import com.google.gson.JsonArray; +import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import com.luminiasoft.bitshares.RPC; +import com.luminiasoft.bitshares.UserAccount; import com.luminiasoft.bitshares.interfaces.WitnessResponseListener; import com.luminiasoft.bitshares.models.ApiCall; import com.luminiasoft.bitshares.models.BaseResponse; @@ -22,13 +22,22 @@ import java.util.Map; /** * Created by henry on 07/12/16. */ -public class GetAccountIdByName extends WebSocketAdapter { +public class LookupAccounts extends WebSocketAdapter { + public static final int DEFAULT_MAX = 1000; private final String accountName; + private int maxAccounts = DEFAULT_MAX; private final WitnessResponseListener mListener; - public GetAccountIdByName(String accountName, WitnessResponseListener listener){ + public LookupAccounts(String accountName, WitnessResponseListener listener){ this.accountName = accountName; + this.maxAccounts = DEFAULT_MAX; + this.mListener = listener; + } + + public LookupAccounts(String accountName, int maxAccounts, WitnessResponseListener listener){ + this.accountName = accountName; + this.maxAccounts = maxAccounts; this.mListener = listener; } @@ -36,19 +45,20 @@ public class GetAccountIdByName extends WebSocketAdapter { public void onConnected(WebSocket websocket, Map> headers) throws Exception { ArrayList accountParams = new ArrayList<>(); accountParams.add(this.accountName); - accountParams.add(50); - ApiCall getAccountByName = new ApiCall(0, RPC.CALL_GET_ID_BY_NAME, accountParams, "2.0", 1); + accountParams.add(this.maxAccounts); + ApiCall getAccountByName = new ApiCall(0, RPC.CALL_LOOKUP_ACCOUNTS, accountParams, RPC.VERSION, 1); websocket.sendText(getAccountByName.toJsonString()); } @Override public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception { + System.out.println("<<< "+frame.getPayloadText()); String response = frame.getPayloadText(); - Gson gson = new Gson(); - - Type GetAccountByNameResponse = new TypeToken>(){}.getType(); - WitnessResponse> witnessResponse = gson.fromJson(response, GetAccountByNameResponse); + Type LookupAccountsResponse = new TypeToken>>(){}.getType(); + GsonBuilder builder = new GsonBuilder(); + builder.registerTypeAdapter(UserAccount.class, new UserAccount.UserAccountDeserializer()); + WitnessResponse> witnessResponse = builder.create().fromJson(response, LookupAccountsResponse); if(witnessResponse.error != null){ this.mListener.onError(witnessResponse.error); }else{ @@ -58,6 +68,12 @@ public class GetAccountIdByName extends WebSocketAdapter { websocket.disconnect(); } + @Override + public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception { + if(frame.isTextFrame()) + System.out.println(">>> "+frame.getPayloadText()); + } + @Override public void onError(WebSocket websocket, WebSocketException cause) throws Exception { mListener.onError(new BaseResponse.Error(cause.getMessage())); @@ -69,4 +85,4 @@ public class GetAccountIdByName extends WebSocketAdapter { mListener.onError(new BaseResponse.Error(cause.getMessage())); websocket.disconnect(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/luminiasoft/bitshares/ws/TransactionBroadcastSequence.java b/src/main/java/com/luminiasoft/bitshares/ws/TransactionBroadcastSequence.java index fa1a312..386693b 100644 --- a/src/main/java/com/luminiasoft/bitshares/ws/TransactionBroadcastSequence.java +++ b/src/main/java/com/luminiasoft/bitshares/ws/TransactionBroadcastSequence.java @@ -1,13 +1,9 @@ package com.luminiasoft.bitshares.ws; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; -import com.luminiasoft.bitshares.BaseOperation; -import com.luminiasoft.bitshares.BlockData; -import com.luminiasoft.bitshares.RPC; -import com.luminiasoft.bitshares.Transaction; -import com.luminiasoft.bitshares.Transfer; -import com.luminiasoft.bitshares.TransferTransactionBuilder; +import com.luminiasoft.bitshares.*; import com.luminiasoft.bitshares.interfaces.WitnessResponseListener; import com.luminiasoft.bitshares.models.ApiCall; import com.luminiasoft.bitshares.models.BaseResponse; @@ -21,11 +17,7 @@ import com.neovisionaries.ws.client.WebSocketFrame; import java.io.Serializable; import java.lang.reflect.Type; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.TimeZone; +import java.util.*; /** * Class that will handle the transaction publication procedure. @@ -36,18 +28,15 @@ public class TransactionBroadcastSequence extends WebSocketAdapter { private final static int LOGIN_ID = 1; private final static int GET_NETWORK_BROADCAST_ID = 2; private final static int GET_NETWORK_DYNAMIC_PARAMETERS = 3; - private final static int BROADCAST_TRANSACTION = 4; - public final static int EXPIRATION_TIME = 30; + private final static int GET_REQUIRED_FEES = 4; + private final static int BROADCAST_TRANSACTION = 5; + private Asset feeAsset; private Transaction transaction; - private long expirationTime; - private String headBlockId; - private long headBlockNumber; private WitnessResponseListener mListener; private int currentId = 1; private int broadcastApiId = -1; - private int retries = 0; /** * Constructor of this class. The ids required @@ -56,8 +45,9 @@ public class TransactionBroadcastSequence extends WebSocketAdapter { * be implemented by the party interested in being notified about the success/failure * of the transaction broadcast operation. */ - public TransactionBroadcastSequence(Transaction transaction, WitnessResponseListener listener){ + public TransactionBroadcastSequence(Transaction transaction, Asset feeAsset, WitnessResponseListener listener){ this.transaction = transaction; + this.feeAsset = feeAsset; this.mListener = listener; } @@ -66,30 +56,39 @@ public class TransactionBroadcastSequence extends WebSocketAdapter { ArrayList loginParams = new ArrayList<>(); loginParams.add(null); loginParams.add(null); - ApiCall loginCall = new ApiCall(1, RPC.CALL_LOGIN, loginParams, "2.0", currentId); + ApiCall loginCall = new ApiCall(1, RPC.CALL_LOGIN, loginParams, RPC.VERSION, currentId); websocket.sendText(loginCall.toJsonString()); } @Override public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception { + if(frame.isTextFrame()) + System.out.println("<<< "+frame.getPayloadText()); String response = frame.getPayloadText(); Gson gson = new Gson(); BaseResponse baseResponse = gson.fromJson(response, BaseResponse.class); - if(baseResponse.error != null && baseResponse.error.message.indexOf("is_canonical") == -1){ + if(baseResponse.error != null){ mListener.onError(baseResponse.error); websocket.disconnect(); }else{ currentId++; ArrayList emptyParams = new ArrayList<>(); if(baseResponse.id == LOGIN_ID){ - ApiCall networkApiIdCall = new ApiCall(1, RPC.CALL_NETWORK_BROADCAST, emptyParams, "2.0", currentId); + ApiCall networkApiIdCall = new ApiCall(1, RPC.CALL_NETWORK_BROADCAST, emptyParams, RPC.VERSION, currentId); websocket.sendText(networkApiIdCall.toJsonString()); }else if(baseResponse.id == GET_NETWORK_BROADCAST_ID){ Type ApiIdResponse = new TypeToken>() {}.getType(); WitnessResponse witnessResponse = gson.fromJson(response, ApiIdResponse); broadcastApiId = witnessResponse.result; - ApiCall getDynamicParametersCall = new ApiCall(0, RPC.CALL_GET_DYNAMIC_GLOBAL_PROPERTIES, emptyParams, "2.0", currentId); + // Building API call to request dynamic network properties + ApiCall getDynamicParametersCall = new ApiCall(0, + RPC.CALL_GET_DYNAMIC_GLOBAL_PROPERTIES, + emptyParams, + RPC.VERSION, + currentId); + + // Requesting network properties websocket.sendText(getDynamicParametersCall.toJsonString()); }else if(baseResponse.id == GET_NETWORK_DYNAMIC_PARAMETERS){ Type DynamicGlobalPropertiesResponse = new TypeToken>(){}.getType(); @@ -100,76 +99,68 @@ public class TransactionBroadcastSequence extends WebSocketAdapter { dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); Date date = dateFormat.parse(dynamicProperties.time); - // Obtained block data - expirationTime = (date.getTime() / 1000) + EXPIRATION_TIME; - headBlockId = dynamicProperties.head_block_id; - headBlockNumber = dynamicProperties.head_block_number; + // Adjusting dynamic block data to every transaction + long expirationTime = (date.getTime() / 1000) + Transaction.DEFAULT_EXPIRATION_TIME; + String headBlockId = dynamicProperties.head_block_id; + long headBlockNumber = dynamicProperties.head_block_number; + transaction.setBlockData(new BlockData(headBlockNumber, headBlockId, expirationTime)); + + // Building a new API call to request fees information + ArrayList accountParams = new ArrayList<>(); + accountParams.add((Serializable) transaction.getOperations()); + accountParams.add(this.feeAsset.getObjectId()); + ApiCall getRequiredFees = new ApiCall(0, RPC.CALL_GET_REQUIRED_FEES, accountParams, RPC.VERSION, currentId); + + // Requesting fee amount + websocket.sendText(getRequiredFees.toJsonString()); + }else if(baseResponse.id == GET_REQUIRED_FEES){ + Type GetRequiredFeesResponse = new TypeToken>>(){}.getType(); + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetDeserializer()); + WitnessResponse> requiredFeesResponse = gsonBuilder.create().fromJson(response, GetRequiredFeesResponse); + + // Setting fees + transaction.setFees(requiredFeesResponse.result); + ArrayList transactions = new ArrayList<>(); + transactions.add(transaction); - ArrayList transactionList = new ArrayList<>(); - transactionList.add(transaction); ApiCall call = new ApiCall(broadcastApiId, RPC.CALL_BROADCAST_TRANSACTION, - transactionList, - "2.0", + transactions, + RPC.VERSION, currentId); - // Finally sending transaction + // Finally broadcasting transaction websocket.sendText(call.toJsonString()); }else if(baseResponse.id >= BROADCAST_TRANSACTION){ Type WitnessResponseType = new TypeToken>(){}.getType(); WitnessResponse> witnessResponse = gson.fromJson(response, WitnessResponseType); - if(witnessResponse.error == null){ - mListener.onSuccess(witnessResponse); - websocket.disconnect(); - }else{ - if(witnessResponse.error.message.indexOf("is_canonical") != -1 && retries < 10){ - /* - * This is a very ugly hack, but it will do for now. - * - * The issue is that the witness is complaining about the signature not - * being canonical even though the bitcoinj ECKey.ECDSASignature.isCanonical() - * method says it is! We'll have to dive deeper into this issue and avoid - * this error altogether - * - * But this MUST BE FIXED! Since this hack will only work for transactions - * with ONE transfer operation. - */ - retries++; - List operations = this.transaction.getOperations(); - Transfer transfer = (Transfer) operations.get(0); - transaction = new TransferTransactionBuilder() - .setSource(transfer.getFrom()) - .setDestination(transfer.getTo()) - .setAmount(transfer.getAmount()) - .setFee(transfer.getFee()) - .setBlockData(new BlockData(headBlockNumber, headBlockId, expirationTime + EXPIRATION_TIME)) - .setPrivateKey(transaction.getPrivateKey()) - .build(); - ArrayList transactionList = new ArrayList<>(); - transactionList.add(transaction); - ApiCall call = new ApiCall(broadcastApiId, - RPC.CALL_BROADCAST_TRANSACTION, - transactionList, - "2.0", - currentId); - websocket.sendText(call.toJsonString()); - }else{ - mListener.onError(witnessResponse.error); - websocket.disconnect(); - } - } + mListener.onSuccess(witnessResponse); + websocket.disconnect(); } } } + @Override + public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception { + if(frame.isTextFrame()){ + System.out.println(">>> "+frame.getPayloadText()); + } + } + @Override public void onError(WebSocket websocket, WebSocketException cause) throws Exception { + System.out.println("onError. cause: "+cause.getMessage()); mListener.onError(new BaseResponse.Error(cause.getMessage())); websocket.disconnect(); } @Override public void handleCallbackError(WebSocket websocket, Throwable cause) throws Exception { + System.out.println("handleCallbackError. cause: "+cause.getMessage()+", error: "+cause.getClass()); + for (StackTraceElement element : cause.getStackTrace()){ + System.out.println(element.getFileName()+"#"+element.getClassName()+":"+element.getLineNumber()); + } mListener.onError(new BaseResponse.Error(cause.getMessage())); websocket.disconnect(); }