AccountUpdateOperation serialization matches cli_wallet

This commit is contained in:
Nelson R. Perez 2016-12-04 19:22:12 -05:00
parent 5f51a1c3e4
commit e0b0330e54
11 changed files with 168 additions and 80 deletions

View file

@ -48,13 +48,17 @@ public class AccountUpdateOperation extends BaseOperation {
@Override @Override
public JsonElement toJsonObject() { public JsonElement toJsonObject() {
JsonArray array = new JsonArray();
array.add(this.getId());
JsonObject accountUpdate = new JsonObject(); JsonObject accountUpdate = new JsonObject();
accountUpdate.add(KEY_FEE, fee.toJsonObject()); accountUpdate.add(KEY_FEE, fee.toJsonObject());
accountUpdate.add(KEY_ACCOUNT, account.toJsonObject()); accountUpdate.addProperty(KEY_ACCOUNT, account.toJsonString());
accountUpdate.add(KEY_OWNER, owner.toJsonObject()); accountUpdate.add(KEY_OWNER, owner.toJsonObject());
accountUpdate.add(KEY_ACTIVE, active.toJsonObject()); accountUpdate.add(KEY_ACTIVE, active.toJsonObject());
accountUpdate.add(KEY_EXTENSIONS, extensions.toJsonObject()); accountUpdate.add(KEY_EXTENSIONS, extensions.toJsonObject());
return accountUpdate; array.add(accountUpdate);
return array;
} }
@Override @Override

View file

@ -0,0 +1,10 @@
package com.luminiasoft.bitshares;
import java.util.List;
/**
* Created by nelson on 12/3/16.
*/
public class AccountUpdateTransactionBuilder {
private List<AccountUpdateOperation> operations;
}

View file

@ -1,7 +1,9 @@
package com.luminiasoft.bitshares; package com.luminiasoft.bitshares;
import com.google.common.primitives.Bytes; import com.google.common.primitives.Bytes;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.luminiasoft.bitshares.errors.MalformedAddressException; import com.luminiasoft.bitshares.errors.MalformedAddressException;
import com.luminiasoft.bitshares.interfaces.ByteSerializable; import com.luminiasoft.bitshares.interfaces.ByteSerializable;
import com.luminiasoft.bitshares.interfaces.JsonSerializable; import com.luminiasoft.bitshares.interfaces.JsonSerializable;
@ -13,6 +15,11 @@ import java.util.*;
* Created by nelson on 11/30/16. * Created by nelson on 11/30/16.
*/ */
public class Authority implements JsonSerializable, ByteSerializable { public class Authority implements JsonSerializable, ByteSerializable {
public static final String KEY_ACCOUNT_AUTHS = "account_auths";
public static final String KEY_KEY_AUTHS = "key_auths";
public static final String KEY_WEIGHT_THRESHOLD = "weight_threshold";
public static final String KEY_EXTENSIONS = "extensions";
private long weight_threshold; private long weight_threshold;
private HashMap<UserAccount, Integer> account_auths; private HashMap<UserAccount, Integer> account_auths;
private HashMap<PublicKey, Integer> key_auths; private HashMap<PublicKey, Integer> key_auths;
@ -36,7 +43,29 @@ public class Authority implements JsonSerializable, ByteSerializable {
@Override @Override
public JsonElement toJsonObject() { public JsonElement toJsonObject() {
return null; JsonObject authority = new JsonObject();
authority.addProperty(KEY_WEIGHT_THRESHOLD, weight_threshold);
JsonArray keyAuthArray = new JsonArray();
JsonArray accountAuthArray = new JsonArray();
for(PublicKey publicKey : key_auths.keySet()){
JsonArray subArray = new JsonArray();
Address address = new Address(publicKey.getKey());
subArray.add(address.toString());
subArray.add(key_auths.get(publicKey));
keyAuthArray.add(subArray);
}
for(UserAccount key : account_auths.keySet()){
JsonArray subArray = new JsonArray();
subArray.add(key.toString());
subArray.add(key_auths.get(key));
accountAuthArray.add(subArray);
}
authority.add(KEY_KEY_AUTHS, keyAuthArray);
authority.add(KEY_ACCOUNT_AUTHS, accountAuthArray);
authority.add(KEY_EXTENSIONS, extensions.toJsonObject());
return authority;
} }
@Override @Override

View file

@ -61,6 +61,7 @@ public class Main {
// test.testingInvoiceGeneration(); // test.testingInvoiceGeneration();
// test.testCompression(); // test.testCompression();
// test.testCreateBinFile(); // test.testCreateBinFile();
test.testAccountUpdateOperationSerialization(); test.testAccountUpdateSerialization();
// test.testAccountUpdateOperationSerialization();
} }
} }

View file

@ -13,6 +13,10 @@ public class PublicKey implements ByteSerializable {
this.publicKey = key; this.publicKey = key;
} }
public ECKey getKey(){
return publicKey;
}
@Override @Override
public byte[] toBytes() { public byte[] toBytes() {
return publicKey.getPubKey(); return publicKey.getPubKey();

View file

@ -432,8 +432,6 @@ public class Test {
ArrayList<Serializable> transactionList = new ArrayList<>(); ArrayList<Serializable> transactionList = new ArrayList<>();
transactionList.add(transaction); transactionList.add(transaction);
transactionList.add(transaction);
SSLContext context = null; SSLContext context = null;
context = NaiveSSLContext.getInstance("TLS"); context = NaiveSSLContext.getInstance("TLS");
WebSocketFactory factory = new WebSocketFactory(); WebSocketFactory factory = new WebSocketFactory();
@ -744,20 +742,76 @@ public class Test {
System.out.println("fileOutput " + Arrays.toString(fileOutput)); System.out.println("fileOutput " + Arrays.toString(fileOutput));
} }
public void testAccountUpdateOperationSerialization(){ public void testAccountUpdateSerialization() {
UserAccount account = new UserAccount("1.2.138632"); UserAccount account = new UserAccount("1.2.138632");
AssetAmount fee = new AssetAmount(UnsignedLong.valueOf("4294967295"), new Asset("1.3.0")); AssetAmount fee = new AssetAmount(UnsignedLong.valueOf("200"), new Asset("1.3.0"));
HashMap<String, Integer> keyAuths = new HashMap<>(); HashMap<String, Integer> keyAuths = new HashMap<>();
keyAuths.put("BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY", 65535); keyAuths.put("BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY", 1);
try {
BlockData blockData = new BlockData(0, 0, 0);
Authority owner = new Authority(1, keyAuths);
Authority active = new Authority(1, keyAuths);
AccountUpdateOperation operation = new AccountUpdateOperation(account, owner, active, fee);
ArrayList<BaseOperation> operations = new ArrayList<BaseOperation>();
operations.add(operation);
Transaction transaction = new Transaction(Main.WIF, blockData, operations);
System.out.println("Json format of transaction");
System.out.println(transaction.toJsonString());
} catch(MalformedAddressException e){
System.out.println("MalformedAddressException. Msg: "+e.getMessage());
}
}
public void testAccountUpdateOperationBroadcast(){
WitnessResponseListener listener = new WitnessResponseListener() {
@Override
public void onSuccess(WitnessResponse response) {
System.out.println("onSuccess");
}
@Override
public void onError(BaseResponse.Error error) {
System.out.println("onError");
}
};
UserAccount account = new UserAccount("1.2.138632");
AssetAmount fee = new AssetAmount(UnsignedLong.valueOf("200"), new Asset("1.3.0"));
HashMap<String, Integer> keyAuths = new HashMap<>();
keyAuths.put("BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY", 1);
try { try {
Authority owner = new Authority(1, keyAuths); Authority owner = new Authority(1, keyAuths);
Authority active = new Authority(1, keyAuths); Authority active = new Authority(1, keyAuths);
AccountUpdateOperation operation = new AccountUpdateOperation(account, owner, active, fee); AccountUpdateOperation operation = new AccountUpdateOperation(account, owner, active, fee);
byte[] serializedOperation = operation.toBytes(); ArrayList<BaseOperation> operations = new ArrayList<BaseOperation>();
System.out.println("serialized operation"); operations.add(operation);
System.out.println(Util.bytesToHex(serializedOperation)); Transaction transaction = new Transaction(Main.WIF, null, operations);
ArrayList<Serializable> transactionList = new ArrayList<>();
transactionList.add(transaction);
ApiCall call = new ApiCall(4, "call", "broadcast_transaction", transactionList, RPC.VERSION, 1);
SSLContext context = null;
context = NaiveSSLContext.getInstance("TLS");
WebSocketFactory factory = new WebSocketFactory();
// Set the custom SSL context.
factory.setSSLContext(context);
WebSocket mWebSocket = factory.createSocket(OPENLEDGER_WITNESS_URL);
mWebSocket.addListener(new TransactionBroadcastSequence(transaction, listener));
mWebSocket.connect();
} catch (MalformedAddressException e) { } catch (MalformedAddressException e) {
System.out.println("MalformedAddressException. Msg: "+e.getMessage()); System.out.println("MalformedAddressException. Msg: "+e.getMessage());
} catch (NoSuchAlgorithmException e) {
System.out.println("NoSuchAlgorithmException. Msg: "+e.getMessage());
} catch (IOException e) {
System.out.println("IOException. Msg: "+e.getMessage());
} catch (WebSocketException e) {
System.out.println("WebSocketException. Msg: "+e.getMessage());
} }
} }
} }

