From e0b0330e54c6ee122d778fc1a780fd25cf4cf82c Mon Sep 17 00:00:00 2001 From: "Nelson R. Perez" Date: Sun, 4 Dec 2016 19:22:12 -0500 Subject: [PATCH] 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()));