From d8c796203afc9951b345d32142b8d412a7f96afe Mon Sep 17 00:00:00 2001
From: superbigfu <512034786@qq.com>
Date: Tue, 2 Apr 2019 15:30:10 +0800
Subject: [PATCH] fix transaction deserializer
---
pom.xml | 16 +++-
.../client/graphenej/objects/Transaction.java | 86 +++++++++++++++----
.../operations/AccountCreateOperation.java | 78 +++++++++++++++--
.../operations/LimitOrderCreateOperation.java | 2 +-
.../operations/TransferOperation.java | 2 +-
.../com/gxchain/client/util/GXGsonUtil.java | 4 +-
.../gxchain/client/util/GXGsonUtilTest.java | 30 +++++++
7 files changed, 188 insertions(+), 30 deletions(-)
create mode 100644 src/test/java/com/gxchain/client/util/GXGsonUtilTest.java
diff --git a/pom.xml b/pom.xml
index 42b75a8..52d599b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,6 +13,14 @@
UTF-8
2.5.0
+
+
+
+ gxchain
+ http://repo.gxchain.cn/repository/maven-public/
+
+
+
junit
@@ -140,13 +148,13 @@
nexus-releases
- http://192.168.1.122:8081/repository/maven-releases/
-
+
+ http://repo.gxchain.cn/repository/maven-releases/
nexus-snapshots
- http://192.168.1.122:8081/repository/maven-snapshots/
-
+
+ http://repo.gxchain.cn/repository/maven-snapshots/
\ No newline at end of file
diff --git a/src/main/java/com/gxchain/client/graphenej/objects/Transaction.java b/src/main/java/com/gxchain/client/graphenej/objects/Transaction.java
index defbfd1..d489c02 100644
--- a/src/main/java/com/gxchain/client/graphenej/objects/Transaction.java
+++ b/src/main/java/com/gxchain/client/graphenej/objects/Transaction.java
@@ -6,25 +6,29 @@
import com.gxchain.client.graphenej.enums.OperationType;
import com.gxchain.client.graphenej.interfaces.ByteSerializable;
import com.gxchain.client.graphenej.interfaces.JsonSerializable;
+import com.gxchain.client.graphenej.operations.AccountCreateOperation;
import com.gxchain.client.graphenej.operations.BaseOperation;
import com.gxchain.client.graphenej.operations.LimitOrderCreateOperation;
import com.gxchain.client.graphenej.operations.TransferOperation;
import com.gxchain.client.util.TxSerializerUtil;
import com.gxchain.common.signature.SignatureUtil;
+import com.gxchain.common.signature.utils.StringUtils;
import com.gxchain.common.signature.utils.Wif;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.bitcoinj.core.DumpedPrivateKey;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Sha256Hash;
-import org.bitcoinj.core.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Type;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
/**
* Class used to represent a generic Graphene transaction.
@@ -39,7 +43,9 @@ public class Transaction implements ByteSerializable, JsonSerializable {
/* Default expiration time */
public static final int DEFAULT_EXPIRATION_TIME = 120;
+
/* Constant field names used for serialization/deserialization purposes */
+ public static final String KEY_CHAIN_ID = "chain_id";
public static final String KEY_EXPIRATION = "expiration";
public static final String KEY_SIGNATURES = "signatures";
public static final String KEY_OPERATIONS = "operations";
@@ -72,6 +78,22 @@ public Transaction(ECKey privateKey, BlockData blockData, List op
this.extensions = new Extensions();
}
+ /**
+ * Constructor used to build a BlockTransaction object without a private key. This kind of object
+ * is used to represent a transaction data that we don't intend to serialize and sign.
+ *
+ * @param chainId
+ * @param blockData: Block data instance, containing information about the location of this transaction in the blockchain.
+ * @param operationList: The list of operations included in this transaction.
+ * @param signature
+ */
+ public Transaction(String chainId, BlockData blockData, List operationList, String signature) {
+ this.chainId = chainId;
+ this.blockData = blockData;
+ this.operations = operationList;
+ this.signature = signature;
+ }
+
/**
* BlockTransaction constructor.
*
@@ -120,8 +142,9 @@ public void setBlockData(BlockData blockData) {
* @param fees: New fees to apply
*/
public void setFees(List fees) {
- for (int i = 0; i < operations.size(); i++)
+ for (int i = 0; i < operations.size(); i++) {
operations.get(i).setFee(fees.get(i));
+ }
}
public ECKey getPrivateKey() {
@@ -152,7 +175,7 @@ public boolean hasPrivateKey() {
* @return: A valid signature of the current transaction.
*/
public byte[] getGrapheneSignature() {
- return SignatureUtil.signature(this.toBytes(),new Wif(privateKey).toString());
+ return SignatureUtil.signature(this.toBytes(), new Wif(privateKey).toString());
}
/**
@@ -165,7 +188,22 @@ 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(getChainId())));
- byteArray.addAll(Bytes.asList(TxSerializerUtil.serializeTransaction(this.toJsonObjectNoSign())));
+
+ // 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 extensions byte
+ byteArray.addAll(Bytes.asList(this.extensions.toBytes()));
+
return Bytes.toArray(byteArray);
}
@@ -183,16 +221,17 @@ public JsonObject toJsonObject() {
// Getting the signature before anything else,
// since this might change the transaction expiration data slightly
- long l1 = System.currentTimeMillis();
- byte[] signature = getGrapheneSignature();
- logger.info("signature consuming " + (System.currentTimeMillis() - l1) + " ms");
- String sign = Util.bytesToHex(signature);
- this.signature = sign;
+ if (StringUtils.isEmpty(this.signature)) {
+ long l1 = System.currentTimeMillis();
+ byte[] signature = getGrapheneSignature();
+ logger.info("signature consuming " + (System.currentTimeMillis() - l1) + " ms");
+ this.signature = Util.bytesToHex(signature);
+ }
JsonObject obj = toJsonObjectNoSign();
// Adding signatures
JsonArray signatureArray = new JsonArray();
- signatureArray.add(sign);
+ signatureArray.add(this.signature);
obj.add(KEY_SIGNATURES, signatureArray);
return obj;
@@ -205,7 +244,7 @@ public JsonObject toJsonObjectNoSign() {
obj.addProperty(KEY_REF_BLOCK_PREFIX, blockData.getRefBlockPrefix());
// Formatting expiration time
- Date expirationTime = new Date(blockData.getExpiration() * 1000);
+ Date expirationTime = new Date(blockData.getExpiration());
SimpleDateFormat dateFormat = new SimpleDateFormat(Util.TIME_DATE_FORMAT);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
@@ -267,29 +306,42 @@ public Transaction deserialize(JsonElement json, Type typeOfT, JsonDeserializati
Date expirationDate = dateFormat.parse(expiration, new ParsePosition(0));
BlockData blockData = new BlockData(refBlockNum, refBlockPrefix, expirationDate.getTime());
+ // Parsing chainId
+ String chainId = null == jsonObject.get(KEY_CHAIN_ID) ? null : jsonObject.get(KEY_CHAIN_ID).getAsString();
+
+ // Parsing signatures
+ String signature = null;
+ try {
+ signature = null == jsonObject.get(KEY_SIGNATURES) ? null : jsonObject.get(KEY_SIGNATURES).getAsString();
+ } catch (Exception e) {
+ }
+
// Parsing operation list
BaseOperation operation = null;
ArrayList operationList = new ArrayList<>();
try {
for (JsonElement jsonOperation : jsonObject.get(KEY_OPERATIONS).getAsJsonArray()) {
int operationId = jsonOperation.getAsJsonArray().get(0).getAsInt();
- if (operationId == OperationType.TRANSFER_OPERATION.ordinal()) {
+ if (operationId == OperationType.TRANSFER_OPERATION.getCode()) {
operation = context.deserialize(jsonOperation, TransferOperation.class);
- } else if (operationId == OperationType.LIMIT_ORDER_CREATE_OPERATION.ordinal()) {
+ } else if (operationId == OperationType.LIMIT_ORDER_CREATE_OPERATION.getCode()) {
operation = context.deserialize(jsonOperation, LimitOrderCreateOperation.class);
+ } else if (operationId == OperationType.ACCOUNT_CREATE_OPERATION.getCode()) {
+ operation = context.deserialize(jsonOperation, AccountCreateOperation.class);
}
- if (operation != null)
+ if (operation != null) {
operationList.add(operation);
+ }
operation = null;
}
- return new Transaction(blockData, operationList);
+ return new Transaction(chainId, blockData, operationList, signature);
} catch (Exception e) {
LOGGER.info("Exception. Msg: " + e.getMessage());
for (StackTraceElement el : e.getStackTrace()) {
LOGGER.info(el.getFileName() + "#" + el.getMethodName() + ":" + el.getLineNumber());
}
}
- return new Transaction(blockData, operationList);
+ return new Transaction(chainId, blockData, operationList, signature);
}
}
}
diff --git a/src/main/java/com/gxchain/client/graphenej/operations/AccountCreateOperation.java b/src/main/java/com/gxchain/client/graphenej/operations/AccountCreateOperation.java
index a650102..bbae1f8 100644
--- a/src/main/java/com/gxchain/client/graphenej/operations/AccountCreateOperation.java
+++ b/src/main/java/com/gxchain/client/graphenej/operations/AccountCreateOperation.java
@@ -2,15 +2,14 @@
import com.google.common.primitives.Bytes;
import com.google.common.primitives.UnsignedLong;
-import com.google.gson.Gson;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
+import com.google.gson.*;
import com.gxchain.client.graphenej.Varint;
import com.gxchain.client.graphenej.enums.OperationType;
import com.gxchain.client.graphenej.objects.*;
import lombok.Data;
+import java.lang.reflect.Type;
+
/**
* Class used to encapsulate operations related to the ACCOUNT_CREATE_OPERATION.
*/
@@ -56,7 +55,35 @@ public AccountCreateOperation(UserAccount registrar, UserAccount referrer, Autho
this.owner = owner;
this.active = active;
this.options = options;
- extensions = new Extensions();
+ this.extensions = new Extensions();
+ }
+
+ /**
+ * Account create operation constructor.
+ *
+ * @param registrar register account
+ * @param referrer
+ * @param owner Owner authority to set. Can be null.
+ * @param active Active authority to set. Can be null.
+ * @param options Can be null
+ * @param fee The fee to pay. Can be null.
+ * @param referrerPercent
+ * @param name register name
+ */
+ public AccountCreateOperation(UserAccount registrar, UserAccount referrer,
+ Authority owner, Authority active,
+ AccountOptions options, AssetAmount fee,
+ int referrerPercent, String name) {
+ super(OperationType.ACCOUNT_CREATE_OPERATION);
+ this.fee = fee;
+ this.registrar = registrar;
+ this.referrer = referrer;
+ this.owner = owner;
+ this.active = active;
+ this.options = options;
+ this.extensions = new Extensions();
+ this.referrerPercent = referrerPercent;
+ this.name = name;
}
public AccountCreateOperation(UserAccount registrar, UserAccount referrer, Authority owner, Authority active, AccountOptions options) {
@@ -118,4 +145,45 @@ public byte[] toBytes() {
byte[] extensionBytes = extensions.toBytes();
return Bytes.concat(feeBytes, registrarBytes, referrerBytes, referrerPercentBytes, new byte[]{(byte) this.name.length()}, nameBytes, ownerBytes, activeBytes, optionsBytes, extensionBytes);
}
+
+ public static class AccountCreateSerializer implements JsonSerializer {
+
+ @Override
+ public JsonElement serialize(AccountCreateOperation accountCreate, Type type, JsonSerializationContext jsonSerializationContext) {
+ return accountCreate.toJsonObject();
+ }
+ }
+
+ public static class AccountCreateDeserializer implements JsonDeserializer {
+
+ @Override
+ public AccountCreateOperation deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+ if (json.isJsonArray()) {
+ JsonArray serializedTransfer = json.getAsJsonArray();
+ if (serializedTransfer.get(0).getAsInt() != OperationType.ACCOUNT_CREATE_OPERATION.getCode()) {
+ return null;
+ } else {
+ return context.deserialize(serializedTransfer.get(1), AccountCreateOperation.class);
+ }
+ } else {
+ JsonObject jsonObject = json.getAsJsonObject();
+
+ AssetAmount fee = context.deserialize(jsonObject.get(KEY_FEE), AssetAmount.class);
+ Authority owner = context.deserialize(jsonObject.get(KEY_OWNER), Authority.class);
+ Authority active = context.deserialize(jsonObject.get(KEY_ACTIVE), Authority.class);
+ AccountOptions options = context.deserialize(jsonObject.get(KEY_OPTIONS), AccountOptions.class);
+
+ UserAccount registrar = new UserAccount(jsonObject.get(KEY_REGISTRAR).getAsString());
+ UserAccount referrer = new UserAccount(jsonObject.get(KEY_REFERRER).getAsString());
+
+ int referrerPercent = jsonObject.get(KEY_REFERRER_PERCENT).getAsInt();
+ String name = jsonObject.get(KEY_NAME).getAsString();
+
+ AccountCreateOperation accountCreate = new AccountCreateOperation(
+ registrar, referrer, owner, active, options, fee, referrerPercent, name);
+
+ return accountCreate;
+ }
+ }
+ }
}
diff --git a/src/main/java/com/gxchain/client/graphenej/operations/LimitOrderCreateOperation.java b/src/main/java/com/gxchain/client/graphenej/operations/LimitOrderCreateOperation.java
index c5972a1..a2ce381 100644
--- a/src/main/java/com/gxchain/client/graphenej/operations/LimitOrderCreateOperation.java
+++ b/src/main/java/com/gxchain/client/graphenej/operations/LimitOrderCreateOperation.java
@@ -188,7 +188,7 @@ public LimitOrderCreateOperation deserialize(JsonElement json, Type typeOfT, Jso
// 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.LIMIT_ORDER_CREATE_OPERATION.ordinal()){
+ if(serializedTransfer.get(0).getAsInt() != OperationType.LIMIT_ORDER_CREATE_OPERATION.getCode()){
// If the operation type does not correspond to a transfer operation, we return null
return null;
}else{
diff --git a/src/main/java/com/gxchain/client/graphenej/operations/TransferOperation.java b/src/main/java/com/gxchain/client/graphenej/operations/TransferOperation.java
index 279e613..ae3723c 100644
--- a/src/main/java/com/gxchain/client/graphenej/operations/TransferOperation.java
+++ b/src/main/java/com/gxchain/client/graphenej/operations/TransferOperation.java
@@ -173,7 +173,7 @@ public TransferOperation deserialize(JsonElement json, Type typeOfT, JsonDeseria
// 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 (serializedTransfer.get(0).getAsInt() != OperationType.TRANSFER_OPERATION.getCode()) {
// If the operation type does not correspond to a transfer operation, we return null
return null;
} else {
diff --git a/src/main/java/com/gxchain/client/util/GXGsonUtil.java b/src/main/java/com/gxchain/client/util/GXGsonUtil.java
index f9e65b2..65dc4fd 100644
--- a/src/main/java/com/gxchain/client/util/GXGsonUtil.java
+++ b/src/main/java/com/gxchain/client/util/GXGsonUtil.java
@@ -2,14 +2,13 @@
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
-import com.google.gson.reflect.TypeToken;
import com.gxchain.client.graphenej.models.ApiCall;
import com.gxchain.client.graphenej.models.DynamicGlobalProperties;
import com.gxchain.client.graphenej.objects.*;
+import com.gxchain.client.graphenej.operations.AccountCreateOperation;
import com.gxchain.client.graphenej.operations.TransferOperation;
import java.lang.reflect.Type;
-import java.util.List;
/**
* @author liruobin
@@ -21,6 +20,7 @@ public class GXGsonUtil {
static {
builder.registerTypeAdapter(Transaction.class, new Transaction.TransactionDeserializer());
builder.registerTypeAdapter(TransferOperation.class, new TransferOperation.TransferDeserializer());
+ builder.registerTypeAdapter(AccountCreateOperation.class, new AccountCreateOperation.AccountCreateDeserializer());
builder.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer());
builder.registerTypeAdapter(UserAccount.class, new UserAccount.UserAccountSimpleDeserializer());
builder.registerTypeAdapter(DynamicGlobalProperties.class, new DynamicGlobalProperties.DynamicGlobalPropertiesDeserializer());
diff --git a/src/test/java/com/gxchain/client/util/GXGsonUtilTest.java b/src/test/java/com/gxchain/client/util/GXGsonUtilTest.java
new file mode 100644
index 0000000..ce10918
--- /dev/null
+++ b/src/test/java/com/gxchain/client/util/GXGsonUtilTest.java
@@ -0,0 +1,30 @@
+package com.gxchain.client.util;
+
+import com.gxchain.client.graphenej.enums.OperationType;
+import com.gxchain.client.graphenej.objects.Transaction;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class GXGsonUtilTest {
+
+
+ @Test
+ public void operationTypeTest() {
+ Assert.assertEquals(5, OperationType.ACCOUNT_CREATE_OPERATION.getCode());
+ }
+
+
+ @Test
+ public void transactionDeserializerTest() {
+ String hex = "{\"ref_block_num\":7971,\"ref_block_prefix\":2672178873,\"expiration\":\"2019-04-01T13:16:24\"," +
+ "\"operations\":[" +
+ "[5,{\"fee\":{\"amount\":1000,\"asset_id\":\"1.3.1\"},\"extensions\":[],\"registrar\":\"1.2.1131733\",\"referrer\":\"1.2.1131733\",\"referrer_percent\":10000," +
+ "\"name\":\"cherryreg22\",\"owner\":{\"weight_threshold\":1,\"account_auths\":[],\"key_auths\":[[\"GXC5u2vbL1ME9PBnJTRtBHRr8JCM7hDEiY4fJS5mY3HjyhvRj8tAP\",1]],\"address_auths\":[]},\"active\":{\"weight_threshold\":1,\"account_auths\":[],\"key_auths\":[[\"GXC5u2vbL1ME9PBnJTRtBHRr8JCM7hDEiY4fJS5mY3HjyhvRj8tAP\",1]],\"address_auths\":[]},\"options\":{\"memo_key\":\"GXC5u2vbL1ME9PBnJTRtBHRr8JCM7hDEiY4fJS5mY3HjyhvRj8tAP\",\"num_committee\":0,\"num_witness\":0,\"voting_account\":\"1.2.5\",\"votes\":[],\"extensions\":[]}}]],\"signatures\":[\"206b20fccb39d914c4af2682c2552f948a7e12039d08d70fad788e9d62a2724b511608ae8c754067e08eb83ba6131910c655f36de26c568c97cca18e54d3c271fe\"],\"extensions\":[]}";
+ Transaction transaction = GXGsonUtil.fromJson(hex, Transaction.class);
+ transaction.setChainId("4f7d07969c446f8342033acb3ab2ae5044cbe0fde93db02de75bd17fa8fd84b8");
+
+ System.out.println(transaction.toJsonString());
+ }
+
+
+}