Added support for the limit_order_cancel_operation

This commit is contained in:
Nelson R. Perez 2017-03-22 16:25:55 -05:00
parent a9a550491b
commit aa642edce9
10 changed files with 395 additions and 30 deletions

View file

@ -88,8 +88,8 @@ public class Main {
// test.testListAssets();
// test.testGetObjects();
// test.testGetBlockHeader();
// test.testGetLimitOrders();
test.testGetTradeHistory();
test.testGetLimitOrders();
// test.testGetTradeHistory();
// test.testAssetSerialization();
// test.testGetMarketHistory();
// test.testGetAccountBalances();

View file

@ -1137,7 +1137,7 @@ public class Test {
WebSocket mWebSocket = factory.createSocket(BLOCK_PAY_DE);
Asset base = new Asset("1.3.120", "EUR", 4);
Asset base = new Asset("1.3.0", "BTS", 4);
Asset quote = new Asset("1.3.121", "USD", 4);
mWebSocket.addListener(new GetLimitOrders(base.getObjectId(), quote.getObjectId(), 100, new WitnessResponseListener() {
@ -1150,10 +1150,10 @@ public class Test {
// System.out.println(String.format("base: %d, quote: %d",
// order.sell_price.base.getAmount().longValue(),
// order.sell_price.quote.getAmount().longValue()));
order.sell_price.base.getAsset().setPrecision(base.getPrecision());
order.sell_price.quote.getAsset().setPrecision(quote.getPrecision());
double baseToQuoteExchange = converter.getConversionRate(order.sell_price, Converter.BASE_TO_QUOTE);
double quoteToBaseExchange = converter.getConversionRate(order.sell_price, Converter.QUOTE_TO_BASE);
order.getSellPrice().base.getAsset().setPrecision(base.getPrecision());
order.getSellPrice().quote.getAsset().setPrecision(quote.getPrecision());
double baseToQuoteExchange = converter.getConversionRate(order.getSellPrice(), Converter.BASE_TO_QUOTE);
double quoteToBaseExchange = converter.getConversionRate(order.getSellPrice(), Converter.QUOTE_TO_BASE);
System.out.println(String.format("base to quote: %.5f, quote to base: %.5f", baseToQuoteExchange, quoteToBaseExchange));
}
}

View file

@ -129,6 +129,9 @@ public class AssetAmount implements ByteSerializable, JsonSerializable {
}
}
/**
* Custom deserializer used for this class
*/
public static class AssetAmountDeserializer implements JsonDeserializer<AssetAmount> {
@Override

View file

@ -1,5 +1,7 @@
package de.bitsharesmunich.graphenej;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import de.bitsharesmunich.graphenej.interfaces.ByteSerializable;
import de.bitsharesmunich.graphenej.interfaces.JsonSerializable;
@ -25,5 +27,9 @@ public abstract class BaseOperation implements ByteSerializable, JsonSerializabl
public abstract void setFee(AssetAmount assetAmount);
public abstract byte[] toBytes();
public JsonElement toJsonObject(){
JsonArray array = new JsonArray();
array.add(this.getId());
return array;
}
}

View file

@ -1,14 +1,131 @@
package de.bitsharesmunich.graphenej;
import com.google.gson.*;
import de.bitsharesmunich.graphenej.interfaces.ByteSerializable;
import java.io.ByteArrayOutputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Type;
/**
*
* @author henry
*/
public class LimitOrder {
public String id;
public String expiration;
public UserAccount seller;
public long for_sale;
public long deferred_fee;
public Price sell_price;
public class LimitOrder extends GrapheneObject implements ByteSerializable {
public static final String KEY_EXPIRATION = "expiration";
public static final String KEY_SELLER = "seller";
public static final String KEY_FOR_SALE = "for_sale";
public static final String KEY_DEFERRED_FEE = "deferred_fee";
public static final String KEY_PRICE = "key_price";
private String expiration;
private UserAccount seller;
private long forSale;
private long deferredFee;
private Price sellPrice;
public LimitOrder(String id) {
super(id);
}
public String getExpiration() {
return expiration;
}
public void setExpiration(String expiration) {
this.expiration = expiration;
}
public UserAccount getSeller() {
return seller;
}
public void setSeller(UserAccount seller) {
this.seller = seller;
}
public long getForSale() {
return forSale;
}
public void setForSale(long forSale) {
this.forSale = forSale;
}
public long getDeferredFee() {
return deferredFee;
}
public void setDeferredFee(long deferredFee) {
this.deferredFee = deferredFee;
}
public Price getSellPrice() {
return sellPrice;
}
public void setSellPrice(Price sellPrice) {
this.sellPrice = sellPrice;
}
@Override
public byte[] toBytes() {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
DataOutput out = new DataOutputStream(byteArrayOutputStream);
byte[] serialized = null;
try {
Varint.writeUnsignedVarLong(this.instance, out);
serialized = byteArrayOutputStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return serialized;
}
/**
* Custom deserializer for the LimitOrder class, used to deserialize a json-formatted string in
* the following format:
*
* {
* "id": "1.7.2389233",
* "expiration": "2017-04-21T15:40:04",
* "seller": "1.2.114363",
* "forSale": "10564959415",
* "sell_price": {
* "base": {
* "amount": "10565237932",
* "asset_id": "1.3.0"
* },
* "quote": {
* "amount": 5803878,
* "asset_id": "1.3.121"
* }
* },
* "deferredFee": 0
* }
*/
public static class LimitOrderDeserializer implements JsonDeserializer<LimitOrder> {
@Override
public LimitOrder deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject object = json.getAsJsonObject();
String id = object.get(KEY_ID).getAsString();
String expiration = object.get(KEY_EXPIRATION).getAsString();
UserAccount seller = context.deserialize(object.get(KEY_SELLER), UserAccount.class);
String forSale = object.get(KEY_FOR_SALE).getAsString();
Price price = context.deserialize(object.get(KEY_PRICE), Price.class);
long deferredFee = object.get(KEY_DEFERRED_FEE).getAsLong();
LimitOrder limitOrder = new LimitOrder(id);
limitOrder.setExpiration(expiration);
limitOrder.setSeller(seller);
limitOrder.setForSale(Long.parseLong(forSale));
limitOrder.setSellPrice(price);
limitOrder.setDeferredFee(deferredFee);
return limitOrder;
}
}
}

View file

@ -59,6 +59,7 @@ public class GetLimitOrders extends WebSocketAdapter {
Type GetLimitOrdersResponse = new TypeToken<WitnessResponse<List<LimitOrder>>>() {}.getType();
builder.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer());
builder.registerTypeAdapter(UserAccount.class, new UserAccount.UserAccountSimpleDeserializer());
builder.registerTypeAdapter(LimitOrder.class, new LimitOrder.LimitOrderDeserializer());
WitnessResponse<List<LimitOrder>> witnessResponse = builder.create().fromJson(response, GetLimitOrdersResponse);
if (witnessResponse.error != null) {
this.mListener.onError(witnessResponse.error);

View file

@ -0,0 +1,61 @@
package de.bitsharesmunich.graphenej.operations;
import com.google.common.primitives.Bytes;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import de.bitsharesmunich.graphenej.*;
/**
* Created by nelson on 3/21/17.
*/
public class LimitOrderCancelOperation extends BaseOperation {
// Constants used in the JSON representation
public static final String KEY_FEE_PAYING_ACCOUNT = "fee_paying_account";
public static final String KEY_ORDER_ID = "order";
public LimitOrderCancelOperation(LimitOrder order, UserAccount feePayingAccount) {
super(OperationType.LIMIT_ORDER_CANCEL_OPERATION);
this.order = order;
this.feePayingAccount = feePayingAccount;
}
// Inner fields of a limit order cancel operation
private AssetAmount fee;
private UserAccount feePayingAccount;
private LimitOrder order;
@Override
public String toJsonString() {
return null;
}
@Override
public JsonElement toJsonObject() {
JsonArray array = (JsonArray) super.toJsonObject();
JsonObject jsonObject = new JsonObject();
if(fee != null)
jsonObject.add(KEY_FEE, fee.toJsonObject());
jsonObject.addProperty(KEY_FEE_PAYING_ACCOUNT, feePayingAccount.getObjectId());
jsonObject.addProperty(KEY_ORDER_ID, order.getObjectId());
jsonObject.add(KEY_EXTENSIONS, new JsonArray());
array.add(jsonObject);
return array;
}
@Override
public void setFee(AssetAmount assetAmount) {
this.fee = assetAmount;
}
@Override
public byte[] toBytes() {
byte[] feeBytes = this.fee.toBytes();
byte[] feePayingAccountBytes = this.feePayingAccount.toBytes();
byte[] orderIdBytes = this.order.toBytes();
byte[] extensions = this.extensions.toBytes();
return Bytes.concat(feeBytes, feePayingAccountBytes, orderIdBytes, extensions);
}
}

View file

@ -65,8 +65,7 @@ public class LimitOrderCreateOperation extends BaseOperation {
@Override
public JsonElement toJsonObject() {
JsonArray array = new JsonArray();
array.add(this.getId());
JsonArray array = (JsonArray) super.toJsonObject();
JsonObject jsonObject = new JsonObject();
if(fee != null)
jsonObject.add(KEY_FEE, fee.toJsonObject());

View file

@ -4,15 +4,13 @@ import com.google.common.primitives.UnsignedLong;
import com.neovisionaries.ws.client.WebSocket;
import com.neovisionaries.ws.client.WebSocketException;
import com.neovisionaries.ws.client.WebSocketFactory;
import de.bitsharesmunich.graphenej.api.GetLimitOrders;
import de.bitsharesmunich.graphenej.api.TransactionBroadcastSequence;
import de.bitsharesmunich.graphenej.interfaces.WitnessResponseListener;
import de.bitsharesmunich.graphenej.models.BaseResponse;
import de.bitsharesmunich.graphenej.models.WitnessResponse;
import de.bitsharesmunich.graphenej.objects.Memo;
import de.bitsharesmunich.graphenej.operations.LimitOrderCreateOperation;
import de.bitsharesmunich.graphenej.operations.LimitOrderCreateOperationTest;
import de.bitsharesmunich.graphenej.operations.TransferOperation;
import de.bitsharesmunich.graphenej.operations.TransferOperationBuilder;
import de.bitsharesmunich.graphenej.operations.*;
import de.bitsharesmunich.graphenej.test.NaiveSSLContext;
import org.bitcoinj.core.ECKey;
import org.junit.Assert;
@ -50,14 +48,22 @@ public class TransactionTest {
private AssetAmount minToReceive = new AssetAmount(UnsignedLong.valueOf(520), BIT_USD);
private long expiration;
private static final class Lock { }
private final Object lockObject = new Lock();
/**
* Generic witness response listener that will just release the lock created in
* main thread.
*/
WitnessResponseListener listener = new WitnessResponseListener() {
@Override
public void onSuccess(WitnessResponse response) {
System.out.println("onSuccess");
WitnessResponse<String> witnessResponse = response;
Assert.assertNull(witnessResponse.result);
synchronized (this){
notifyAll();
this.notifyAll();
}
}
@ -74,7 +80,6 @@ public class TransactionTest {
@Before
public void setup(){
}
/**
@ -82,8 +87,9 @@ public class TransactionTest {
* @param privateKey: The private key used to sign the transaction.
* @param operationList: The list of operations to include
* @param responseListener: The response listener.
* @param lockObject: Optional object to use as a lock
*/
private void broadcastTransaction(ECKey privateKey, List<BaseOperation> operationList, WitnessResponseListener responseListener) {
private void broadcastTransaction(ECKey privateKey, List<BaseOperation> operationList, WitnessResponseListener responseListener, Object lockObject) {
try{
Transaction transaction = new Transaction(privateKey, null, operationList);
@ -96,11 +102,19 @@ public class TransactionTest {
WebSocket mWebSocket = factory.createSocket(BLOCK_PAY_DE);
mWebSocket.addListener(new TransactionBroadcastSequence(transaction, CORE_ASSET, listener));
mWebSocket.addListener(new TransactionBroadcastSequence(transaction, CORE_ASSET, responseListener));
mWebSocket.connect();
synchronized (responseListener){
responseListener.wait();
System.out.println("Wait released");
// If a lock object is specified, we use it
if(lockObject != null){
synchronized (lockObject){
lockObject.wait();
}
}else{
// Otherwise we just use this listener as the lock
synchronized (this){
this.wait();
}
}
}catch(NoSuchAlgorithmException e){
System.out.println("NoSuchAlgoritmException. Msg: " + e.getMessage());
@ -147,7 +161,7 @@ public class TransactionTest {
operationList.add(transferOperation2);
// Broadcasting transaction
broadcastTransaction(sourcePrivateKey, operationList, listener);
broadcastTransaction(sourcePrivateKey, operationList, listener, null);
}
@Test
@ -163,6 +177,138 @@ public class TransactionTest {
operationList.add(operation);
// Broadcasting transaction
broadcastTransaction(privateKey, operationList, listener);
broadcastTransaction(privateKey, operationList, listener, null);
}
/**
* Since tests should be independent of each other, in order to be able to test the cancellation of an
* existing order we must first proceed to create one. And after creating one, we must also retrieve
* its id in a separate call.
*
* All of this just makes this test a bit more complex, since we have 3 clearly defined tasks that require
* network communication
*
* 1- Create order
* 2- Retrieve order id
* 3- Send order cancellation tx
*
* Only the last one is what we actually want to test
*
* @throws NoSuchAlgorithmException
* @throws IOException
* @throws WebSocketException
*/
@Test
public void testLimitOrderCancelTransaction() throws NoSuchAlgorithmException, IOException, WebSocketException {
// We first must create a limit order for this test
ECKey privateKey = new BrainKey(BILTHON_15_BRAIN_KEY, 0).getPrivateKey();
expiration = (System.currentTimeMillis() / 1000) + 60 * 5;
// Creating limit order creation operation
LimitOrderCreateOperation operation = new LimitOrderCreateOperation(seller, amountToSell, minToReceive, (int) expiration, false);
operation.setFee(new AssetAmount(UnsignedLong.valueOf(2), CORE_ASSET));
ArrayList<BaseOperation> operationList = new ArrayList<>();
operationList.add(operation);
// Broadcasting transaction (Task 1)
broadcastTransaction(privateKey, operationList, new WitnessResponseListener() {
@Override
public void onSuccess(WitnessResponse response) {
System.out.println("onSuccess.0");
try{
// Setting up the assets
Asset base = amountToSell.getAsset();
Asset quote = minToReceive.getAsset();
SSLContext context = NaiveSSLContext.getInstance("TLS");
WebSocketFactory factory = new WebSocketFactory();
// Set the custom SSL context.
factory.setSSLContext(context);
WebSocket mWebSocket = factory.createSocket(BLOCK_PAY_DE);
// Requesting limit order to cancel (Task 2)
mWebSocket.addListener(new GetLimitOrders(base.getObjectId(), quote.getObjectId(), 100, new WitnessResponseListener() {
@Override
public void onSuccess(WitnessResponse response) {
System.out.println("onSuccess.1");
List<LimitOrder> orders = (List<LimitOrder>) response.result;
for(LimitOrder order : orders){
if(order.getSeller().getObjectId().equals(bilthon_15.getObjectId())){
// Instantiating a private key for bilthon-15
ECKey privateKey = new BrainKey(BILTHON_15_BRAIN_KEY, 0).getPrivateKey();
// Creating limit order cancellation operation
LimitOrderCancelOperation operation = new LimitOrderCancelOperation(order, bilthon_15);
ArrayList<BaseOperation> operationList = new ArrayList<>();
operationList.add(operation);
// Broadcasting order cancellation tx (Task 3)
broadcastTransaction(privateKey, operationList, new WitnessResponseListener() {
@Override
public void onSuccess(WitnessResponse response) {
System.out.println("onSuccess.2");
Assert.assertNull(response.result);
synchronized (this){
notifyAll();
}
synchronized (lockObject){
lockObject.notifyAll();
}
}
@Override
public void onError(BaseResponse.Error error) {
System.out.println("onError.2");
Assert.assertNull(error);
synchronized (this){
notifyAll();
}
synchronized (lockObject){
lockObject.notifyAll();
}
}
}, null);
}
}
}
@Override
public void onError(BaseResponse.Error error) {
System.out.println("onError.1");
System.out.println(error.data.message);
Assert.assertNull(error);
synchronized (lockObject){
lockObject.notifyAll();
}
}
}));
mWebSocket.connect();
}catch(NoSuchAlgorithmException e){
System.out.println("NoSuchAlgorithmException. Msg: "+e.getMessage());
} catch (WebSocketException e) {
System.out.println("WebSocketException. Msg: "+e.getMessage());
} catch (IOException e) {
System.out.println("IOException. Msg: "+e.getMessage());
}
}
@Override
public void onError(BaseResponse.Error error) {
System.out.println("OnError. Msg: "+error.message);
synchronized (this){
notifyAll();
}
}
}, lockObject);
}
}

View file

@ -0,0 +1,32 @@
package de.bitsharesmunich.graphenej.operations;
import com.google.common.primitives.UnsignedLong;
import de.bitsharesmunich.graphenej.*;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
/**
* Created by nelson on 3/21/17.
*/
public class LimitOrderCancelOperationTest {
private static final Asset CORE_ASSET = new Asset("1.3.0");
private UserAccount feePayingAccount;
private LimitOrder limitOrder;
@Before
public void setup(){
feePayingAccount = new UserAccount("1.2.143563");
limitOrder = new LimitOrder("1.7.2360289");
}
@Test
public void toBytes() throws Exception {
LimitOrderCancelOperation operation = new LimitOrderCancelOperation(limitOrder, feePayingAccount);
operation.setFee(new AssetAmount(UnsignedLong.valueOf(2), CORE_ASSET));
byte[] serialized = operation.toBytes();
assertArrayEquals("Correct serialization", Util.hexToBytes("020000000000000000cbe108e187900100"), serialized);
}
}