Reverting wrong renaming

This commit is contained in:
Nelson R. Perez 2016-12-02 16:24:42 -05:00
parent 07dbbca371
commit 8b81c30fa9
10 changed files with 383 additions and 392 deletions

View file

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

View file

@ -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<Serializable> 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<Serializable> 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<BaseOperation> operations = new ArrayList<>();
operations.add(transfer);

View file

@ -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<BaseOperation> operations;
private List<Extension> 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<BaseOperation> operation_list){
this.privateKey = DumpedPrivateKey.fromBase58(null, wif).getKey();
this.blockData = block_data;
this.operations = operation_list;
this.extensions = new ArrayList<Extension>();
}
/**
* 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<BaseOperation> operationList){
this.privateKey = privateKey;
this.blockData = blockData;
this.operations = operationList;
this.extensions = new ArrayList<Extension>();
}
public ECKey getPrivateKey(){
return this.privateKey;
}
public List<BaseOperation> 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<Byte> byteArray = new ArrayList<Byte>();
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<Transaction> {
@Override
public JsonElement serialize(Transaction transaction, Type type, JsonSerializationContext jsonSerializationContext) {
return transaction.toJsonObject();
}
}
}

View file

@ -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;
}

View file

@ -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<Transfer> {
@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<Transfer> {
@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;
}
}
}
}

View file

@ -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<BaseOperation> operations;
private List<Extension> 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<BaseOperation> 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<Extension>();
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<BaseOperation> 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<Extension>();
this.from = from;
this.to = to;
this.amount = transferAmount;
this.memo = new Memo();
}
public ECKey getPrivateKey(){
return this.privateKey;
}
public List<BaseOperation> 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 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() {
// 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(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);
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<TransferOperation> {
public static class TransferSerializer implements JsonSerializer<TransferOperation> {
@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;
}
}
/**
* 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<TransferOperation> {
@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;
}
}
}
}

View file

@ -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<BaseOperation>();
}
@ -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);
}
}

View file

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

View file

@ -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<WitnessResponse<List<HistoricalTransfer>>>(){}.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<List<HistoricalTransfer>> transfersResponse = gsonBuilder.create().fromJson(response, RelativeAccountHistoryResponse);
mListener.onSuccess(transfersResponse);

View file

@ -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<Serializable> 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<WitnessResponse<String>>(){}.getType();
@ -131,18 +131,18 @@ public class TransactionBroadcastSequence extends WebSocketAdapter {
* with ONE transfer operation.
*/
retries++;
List<BaseOperation> operations = this.transferOperation.getOperations();
Transfer transfer = (Transfer) operations.get(0);
transferOperation = new TransferTransactionBuilder()
List<BaseOperation> 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<Serializable> transactionList = new ArrayList<>();
transactionList.add(transferOperation);
transactionList.add(transaction);
ApiCall call = new ApiCall(broadcastApiId,
RPC.CALL_BROADCAST_TRANSACTION,
transactionList,