View file

@ -7,6 +7,7 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer; import com.google.gson.JsonSerializer;
import com.luminiasoft.bitshares.errors.MalformedTransactionException;
import com.luminiasoft.bitshares.interfaces.ByteSerializable; import com.luminiasoft.bitshares.interfaces.ByteSerializable;
import com.luminiasoft.bitshares.interfaces.JsonSerializable; import com.luminiasoft.bitshares.interfaces.JsonSerializable;
@ -28,6 +29,7 @@ import java.util.TimeZone;
public class Transaction implements ByteSerializable, JsonSerializable { public class Transaction implements ByteSerializable, JsonSerializable {
private final String TAG = this.getClass().getName(); private final String TAG = this.getClass().getName();
public static final int DEFAULT_EXPIRATION_TIME = 30;
public static final String KEY_EXPIRATION = "expiration"; public static final String KEY_EXPIRATION = "expiration";
public static final String KEY_SIGNATURES = "signatures"; public static final String KEY_SIGNATURES = "signatures";
public static final String KEY_OPERATIONS = "operations"; public static final String KEY_OPERATIONS = "operations";
@ -40,19 +42,6 @@ public class Transaction implements ByteSerializable, JsonSerializable {
private List<BaseOperation> operations; private List<BaseOperation> operations;
private List<Extensions> extensions; private List<Extensions> 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<Extensions>();
}
/** /**
* Transaction constructor. * Transaction constructor.
* @param privateKey : Instance of a ECKey containing the private key that will be used to sign this transaction. * @param privateKey : Instance of a ECKey containing the private key that will be used to sign this transaction.
@ -66,6 +55,20 @@ public class Transaction implements ByteSerializable, JsonSerializable {
this.extensions = new ArrayList<Extensions>(); this.extensions = new ArrayList<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(DumpedPrivateKey.fromBase58(null, wif).getKey(), block_data, operation_list);
}
public void setBlockData(BlockData blockData){
this.blockData = blockData;
}
public ECKey getPrivateKey(){ public ECKey getPrivateKey(){
return this.privateKey; return this.privateKey;
} }
@ -87,6 +90,8 @@ public class Transaction implements ByteSerializable, JsonSerializable {
while(!isGrapheneCanonical) { while(!isGrapheneCanonical) {
byte[] serializedTransaction = this.toBytes(); byte[] serializedTransaction = this.toBytes();
System.out.println("Signing serialized transaction");
System.out.println(Util.bytesToHex(serializedTransaction));
Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction)); Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction));
int recId = -1; int recId = -1;
ECKey.ECDSASignature sig = privateKey.sign(hash); ECKey.ECDSASignature sig = privateKey.sign(hash);

View file

@ -58,8 +58,6 @@ public class TransferTransactionBuilder extends TransactionBuilder {
public Transaction 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){
throw new MalformedTransactionException("Missing block data information");
}else if(operations == null){ }else if(operations == null){
// If the operations list has not been set, we might be able to build one with the // If the operations list has not been set, we might be able to build one with the
// previously provided data. But in order for this to work we have to have all // previously provided data. But in order for this to work we have to have all

View file

@ -45,4 +45,9 @@ public class UserAccount extends GrapheneObject implements ByteSerializable, Jso
public JsonObject toJsonObject() { public JsonObject toJsonObject() {
return null; return null;
} }
@Override
public String toString() {
return this.toJsonString();
}
} }

View file

@ -88,6 +88,8 @@ public class ApiCall implements JsonSerializable {
} }
} }
methodParams.add(array); methodParams.add(array);
}else{
System.out.println("Skipping parameter of type: "+this.params.get(i).getClass());
} }
} }
paramsArray.add(methodParams); paramsArray.add(methodParams);

View file

@ -2,7 +2,8 @@ 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.BlockData;
import com.luminiasoft.bitshares.RPC;
import com.luminiasoft.bitshares.Transaction; 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;
@ -33,17 +34,12 @@ public class TransactionBroadcastSequence extends WebSocketAdapter {
private final static int GET_NETWORK_BROADCAST_ID = 2; private final static int GET_NETWORK_BROADCAST_ID = 2;
private final static int GET_NETWORK_DYNAMIC_PARAMETERS = 3; private final static int GET_NETWORK_DYNAMIC_PARAMETERS = 3;
private final static int BROADCAST_TRANSACTION = 4; private final static int BROADCAST_TRANSACTION = 4;
public final static int EXPIRATION_TIME = 30;
private Transaction transaction; private Transaction transaction;
private long expirationTime;
private String headBlockId;
private long headBlockNumber;
private WitnessResponseListener mListener; private WitnessResponseListener mListener;
private int currentId = 1; private int currentId = 1;
private int broadcastApiId = -1; private int broadcastApiId = -1;
private int retries = 0;
/** /**
* Constructor of this class. The ids required * Constructor of this class. The ids required
@ -62,16 +58,18 @@ public class TransactionBroadcastSequence extends WebSocketAdapter {
ArrayList<Serializable> loginParams = new ArrayList<>(); ArrayList<Serializable> loginParams = new ArrayList<>();
loginParams.add(null); loginParams.add(null);
loginParams.add(null); loginParams.add(null);
ApiCall loginCall = new ApiCall(1, RPC.CALL_LOGIN, loginParams, "2.0", currentId); ApiCall loginCall = new ApiCall(1, RPC.CALL_LOGIN, loginParams, RPC.VERSION, currentId);
websocket.sendText(loginCall.toJsonString()); websocket.sendText(loginCall.toJsonString());
} }
@Override @Override
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception { public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
if(frame.isTextFrame())
System.out.println("<<< "+frame.getPayloadText());
String response = frame.getPayloadText(); String response = frame.getPayloadText();
Gson gson = new Gson(); Gson gson = new Gson();
BaseResponse baseResponse = gson.fromJson(response, BaseResponse.class); BaseResponse baseResponse = gson.fromJson(response, BaseResponse.class);
if(baseResponse.error != null && baseResponse.error.message.indexOf("is_canonical") == -1){ if(baseResponse.error != null){
mListener.onError(baseResponse.error); mListener.onError(baseResponse.error);
websocket.disconnect(); websocket.disconnect();
}else{ }else{
@ -97,64 +95,42 @@ public class TransactionBroadcastSequence extends WebSocketAdapter {
Date date = dateFormat.parse(dynamicProperties.time); Date date = dateFormat.parse(dynamicProperties.time);
// Obtained block data // Obtained block data
expirationTime = (date.getTime() / 1000) + EXPIRATION_TIME; long expirationTime = (date.getTime() / 1000) + Transaction.DEFAULT_EXPIRATION_TIME;
headBlockId = dynamicProperties.head_block_id; String headBlockId = dynamicProperties.head_block_id;
headBlockNumber = dynamicProperties.head_block_number; long headBlockNumber = dynamicProperties.head_block_number;
transaction.setBlockData(new BlockData(headBlockNumber, headBlockId, expirationTime));
ArrayList<Serializable> transactionList = new ArrayList<>(); ArrayList<Serializable> transactionList = new ArrayList<>();
transactionList.add(transaction); 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", RPC.VERSION,
currentId); currentId);
System.out.println("Json of transaction");
System.out.println(transaction.toJsonString());
//TODO: Remove this debug code
String jsonCall = call.toJsonString();
System.out.println("json call");
System.out.println(jsonCall);
// Finally sending transaction // 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();
WitnessResponse<WitnessResponse<String>> witnessResponse = gson.fromJson(response, WitnessResponseType); WitnessResponse<WitnessResponse<String>> witnessResponse = gson.fromJson(response, WitnessResponseType);
if(witnessResponse.error == null){
mListener.onSuccess(witnessResponse); mListener.onSuccess(witnessResponse);
websocket.disconnect(); websocket.disconnect();
}else{
if(witnessResponse.error.message.indexOf("is_canonical") != -1 && retries < 10){
/*
* This is a very ugly hack, but it will do for now.
*
* The issue is that the witness is complaining about the signature not
* being canonical even though the bitcoinj ECKey.ECDSASignature.isCanonical()
* method says it is! We'll have to dive deeper into this issue and avoid
* this error altogether
*
* But this MUST BE FIXED! Since this hack will only work for transactions
* with ONE transfer operation.
*/
retries++;
List<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(transaction.getPrivateKey())
.build();
ArrayList<Serializable> transactionList = new ArrayList<>();
transactionList.add(transaction);
ApiCall call = new ApiCall(broadcastApiId,
RPC.CALL_BROADCAST_TRANSACTION,
transactionList,
"2.0",
currentId);
websocket.sendText(call.toJsonString());
}else{
mListener.onError(witnessResponse.error);
websocket.disconnect();
} }
} }
} }
@Override
public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception {
if(frame.isTextFrame()){
System.out.println(">>> "+frame.getPayloadText());
} }
} }