From 07dbbca3714d2ba3baeda8d964cb38fd7b9cbad3 Mon Sep 17 00:00:00 2001 From: "Nelson R. Perez" Date: Fri, 2 Dec 2016 14:05:36 -0500 Subject: [PATCH 1/7] Initial steps to add account_update operation support --- .../com/luminiasoft/bitshares/Address.java | 65 ++++++++++-------- .../com/luminiasoft/bitshares/Authority.java | 35 ++++++++++ .../java/com/luminiasoft/bitshares/Main.java | 3 +- .../com/luminiasoft/bitshares/PublicKey.java | 20 ++++++ .../java/com/luminiasoft/bitshares/Test.java | 66 +++++++++++-------- .../bitshares/TransactionBuilder.java | 2 +- ...ransaction.java => TransferOperation.java} | 26 +++++--- .../bitshares/TransferTransactionBuilder.java | 4 +- .../errors/MalformedAddressException.java | 10 +++ .../ws/TransactionBroadcastSequence.java | 32 ++++----- 10 files changed, 176 insertions(+), 87 deletions(-) create mode 100644 src/main/java/com/luminiasoft/bitshares/Authority.java create mode 100644 src/main/java/com/luminiasoft/bitshares/PublicKey.java rename src/main/java/com/luminiasoft/bitshares/{Transaction.java => TransferOperation.java} (89%) create mode 100644 src/main/java/com/luminiasoft/bitshares/errors/MalformedAddressException.java 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..eae9224 --- /dev/null +++ b/src/main/java/com/luminiasoft/bitshares/Authority.java @@ -0,0 +1,35 @@ +package com.luminiasoft.bitshares; + +import com.google.gson.JsonElement; +import com.luminiasoft.bitshares.errors.MalformedAddressException; +import com.luminiasoft.bitshares.interfaces.JsonSerializable; + +import java.util.HashMap; + +/** + * Created by nelson on 11/30/16. + */ +public class Authority implements JsonSerializable { + private long weight_threshold; + private HashMap address_auths; + private HashMap account_auths; + private HashMap key_auths; + + public Authority(HashMap keyAuths) throws MalformedAddressException { + key_auths = new HashMap(); + for(String key : keyAuths.keySet()){ + Address address = new Address(key); + key_auths.put(address.getPublicKey(), keyAuths.get(key)); + } + } + + @Override + public String toJsonString() { + return null; + } + + @Override + public JsonElement toJsonObject() { + return null; + } +} diff --git a/src/main/java/com/luminiasoft/bitshares/Main.java b/src/main/java/com/luminiasoft/bitshares/Main.java index 01839ae..69da478 100644 --- a/src/main/java/com/luminiasoft/bitshares/Main.java +++ b/src/main/java/com/luminiasoft/bitshares/Main.java @@ -38,7 +38,7 @@ public class Main { // e.printStackTrace(); // } // test.testCustomSerializer(); - test.testUserAccountSerialization(); +// test.testUserAccountSerialization(); // test.testTransactionSerialization(); // test.testLoginSerialization(); // test.testNetworkBroadcastSerialization(); @@ -49,6 +49,7 @@ public class Main { // test.testTransactionBroadcastSequence(); // test.testAccountLookupDeserialization(); // test.testPrivateKeyManipulations(); + test.testPublicKeyManipulations(); // test.testGetAccountByName(); // test.testGetRequiredFees(); // test.testRandomNumberGeneration(); 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..31961ae --- /dev/null +++ b/src/main/java/com/luminiasoft/bitshares/PublicKey.java @@ -0,0 +1,20 @@ +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; + } + + @Override + public byte[] toBytes() { + return publicKey.getPubKey(); + } +} diff --git a/src/main/java/com/luminiasoft/bitshares/Test.java b/src/main/java/com/luminiasoft/bitshares/Test.java index 02a25de..01bf02f 100644 --- a/src/main/java/com/luminiasoft/bitshares/Test.java +++ b/src/main/java/com/luminiasoft/bitshares/Test.java @@ -4,6 +4,7 @@ import com.google.common.primitives.UnsignedLong; import com.google.gson.Gson; import com.google.gson.GsonBuilder; 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,20 +13,14 @@ import com.luminiasoft.bitshares.ws.*; import com.neovisionaries.ws.client.*; import org.bitcoinj.core.*; import org.spongycastle.crypto.Digest; -import org.spongycastle.crypto.digests.RIPEMD128Digest; import org.spongycastle.crypto.digests.RIPEMD160Digest; import org.spongycastle.crypto.digests.SHA512Digest; import org.spongycastle.crypto.prng.DigestRandomGenerator; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLServerSocketFactory; -import javax.net.ssl.SSLSocketFactory; import java.io.*; import java.lang.reflect.Type; -import java.net.URL; -import java.nio.file.Paths; import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; import java.text.SimpleDateFormat; import java.util.*; import java.util.logging.Level; @@ -40,10 +35,10 @@ public class Test { public static final String OPENLEDGER_WITNESS_URL = "wss://bitshares.openledger.info/ws"; // public static final String WITNESS_URL = "wss://fr.blockpay.ch:8089"; - private Transaction transaction; + private TransferOperation transferOperation; - public Transaction getTransaction() { - return transaction; + public TransferOperation getTransferOperation() { + return transferOperation; } private WitnessResponseListener mListener = new WitnessResponseListener() { @@ -114,27 +109,27 @@ public class Test { }; public ECKey.ECDSASignature testSigning() { - byte[] serializedTransaction = this.transaction.toBytes(); + byte[] serializedTransaction = this.transferOperation.toBytes(); Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction)); byte[] bytesDigest = hash.getBytes(); - ECKey sk = transaction.getPrivateKey(); + ECKey sk = transferOperation.getPrivateKey(); ECKey.ECDSASignature signature = sk.sign(hash); return signature; } public String testSigningMessage() { - byte[] serializedTransaction = this.transaction.toBytes(); + byte[] serializedTransaction = this.transferOperation.toBytes(); Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction)); - ECKey sk = transaction.getPrivateKey(); + ECKey sk = transferOperation.getPrivateKey(); return sk.signMessage(hash.toString()); } public byte[] signMessage() { - byte[] serializedTransaction = this.transaction.toBytes(); + byte[] serializedTransaction = this.transferOperation.toBytes(); Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction)); System.out.println(">> digest <<"); System.out.println(Util.bytesToHex(hash.getBytes())); - ECKey sk = transaction.getPrivateKey(); + ECKey sk = transferOperation.getPrivateKey(); System.out.println("Private key bytes"); System.out.println(Util.bytesToHex(sk.getPrivKeyBytes())); boolean isCanonical = false; @@ -189,9 +184,9 @@ public class Test { 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); - byte[] serializedTransaction = this.transaction.toBytes(); - System.out.println("Serialized transaction"); + this.transferOperation = new TransferOperation(Main.WIF, blockData, operations); + byte[] serializedTransaction = this.transferOperation.toBytes(); + System.out.println("Serialized transferOperation"); System.out.println(Util.bytesToHex(serializedTransaction)); } @@ -340,7 +335,7 @@ public class Test { public void testTransactionSerialization() { try { - Transaction transaction = new TransferTransactionBuilder() + TransferOperation transferOperation = new TransferTransactionBuilder() .setSource(new UserAccount("1.2.138632")) .setDestination(new UserAccount("1.2.129848")) .setAmount(new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120"))) @@ -350,9 +345,9 @@ public class Test { .build(); ArrayList transactionList = new ArrayList<>(); - transactionList.add(transaction); + transactionList.add(transferOperation); - byte[] signature = transaction.getGrapheneSignature(); + byte[] signature = transferOperation.getGrapheneSignature(); System.out.println(Util.bytesToHex(signature)); ApiCall call = new ApiCall(4, "call", "broadcast_transaction", transactionList, "2.0", 1); String jsonCall = call.toJsonString(); @@ -425,7 +420,7 @@ public class Test { }; try { - Transaction transaction = new TransferTransactionBuilder() + TransferOperation transferOperation = new TransferTransactionBuilder() .setSource(new UserAccount("1.2.138632")) .setDestination(new UserAccount("1.2.129848")) .setAmount(new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120"))) @@ -435,9 +430,9 @@ public class Test { .build(); ArrayList transactionList = new ArrayList<>(); - transactionList.add(transaction); + transactionList.add(transferOperation); - transactionList.add(transaction); + transactionList.add(transferOperation); SSLContext context = null; context = NaiveSSLContext.getInstance("TLS"); @@ -448,7 +443,7 @@ public class Test { WebSocket mWebSocket = factory.createSocket(OPENLEDGER_WITNESS_URL); - mWebSocket.addListener(new TransactionBroadcastSequence(transaction, listener)); + mWebSocket.addListener(new TransactionBroadcastSequence(transferOperation, listener)); mWebSocket.connect(); } catch (MalformedTransactionException e) { @@ -474,14 +469,29 @@ public class Test { } public void testPrivateKeyManipulations() { - ECKey privateKey = DumpedPrivateKey.fromBase58(null, Main.WIF).getKey(); + String brainKeyWords = "UNMATE AURIGAL NAVET WAVICLE REWOVE ABBOTCY COWHERB OUTKICK STOPPER JUSSORY BEAMLET WIRY"; + 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() { diff --git a/src/main/java/com/luminiasoft/bitshares/TransactionBuilder.java b/src/main/java/com/luminiasoft/bitshares/TransactionBuilder.java index 05249a8..4d2e1ac 100644 --- a/src/main/java/com/luminiasoft/bitshares/TransactionBuilder.java +++ b/src/main/java/com/luminiasoft/bitshares/TransactionBuilder.java @@ -11,5 +11,5 @@ public abstract class TransactionBuilder { protected ECKey privateKey; protected BlockData blockData; - public abstract Transaction build() throws MalformedTransactionException; + public abstract TransferOperation build() throws MalformedTransactionException; } diff --git a/src/main/java/com/luminiasoft/bitshares/Transaction.java b/src/main/java/com/luminiasoft/bitshares/TransferOperation.java similarity index 89% rename from src/main/java/com/luminiasoft/bitshares/Transaction.java rename to src/main/java/com/luminiasoft/bitshares/TransferOperation.java index 58ad0b8..ad6e8f4 100644 --- a/src/main/java/com/luminiasoft/bitshares/Transaction.java +++ b/src/main/java/com/luminiasoft/bitshares/TransferOperation.java @@ -25,7 +25,7 @@ import java.util.TimeZone; /** * Class used to represent a generic graphene transaction. */ -public class Transaction implements ByteSerializable, JsonSerializable { +public class TransferOperation extends BaseOperation implements ByteSerializable, JsonSerializable { private final String TAG = this.getClass().getName(); public static final String KEY_EXPIRATION = "expiration"; @@ -41,12 +41,13 @@ public class Transaction implements ByteSerializable, JsonSerializable { private List extensions; /** - * Transaction constructor. + * TransferOperation 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){ + public TransferOperation(String wif, BlockData block_data, List operation_list){ + super(OperationType.transfer_operation); this.privateKey = DumpedPrivateKey.fromBase58(null, wif).getKey(); this.blockData = block_data; this.operations = operation_list; @@ -54,12 +55,13 @@ public class Transaction implements ByteSerializable, JsonSerializable { } /** - * Transaction constructor. + * TransferOperation constructor. * @param privateKey : Instance of a ECKey containing the private key that will be used to sign this transaction. * @param blockData : Block data containing important information used to sign a transaction. * @param operationList : List of operations to include in the transaction. */ - public Transaction(ECKey privateKey, BlockData blockData, List operationList){ + public TransferOperation(ECKey privateKey, BlockData blockData, List operationList){ + super(OperationType.transfer_operation); this.privateKey = privateKey; this.blockData = blockData; this.operations = operationList; @@ -117,6 +119,12 @@ public class Transaction implements ByteSerializable, JsonSerializable { } return sigData; } + + @Override + public byte getId() { + return 0; + } + /** * Method that creates a serialized byte array with compact information about this transaction * that is needed for the creation of a signature. @@ -157,7 +165,7 @@ public class Transaction implements ByteSerializable, JsonSerializable { @Override public String toJsonString() { GsonBuilder gsonBuilder = new GsonBuilder(); - gsonBuilder.registerTypeAdapter(Transaction.class, new TransactionSerializer()); + gsonBuilder.registerTypeAdapter(TransferOperation.class, new TransactionSerializer()); return gsonBuilder.create().toJson(this); } @@ -200,11 +208,11 @@ public class Transaction implements ByteSerializable, JsonSerializable { } - class TransactionSerializer implements JsonSerializer { + class TransactionSerializer implements JsonSerializer { @Override - public JsonElement serialize(Transaction transaction, Type type, JsonSerializationContext jsonSerializationContext) { - return transaction.toJsonObject(); + public JsonElement serialize(TransferOperation transferOperation, Type type, JsonSerializationContext jsonSerializationContext) { + return transferOperation.toJsonObject(); } } } \ No newline at end of file diff --git a/src/main/java/com/luminiasoft/bitshares/TransferTransactionBuilder.java b/src/main/java/com/luminiasoft/bitshares/TransferTransactionBuilder.java index 61f046c..8ea9ac7 100644 --- a/src/main/java/com/luminiasoft/bitshares/TransferTransactionBuilder.java +++ b/src/main/java/com/luminiasoft/bitshares/TransferTransactionBuilder.java @@ -54,7 +54,7 @@ public class TransferTransactionBuilder extends TransactionBuilder { } @Override - public Transaction build() throws MalformedTransactionException { + public TransferOperation build() throws MalformedTransactionException { if(privateKey == null){ throw new MalformedTransactionException("Missing private key information"); }else if(blockData == null){ @@ -81,6 +81,6 @@ public class TransferTransactionBuilder extends TransactionBuilder { } operations.add(transferOperation); } - return new Transaction(privateKey, blockData, operations); + return new TransferOperation(privateKey, blockData, operations); } } 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/ws/TransactionBroadcastSequence.java b/src/main/java/com/luminiasoft/bitshares/ws/TransactionBroadcastSequence.java index fa1a312..bbd6b68 100644 --- a/src/main/java/com/luminiasoft/bitshares/ws/TransactionBroadcastSequence.java +++ b/src/main/java/com/luminiasoft/bitshares/ws/TransactionBroadcastSequence.java @@ -2,12 +2,8 @@ package com.luminiasoft.bitshares.ws; import com.google.gson.Gson; 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.TransferOperation; import com.luminiasoft.bitshares.interfaces.WitnessResponseListener; import com.luminiasoft.bitshares.models.ApiCall; import com.luminiasoft.bitshares.models.BaseResponse; @@ -28,7 +24,7 @@ import java.util.Map; import java.util.TimeZone; /** - * Class that will handle the transaction publication procedure. + * Class that will handle the transferOperation publication procedure. */ public class TransactionBroadcastSequence extends WebSocketAdapter { private final String TAG = this.getClass().getName(); @@ -39,7 +35,7 @@ public class TransactionBroadcastSequence extends WebSocketAdapter { private final static int BROADCAST_TRANSACTION = 4; public final static int EXPIRATION_TIME = 30; - private Transaction transaction; + private TransferOperation transferOperation; private long expirationTime; private String headBlockId; private long headBlockNumber; @@ -51,13 +47,13 @@ public class TransactionBroadcastSequence extends WebSocketAdapter { /** * Constructor of this class. The ids required - * @param transaction: The transaction to be broadcasted. + * @param transferOperation: The transferOperation to be broadcasted. * @param listener: A class implementing the WitnessResponseListener interface. This should * be implemented by the party interested in being notified about the success/failure - * of the transaction broadcast operation. + * of the transferOperation broadcast operation. */ - public TransactionBroadcastSequence(Transaction transaction, WitnessResponseListener listener){ - this.transaction = transaction; + public TransactionBroadcastSequence(TransferOperation transferOperation, WitnessResponseListener listener){ + this.transferOperation = transferOperation; this.mListener = listener; } @@ -106,14 +102,14 @@ public class TransactionBroadcastSequence extends WebSocketAdapter { headBlockNumber = dynamicProperties.head_block_number; ArrayList transactionList = new ArrayList<>(); - transactionList.add(transaction); + transactionList.add(transferOperation); ApiCall call = new ApiCall(broadcastApiId, RPC.CALL_BROADCAST_TRANSACTION, transactionList, "2.0", currentId); - // Finally sending transaction + // Finally sending transferOperation websocket.sendText(call.toJsonString()); }else if(baseResponse.id >= BROADCAST_TRANSACTION){ Type WitnessResponseType = new TypeToken>(){}.getType(); @@ -135,18 +131,18 @@ public class TransactionBroadcastSequence extends WebSocketAdapter { * with ONE transfer operation. */ retries++; - List operations = this.transaction.getOperations(); + List operations = this.transferOperation.getOperations(); Transfer transfer = (Transfer) operations.get(0); - transaction = new TransferTransactionBuilder() + transferOperation = 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()) + .setPrivateKey(transferOperation.getPrivateKey()) .build(); ArrayList transactionList = new ArrayList<>(); - transactionList.add(transaction); + transactionList.add(transferOperation); ApiCall call = new ApiCall(broadcastApiId, RPC.CALL_BROADCAST_TRANSACTION, transactionList, From 8b81c30fa948aeec7c9cfe6471d0caf07e0e30b3 Mon Sep 17 00:00:00 2001 From: "Nelson R. Perez" Date: Fri, 2 Dec 2016 16:24:42 -0500 Subject: [PATCH 2/7] Reverting wrong renaming --- .../com/luminiasoft/bitshares/Authority.java | 1 + .../java/com/luminiasoft/bitshares/Test.java | 42 +-- .../luminiasoft/bitshares/Transaction.java | 211 ++++++++++++ .../bitshares/TransactionBuilder.java | 2 +- .../com/luminiasoft/bitshares/Transfer.java | 168 ---------- .../bitshares/TransferOperation.java | 301 ++++++++---------- .../bitshares/TransferTransactionBuilder.java | 12 +- .../bitshares/models/HistoricalTransfer.java | 6 +- .../ws/GetRelativeAccountHistory.java | 4 +- .../ws/TransactionBroadcastSequence.java | 28 +- 10 files changed, 383 insertions(+), 392 deletions(-) create mode 100644 src/main/java/com/luminiasoft/bitshares/Transaction.java delete mode 100644 src/main/java/com/luminiasoft/bitshares/Transfer.java diff --git a/src/main/java/com/luminiasoft/bitshares/Authority.java b/src/main/java/com/luminiasoft/bitshares/Authority.java index eae9224..a9e9b18 100644 --- a/src/main/java/com/luminiasoft/bitshares/Authority.java +++ b/src/main/java/com/luminiasoft/bitshares/Authority.java @@ -2,6 +2,7 @@ package com.luminiasoft.bitshares; 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; diff --git a/src/main/java/com/luminiasoft/bitshares/Test.java b/src/main/java/com/luminiasoft/bitshares/Test.java index 01bf02f..8dba981 100644 --- a/src/main/java/com/luminiasoft/bitshares/Test.java +++ b/src/main/java/com/luminiasoft/bitshares/Test.java @@ -35,10 +35,10 @@ public class Test { public static final String OPENLEDGER_WITNESS_URL = "wss://bitshares.openledger.info/ws"; // public static final String WITNESS_URL = "wss://fr.blockpay.ch:8089"; - private TransferOperation transferOperation; + private Transaction transaction; - public TransferOperation getTransferOperation() { - return transferOperation; + public Transaction getTransaction() { + return transaction; } private WitnessResponseListener mListener = new WitnessResponseListener() { @@ -109,27 +109,27 @@ public class Test { }; public ECKey.ECDSASignature testSigning() { - byte[] serializedTransaction = this.transferOperation.toBytes(); + byte[] serializedTransaction = this.transaction.toBytes(); Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction)); byte[] bytesDigest = hash.getBytes(); - ECKey sk = transferOperation.getPrivateKey(); + ECKey sk = transaction.getPrivateKey(); ECKey.ECDSASignature signature = sk.sign(hash); return signature; } public String testSigningMessage() { - byte[] serializedTransaction = this.transferOperation.toBytes(); + byte[] serializedTransaction = this.transaction.toBytes(); Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction)); - ECKey sk = transferOperation.getPrivateKey(); + ECKey sk = transaction.getPrivateKey(); return sk.signMessage(hash.toString()); } public byte[] signMessage() { - byte[] serializedTransaction = this.transferOperation.toBytes(); + byte[] serializedTransaction = this.transaction.toBytes(); Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction)); System.out.println(">> digest <<"); System.out.println(Util.bytesToHex(hash.getBytes())); - ECKey sk = transferOperation.getPrivateKey(); + ECKey sk = transaction.getPrivateKey(); System.out.println("Private key bytes"); System.out.println(Util.bytesToHex(sk.getPrivKeyBytes())); boolean isCanonical = false; @@ -183,10 +183,10 @@ 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.transferOperation = new TransferOperation(Main.WIF, blockData, operations); - byte[] serializedTransaction = this.transferOperation.toBytes(); - System.out.println("Serialized transferOperation"); + operations.add(new TransferOperation(from, to, amount, fee)); + this.transaction = new Transaction(Main.WIF, blockData, operations); + byte[] serializedTransaction = this.transaction.toBytes(); + System.out.println("Serialized transaction"); System.out.println(Util.bytesToHex(serializedTransaction)); } @@ -335,7 +335,7 @@ public class Test { public void testTransactionSerialization() { try { - TransferOperation transferOperation = new TransferTransactionBuilder() + Transaction transaction = new TransferTransactionBuilder() .setSource(new UserAccount("1.2.138632")) .setDestination(new UserAccount("1.2.129848")) .setAmount(new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120"))) @@ -345,9 +345,9 @@ public class Test { .build(); ArrayList transactionList = new ArrayList<>(); - transactionList.add(transferOperation); + transactionList.add(transaction); - byte[] signature = transferOperation.getGrapheneSignature(); + byte[] signature = transaction.getGrapheneSignature(); System.out.println(Util.bytesToHex(signature)); ApiCall call = new ApiCall(4, "call", "broadcast_transaction", transactionList, "2.0", 1); String jsonCall = call.toJsonString(); @@ -420,7 +420,7 @@ public class Test { }; try { - TransferOperation transferOperation = new TransferTransactionBuilder() + Transaction transaction = new TransferTransactionBuilder() .setSource(new UserAccount("1.2.138632")) .setDestination(new UserAccount("1.2.129848")) .setAmount(new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120"))) @@ -430,9 +430,9 @@ public class Test { .build(); ArrayList transactionList = new ArrayList<>(); - transactionList.add(transferOperation); + transactionList.add(transaction); - transactionList.add(transferOperation); + transactionList.add(transaction); SSLContext context = null; context = NaiveSSLContext.getInstance("TLS"); @@ -443,7 +443,7 @@ public class Test { WebSocket mWebSocket = factory.createSocket(OPENLEDGER_WITNESS_URL); - mWebSocket.addListener(new TransactionBroadcastSequence(transferOperation, listener)); + mWebSocket.addListener(new TransactionBroadcastSequence(transaction, listener)); mWebSocket.connect(); } catch (MalformedTransactionException e) { @@ -514,7 +514,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); diff --git a/src/main/java/com/luminiasoft/bitshares/Transaction.java b/src/main/java/com/luminiasoft/bitshares/Transaction.java new file mode 100644 index 0000000..e0f4341 --- /dev/null +++ b/src/main/java/com/luminiasoft/bitshares/Transaction.java @@ -0,0 +1,211 @@ +package com.luminiasoft.bitshares; + +import com.google.common.primitives.Bytes; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.luminiasoft.bitshares.interfaces.ByteSerializable; +import com.luminiasoft.bitshares.interfaces.JsonSerializable; + +import org.bitcoinj.core.DumpedPrivateKey; +import org.bitcoinj.core.ECKey; +import org.bitcoinj.core.Sha256Hash; +import org.bitcoinj.core.Utils; + +import java.lang.reflect.Type; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + +/** + * Class used to represent a generic graphene transaction. + */ +public class Transaction implements ByteSerializable, JsonSerializable { + private final String TAG = this.getClass().getName(); + + public static final String KEY_EXPIRATION = "expiration"; + public static final String KEY_SIGNATURES = "signatures"; + public static final String KEY_OPERATIONS = "operations"; + public static final String KEY_EXTENSIONS = "extensions"; + public static final String KEY_REF_BLOCK_NUM = "ref_block_num"; + public static final String KEY_REF_BLOCK_PREFIX = "ref_block_prefix"; + + 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(); + } + + /** + * Transaction constructor. + * @param privateKey : Instance of a ECKey containing the private key that will be used to sign this transaction. + * @param blockData : Block data containing important information used to sign a transaction. + * @param operationList : List of operations to include in the transaction. + */ + public Transaction(ECKey privateKey, BlockData blockData, List operationList){ + this.privateKey = privateKey; + this.blockData = blockData; + this.operations = operationList; + this.extensions = new ArrayList(); + } + + public ECKey getPrivateKey(){ + return this.privateKey; + } + + public List getOperations(){ return this.operations; } + + /** + * Obtains a signature of this transaction. Please note that due to the current reliance on + * bitcoinj to generate the signatures, and due to the fact that it uses deterministic + * ecdsa signatures, we are slightly modifying the expiration time of the transaction while + * we look for a signature that will be accepted by the graphene network. + * + * This should then be called before any other serialization method. + * @return: A valid signature of the current transaction. + */ + public byte[] getGrapheneSignature(){ + boolean isGrapheneCanonical = false; + byte[] sigData = null; + + while(!isGrapheneCanonical) { + byte[] serializedTransaction = this.toBytes(); + Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction)); + int recId = -1; + ECKey.ECDSASignature sig = privateKey.sign(hash); + + // Now we have to work backwards to figure out the recId needed to recover the signature. + for (int i = 0; i < 4; i++) { + ECKey k = ECKey.recoverFromSignature(i, sig, hash, privateKey.isCompressed()); + if (k != null && k.getPubKeyPoint().equals(privateKey.getPubKeyPoint())) { + recId = i; + break; + } + } + + sigData = new byte[65]; // 1 header + 32 bytes for R + 32 bytes for S + int headerByte = recId + 27 + (privateKey.isCompressed() ? 4 : 0); + sigData[0] = (byte) headerByte; + System.arraycopy(Utils.bigIntegerToBytes(sig.r, 32), 0, sigData, 1, 32); + System.arraycopy(Utils.bigIntegerToBytes(sig.s, 32), 0, sigData, 33, 32); + + // Further "canonicality" tests + if(((sigData[0] & 0x80) != 0) || (sigData[0] == 0) || + ((sigData[1] & 0x80) != 0) || ((sigData[32] & 0x80) != 0) || + (sigData[32] == 0) || ((sigData[33] & 0x80) != 0)){ + this.blockData.setRelativeExpiration(this.blockData.getRelativeExpiration() + 1); + }else{ + isGrapheneCanonical = true; + } + } + return sigData; + } + + /** + * Method that creates a serialized byte array with compact information about this transaction + * that is needed for the creation of a signature. + * @return: byte array with serialized information about this transaction. + */ + public byte[] toBytes(){ + // Creating a List of Bytes and adding the first bytes from the chain apiId + List byteArray = new ArrayList(); + byteArray.addAll(Bytes.asList(Util.hexToBytes(Chains.BITSHARES.CHAIN_ID))); + + // Adding the block data + byteArray.addAll(Bytes.asList(this.blockData.toBytes())); + + // Adding the number of operations + byteArray.add((byte) this.operations.size()); + + // Adding all the operations + for(BaseOperation operation : operations){ + byteArray.add(operation.getId()); + byteArray.addAll(Bytes.asList(operation.toBytes())); + } + + //Adding the number of extensions + byteArray.add((byte) this.extensions.size()); + + for(Extension extension : extensions){ + //TODO: Implement the extension 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 + // leave it here and work on signing the transaction. + //TODO: Investigate the origin and meaning of this last byte. + byteArray.add((byte) 0 ); + + return Bytes.toArray(byteArray); + } + + @Override + public String toJsonString() { + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.registerTypeAdapter(Transaction.class, new TransactionSerializer()); + return gsonBuilder.create().toJson(this); + } + + @Override + public JsonObject toJsonObject() { + JsonObject obj = new JsonObject(); + + // Getting the signature before anything else, + // since this might change the transaction expiration data slightly + byte[] signature = getGrapheneSignature(); + + // Formatting expiration time + Date expirationTime = new Date(blockData.getRelativeExpiration() * 1000); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); + + // Adding expiration + obj.addProperty(KEY_EXPIRATION, dateFormat.format(expirationTime)); + + // Adding signatures + JsonArray signatureArray = new JsonArray(); + signatureArray.add(Util.bytesToHex(signature)); + obj.add(KEY_SIGNATURES, signatureArray); + + JsonArray operationsArray = new JsonArray(); + for(BaseOperation operation : operations){ + operationsArray.add(operation.toJsonObject()); + } + // Adding operations + obj.add(KEY_OPERATIONS, operationsArray); + + // Adding extensions + obj.add(KEY_EXTENSIONS, new JsonArray()); + + // Adding block data + obj.addProperty(KEY_REF_BLOCK_NUM, blockData.getRefBlockNum()); + obj.addProperty(KEY_REF_BLOCK_PREFIX, blockData.getRefBlockPrefix()); + + return obj; + + } + + class TransactionSerializer implements JsonSerializer { + + @Override + public JsonElement serialize(Transaction transaction, Type type, JsonSerializationContext jsonSerializationContext) { + return transaction.toJsonObject(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/luminiasoft/bitshares/TransactionBuilder.java b/src/main/java/com/luminiasoft/bitshares/TransactionBuilder.java index 4d2e1ac..05249a8 100644 --- a/src/main/java/com/luminiasoft/bitshares/TransactionBuilder.java +++ b/src/main/java/com/luminiasoft/bitshares/TransactionBuilder.java @@ -11,5 +11,5 @@ public abstract class TransactionBuilder { protected ECKey privateKey; protected BlockData blockData; - public abstract TransferOperation build() throws MalformedTransactionException; + public abstract Transaction build() throws MalformedTransactionException; } diff --git a/src/main/java/com/luminiasoft/bitshares/Transfer.java b/src/main/java/com/luminiasoft/bitshares/Transfer.java deleted file mode 100644 index d2b2bc0..0000000 --- a/src/main/java/com/luminiasoft/bitshares/Transfer.java +++ /dev/null @@ -1,168 +0,0 @@ -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. - * TODO: Add extensions support - */ -public class Transfer extends BaseOperation { - public static final String KEY_FEE = "fee"; - public static final String KEY_AMOUNT = "amount"; - public static final String KEY_EXTENSIONS = "extensions"; - public static final String KEY_FROM = "from"; - public static final String KEY_TO = "to"; - - private AssetAmount fee; - private AssetAmount amount; - private UserAccount from; - private UserAccount to; - private Memo memo; - private String[] extensions; - - public Transfer(UserAccount from, UserAccount to, AssetAmount transferAmount, AssetAmount fee){ - super(OperationType.transfer_operation); - this.from = from; - this.to = to; - this.amount = transferAmount; - this.fee = fee; - this.memo = new Memo(); - } - - public Transfer(UserAccount from, UserAccount to, AssetAmount transferAmount){ - super(OperationType.transfer_operation); - this.from = from; - this.to = to; - this.amount = transferAmount; - this.memo = new Memo(); - } - - public void setFee(AssetAmount newFee){ - this.fee = newFee; - } - - @Override - public byte getId() { - return (byte) this.type.ordinal(); - } - - public UserAccount getFrom(){ - return this.from; - } - - public UserAccount getTo(){ - return this.to; - } - - public AssetAmount getAmount(){ - return this.amount; - } - - public AssetAmount getFee(){ - return this.fee; - } - - @Override - public byte[] toBytes() { - byte[] feeBytes = fee.toBytes(); - byte[] fromBytes = from.toBytes(); - byte[] toBytes = to.toBytes(); - byte[] amountBytes = amount.toBytes(); - byte[] memoBytes = memo.toBytes(); - return Bytes.concat(feeBytes, fromBytes, toBytes, amountBytes, memoBytes); - } - - @Override - public String toJsonString() { - GsonBuilder gsonBuilder = new GsonBuilder(); - gsonBuilder.registerTypeAdapter(Transfer.class, new TransferSerializer()); - return gsonBuilder.create().toJson(this); - } - - @Override - public JsonElement toJsonObject() { - JsonArray array = new JsonArray(); - array.add(this.getId()); - JsonObject jsonObject = new JsonObject(); - jsonObject.add(KEY_FEE, fee.toJsonObject()); - jsonObject.add(KEY_AMOUNT, amount.toJsonObject()); - jsonObject.add(KEY_EXTENSIONS, new JsonArray()); - jsonObject.addProperty(KEY_FROM, from.toJsonString()); - jsonObject.addProperty(KEY_TO, to.toJsonString()); - array.add(jsonObject); - return array; - } - - public static class TransferSerializer implements JsonSerializer { - - @Override - public JsonElement serialize(Transfer transfer, Type type, JsonSerializationContext jsonSerializationContext) { - JsonArray arrayRep = new JsonArray(); - arrayRep.add(transfer.getId()); - arrayRep.add(transfer.toJsonObject()); - return arrayRep; - } - } - - /** - * This deserializer will work on any transfer operation serialized in the 'array form' used a lot in - * the Graphene Blockchain API. - * - * An example of this serialized form is the following: - * - * [ - * 0, - * { - * "fee": { - * "amount": 264174, - * "asset_id": "1.3.0" - * }, - * "from": "1.2.138632", - * "to": "1.2.129848", - * "amount": { - * "amount": 100, - * "asset_id": "1.3.0" - * }, - * "extensions": [] - * } - * ] - * - * It will convert this data into a nice Transfer object. - */ - public static class TransferDeserializer implements JsonDeserializer { - - @Override - public Transfer 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. - JsonArray serializedTransfer = json.getAsJsonArray(); - if(serializedTransfer.get(0).getAsInt() != OperationType.transfer_operation.ordinal()){ - // If the operation type does not correspond to a transfer operation, we return null - 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); - } - }else{ - // This block is called in the second recursion and takes care of deserializing the - // transfer data itself. - JsonObject jsonObject = json.getAsJsonObject(); - - // Deserializing AssetAmount objects - AssetAmount amount = context.deserialize(jsonObject.get("amount"), AssetAmount.class); - AssetAmount fee = context.deserialize(jsonObject.get("fee"), AssetAmount.class); - - // 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); - return transfer; - } - } - } -} diff --git a/src/main/java/com/luminiasoft/bitshares/TransferOperation.java b/src/main/java/com/luminiasoft/bitshares/TransferOperation.java index ad6e8f4..3940e66 100644 --- a/src/main/java/com/luminiasoft/bitshares/TransferOperation.java +++ b/src/main/java/com/luminiasoft/bitshares/TransferOperation.java @@ -1,218 +1,167 @@ package com.luminiasoft.bitshares; import com.google.common.primitives.Bytes; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; -import com.luminiasoft.bitshares.interfaces.ByteSerializable; -import com.luminiasoft.bitshares.interfaces.JsonSerializable; - -import org.bitcoinj.core.DumpedPrivateKey; -import org.bitcoinj.core.ECKey; -import org.bitcoinj.core.Sha256Hash; -import org.bitcoinj.core.Utils; +import com.google.gson.*; import java.lang.reflect.Type; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.TimeZone; /** - * Class used to represent a generic graphene transaction. + * Class used to encapsulate the TransferOperation operation related functionalities. + * TODO: Add extensions support */ -public class TransferOperation extends BaseOperation implements ByteSerializable, JsonSerializable { - private final String TAG = this.getClass().getName(); - - public static final String KEY_EXPIRATION = "expiration"; - public static final String KEY_SIGNATURES = "signatures"; - public static final String KEY_OPERATIONS = "operations"; +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"; - public static final String KEY_REF_BLOCK_NUM = "ref_block_num"; - public static final String KEY_REF_BLOCK_PREFIX = "ref_block_prefix"; + public static final String KEY_FROM = "from"; + public static final String KEY_TO = "to"; - private ECKey privateKey; - private BlockData blockData; - private List operations; - private List extensions; + private AssetAmount fee; + private AssetAmount amount; + private UserAccount from; + private UserAccount to; + private Memo memo; + private String[] extensions; - /** - * TransferOperation 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 TransferOperation(String wif, BlockData block_data, List operation_list){ + public TransferOperation(UserAccount from, UserAccount to, AssetAmount transferAmount, AssetAmount fee){ super(OperationType.transfer_operation); - this.privateKey = DumpedPrivateKey.fromBase58(null, wif).getKey(); - this.blockData = block_data; - this.operations = operation_list; - this.extensions = new ArrayList(); + this.from = from; + this.to = to; + this.amount = transferAmount; + this.fee = fee; + this.memo = new Memo(); } - /** - * TransferOperation constructor. - * @param privateKey : Instance of a ECKey containing the private key that will be used to sign this transaction. - * @param blockData : Block data containing important information used to sign a transaction. - * @param operationList : List of operations to include in the transaction. - */ - public TransferOperation(ECKey privateKey, BlockData blockData, List operationList){ + public TransferOperation(UserAccount from, UserAccount to, AssetAmount transferAmount){ super(OperationType.transfer_operation); - this.privateKey = privateKey; - this.blockData = blockData; - this.operations = operationList; - this.extensions = new ArrayList(); + this.from = from; + this.to = to; + this.amount = transferAmount; + this.memo = new Memo(); } - public ECKey getPrivateKey(){ - return this.privateKey; - } - - public List getOperations(){ return this.operations; } - - /** - * Obtains a signature of this transaction. Please note that due to the current reliance on - * bitcoinj to generate the signatures, and due to the fact that it uses deterministic - * ecdsa signatures, we are slightly modifying the expiration time of the transaction while - * we look for a signature that will be accepted by the graphene network. - * - * This should then be called before any other serialization method. - * @return: A valid signature of the current transaction. - */ - public byte[] getGrapheneSignature(){ - boolean isGrapheneCanonical = false; - byte[] sigData = null; - - while(!isGrapheneCanonical) { - byte[] serializedTransaction = this.toBytes(); - Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction)); - int recId = -1; - ECKey.ECDSASignature sig = privateKey.sign(hash); - - // Now we have to work backwards to figure out the recId needed to recover the signature. - for (int i = 0; i < 4; i++) { - ECKey k = ECKey.recoverFromSignature(i, sig, hash, privateKey.isCompressed()); - if (k != null && k.getPubKeyPoint().equals(privateKey.getPubKeyPoint())) { - recId = i; - break; - } - } - - sigData = new byte[65]; // 1 header + 32 bytes for R + 32 bytes for S - int headerByte = recId + 27 + (privateKey.isCompressed() ? 4 : 0); - sigData[0] = (byte) headerByte; - System.arraycopy(Utils.bigIntegerToBytes(sig.r, 32), 0, sigData, 1, 32); - System.arraycopy(Utils.bigIntegerToBytes(sig.s, 32), 0, sigData, 33, 32); - - // Further "canonicality" tests - if(((sigData[0] & 0x80) != 0) || (sigData[0] == 0) || - ((sigData[1] & 0x80) != 0) || ((sigData[32] & 0x80) != 0) || - (sigData[32] == 0) || ((sigData[33] & 0x80) != 0)){ - this.blockData.setRelativeExpiration(this.blockData.getRelativeExpiration() + 1); - }else{ - isGrapheneCanonical = true; - } - } - return sigData; + public void setFee(AssetAmount newFee){ + this.fee = newFee; } @Override public byte getId() { - return 0; + return (byte) this.type.ordinal(); } - /** - * Method that creates a serialized byte array with compact information about this transaction - * that is needed for the creation of a signature. - * @return: byte array with serialized information about this transaction. - */ - public byte[] toBytes(){ - // Creating a List of Bytes and adding the first bytes from the chain apiId - List byteArray = new ArrayList(); - byteArray.addAll(Bytes.asList(Util.hexToBytes(Chains.BITSHARES.CHAIN_ID))); + public UserAccount getFrom(){ + return this.from; + } - // Adding the block data - byteArray.addAll(Bytes.asList(this.blockData.toBytes())); + public UserAccount getTo(){ + return this.to; + } - // Adding the number of operations - byteArray.add((byte) this.operations.size()); + public AssetAmount getAmount(){ + return this.amount; + } - // Adding all the operations - for(BaseOperation operation : operations){ - byteArray.add(operation.getId()); - byteArray.addAll(Bytes.asList(operation.toBytes())); - } + public AssetAmount getFee(){ + return this.fee; + } - //Adding the number of extensions - byteArray.add((byte) this.extensions.size()); - - for(Extension extension : extensions){ - //TODO: Implement the extension 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 - // leave it here and work on signing the transaction. - //TODO: Investigate the origin and meaning of this last byte. - byteArray.add((byte) 0 ); - - return Bytes.toArray(byteArray); + @Override + public byte[] toBytes() { + byte[] feeBytes = fee.toBytes(); + byte[] fromBytes = from.toBytes(); + byte[] toBytes = to.toBytes(); + byte[] amountBytes = amount.toBytes(); + byte[] memoBytes = memo.toBytes(); + return Bytes.concat(feeBytes, fromBytes, toBytes, amountBytes, memoBytes); } @Override public String toJsonString() { GsonBuilder gsonBuilder = new GsonBuilder(); - gsonBuilder.registerTypeAdapter(TransferOperation.class, new TransactionSerializer()); + gsonBuilder.registerTypeAdapter(TransferOperation.class, new TransferSerializer()); return gsonBuilder.create().toJson(this); } @Override - public JsonObject toJsonObject() { - JsonObject obj = new JsonObject(); - - // Getting the signature before anything else, - // since this might change the transaction expiration data slightly - byte[] signature = getGrapheneSignature(); - - // Formatting expiration time - Date expirationTime = new Date(blockData.getRelativeExpiration() * 1000); - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); - - // Adding expiration - obj.addProperty(KEY_EXPIRATION, dateFormat.format(expirationTime)); - - // Adding signatures - JsonArray signatureArray = new JsonArray(); - signatureArray.add(Util.bytesToHex(signature)); - obj.add(KEY_SIGNATURES, signatureArray); - - JsonArray operationsArray = new JsonArray(); - for(BaseOperation operation : operations){ - operationsArray.add(operation.toJsonObject()); - } - // Adding operations - obj.add(KEY_OPERATIONS, operationsArray); - - // Adding extensions - obj.add(KEY_EXTENSIONS, new JsonArray()); - - // Adding block data - obj.addProperty(KEY_REF_BLOCK_NUM, blockData.getRefBlockNum()); - obj.addProperty(KEY_REF_BLOCK_PREFIX, blockData.getRefBlockPrefix()); - - return obj; - + public JsonElement toJsonObject() { + JsonArray array = new JsonArray(); + array.add(this.getId()); + JsonObject jsonObject = new JsonObject(); + jsonObject.add(KEY_FEE, fee.toJsonObject()); + jsonObject.add(KEY_AMOUNT, amount.toJsonObject()); + jsonObject.add(KEY_EXTENSIONS, new JsonArray()); + jsonObject.addProperty(KEY_FROM, from.toJsonString()); + jsonObject.addProperty(KEY_TO, to.toJsonString()); + array.add(jsonObject); + return array; } - class TransactionSerializer implements JsonSerializer { + public static class TransferSerializer implements JsonSerializer { @Override - public JsonElement serialize(TransferOperation transferOperation, Type type, JsonSerializationContext jsonSerializationContext) { - return transferOperation.toJsonObject(); + public JsonElement serialize(TransferOperation transfer, Type type, JsonSerializationContext jsonSerializationContext) { + JsonArray arrayRep = new JsonArray(); + arrayRep.add(transfer.getId()); + arrayRep.add(transfer.toJsonObject()); + return arrayRep; } } -} \ No newline at end of file + + /** + * This deserializer will work on any transfer operation serialized in the 'array form' used a lot in + * the Graphene Blockchain API. + * + * An example of this serialized form is the following: + * + * [ + * 0, + * { + * "fee": { + * "amount": 264174, + * "asset_id": "1.3.0" + * }, + * "from": "1.2.138632", + * "to": "1.2.129848", + * "amount": { + * "amount": 100, + * "asset_id": "1.3.0" + * }, + * "extensions": [] + * } + * ] + * + * It will convert this data into a nice TransferOperation object. + */ + public static class TransferDeserializer implements JsonDeserializer { + + @Override + 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. + JsonArray serializedTransfer = json.getAsJsonArray(); + if(serializedTransfer.get(0).getAsInt() != OperationType.transfer_operation.ordinal()){ + // If the operation type does not correspond to a transfer operation, we return null + return null; + }else{ + // Calling itself recursively, this is only done once, so there will be no problems. + return context.deserialize(serializedTransfer.get(1), TransferOperation.class); + } + }else{ + // This block is called in the second recursion and takes care of deserializing the + // transfer data itself. + JsonObject jsonObject = json.getAsJsonObject(); + + // Deserializing AssetAmount objects + AssetAmount amount = context.deserialize(jsonObject.get("amount"), AssetAmount.class); + AssetAmount fee = context.deserialize(jsonObject.get("fee"), AssetAmount.class); + + // Deserializing UserAccount objects + UserAccount from = new UserAccount(jsonObject.get("from").getAsString()); + UserAccount to = new UserAccount(jsonObject.get("to").getAsString()); + 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 8ea9ac7..97d07a2 100644 --- a/src/main/java/com/luminiasoft/bitshares/TransferTransactionBuilder.java +++ b/src/main/java/com/luminiasoft/bitshares/TransferTransactionBuilder.java @@ -46,7 +46,7 @@ public class TransferTransactionBuilder extends TransactionBuilder { return this; } - public TransferTransactionBuilder addOperation(Transfer transferOperation){ + public TransferTransactionBuilder addOperation(TransferOperation transferOperation){ if(operations == null){ operations = new ArrayList(); } @@ -54,7 +54,7 @@ public class TransferTransactionBuilder extends TransactionBuilder { } @Override - public TransferOperation build() throws MalformedTransactionException { + public Transaction build() throws MalformedTransactionException { if(privateKey == null){ throw new MalformedTransactionException("Missing private key information"); }else if(blockData == null){ @@ -73,14 +73,14 @@ 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); } - return new TransferOperation(privateKey, blockData, operations); + return new Transaction(privateKey, blockData, operations); } } 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/TransactionBroadcastSequence.java b/src/main/java/com/luminiasoft/bitshares/ws/TransactionBroadcastSequence.java index bbd6b68..c4d129d 100644 --- a/src/main/java/com/luminiasoft/bitshares/ws/TransactionBroadcastSequence.java +++ b/src/main/java/com/luminiasoft/bitshares/ws/TransactionBroadcastSequence.java @@ -3,7 +3,7 @@ package com.luminiasoft.bitshares.ws; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.luminiasoft.bitshares.*; -import com.luminiasoft.bitshares.TransferOperation; +import com.luminiasoft.bitshares.Transaction; import com.luminiasoft.bitshares.interfaces.WitnessResponseListener; import com.luminiasoft.bitshares.models.ApiCall; import com.luminiasoft.bitshares.models.BaseResponse; @@ -24,7 +24,7 @@ import java.util.Map; import java.util.TimeZone; /** - * Class that will handle the transferOperation publication procedure. + * Class that will handle the transaction publication procedure. */ public class TransactionBroadcastSequence extends WebSocketAdapter { private final String TAG = this.getClass().getName(); @@ -35,7 +35,7 @@ public class TransactionBroadcastSequence extends WebSocketAdapter { private final static int BROADCAST_TRANSACTION = 4; public final static int EXPIRATION_TIME = 30; - private TransferOperation transferOperation; + private Transaction transaction; private long expirationTime; private String headBlockId; private long headBlockNumber; @@ -47,13 +47,13 @@ public class TransactionBroadcastSequence extends WebSocketAdapter { /** * Constructor of this class. The ids required - * @param transferOperation: The transferOperation to be broadcasted. + * @param transaction: The transaction to be broadcasted. * @param listener: A class implementing the WitnessResponseListener interface. This should * be implemented by the party interested in being notified about the success/failure - * of the transferOperation broadcast operation. + * of the transaction broadcast operation. */ - public TransactionBroadcastSequence(TransferOperation transferOperation, WitnessResponseListener listener){ - this.transferOperation = transferOperation; + public TransactionBroadcastSequence(Transaction transaction, WitnessResponseListener listener){ + this.transaction = transaction; this.mListener = listener; } @@ -102,14 +102,14 @@ public class TransactionBroadcastSequence extends WebSocketAdapter { headBlockNumber = dynamicProperties.head_block_number; ArrayList transactionList = new ArrayList<>(); - transactionList.add(transferOperation); + transactionList.add(transaction); ApiCall call = new ApiCall(broadcastApiId, RPC.CALL_BROADCAST_TRANSACTION, transactionList, "2.0", currentId); - // Finally sending transferOperation + // Finally sending transaction websocket.sendText(call.toJsonString()); }else if(baseResponse.id >= BROADCAST_TRANSACTION){ Type WitnessResponseType = new TypeToken>(){}.getType(); @@ -131,18 +131,18 @@ public class TransactionBroadcastSequence extends WebSocketAdapter { * with ONE transfer operation. */ retries++; - List operations = this.transferOperation.getOperations(); - Transfer transfer = (Transfer) operations.get(0); - transferOperation = new TransferTransactionBuilder() + List operations = this.transaction.getOperations(); + TransferOperation transfer = (TransferOperation) 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(transferOperation.getPrivateKey()) + .setPrivateKey(transaction.getPrivateKey()) .build(); ArrayList transactionList = new ArrayList<>(); - transactionList.add(transferOperation); + transactionList.add(transaction); ApiCall call = new ApiCall(broadcastApiId, RPC.CALL_BROADCAST_TRANSACTION, transactionList, From 5c449da04f65527d031787ad03fce3c396af95a1 Mon Sep 17 00:00:00 2001 From: "Nelson R. Perez" Date: Sat, 3 Dec 2016 18:35:11 -0500 Subject: [PATCH 3/7] Introducing the class AccountUpdateOperation --- .../bitshares/AccountUpdateOperation.java | 69 +++++++++++++++++++ .../com/luminiasoft/bitshares/Authority.java | 47 +++++++++++-- .../luminiasoft/bitshares/BaseOperation.java | 4 +- .../com/luminiasoft/bitshares/Extension.java | 8 --- .../com/luminiasoft/bitshares/Extensions.java | 41 +++++++++++ .../java/com/luminiasoft/bitshares/Main.java | 3 +- .../java/com/luminiasoft/bitshares/Test.java | 17 +++++ .../luminiasoft/bitshares/Transaction.java | 10 +-- .../bitshares/TransferOperation.java | 6 +- .../bitshares/TransferTransactionBuilder.java | 1 + .../java/com/luminiasoft/bitshares/Util.java | 31 +++++++++ 11 files changed, 210 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/luminiasoft/bitshares/AccountUpdateOperation.java delete mode 100644 src/main/java/com/luminiasoft/bitshares/Extension.java create mode 100644 src/main/java/com/luminiasoft/bitshares/Extensions.java 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(); + } } From 5f51a1c3e4cf219ab722e744e3bf523de6297ae2 Mon Sep 17 00:00:00 2001 From: "Nelson R. Perez" Date: Sun, 4 Dec 2016 17:04:41 -0500 Subject: [PATCH 4/7] Adding missing extension byte --- src/main/java/com/luminiasoft/bitshares/Extensions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/luminiasoft/bitshares/Extensions.java b/src/main/java/com/luminiasoft/bitshares/Extensions.java index 102dae8..5481685 100644 --- a/src/main/java/com/luminiasoft/bitshares/Extensions.java +++ b/src/main/java/com/luminiasoft/bitshares/Extensions.java @@ -32,7 +32,7 @@ public class Extensions implements JsonSerializable, ByteSerializable { @Override public byte[] toBytes() { - return new byte[0]; + return new byte[1]; } public int size(){ From e0b0330e54c6ee122d778fc1a780fd25cf4cf82c Mon Sep 17 00:00:00 2001 From: "Nelson R. Perez" Date: Sun, 4 Dec 2016 19:22:12 -0500 Subject: [PATCH 5/7] AccountUpdateOperation serialization matches cli_wallet --- .../bitshares/AccountUpdateOperation.java | 8 +- .../AccountUpdateTransactionBuilder.java | 10 +++ .../com/luminiasoft/bitshares/Authority.java | 31 ++++++- .../java/com/luminiasoft/bitshares/Main.java | 3 +- .../com/luminiasoft/bitshares/PublicKey.java | 4 + .../java/com/luminiasoft/bitshares/Test.java | 70 ++++++++++++++-- .../luminiasoft/bitshares/Transaction.java | 31 ++++--- .../bitshares/TransferTransactionBuilder.java | 2 - .../luminiasoft/bitshares/UserAccount.java | 5 ++ .../luminiasoft/bitshares/models/ApiCall.java | 2 + .../ws/TransactionBroadcastSequence.java | 82 +++++++------------ 11 files changed, 168 insertions(+), 80 deletions(-) create mode 100644 src/main/java/com/luminiasoft/bitshares/AccountUpdateTransactionBuilder.java diff --git a/src/main/java/com/luminiasoft/bitshares/AccountUpdateOperation.java b/src/main/java/com/luminiasoft/bitshares/AccountUpdateOperation.java index 353f6ac..0eee65f 100644 --- a/src/main/java/com/luminiasoft/bitshares/AccountUpdateOperation.java +++ b/src/main/java/com/luminiasoft/bitshares/AccountUpdateOperation.java @@ -48,13 +48,17 @@ public class AccountUpdateOperation extends BaseOperation { @Override public JsonElement toJsonObject() { + JsonArray array = new JsonArray(); + array.add(this.getId()); + JsonObject accountUpdate = new JsonObject(); accountUpdate.add(KEY_FEE, fee.toJsonObject()); - accountUpdate.add(KEY_ACCOUNT, account.toJsonObject()); + accountUpdate.addProperty(KEY_ACCOUNT, account.toJsonString()); accountUpdate.add(KEY_OWNER, owner.toJsonObject()); accountUpdate.add(KEY_ACTIVE, active.toJsonObject()); accountUpdate.add(KEY_EXTENSIONS, extensions.toJsonObject()); - return accountUpdate; + array.add(accountUpdate); + return array; } @Override 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..ecec78e --- /dev/null +++ b/src/main/java/com/luminiasoft/bitshares/AccountUpdateTransactionBuilder.java @@ -0,0 +1,10 @@ +package com.luminiasoft.bitshares; + +import java.util.List; + +/** + * Created by nelson on 12/3/16. + */ +public class AccountUpdateTransactionBuilder { + private List operations; +} diff --git a/src/main/java/com/luminiasoft/bitshares/Authority.java b/src/main/java/com/luminiasoft/bitshares/Authority.java index 98e991c..646209f 100644 --- a/src/main/java/com/luminiasoft/bitshares/Authority.java +++ b/src/main/java/com/luminiasoft/bitshares/Authority.java @@ -1,7 +1,9 @@ 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.ByteSerializable; import com.luminiasoft.bitshares.interfaces.JsonSerializable; @@ -13,6 +15,11 @@ import java.util.*; * Created by nelson on 11/30/16. */ public class Authority implements JsonSerializable, ByteSerializable { + 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; @@ -36,7 +43,29 @@ public class Authority implements JsonSerializable, ByteSerializable { @Override public JsonElement toJsonObject() { - return null; + 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 diff --git a/src/main/java/com/luminiasoft/bitshares/Main.java b/src/main/java/com/luminiasoft/bitshares/Main.java index c162eb3..8696230 100644 --- a/src/main/java/com/luminiasoft/bitshares/Main.java +++ b/src/main/java/com/luminiasoft/bitshares/Main.java @@ -61,6 +61,7 @@ public class Main { // test.testingInvoiceGeneration(); // test.testCompression(); // test.testCreateBinFile(); - test.testAccountUpdateOperationSerialization(); + test.testAccountUpdateSerialization(); +// test.testAccountUpdateOperationSerialization(); } } diff --git a/src/main/java/com/luminiasoft/bitshares/PublicKey.java b/src/main/java/com/luminiasoft/bitshares/PublicKey.java index 31961ae..14dd927 100644 --- a/src/main/java/com/luminiasoft/bitshares/PublicKey.java +++ b/src/main/java/com/luminiasoft/bitshares/PublicKey.java @@ -13,6 +13,10 @@ public class PublicKey implements ByteSerializable { this.publicKey = key; } + public ECKey getKey(){ + return publicKey; + } + @Override public byte[] toBytes() { return publicKey.getPubKey(); diff --git a/src/main/java/com/luminiasoft/bitshares/Test.java b/src/main/java/com/luminiasoft/bitshares/Test.java index 62ea660..f19efb8 100644 --- a/src/main/java/com/luminiasoft/bitshares/Test.java +++ b/src/main/java/com/luminiasoft/bitshares/Test.java @@ -432,8 +432,6 @@ public class Test { ArrayList transactionList = new ArrayList<>(); transactionList.add(transaction); - transactionList.add(transaction); - SSLContext context = null; context = NaiveSSLContext.getInstance("TLS"); WebSocketFactory factory = new WebSocketFactory(); @@ -744,20 +742,76 @@ public class Test { System.out.println("fileOutput " + Arrays.toString(fileOutput)); } - public void testAccountUpdateOperationSerialization(){ + public void testAccountUpdateSerialization() { UserAccount account = new UserAccount("1.2.138632"); - AssetAmount fee = new AssetAmount(UnsignedLong.valueOf("4294967295"), new Asset("1.3.0")); + AssetAmount fee = new AssetAmount(UnsignedLong.valueOf("200"), new Asset("1.3.0")); HashMap keyAuths = new HashMap<>(); - keyAuths.put("BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY", 65535); + keyAuths.put("BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY", 1); + try { + BlockData blockData = new BlockData(0, 0, 0); + Authority owner = new Authority(1, keyAuths); + Authority active = new Authority(1, keyAuths); + AccountUpdateOperation operation = new AccountUpdateOperation(account, owner, active, fee); + ArrayList operations = new ArrayList(); + operations.add(operation); + Transaction transaction = new Transaction(Main.WIF, blockData, operations); + System.out.println("Json format of transaction"); + System.out.println(transaction.toJsonString()); + } catch(MalformedAddressException e){ + System.out.println("MalformedAddressException. 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"); + } + }; + + UserAccount account = new UserAccount("1.2.138632"); + AssetAmount fee = new AssetAmount(UnsignedLong.valueOf("200"), new Asset("1.3.0")); + HashMap keyAuths = new HashMap<>(); + keyAuths.put("BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY", 1); 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)); + ArrayList operations = new ArrayList(); + operations.add(operation); + Transaction transaction = new Transaction(Main.WIF, null, operations); + + ArrayList transactionList = new ArrayList<>(); + transactionList.add(transaction); + + ApiCall call = new ApiCall(4, "call", "broadcast_transaction", transactionList, RPC.VERSION, 1); + + SSLContext context = null; + 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 TransactionBroadcastSequence(transaction, listener)); + mWebSocket.connect(); } catch (MalformedAddressException e) { System.out.println("MalformedAddressException. 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()); } } } diff --git a/src/main/java/com/luminiasoft/bitshares/Transaction.java b/src/main/java/com/luminiasoft/bitshares/Transaction.java index 109bb98..b80d4f2 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; @@ -28,6 +29,7 @@ import java.util.TimeZone; 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"; @@ -40,19 +42,6 @@ public class Transaction implements ByteSerializable, JsonSerializable { 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(); - } - /** * Transaction constructor. * @param privateKey : Instance of a ECKey containing the private key that will be used to sign this transaction. @@ -66,6 +55,20 @@ public class Transaction implements ByteSerializable, JsonSerializable { 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); + } + + public void setBlockData(BlockData blockData){ + this.blockData = blockData; + } + public ECKey getPrivateKey(){ return this.privateKey; } @@ -87,6 +90,8 @@ public class Transaction implements ByteSerializable, JsonSerializable { while(!isGrapheneCanonical) { byte[] serializedTransaction = this.toBytes(); + System.out.println("Signing serialized transaction"); + System.out.println(Util.bytesToHex(serializedTransaction)); Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction)); int recId = -1; ECKey.ECDSASignature sig = privateKey.sign(hash); diff --git a/src/main/java/com/luminiasoft/bitshares/TransferTransactionBuilder.java b/src/main/java/com/luminiasoft/bitshares/TransferTransactionBuilder.java index 10be1f7..0caf11e 100644 --- a/src/main/java/com/luminiasoft/bitshares/TransferTransactionBuilder.java +++ b/src/main/java/com/luminiasoft/bitshares/TransferTransactionBuilder.java @@ -58,8 +58,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 diff --git a/src/main/java/com/luminiasoft/bitshares/UserAccount.java b/src/main/java/com/luminiasoft/bitshares/UserAccount.java index a99eb62..a1dba3e 100644 --- a/src/main/java/com/luminiasoft/bitshares/UserAccount.java +++ b/src/main/java/com/luminiasoft/bitshares/UserAccount.java @@ -45,4 +45,9 @@ public class UserAccount extends GrapheneObject implements ByteSerializable, Jso public JsonObject toJsonObject() { return null; } + + @Override + public String toString() { + return this.toJsonString(); + } } 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/ws/TransactionBroadcastSequence.java b/src/main/java/com/luminiasoft/bitshares/ws/TransactionBroadcastSequence.java index c4d129d..51be6ba 100644 --- a/src/main/java/com/luminiasoft/bitshares/ws/TransactionBroadcastSequence.java +++ b/src/main/java/com/luminiasoft/bitshares/ws/TransactionBroadcastSequence.java @@ -2,7 +2,8 @@ package com.luminiasoft.bitshares.ws; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; -import com.luminiasoft.bitshares.*; +import com.luminiasoft.bitshares.BlockData; +import com.luminiasoft.bitshares.RPC; import com.luminiasoft.bitshares.Transaction; import com.luminiasoft.bitshares.interfaces.WitnessResponseListener; import com.luminiasoft.bitshares.models.ApiCall; @@ -33,17 +34,12 @@ public class TransactionBroadcastSequence extends WebSocketAdapter { 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 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 @@ -62,16 +58,18 @@ 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{ @@ -97,67 +95,45 @@ public class TransactionBroadcastSequence extends WebSocketAdapter { 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; + 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)); ArrayList transactionList = new ArrayList<>(); transactionList.add(transaction); ApiCall call = new ApiCall(broadcastApiId, RPC.CALL_BROADCAST_TRANSACTION, transactionList, - "2.0", + RPC.VERSION, currentId); + System.out.println("Json of transaction"); + System.out.println(transaction.toJsonString()); + + //TODO: Remove this debug code + String jsonCall = call.toJsonString(); + System.out.println("json call"); + System.out.println(jsonCall); + // Finally sending transaction - websocket.sendText(call.toJsonString()); +// 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(); - TransferOperation transfer = (TransferOperation) 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 { mListener.onError(new BaseResponse.Error(cause.getMessage())); From 7eb6a15c7e47370bbaed66be46a15227eda677c3 Mon Sep 17 00:00:00 2001 From: "Nelson R. Perez" Date: Sun, 4 Dec 2016 23:25:42 -0500 Subject: [PATCH 6/7] Successfully changing owner and account keys information --- .../bitshares/AccountUpdateOperation.java | 1 + .../luminiasoft/bitshares/BaseOperation.java | 2 + .../java/com/luminiasoft/bitshares/Main.java | 8 ++- .../java/com/luminiasoft/bitshares/Test.java | 23 +++---- .../luminiasoft/bitshares/Transaction.java | 7 ++- .../bitshares/TransferOperation.java | 1 + .../ws/TransactionBroadcastSequence.java | 60 ++++++++++--------- 7 files changed, 56 insertions(+), 46 deletions(-) diff --git a/src/main/java/com/luminiasoft/bitshares/AccountUpdateOperation.java b/src/main/java/com/luminiasoft/bitshares/AccountUpdateOperation.java index 0eee65f..8e7adb3 100644 --- a/src/main/java/com/luminiasoft/bitshares/AccountUpdateOperation.java +++ b/src/main/java/com/luminiasoft/bitshares/AccountUpdateOperation.java @@ -36,6 +36,7 @@ public class AccountUpdateOperation extends BaseOperation { this(account, owner, active, new AssetAmount(UnsignedLong.valueOf(0), new Asset("1.3.0"))); } + @Override public void setFee(AssetAmount fee){ this.fee = fee; } diff --git a/src/main/java/com/luminiasoft/bitshares/BaseOperation.java b/src/main/java/com/luminiasoft/bitshares/BaseOperation.java index 414a88a..89616f2 100644 --- a/src/main/java/com/luminiasoft/bitshares/BaseOperation.java +++ b/src/main/java/com/luminiasoft/bitshares/BaseOperation.java @@ -18,5 +18,7 @@ public abstract class BaseOperation implements ByteSerializable, JsonSerializabl return (byte) this.type.ordinal(); } + public abstract void setFee(AssetAmount assetAmount); + public abstract byte[] toBytes(); } diff --git a/src/main/java/com/luminiasoft/bitshares/Main.java b/src/main/java/com/luminiasoft/bitshares/Main.java index 8696230..2fb7685 100644 --- a/src/main/java/com/luminiasoft/bitshares/Main.java +++ b/src/main/java/com/luminiasoft/bitshares/Main.java @@ -9,6 +9,8 @@ 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_5_BRAIN_KEY = "UNMATE AURIGAL NAVET WAVICLE REWOVE ABBOTCY COWHERB OUTKICK STOPPER JUSSORY BEAMLET WIRY"; + //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 BIP39_KEY = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; @@ -17,7 +19,7 @@ public class Main { // 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 + // WIF from an empty account created by the cli_wallet public static final String WIF = "5KMzB2GqGhnh7ufhgddmz1eKPHS72uTLeL9hHjSvPb1UywWknF5"; public static final String EXTERNAL_SIGNATURE = "1f36c41acb774fcbc9c231b5895ec9701d6872729098d8ea56d78dda72a6b54252694db85d7591de5751b7aea06871da15d63a1028758421607ffc143e53ef3306"; @@ -61,7 +63,7 @@ public class Main { // test.testingInvoiceGeneration(); // test.testCompression(); // test.testCreateBinFile(); - test.testAccountUpdateSerialization(); -// test.testAccountUpdateOperationSerialization(); +// test.testAccountUpdateSerialization(); + test.testAccountUpdateOperationBroadcast(); } } diff --git a/src/main/java/com/luminiasoft/bitshares/Test.java b/src/main/java/com/luminiasoft/bitshares/Test.java index f19efb8..afedc93 100644 --- a/src/main/java/com/luminiasoft/bitshares/Test.java +++ b/src/main/java/com/luminiasoft/bitshares/Test.java @@ -184,7 +184,8 @@ public class Test { 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 TransferOperation(from, to, amount, fee)); - this.transaction = new Transaction(Main.WIF, blockData, operations); + BrainKey brainKey = new BrainKey(Main.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)); @@ -426,7 +427,7 @@ 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<>(); @@ -441,7 +442,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) { @@ -591,7 +592,7 @@ public class Test { String suggestion = BrainKey.suggest(words); brainKey = new BrainKey(suggestion, 0); } else { - brainKey = new BrainKey(Main.BRAIN_KEY, 0); + brainKey = new BrainKey(Main.BILTHON_5_BRAIN_KEY, 0); } ECKey key = brainKey.getPrivateKey(); System.out.println("Private key"); @@ -755,8 +756,6 @@ public class Test { ArrayList operations = new ArrayList(); operations.add(operation); Transaction transaction = new Transaction(Main.WIF, blockData, operations); - System.out.println("Json format of transaction"); - System.out.println(transaction.toJsonString()); } catch(MalformedAddressException e){ System.out.println("MalformedAddressException. Msg: "+e.getMessage()); } @@ -776,7 +775,7 @@ public class Test { } }; - UserAccount account = new UserAccount("1.2.138632"); + UserAccount account = new UserAccount("1.2.139313"); AssetAmount fee = new AssetAmount(UnsignedLong.valueOf("200"), new Asset("1.3.0")); HashMap keyAuths = new HashMap<>(); keyAuths.put("BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY", 1); @@ -786,12 +785,8 @@ public class Test { AccountUpdateOperation operation = new AccountUpdateOperation(account, owner, active, fee); ArrayList operations = new ArrayList(); operations.add(operation); - Transaction transaction = new Transaction(Main.WIF, null, operations); - - ArrayList transactionList = new ArrayList<>(); - transactionList.add(transaction); - - ApiCall call = new ApiCall(4, "call", "broadcast_transaction", transactionList, RPC.VERSION, 1); + BrainKey brainKey = new BrainKey(Main.BILTHON_5_BRAIN_KEY, 0); + Transaction transaction = new Transaction(brainKey.getWalletImportFormat(), null, operations); SSLContext context = null; context = NaiveSSLContext.getInstance("TLS"); @@ -802,7 +797,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 (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 b80d4f2..967d5a4 100644 --- a/src/main/java/com/luminiasoft/bitshares/Transaction.java +++ b/src/main/java/com/luminiasoft/bitshares/Transaction.java @@ -69,6 +69,11 @@ public class Transaction implements ByteSerializable, JsonSerializable { this.blockData = blockData; } + public void setFees(List fees){ + for(int i = 0; i < operations.size(); i++) + operations.get(i).setFee(fees.get(i)); + } + public ECKey getPrivateKey(){ return this.privateKey; } @@ -90,8 +95,6 @@ public class Transaction implements ByteSerializable, JsonSerializable { while(!isGrapheneCanonical) { byte[] serializedTransaction = this.toBytes(); - System.out.println("Signing serialized transaction"); - System.out.println(Util.bytesToHex(serializedTransaction)); Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction)); int recId = -1; ECKey.ECDSASignature sig = privateKey.sign(hash); diff --git a/src/main/java/com/luminiasoft/bitshares/TransferOperation.java b/src/main/java/com/luminiasoft/bitshares/TransferOperation.java index 87692a2..4f12f70 100644 --- a/src/main/java/com/luminiasoft/bitshares/TransferOperation.java +++ b/src/main/java/com/luminiasoft/bitshares/TransferOperation.java @@ -40,6 +40,7 @@ public class TransferOperation extends BaseOperation { this.memo = new Memo(); } + @Override public void setFee(AssetAmount newFee){ this.fee = newFee; } diff --git a/src/main/java/com/luminiasoft/bitshares/ws/TransactionBroadcastSequence.java b/src/main/java/com/luminiasoft/bitshares/ws/TransactionBroadcastSequence.java index 51be6ba..71202ba 100644 --- a/src/main/java/com/luminiasoft/bitshares/ws/TransactionBroadcastSequence.java +++ b/src/main/java/com/luminiasoft/bitshares/ws/TransactionBroadcastSequence.java @@ -1,10 +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.BlockData; -import com.luminiasoft.bitshares.RPC; -import com.luminiasoft.bitshares.Transaction; +import com.luminiasoft.bitshares.*; import com.luminiasoft.bitshares.interfaces.WitnessResponseListener; import com.luminiasoft.bitshares.models.ApiCall; import com.luminiasoft.bitshares.models.BaseResponse; @@ -18,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. @@ -33,8 +28,12 @@ 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; + private final static int GET_REQUIRED_FEES = 4; + private final static int BROADCAST_TRANSACTION = 5; +// private Transaction transaction; +// private ArrayList transactions; + private Asset feeAsset; private Transaction transaction; private WitnessResponseListener mListener; @@ -48,8 +47,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; } @@ -94,30 +94,36 @@ public class TransactionBroadcastSequence extends WebSocketAdapter { dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); Date date = dateFormat.parse(dynamicProperties.time); - // Obtained block data + // 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)); - ArrayList transactionList = new ArrayList<>(); - transactionList.add(transaction); - ApiCall call = new ApiCall(broadcastApiId, - RPC.CALL_BROADCAST_TRANSACTION, - transactionList, - RPC.VERSION, - currentId); - - System.out.println("Json of transaction"); - System.out.println(transaction.toJsonString()); - - //TODO: Remove this debug code - String jsonCall = call.toJsonString(); - System.out.println("json call"); - System.out.println(jsonCall); + 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); // Finally sending transaction -// websocket.sendText(call.toJsonString()); + 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); + + transaction.setFees(requiredFeesResponse.result); + ArrayList transactions = new ArrayList<>(); + transactions.add(transaction); + + ApiCall call = new ApiCall(broadcastApiId, + RPC.CALL_BROADCAST_TRANSACTION, + transactions, + RPC.VERSION, + currentId); + websocket.sendText(call.toJsonString()); +// websocket.disconnect(); }else if(baseResponse.id >= BROADCAST_TRANSACTION){ Type WitnessResponseType = new TypeToken>(){}.getType(); WitnessResponse> witnessResponse = gson.fromJson(response, WitnessResponseType); From df41775f5052e6b58a072bb04b86dbb127f63dae Mon Sep 17 00:00:00 2001 From: "Nelson R. Perez" Date: Mon, 5 Dec 2016 14:44:56 -0500 Subject: [PATCH 7/7] Adding support for account options update --- .../luminiasoft/bitshares/AccountOptions.java | 134 +++++++++++++++ .../bitshares/AccountUpdateOperation.java | 47 +++-- .../AccountUpdateTransactionBuilder.java | 62 ++++++- .../com/luminiasoft/bitshares/Authority.java | 85 ++++++--- .../com/luminiasoft/bitshares/Extensions.java | 2 + .../java/com/luminiasoft/bitshares/Main.java | 4 +- .../com/luminiasoft/bitshares/Optional.java | 33 ++++ .../java/com/luminiasoft/bitshares/Test.java | 161 +++++++++--------- .../luminiasoft/bitshares/Transaction.java | 10 +- .../bitshares/TransactionBuilder.java | 11 ++ .../bitshares/TransferTransactionBuilder.java | 6 + .../luminiasoft/bitshares/UserAccount.java | 2 + .../java/com/luminiasoft/bitshares/Vote.java | 33 ++++ .../interfaces/GrapheneSerializable.java | 7 + .../interfaces/JsonSerializable.java | 3 +- .../ws/TransactionBroadcastSequence.java | 5 + 16 files changed, 482 insertions(+), 123 deletions(-) create mode 100644 src/main/java/com/luminiasoft/bitshares/AccountOptions.java create mode 100644 src/main/java/com/luminiasoft/bitshares/Optional.java create mode 100644 src/main/java/com/luminiasoft/bitshares/Vote.java create mode 100644 src/main/java/com/luminiasoft/bitshares/interfaces/GrapheneSerializable.java 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 index 8e7adb3..c716e1c 100644 --- a/src/main/java/com/luminiasoft/bitshares/AccountUpdateOperation.java +++ b/src/main/java/com/luminiasoft/bitshares/AccountUpdateOperation.java @@ -6,6 +6,8 @@ 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. @@ -15,25 +17,36 @@ public class AccountUpdateOperation extends BaseOperation { 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 UserAccount account; private AssetAmount fee; - private Authority owner; - private Authority active; + private UserAccount account; + private Optional owner; + private Optional active; + private Optional new_options; private Extensions extensions; - public AccountUpdateOperation(UserAccount account, Authority owner, Authority active, AssetAmount fee){ + /** + * 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.account = account; - this.owner = owner; - this.active = active; 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){ - this(account, owner, active, new AssetAmount(UnsignedLong.valueOf(0), new Asset("1.3.0"))); + 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 @@ -41,6 +54,18 @@ public class AccountUpdateOperation extends BaseOperation { 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(); @@ -57,6 +82,7 @@ public class AccountUpdateOperation extends BaseOperation { 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; @@ -68,7 +94,8 @@ public class AccountUpdateOperation extends BaseOperation { 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, extensionBytes); + 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 index ecec78e..435f8a3 100644 --- a/src/main/java/com/luminiasoft/bitshares/AccountUpdateTransactionBuilder.java +++ b/src/main/java/com/luminiasoft/bitshares/AccountUpdateTransactionBuilder.java @@ -1,10 +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; /** - * Created by nelson on 12/3/16. + * Class used to build a transaction containing an account update operation. */ -public class AccountUpdateTransactionBuilder { - private List operations; +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/Authority.java b/src/main/java/com/luminiasoft/bitshares/Authority.java index 646209f..a3637af 100644 --- a/src/main/java/com/luminiasoft/bitshares/Authority.java +++ b/src/main/java/com/luminiasoft/bitshares/Authority.java @@ -5,16 +5,14 @@ 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.ByteSerializable; -import com.luminiasoft.bitshares.interfaces.JsonSerializable; +import com.luminiasoft.bitshares.interfaces.GrapheneSerializable; -import java.nio.ByteBuffer; import java.util.*; /** * Created by nelson on 11/30/16. */ -public class Authority implements JsonSerializable, ByteSerializable { +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"; @@ -25,17 +23,41 @@ public class Authority implements JsonSerializable, ByteSerializable { private HashMap key_auths; private Extensions extensions; - 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(); + 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; @@ -74,25 +96,34 @@ public class Authority implements JsonSerializable, ByteSerializable { // 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)))); + // 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()); + // Number of account authorities + byteArray.add((byte) account_auths.size()); - //TODO: Add account authorities + //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()); + // 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()))); + // 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()); } - - // 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/Extensions.java b/src/main/java/com/luminiasoft/bitshares/Extensions.java index 5481685..18c9a6d 100644 --- a/src/main/java/com/luminiasoft/bitshares/Extensions.java +++ b/src/main/java/com/luminiasoft/bitshares/Extensions.java @@ -11,6 +11,8 @@ 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(){ diff --git a/src/main/java/com/luminiasoft/bitshares/Main.java b/src/main/java/com/luminiasoft/bitshares/Main.java index 2fb7685..171e15a 100644 --- a/src/main/java/com/luminiasoft/bitshares/Main.java +++ b/src/main/java/com/luminiasoft/bitshares/Main.java @@ -11,6 +11,8 @@ public class Main { 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 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 BIP39_KEY = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; @@ -55,7 +57,7 @@ public class Main { // test.testGetAccountByName(); // test.testGetRequiredFees(); // test.testRandomNumberGeneration(); -// test.testBrainKeyOperations(false); +// test.testBrainKeyOperations(true); // test.testBip39Opertion(); // test.testAccountNamebyAddress(); // test.testAccountNameById(); 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/Test.java b/src/main/java/com/luminiasoft/bitshares/Test.java index afedc93..d623b96 100644 --- a/src/main/java/com/luminiasoft/bitshares/Test.java +++ b/src/main/java/com/luminiasoft/bitshares/Test.java @@ -341,8 +341,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<>(); @@ -532,49 +532,6 @@ 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 @@ -595,29 +552,21 @@ public class Test { brainKey = new BrainKey(Main.BILTHON_5_BRAIN_KEY, 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); 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)); - 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(pubKey1)); + System.out.println("Public Key Compressed..........: " + Util.bytesToHex(pubKey2)); // Address generation test Address address = new Address(key); - System.out.println("Block explorer's address: " + address); - - System.out.println("Wif: : " + brainKey.getWalletImportFormat()); + System.out.println("Block explorer's address.....: " + address); } catch (FileNotFoundException e) { System.out.println("FileNotFoundException. Msg: " + e.getMessage()); } catch (IOException e) { @@ -744,20 +693,30 @@ public class Test { } public void testAccountUpdateSerialization() { - UserAccount account = new UserAccount("1.2.138632"); - AssetAmount fee = new AssetAmount(UnsignedLong.valueOf("200"), new Asset("1.3.0")); - HashMap keyAuths = new HashMap<>(); - keyAuths.put("BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY", 1); + String newAddress = "BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY"; try { - BlockData blockData = new BlockData(0, 0, 0); - Authority owner = new Authority(1, keyAuths); - Authority active = new Authority(1, keyAuths); - AccountUpdateOperation operation = new AccountUpdateOperation(account, owner, active, fee); - ArrayList operations = new ArrayList(); - operations.add(operation); - Transaction transaction = new Transaction(Main.WIF, blockData, operations); + 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()); } } @@ -775,18 +734,20 @@ public class Test { } }; - UserAccount account = new UserAccount("1.2.139313"); - AssetAmount fee = new AssetAmount(UnsignedLong.valueOf("200"), new Asset("1.3.0")); - HashMap keyAuths = new HashMap<>(); - keyAuths.put("BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY", 1); + String newAddress = "BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY"; try { - Authority owner = new Authority(1, keyAuths); - Authority active = new Authority(1, keyAuths); - AccountUpdateOperation operation = new AccountUpdateOperation(account, owner, active, fee); - ArrayList operations = new ArrayList(); - operations.add(operation); - BrainKey brainKey = new BrainKey(Main.BILTHON_5_BRAIN_KEY, 0); - Transaction transaction = new Transaction(brainKey.getWalletImportFormat(), null, operations); + 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"); @@ -799,8 +760,11 @@ public class Test { mWebSocket.addListener(new TransactionBroadcastSequence(transaction, new Asset("1.3.0"), listener)); mWebSocket.connect(); + } 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) { @@ -808,5 +772,42 @@ public class Test { } catch (WebSocketException e) { System.out.println("WebSocketException. Msg: "+e.getMessage()); } + + + // -- + +// UserAccount account = new UserAccount("1.2.139313"); +// AssetAmount fee = new AssetAmount(UnsignedLong.valueOf("200"), new Asset("1.3.0")); +// HashMap keyAuths = new HashMap<>(); +// keyAuths.put("BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY", 1); +// try { +// Authority owner = new Authority(1, keyAuths); +// Authority active = new Authority(1, keyAuths); +// AccountUpdateOperation operation = new AccountUpdateOperation(account, owner, active, null, fee); +// ArrayList operations = new ArrayList(); +// operations.add(operation); +// BrainKey brainKey = new BrainKey(Main.BILTHON_5_BRAIN_KEY, 0); +// Transaction transaction = new Transaction(brainKey.getWalletImportFormat(), null, operations); +// +// SSLContext context = null; +// 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 TransactionBroadcastSequence(transaction, new Asset("1.3.0"), listener)); +// mWebSocket.connect(); +// } catch (MalformedAddressException e) { +// System.out.println("MalformedAddressException. 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()); +// } } } diff --git a/src/main/java/com/luminiasoft/bitshares/Transaction.java b/src/main/java/com/luminiasoft/bitshares/Transaction.java index 967d5a4..29a4620 100644 --- a/src/main/java/com/luminiasoft/bitshares/Transaction.java +++ b/src/main/java/com/luminiasoft/bitshares/Transaction.java @@ -24,7 +24,7 @@ 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(); @@ -65,10 +65,18 @@ public class Transaction implements ByteSerializable, JsonSerializable { 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)); 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/TransferTransactionBuilder.java b/src/main/java/com/luminiasoft/bitshares/TransferTransactionBuilder.java index 0caf11e..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; diff --git a/src/main/java/com/luminiasoft/bitshares/UserAccount.java b/src/main/java/com/luminiasoft/bitshares/UserAccount.java index a1dba3e..e25c261 100644 --- a/src/main/java/com/luminiasoft/bitshares/UserAccount.java +++ b/src/main/java/com/luminiasoft/bitshares/UserAccount.java @@ -15,6 +15,8 @@ import java.io.IOException; */ public class UserAccount extends GrapheneObject implements ByteSerializable, JsonSerializable { + public static final String PROXY_TO_SELF = "1.2.5"; + /** * Constructor that expects a user account in the string representation. * That is in the 1.2.x format. 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/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/ws/TransactionBroadcastSequence.java b/src/main/java/com/luminiasoft/bitshares/ws/TransactionBroadcastSequence.java index 71202ba..ecd93b7 100644 --- a/src/main/java/com/luminiasoft/bitshares/ws/TransactionBroadcastSequence.java +++ b/src/main/java/com/luminiasoft/bitshares/ws/TransactionBroadcastSequence.java @@ -142,12 +142,17 @@ public class TransactionBroadcastSequence extends WebSocketAdapter { @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(); }