Reverting wrong renaming
This commit is contained in:
parent
07dbbca371
commit
8b81c30fa9
10 changed files with 383 additions and 392 deletions
|
@ -2,6 +2,7 @@ package com.luminiasoft.bitshares;
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.luminiasoft.bitshares.errors.MalformedAddressException;
|
import com.luminiasoft.bitshares.errors.MalformedAddressException;
|
||||||
|
import com.luminiasoft.bitshares.interfaces.ByteSerializable;
|
||||||
import com.luminiasoft.bitshares.interfaces.JsonSerializable;
|
import com.luminiasoft.bitshares.interfaces.JsonSerializable;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
|
@ -35,10 +35,10 @@ public class Test {
|
||||||
public static final String OPENLEDGER_WITNESS_URL = "wss://bitshares.openledger.info/ws";
|
public static final String OPENLEDGER_WITNESS_URL = "wss://bitshares.openledger.info/ws";
|
||||||
// public static final String WITNESS_URL = "wss://fr.blockpay.ch:8089";
|
// public static final String WITNESS_URL = "wss://fr.blockpay.ch:8089";
|
||||||
|
|
||||||
private TransferOperation transferOperation;
|
private Transaction transaction;
|
||||||
|
|
||||||
public TransferOperation getTransferOperation() {
|
public Transaction getTransaction() {
|
||||||
return transferOperation;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
private WitnessResponseListener mListener = new WitnessResponseListener() {
|
private WitnessResponseListener mListener = new WitnessResponseListener() {
|
||||||
|
@ -109,27 +109,27 @@ public class Test {
|
||||||
};
|
};
|
||||||
|
|
||||||
public ECKey.ECDSASignature testSigning() {
|
public ECKey.ECDSASignature testSigning() {
|
||||||
byte[] serializedTransaction = this.transferOperation.toBytes();
|
byte[] serializedTransaction = this.transaction.toBytes();
|
||||||
Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction));
|
Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction));
|
||||||
byte[] bytesDigest = hash.getBytes();
|
byte[] bytesDigest = hash.getBytes();
|
||||||
ECKey sk = transferOperation.getPrivateKey();
|
ECKey sk = transaction.getPrivateKey();
|
||||||
ECKey.ECDSASignature signature = sk.sign(hash);
|
ECKey.ECDSASignature signature = sk.sign(hash);
|
||||||
return signature;
|
return signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String testSigningMessage() {
|
public String testSigningMessage() {
|
||||||
byte[] serializedTransaction = this.transferOperation.toBytes();
|
byte[] serializedTransaction = this.transaction.toBytes();
|
||||||
Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction));
|
Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction));
|
||||||
ECKey sk = transferOperation.getPrivateKey();
|
ECKey sk = transaction.getPrivateKey();
|
||||||
return sk.signMessage(hash.toString());
|
return sk.signMessage(hash.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] signMessage() {
|
public byte[] signMessage() {
|
||||||
byte[] serializedTransaction = this.transferOperation.toBytes();
|
byte[] serializedTransaction = this.transaction.toBytes();
|
||||||
Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction));
|
Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction));
|
||||||
System.out.println(">> digest <<");
|
System.out.println(">> digest <<");
|
||||||
System.out.println(Util.bytesToHex(hash.getBytes()));
|
System.out.println(Util.bytesToHex(hash.getBytes()));
|
||||||
ECKey sk = transferOperation.getPrivateKey();
|
ECKey sk = transaction.getPrivateKey();
|
||||||
System.out.println("Private key bytes");
|
System.out.println("Private key bytes");
|
||||||
System.out.println(Util.bytesToHex(sk.getPrivKeyBytes()));
|
System.out.println(Util.bytesToHex(sk.getPrivKeyBytes()));
|
||||||
boolean isCanonical = false;
|
boolean isCanonical = false;
|
||||||
|
@ -183,10 +183,10 @@ public class Test {
|
||||||
UserAccount to = new UserAccount("1.2.129848");
|
UserAccount to = new UserAccount("1.2.129848");
|
||||||
AssetAmount amount = new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120"));
|
AssetAmount amount = new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120"));
|
||||||
AssetAmount fee = new AssetAmount(UnsignedLong.valueOf(264174), new Asset("1.3.0"));
|
AssetAmount fee = new AssetAmount(UnsignedLong.valueOf(264174), new Asset("1.3.0"));
|
||||||
operations.add(new Transfer(from, to, amount, fee));
|
operations.add(new TransferOperation(from, to, amount, fee));
|
||||||
this.transferOperation = new TransferOperation(Main.WIF, blockData, operations);
|
this.transaction = new Transaction(Main.WIF, blockData, operations);
|
||||||
byte[] serializedTransaction = this.transferOperation.toBytes();
|
byte[] serializedTransaction = this.transaction.toBytes();
|
||||||
System.out.println("Serialized transferOperation");
|
System.out.println("Serialized transaction");
|
||||||
System.out.println(Util.bytesToHex(serializedTransaction));
|
System.out.println(Util.bytesToHex(serializedTransaction));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,7 +335,7 @@ public class Test {
|
||||||
|
|
||||||
public void testTransactionSerialization() {
|
public void testTransactionSerialization() {
|
||||||
try {
|
try {
|
||||||
TransferOperation transferOperation = new TransferTransactionBuilder()
|
Transaction transaction = new TransferTransactionBuilder()
|
||||||
.setSource(new UserAccount("1.2.138632"))
|
.setSource(new UserAccount("1.2.138632"))
|
||||||
.setDestination(new UserAccount("1.2.129848"))
|
.setDestination(new UserAccount("1.2.129848"))
|
||||||
.setAmount(new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120")))
|
.setAmount(new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120")))
|
||||||
|
@ -345,9 +345,9 @@ public class Test {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
ArrayList<Serializable> transactionList = new ArrayList<>();
|
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));
|
System.out.println(Util.bytesToHex(signature));
|
||||||
ApiCall call = new ApiCall(4, "call", "broadcast_transaction", transactionList, "2.0", 1);
|
ApiCall call = new ApiCall(4, "call", "broadcast_transaction", transactionList, "2.0", 1);
|
||||||
String jsonCall = call.toJsonString();
|
String jsonCall = call.toJsonString();
|
||||||
|
@ -420,7 +420,7 @@ public class Test {
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
TransferOperation transferOperation = new TransferTransactionBuilder()
|
Transaction transaction = new TransferTransactionBuilder()
|
||||||
.setSource(new UserAccount("1.2.138632"))
|
.setSource(new UserAccount("1.2.138632"))
|
||||||
.setDestination(new UserAccount("1.2.129848"))
|
.setDestination(new UserAccount("1.2.129848"))
|
||||||
.setAmount(new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120")))
|
.setAmount(new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120")))
|
||||||
|
@ -430,9 +430,9 @@ public class Test {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
ArrayList<Serializable> transactionList = new ArrayList<>();
|
ArrayList<Serializable> transactionList = new ArrayList<>();
|
||||||
transactionList.add(transferOperation);
|
transactionList.add(transaction);
|
||||||
|
|
||||||
transactionList.add(transferOperation);
|
transactionList.add(transaction);
|
||||||
|
|
||||||
SSLContext context = null;
|
SSLContext context = null;
|
||||||
context = NaiveSSLContext.getInstance("TLS");
|
context = NaiveSSLContext.getInstance("TLS");
|
||||||
|
@ -443,7 +443,7 @@ public class Test {
|
||||||
|
|
||||||
WebSocket mWebSocket = factory.createSocket(OPENLEDGER_WITNESS_URL);
|
WebSocket mWebSocket = factory.createSocket(OPENLEDGER_WITNESS_URL);
|
||||||
|
|
||||||
mWebSocket.addListener(new TransactionBroadcastSequence(transferOperation, listener));
|
mWebSocket.addListener(new TransactionBroadcastSequence(transaction, listener));
|
||||||
mWebSocket.connect();
|
mWebSocket.connect();
|
||||||
|
|
||||||
} catch (MalformedTransactionException e) {
|
} catch (MalformedTransactionException e) {
|
||||||
|
@ -514,7 +514,7 @@ public class Test {
|
||||||
UserAccount to = new UserAccount("1.2.129848");
|
UserAccount to = new UserAccount("1.2.129848");
|
||||||
AssetAmount amount = new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120"));
|
AssetAmount amount = new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120"));
|
||||||
AssetAmount fee = new AssetAmount(UnsignedLong.valueOf(264174), new Asset("1.3.0"));
|
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<>();
|
ArrayList<BaseOperation> operations = new ArrayList<>();
|
||||||
operations.add(transfer);
|
operations.add(transfer);
|
||||||
|
|
||||||
|
|
211
src/main/java/com/luminiasoft/bitshares/Transaction.java
Normal file
211
src/main/java/com/luminiasoft/bitshares/Transaction.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,5 +11,5 @@ public abstract class TransactionBuilder {
|
||||||
protected ECKey privateKey;
|
protected ECKey privateKey;
|
||||||
protected BlockData blockData;
|
protected BlockData blockData;
|
||||||
|
|
||||||
public abstract TransferOperation build() throws MalformedTransactionException;
|
public abstract Transaction build() throws MalformedTransactionException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,218 +1,167 @@
|
||||||
package com.luminiasoft.bitshares;
|
package com.luminiasoft.bitshares;
|
||||||
|
|
||||||
import com.google.common.primitives.Bytes;
|
import com.google.common.primitives.Bytes;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.*;
|
||||||
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.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 {
|
public class TransferOperation extends BaseOperation {
|
||||||
private final String TAG = this.getClass().getName();
|
public static final String KEY_FEE = "fee";
|
||||||
|
public static final String KEY_AMOUNT = "amount";
|
||||||
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_EXTENSIONS = "extensions";
|
||||||
public static final String KEY_REF_BLOCK_NUM = "ref_block_num";
|
public static final String KEY_FROM = "from";
|
||||||
public static final String KEY_REF_BLOCK_PREFIX = "ref_block_prefix";
|
public static final String KEY_TO = "to";
|
||||||
|
|
||||||
private ECKey privateKey;
|
private AssetAmount fee;
|
||||||
private BlockData blockData;
|
private AssetAmount amount;
|
||||||
private List<BaseOperation> operations;
|
private UserAccount from;
|
||||||
private List<Extension> extensions;
|
private UserAccount to;
|
||||||
|
private Memo memo;
|
||||||
|
private String[] extensions;
|
||||||
|
|
||||||
/**
|
public TransferOperation(UserAccount from, UserAccount to, AssetAmount transferAmount, AssetAmount fee){
|
||||||
* 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){
|
|
||||||
super(OperationType.transfer_operation);
|
super(OperationType.transfer_operation);
|
||||||
this.privateKey = DumpedPrivateKey.fromBase58(null, wif).getKey();
|
this.from = from;
|
||||||
this.blockData = block_data;
|
this.to = to;
|
||||||
this.operations = operation_list;
|
this.amount = transferAmount;
|
||||||
this.extensions = new ArrayList<Extension>();
|
this.fee = fee;
|
||||||
|
this.memo = new Memo();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public TransferOperation(UserAccount from, UserAccount to, AssetAmount transferAmount){
|
||||||
* 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){
|
|
||||||
super(OperationType.transfer_operation);
|
super(OperationType.transfer_operation);
|
||||||
this.privateKey = privateKey;
|
this.from = from;
|
||||||
this.blockData = blockData;
|
this.to = to;
|
||||||
this.operations = operationList;
|
this.amount = transferAmount;
|
||||||
this.extensions = new ArrayList<Extension>();
|
this.memo = new Memo();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ECKey getPrivateKey(){
|
public void setFee(AssetAmount newFee){
|
||||||
return this.privateKey;
|
this.fee = newFee;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte getId() {
|
public byte getId() {
|
||||||
return 0;
|
return (byte) this.type.ordinal();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public UserAccount getFrom(){
|
||||||
* Method that creates a serialized byte array with compact information about this transaction
|
return this.from;
|
||||||
* that is needed for the creation of a signature.
|
}
|
||||||
* @return: byte array with serialized information about this transaction.
|
|
||||||
*/
|
public UserAccount getTo(){
|
||||||
|
return this.to;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AssetAmount getAmount(){
|
||||||
|
return this.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AssetAmount getFee(){
|
||||||
|
return this.fee;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public byte[] toBytes() {
|
public byte[] toBytes() {
|
||||||
// Creating a List of Bytes and adding the first bytes from the chain apiId
|
byte[] feeBytes = fee.toBytes();
|
||||||
List<Byte> byteArray = new ArrayList<Byte>();
|
byte[] fromBytes = from.toBytes();
|
||||||
byteArray.addAll(Bytes.asList(Util.hexToBytes(Chains.BITSHARES.CHAIN_ID)));
|
byte[] toBytes = to.toBytes();
|
||||||
|
byte[] amountBytes = amount.toBytes();
|
||||||
// Adding the block data
|
byte[] memoBytes = memo.toBytes();
|
||||||
byteArray.addAll(Bytes.asList(this.blockData.toBytes()));
|
return Bytes.concat(feeBytes, fromBytes, toBytes, amountBytes, memoBytes);
|
||||||
|
|
||||||
// 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
|
@Override
|
||||||
public String toJsonString() {
|
public String toJsonString() {
|
||||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||||
gsonBuilder.registerTypeAdapter(TransferOperation.class, new TransactionSerializer());
|
gsonBuilder.registerTypeAdapter(TransferOperation.class, new TransferSerializer());
|
||||||
return gsonBuilder.create().toJson(this);
|
return gsonBuilder.create().toJson(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JsonObject toJsonObject() {
|
public JsonElement toJsonObject() {
|
||||||
JsonObject obj = new JsonObject();
|
JsonArray array = new JsonArray();
|
||||||
|
array.add(this.getId());
|
||||||
// Getting the signature before anything else,
|
JsonObject jsonObject = new JsonObject();
|
||||||
// since this might change the transaction expiration data slightly
|
jsonObject.add(KEY_FEE, fee.toJsonObject());
|
||||||
byte[] signature = getGrapheneSignature();
|
jsonObject.add(KEY_AMOUNT, amount.toJsonObject());
|
||||||
|
jsonObject.add(KEY_EXTENSIONS, new JsonArray());
|
||||||
// Formatting expiration time
|
jsonObject.addProperty(KEY_FROM, from.toJsonString());
|
||||||
Date expirationTime = new Date(blockData.getRelativeExpiration() * 1000);
|
jsonObject.addProperty(KEY_TO, to.toJsonString());
|
||||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
|
array.add(jsonObject);
|
||||||
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
|
return array;
|
||||||
|
|
||||||
// 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<TransferOperation> {
|
public static class TransferSerializer implements JsonSerializer<TransferOperation> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JsonElement serialize(TransferOperation transferOperation, Type type, JsonSerializationContext jsonSerializationContext) {
|
public JsonElement serialize(TransferOperation transfer, Type type, JsonSerializationContext jsonSerializationContext) {
|
||||||
return transferOperation.toJsonObject();
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -46,7 +46,7 @@ public class TransferTransactionBuilder extends TransactionBuilder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TransferTransactionBuilder addOperation(Transfer transferOperation){
|
public TransferTransactionBuilder addOperation(TransferOperation transferOperation){
|
||||||
if(operations == null){
|
if(operations == null){
|
||||||
operations = new ArrayList<BaseOperation>();
|
operations = new ArrayList<BaseOperation>();
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ public class TransferTransactionBuilder extends TransactionBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TransferOperation build() throws MalformedTransactionException {
|
public Transaction build() throws MalformedTransactionException {
|
||||||
if(privateKey == null){
|
if(privateKey == null){
|
||||||
throw new MalformedTransactionException("Missing private key information");
|
throw new MalformedTransactionException("Missing private key information");
|
||||||
}else if(blockData == null){
|
}else if(blockData == null){
|
||||||
|
@ -73,14 +73,14 @@ public class TransferTransactionBuilder extends TransactionBuilder {
|
||||||
if(transferAmount == null){
|
if(transferAmount == null){
|
||||||
throw new MalformedTransactionException("Missing transfer amount information");
|
throw new MalformedTransactionException("Missing transfer amount information");
|
||||||
}
|
}
|
||||||
Transfer transferOperation;
|
TransferOperation transferOperation;
|
||||||
if(feeAmount == null){
|
if(feeAmount == null){
|
||||||
transferOperation = new Transfer(sourceAccount, destinationAccount, transferAmount);
|
transferOperation = new TransferOperation(sourceAccount, destinationAccount, transferAmount);
|
||||||
}else{
|
}else{
|
||||||
transferOperation = new Transfer(sourceAccount, destinationAccount, transferAmount, feeAmount);
|
transferOperation = new TransferOperation(sourceAccount, destinationAccount, transferAmount, feeAmount);
|
||||||
}
|
}
|
||||||
operations.add(transferOperation);
|
operations.add(transferOperation);
|
||||||
}
|
}
|
||||||
return new TransferOperation(privateKey, blockData, operations);
|
return new Transaction(privateKey, blockData, operations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package com.luminiasoft.bitshares.models;
|
package com.luminiasoft.bitshares.models;
|
||||||
|
|
||||||
import com.luminiasoft.bitshares.BaseOperation;
|
import com.luminiasoft.bitshares.TransferOperation;
|
||||||
import com.luminiasoft.bitshares.GrapheneObject;
|
|
||||||
import com.luminiasoft.bitshares.Transfer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class offers support to deserialization of transfer operations received by the API
|
* 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 class HistoricalTransfer {
|
||||||
public String id;
|
public String id;
|
||||||
public Transfer op;
|
public TransferOperation op;
|
||||||
public Object[] result;
|
public Object[] result;
|
||||||
public long block_num;
|
public long block_num;
|
||||||
public long trx_in_block;
|
public long trx_in_block;
|
||||||
|
|
|
@ -5,7 +5,7 @@ import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import com.luminiasoft.bitshares.AssetAmount;
|
import com.luminiasoft.bitshares.AssetAmount;
|
||||||
import com.luminiasoft.bitshares.RPC;
|
import com.luminiasoft.bitshares.RPC;
|
||||||
import com.luminiasoft.bitshares.Transfer;
|
import com.luminiasoft.bitshares.TransferOperation;
|
||||||
import com.luminiasoft.bitshares.UserAccount;
|
import com.luminiasoft.bitshares.UserAccount;
|
||||||
import com.luminiasoft.bitshares.interfaces.WitnessResponseListener;
|
import com.luminiasoft.bitshares.interfaces.WitnessResponseListener;
|
||||||
import com.luminiasoft.bitshares.models.ApiCall;
|
import com.luminiasoft.bitshares.models.ApiCall;
|
||||||
|
@ -119,7 +119,7 @@ public class GetRelativeAccountHistory extends WebSocketAdapter {
|
||||||
System.out.println(frame.getPayloadText());
|
System.out.println(frame.getPayloadText());
|
||||||
Type RelativeAccountHistoryResponse = new TypeToken<WitnessResponse<List<HistoricalTransfer>>>(){}.getType();
|
Type RelativeAccountHistoryResponse = new TypeToken<WitnessResponse<List<HistoricalTransfer>>>(){}.getType();
|
||||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
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());
|
gsonBuilder.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetDeserializer());
|
||||||
WitnessResponse<List<HistoricalTransfer>> transfersResponse = gsonBuilder.create().fromJson(response, RelativeAccountHistoryResponse);
|
WitnessResponse<List<HistoricalTransfer>> transfersResponse = gsonBuilder.create().fromJson(response, RelativeAccountHistoryResponse);
|
||||||
mListener.onSuccess(transfersResponse);
|
mListener.onSuccess(transfersResponse);
|
||||||
|
|
|
@ -3,7 +3,7 @@ package com.luminiasoft.bitshares.ws;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import com.luminiasoft.bitshares.*;
|
import com.luminiasoft.bitshares.*;
|
||||||
import com.luminiasoft.bitshares.TransferOperation;
|
import com.luminiasoft.bitshares.Transaction;
|
||||||
import com.luminiasoft.bitshares.interfaces.WitnessResponseListener;
|
import com.luminiasoft.bitshares.interfaces.WitnessResponseListener;
|
||||||
import com.luminiasoft.bitshares.models.ApiCall;
|
import com.luminiasoft.bitshares.models.ApiCall;
|
||||||
import com.luminiasoft.bitshares.models.BaseResponse;
|
import com.luminiasoft.bitshares.models.BaseResponse;
|
||||||
|
@ -24,7 +24,7 @@ import java.util.Map;
|
||||||
import java.util.TimeZone;
|
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 {
|
public class TransactionBroadcastSequence extends WebSocketAdapter {
|
||||||
private final String TAG = this.getClass().getName();
|
private final String TAG = this.getClass().getName();
|
||||||
|
@ -35,7 +35,7 @@ public class TransactionBroadcastSequence extends WebSocketAdapter {
|
||||||
private final static int BROADCAST_TRANSACTION = 4;
|
private final static int BROADCAST_TRANSACTION = 4;
|
||||||
public final static int EXPIRATION_TIME = 30;
|
public final static int EXPIRATION_TIME = 30;
|
||||||
|
|
||||||
private TransferOperation transferOperation;
|
private Transaction transaction;
|
||||||
private long expirationTime;
|
private long expirationTime;
|
||||||
private String headBlockId;
|
private String headBlockId;
|
||||||
private long headBlockNumber;
|
private long headBlockNumber;
|
||||||
|
@ -47,13 +47,13 @@ public class TransactionBroadcastSequence extends WebSocketAdapter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor of this class. The ids required
|
* 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
|
* @param listener: A class implementing the WitnessResponseListener interface. This should
|
||||||
* be implemented by the party interested in being notified about the success/failure
|
* 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){
|
public TransactionBroadcastSequence(Transaction transaction, WitnessResponseListener listener){
|
||||||
this.transferOperation = transferOperation;
|
this.transaction = transaction;
|
||||||
this.mListener = listener;
|
this.mListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,14 +102,14 @@ public class TransactionBroadcastSequence extends WebSocketAdapter {
|
||||||
headBlockNumber = dynamicProperties.head_block_number;
|
headBlockNumber = dynamicProperties.head_block_number;
|
||||||
|
|
||||||
ArrayList<Serializable> transactionList = new ArrayList<>();
|
ArrayList<Serializable> transactionList = new ArrayList<>();
|
||||||
transactionList.add(transferOperation);
|
transactionList.add(transaction);
|
||||||
ApiCall call = new ApiCall(broadcastApiId,
|
ApiCall call = new ApiCall(broadcastApiId,
|
||||||
RPC.CALL_BROADCAST_TRANSACTION,
|
RPC.CALL_BROADCAST_TRANSACTION,
|
||||||
transactionList,
|
transactionList,
|
||||||
"2.0",
|
"2.0",
|
||||||
currentId);
|
currentId);
|
||||||
|
|
||||||
// Finally sending transferOperation
|
// Finally sending transaction
|
||||||
websocket.sendText(call.toJsonString());
|
websocket.sendText(call.toJsonString());
|
||||||
}else if(baseResponse.id >= BROADCAST_TRANSACTION){
|
}else if(baseResponse.id >= BROADCAST_TRANSACTION){
|
||||||
Type WitnessResponseType = new TypeToken<WitnessResponse<String>>(){}.getType();
|
Type WitnessResponseType = new TypeToken<WitnessResponse<String>>(){}.getType();
|
||||||
|
@ -131,18 +131,18 @@ public class TransactionBroadcastSequence extends WebSocketAdapter {
|
||||||
* with ONE transfer operation.
|
* with ONE transfer operation.
|
||||||
*/
|
*/
|
||||||
retries++;
|
retries++;
|
||||||
List<BaseOperation> operations = this.transferOperation.getOperations();
|
List<BaseOperation> operations = this.transaction.getOperations();
|
||||||
Transfer transfer = (Transfer) operations.get(0);
|
TransferOperation transfer = (TransferOperation) operations.get(0);
|
||||||
transferOperation = new TransferTransactionBuilder()
|
transaction = new TransferTransactionBuilder()
|
||||||
.setSource(transfer.getFrom())
|
.setSource(transfer.getFrom())
|
||||||
.setDestination(transfer.getTo())
|
.setDestination(transfer.getTo())
|
||||||
.setAmount(transfer.getAmount())
|
.setAmount(transfer.getAmount())
|
||||||
.setFee(transfer.getFee())
|
.setFee(transfer.getFee())
|
||||||
.setBlockData(new BlockData(headBlockNumber, headBlockId, expirationTime + EXPIRATION_TIME))
|
.setBlockData(new BlockData(headBlockNumber, headBlockId, expirationTime + EXPIRATION_TIME))
|
||||||
.setPrivateKey(transferOperation.getPrivateKey())
|
.setPrivateKey(transaction.getPrivateKey())
|
||||||
.build();
|
.build();
|
||||||
ArrayList<Serializable> transactionList = new ArrayList<>();
|
ArrayList<Serializable> transactionList = new ArrayList<>();
|
||||||
transactionList.add(transferOperation);
|
transactionList.add(transaction);
|
||||||
ApiCall call = new ApiCall(broadcastApiId,
|
ApiCall call = new ApiCall(broadcastApiId,
|
||||||
RPC.CALL_BROADCAST_TRANSACTION,
|
RPC.CALL_BROADCAST_TRANSACTION,
|
||||||
transactionList,
|
transactionList,
|
||||||
|
|
Loading…
Reference in a new issue