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..353f6ac --- /dev/null +++ b/src/main/java/com/luminiasoft/bitshares/AccountUpdateOperation.java @@ -0,0 +1,69 @@ +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; + +/** + * 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_EXTENSIONS = "extensions"; + + private UserAccount account; + private AssetAmount fee; + private Authority owner; + private Authority active; + private Extensions extensions; + + public AccountUpdateOperation(UserAccount account, Authority owner, Authority active, AssetAmount fee){ + super(OperationType.account_update_operation); + this.account = account; + this.owner = owner; + this.active = active; + this.fee = fee; + extensions = new Extensions(); + } + + public AccountUpdateOperation(UserAccount account, Authority owner, Authority active){ + this(account, owner, active, new AssetAmount(UnsignedLong.valueOf(0), new Asset("1.3.0"))); + } + + public void setFee(AssetAmount fee){ + this.fee = fee; + } + + @Override + public String toJsonString() { + Gson gson = new Gson(); + return gson.toJson(this); + } + + @Override + public JsonElement toJsonObject() { + JsonObject accountUpdate = new JsonObject(); + accountUpdate.add(KEY_FEE, fee.toJsonObject()); + accountUpdate.add(KEY_ACCOUNT, account.toJsonObject()); + accountUpdate.add(KEY_OWNER, owner.toJsonObject()); + accountUpdate.add(KEY_ACTIVE, active.toJsonObject()); + accountUpdate.add(KEY_EXTENSIONS, extensions.toJsonObject()); + return accountUpdate; + } + + @Override + public byte[] toBytes() { + byte[] feeBytes = fee.toBytes(); + byte[] accountBytes = account.toBytes(); + byte[] ownerBytes = owner.toBytes(); + byte[] activeBytes = active.toBytes(); + byte[] extensionBytes = extensions.toBytes(); + return Bytes.concat(feeBytes, accountBytes, ownerBytes, activeBytes, extensionBytes); + } +} diff --git a/src/main/java/com/luminiasoft/bitshares/Authority.java b/src/main/java/com/luminiasoft/bitshares/Authority.java index a9e9b18..98e991c 100644 --- a/src/main/java/com/luminiasoft/bitshares/Authority.java +++ b/src/main/java/com/luminiasoft/bitshares/Authority.java @@ -1,27 +1,32 @@ package com.luminiasoft.bitshares; +import com.google.common.primitives.Bytes; import com.google.gson.JsonElement; import com.luminiasoft.bitshares.errors.MalformedAddressException; import com.luminiasoft.bitshares.interfaces.ByteSerializable; import com.luminiasoft.bitshares.interfaces.JsonSerializable; -import java.util.HashMap; +import java.nio.ByteBuffer; +import java.util.*; /** * Created by nelson on 11/30/16. */ -public class Authority implements JsonSerializable { +public class Authority implements JsonSerializable, ByteSerializable { private long weight_threshold; - private HashMap address_auths; - private HashMap account_auths; - private HashMap key_auths; + private HashMap account_auths; + private HashMap key_auths; + private Extensions extensions; - public Authority(HashMap keyAuths) throws MalformedAddressException { - key_auths = new HashMap(); + public Authority(long weight_threshold, HashMap keyAuths) throws MalformedAddressException { + this.weight_threshold = weight_threshold; + key_auths = new HashMap(); for(String key : keyAuths.keySet()){ Address address = new Address(key); key_auths.put(address.getPublicKey(), keyAuths.get(key)); } + account_auths = new HashMap(); + extensions = new Extensions(); } @Override @@ -33,4 +38,32 @@ public class Authority implements JsonSerializable { public JsonElement toJsonObject() { return null; } + + @Override + public byte[] toBytes() { + List byteArray = new ArrayList(); + // Adding number of authorities + byteArray.add(Byte.valueOf((byte) (account_auths.size() + key_auths.size()))); + + // Weight threshold + byteArray.addAll(Bytes.asList(Util.revertInteger(new Integer((int) weight_threshold)))); + + // Number of account authorities + byteArray.add((byte) account_auths.size()); + + //TODO: Add account authorities + + // Number of key authorities + byteArray.add((byte) key_auths.size()); + + 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); + } } diff --git a/src/main/java/com/luminiasoft/bitshares/BaseOperation.java b/src/main/java/com/luminiasoft/bitshares/BaseOperation.java index eaef155..414a88a 100644 --- a/src/main/java/com/luminiasoft/bitshares/BaseOperation.java +++ b/src/main/java/com/luminiasoft/bitshares/BaseOperation.java @@ -14,7 +14,9 @@ public abstract class BaseOperation implements ByteSerializable, JsonSerializabl this.type = type; } - public abstract byte getId(); + public byte getId() { + return (byte) this.type.ordinal(); + } 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..102dae8 --- /dev/null +++ b/src/main/java/com/luminiasoft/bitshares/Extensions.java @@ -0,0 +1,41 @@ +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 { + 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[0]; + } + + 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 69da478..c162eb3 100644 --- a/src/main/java/com/luminiasoft/bitshares/Main.java +++ b/src/main/java/com/luminiasoft/bitshares/Main.java @@ -49,7 +49,7 @@ public class Main { // test.testTransactionBroadcastSequence(); // test.testAccountLookupDeserialization(); // test.testPrivateKeyManipulations(); - test.testPublicKeyManipulations(); +// test.testPublicKeyManipulations(); // test.testGetAccountByName(); // test.testGetRequiredFees(); // test.testRandomNumberGeneration(); @@ -61,5 +61,6 @@ public class Main { // test.testingInvoiceGeneration(); // test.testCompression(); // test.testCreateBinFile(); + test.testAccountUpdateOperationSerialization(); } } diff --git a/src/main/java/com/luminiasoft/bitshares/Test.java b/src/main/java/com/luminiasoft/bitshares/Test.java index 8dba981..62ea660 100644 --- a/src/main/java/com/luminiasoft/bitshares/Test.java +++ b/src/main/java/com/luminiasoft/bitshares/Test.java @@ -743,4 +743,21 @@ public class Test { byte[] fileOutput = FileBin.getBytesFromBrainKey(Main.BRAIN_KEY, "123456","bithon-83"); System.out.println("fileOutput " + Arrays.toString(fileOutput)); } + + public void testAccountUpdateOperationSerialization(){ + UserAccount account = new UserAccount("1.2.138632"); + AssetAmount fee = new AssetAmount(UnsignedLong.valueOf("4294967295"), new Asset("1.3.0")); + HashMap keyAuths = new HashMap<>(); + keyAuths.put("BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY", 65535); + try { + Authority owner = new Authority(1, keyAuths); + Authority active = new Authority(1, keyAuths); + AccountUpdateOperation operation = new AccountUpdateOperation(account, owner, active, fee); + byte[] serializedOperation = operation.toBytes(); + System.out.println("serialized operation"); + System.out.println(Util.bytesToHex(serializedOperation)); + } catch (MalformedAddressException e) { + System.out.println("MalformedAddressException. Msg: "+e.getMessage()); + } + } } diff --git a/src/main/java/com/luminiasoft/bitshares/Transaction.java b/src/main/java/com/luminiasoft/bitshares/Transaction.java index e0f4341..109bb98 100644 --- a/src/main/java/com/luminiasoft/bitshares/Transaction.java +++ b/src/main/java/com/luminiasoft/bitshares/Transaction.java @@ -38,7 +38,7 @@ public class Transaction implements ByteSerializable, JsonSerializable { private ECKey privateKey; private BlockData blockData; private List operations; - private List extensions; + private List extensions; /** * Transaction constructor. @@ -50,7 +50,7 @@ public class Transaction implements ByteSerializable, JsonSerializable { this.privateKey = DumpedPrivateKey.fromBase58(null, wif).getKey(); this.blockData = block_data; this.operations = operation_list; - this.extensions = new ArrayList(); + this.extensions = new ArrayList(); } /** @@ -63,7 +63,7 @@ public class Transaction implements ByteSerializable, JsonSerializable { this.privateKey = privateKey; this.blockData = blockData; this.operations = operationList; - this.extensions = new ArrayList(); + this.extensions = new ArrayList(); } public ECKey getPrivateKey(){ @@ -143,8 +143,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/TransferOperation.java b/src/main/java/com/luminiasoft/bitshares/TransferOperation.java index 3940e66..87692a2 100644 --- a/src/main/java/com/luminiasoft/bitshares/TransferOperation.java +++ b/src/main/java/com/luminiasoft/bitshares/TransferOperation.java @@ -44,11 +44,6 @@ public class TransferOperation extends BaseOperation { this.fee = newFee; } - @Override - public byte getId() { - return (byte) this.type.ordinal(); - } - public UserAccount getFrom(){ return this.from; } @@ -77,6 +72,7 @@ public class TransferOperation 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(TransferOperation.class, new TransferSerializer()); return gsonBuilder.create().toJson(this); diff --git a/src/main/java/com/luminiasoft/bitshares/TransferTransactionBuilder.java b/src/main/java/com/luminiasoft/bitshares/TransferTransactionBuilder.java index 97d07a2..10be1f7 100644 --- a/src/main/java/com/luminiasoft/bitshares/TransferTransactionBuilder.java +++ b/src/main/java/com/luminiasoft/bitshares/TransferTransactionBuilder.java @@ -46,6 +46,7 @@ public class TransferTransactionBuilder extends TransactionBuilder { return this; } + //TODO: Add support for multiple transfer operations in a single transaction public TransferTransactionBuilder addOperation(TransferOperation transferOperation){ if(operations == null){ operations = new ArrayList(); diff --git a/src/main/java/com/luminiasoft/bitshares/Util.java b/src/main/java/com/luminiasoft/bitshares/Util.java index a333deb..f02fbbe 100644 --- a/src/main/java/com/luminiasoft/bitshares/Util.java +++ b/src/main/java/com/luminiasoft/bitshares/Util.java @@ -1,5 +1,6 @@ package com.luminiasoft.bitshares; +import com.google.common.primitives.Bytes; import org.tukaani.xz.LZMA2Options; import org.tukaani.xz.LZMAInputStream; import org.tukaani.xz.LZMAOutputStream; @@ -7,6 +8,7 @@ import org.tukaani.xz.LZMAOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.nio.ByteBuffer; import java.util.logging.Level; import java.util.logging.Logger; import org.tukaani.xz.XZOutputStream; @@ -99,4 +101,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.BYTES).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.BYTES).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.BYTES).putLong(Long.reverseBytes(input)).array(); + } }