Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix transaction deserializer #2

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<com.squareup.retrofit2.version>2.5.0</com.squareup.retrofit2.version>
</properties>

<repositories>
<repository>
<id>gxchain</id>
<url>http://repo.gxchain.cn/repository/maven-public/</url>
</repository>
</repositories>

<dependencies>
<dependency>
<groupId>junit</groupId>
Expand Down Expand Up @@ -140,13 +148,13 @@
<distributionManagement>
<repository>
<id>nexus-releases</id>
<url>http://192.168.1.122:8081/repository/maven-releases/</url>
<!--<url>http://repo.gxchain.cn/repository/maven-releases/</url>-->
<!--<url>http://192.168.1.122:8081/repository/maven-releases/</url>-->
<url>http://repo.gxchain.cn/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>nexus-snapshots</id>
<url>http://192.168.1.122:8081/repository/maven-snapshots/</url>
<!--<url>http://repo.gxchain.cn/repository/maven-snapshots/</url>-->
<!--<url>http://192.168.1.122:8081/repository/maven-snapshots/</url>-->
<url>http://repo.gxchain.cn/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
</project>
86 changes: 69 additions & 17 deletions src/main/java/com/gxchain/client/graphenej/objects/Transaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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";
Expand Down Expand Up @@ -72,6 +78,22 @@ public Transaction(ECKey privateKey, BlockData blockData, List<BaseOperation> 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<BaseOperation> operationList, String signature) {
this.chainId = chainId;
this.blockData = blockData;
this.operations = operationList;
this.signature = signature;
}

/**
* BlockTransaction constructor.
*
Expand Down Expand Up @@ -120,8 +142,9 @@ public void setBlockData(BlockData blockData) {
* @param fees: New fees to apply
*/
public void setFees(List<AssetAmount> 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() {
Expand Down Expand Up @@ -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());
}

/**
Expand All @@ -165,7 +188,22 @@ public byte[] toBytes() {
// Creating a List of Bytes and adding the first bytes from the chain apiId
List<Byte> byteArray = new ArrayList<Byte>();
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);
}

Expand All @@ -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;

Expand All @@ -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"));

Expand Down Expand Up @@ -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<BaseOperation> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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<AccountCreateOperation> {

@Override
public JsonElement serialize(AccountCreateOperation accountCreate, Type type, JsonSerializationContext jsonSerializationContext) {
return accountCreate.toJsonObject();
}
}

public static class AccountCreateDeserializer implements JsonDeserializer<AccountCreateOperation> {

@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;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/gxchain/client/util/GXGsonUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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());
Expand Down
30 changes: 30 additions & 0 deletions src/test/java/com/gxchain/client/util/GXGsonUtilTest.java
Original file line number Diff line number Diff line change
@@ -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());
}


}