Merge pull request #2 from Carix/add_custom_operation

Custom operation support
develop
Nelson Perez 2017-12-20 22:09:24 -05:00 committed by GitHub
commit 0787b13483
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 381 additions and 33 deletions

View File

@ -11,6 +11,7 @@ import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import cy.agorise.graphenej.operations.CustomOperation;
import org.bitcoinj.core.DumpedPrivateKey;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Sha256Hash;
@ -343,7 +344,7 @@ public class Transaction implements ByteSerializable, JsonSerializable {
} else if (operationId == OperationType.WORKER_CREATE_OPERATION.ordinal()) {
//TODO: Add operation deserialization support
} else if (operationId == OperationType.CUSTOM_OPERATION.ordinal()) {
//TODO: Add operation deserialization support
operation = context.deserialize(jsonOperation, CustomOperation.class);
} else if (operationId == OperationType.ASSERT_OPERATION.ordinal()) {
//TODO: Add operation deserialization support
} else if (operationId == OperationType.BALANCE_CLAIM_OPERATION.ordinal()) {

View File

@ -26,6 +26,8 @@ import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -92,6 +94,26 @@ public class Util {
return buffer.array();
}
/**
* Serializes long value to a byte array.
* @param data Long value.
* @return Array of bytes.
*/
public static byte[] serializeLongToBytes(long data) {
List<Byte> bytes = new LinkedList<>();
long value = data;
do {
byte b = (byte)(value & 0x7F);
value >>= 7;
if (value != 0) {
b |= 0x80;
}
bytes.add(b);
} while (value != 0);
return Bytes.toArray(bytes);
}
/**
* Utility function that compresses data using the LZMA algorithm.
* @param inputBytes Input bytes of the data to be compressed.

View File

@ -13,6 +13,8 @@ import cy.agorise.graphenej.models.ApiCall;
import cy.agorise.graphenej.models.BaseResponse;
import cy.agorise.graphenej.models.Block;
import cy.agorise.graphenej.models.WitnessResponse;
import cy.agorise.graphenej.operations.CustomOperation;
import cy.agorise.graphenej.operations.LimitOrderCreateOperation;
import cy.agorise.graphenej.operations.TransferOperation;
import java.io.Serializable;
@ -88,6 +90,8 @@ public class GetBlock extends BaseGrapheneHandler {
gson = new GsonBuilder()
.registerTypeAdapter(Transaction.class, new Transaction.TransactionDeserializer())
.registerTypeAdapter(TransferOperation.class, new TransferOperation.TransferDeserializer())
.registerTypeAdapter(LimitOrderCreateOperation.class, new LimitOrderCreateOperation.LimitOrderCreateDeserializer())
.registerTypeAdapter(CustomOperation.class, new CustomOperation.CustomOperationDeserializer())
.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer())
.create();
WitnessResponse<Block> blockResponse = gson.fromJson(response, BlockResponse);

View File

@ -26,6 +26,7 @@ import cy.agorise.graphenej.models.DynamicGlobalProperties;
import cy.agorise.graphenej.models.SubscriptionResponse;
import cy.agorise.graphenej.models.WitnessResponse;
import cy.agorise.graphenej.objects.Memo;
import cy.agorise.graphenej.operations.CustomOperation;
import cy.agorise.graphenej.operations.LimitOrderCreateOperation;
import cy.agorise.graphenej.operations.TransferOperation;
@ -89,6 +90,7 @@ public class SubscriptionMessagesHub extends BaseGrapheneHandler implements Subs
builder.registerTypeAdapter(Transaction.class, new Transaction.TransactionDeserializer());
builder.registerTypeAdapter(TransferOperation.class, new TransferOperation.TransferDeserializer());
builder.registerTypeAdapter(LimitOrderCreateOperation.class, new LimitOrderCreateOperation.LimitOrderCreateDeserializer());
builder.registerTypeAdapter(CustomOperation.class, new CustomOperation.CustomOperationDeserializer());
builder.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer());
builder.registerTypeAdapter(UserAccount.class, new UserAccount.UserAccountSimpleDeserializer());
builder.registerTypeAdapter(DynamicGlobalProperties.class, new DynamicGlobalProperties.DynamicGlobalPropertiesDeserializer());

View File

@ -0,0 +1,185 @@
package cy.agorise.graphenej.operations;
import com.google.common.primitives.Bytes;
import com.google.gson.*;
import cy.agorise.graphenej.*;
import java.lang.reflect.Type;
import java.util.LinkedList;
import java.util.List;
public class CustomOperation extends BaseOperation {
private static final String KEY_FEE = "fee";
private static final String KEY_PAYER = "payer";
private static final String KEY_REQUIRED_AUTHS = "required_auths";
private static final String KEY_ID = "id";
private static final String KEY_DATA = "data";
private AssetAmount fee;
private UserAccount payer;
private List<UserAccount> requiredAuths;
private int operationId;
private String data;
public CustomOperation(AssetAmount fee, UserAccount payer, int operationId, List<UserAccount> requiredAuths, String data) {
super(OperationType.CUSTOM_OPERATION);
this.fee = fee;
this.payer = payer;
this.operationId = operationId;
this.requiredAuths = new LinkedList<>();
if (requiredAuths != null) {
this.requiredAuths.addAll(requiredAuths);
}
this.data = data;
}
public AssetAmount getFee() {
return fee;
}
@Override
public void setFee(AssetAmount fee) {
this.fee = fee;
}
public UserAccount getPayer() {
return payer;
}
public void setPayer(UserAccount payer) {
this.payer = payer;
}
public List<UserAccount> getRequiredAuths() {
return requiredAuths;
}
public void setRequiredAuths(List<UserAccount> requiredAuths) {
this.requiredAuths = requiredAuths;
}
public int getOperationId() {
return operationId;
}
public void setOperationId(int operationId) {
this.operationId = operationId;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
@Override
public byte[] toBytes() {
byte[] feeBytes = fee.toBytes();
byte[] payerBytes = payer.toBytes();
List<Byte> requiredAuthsSerialized = new LinkedList<>();
if (this.requiredAuths != null) {
for (UserAccount userAccount : this.requiredAuths) {
requiredAuthsSerialized.addAll(Bytes.asList(userAccount.toBytes()));
}
}
byte[] requiredAuthsBytes = Bytes.toArray(requiredAuthsSerialized);
byte[] requiredAuthsLength = {(byte)this.requiredAuths.size()};
byte[] operationIdBytes = Util.revertShort((short)operationId);
byte[] dataLength = Util.serializeLongToBytes(data.length());
byte[] dataBytes = Util.hexlify(data);
return Bytes.concat(feeBytes, payerBytes, requiredAuthsLength, requiredAuthsBytes, operationIdBytes, dataLength, dataBytes);
}
@Override
public String toJsonString() {
return toJsonObject().toString();
}
@Override
public JsonElement toJsonObject() {
JsonArray array = new JsonArray();
array.add(this.getId());
JsonObject jsonObject = new JsonObject();
jsonObject.add(KEY_FEE, fee.toJsonObject());
jsonObject.addProperty(KEY_PAYER, payer.getObjectId());
JsonArray requiredAuthArray = new JsonArray();
if (requiredAuths != null) {
for (UserAccount userAccount : requiredAuths) {
requiredAuthArray.add(userAccount.getObjectId());
}
}
jsonObject.add(KEY_REQUIRED_AUTHS, requiredAuthArray);
jsonObject.addProperty(KEY_ID, operationId);
jsonObject.addProperty(KEY_DATA, Util.bytesToHex(Util.hexlify(data)));
array.add(jsonObject);
return array;
}
/**
* Deserializer used to convert the JSON-formatted representation of a custom_operation
* into its java object version.
*
* The following is an example of the serialized form of this operation:
*
* [
* 35,
* {
* "fee": {
* "amount": 100000,
* "asset_id": "1.3.0"
* },
* "payer": "1.2.20",
* "required_auths": [
* "1.2.20"
* ],
* "id": 61166,
* "data": "736f6d652064617461"
* }
* ]
*/
public static class CustomOperationDeserializer implements JsonDeserializer<CustomOperation> {
@Override
public CustomOperation 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 serializedCustomOperation = json.getAsJsonArray();
if (serializedCustomOperation.get(0).getAsInt() != OperationType.CUSTOM_OPERATION.ordinal()){
// If the operation type does not correspond to a custom operation, we return null
return null;
} else {
// Calling itself recursively, this is only done once, so there will be no problems.
return context.deserialize(serializedCustomOperation.get(1), CustomOperation.class);
}
}else{
// This block is called in the second recursion and takes care of deserializing the
// limit order data itself.
JsonObject jsonObject = json.getAsJsonObject();
AssetAmount fee = context.deserialize(jsonObject.get(KEY_FEE), AssetAmount.class);
String payerId = jsonObject.get(KEY_PAYER) .getAsString();
UserAccount payer = new UserAccount(payerId);
List<UserAccount> requiredAuths = new LinkedList<>();
JsonElement requiredAuthsElement = jsonObject.get(KEY_REQUIRED_AUTHS);
if ((requiredAuthsElement != null) && (requiredAuthsElement.isJsonArray())) {
JsonArray requiredAuthsArray = requiredAuthsElement.getAsJsonArray();
for (JsonElement jsonElement : requiredAuthsArray) {
String userAccountId = jsonElement.getAsString();
requiredAuths.add(new UserAccount(userAccountId));
}
}
int operationId = jsonObject.get(KEY_ID).getAsInt();
String data = new String(Util.hexToBytes(jsonObject.get(KEY_DATA).getAsString()));
return new CustomOperation(fee, payer, operationId, requiredAuths, data);
}
}
}
}

View File

@ -208,7 +208,7 @@ public class LimitOrderCreateOperation extends BaseOperation {
JsonObject jsonObject = json.getAsJsonObject();
AssetAmount fee = context.deserialize(jsonObject.get(KEY_FEE), AssetAmount.class);
UserAccount seller = context.deserialize(jsonObject.get(KEY_SELLER), UserAccount.class);
UserAccount seller = new UserAccount(jsonObject.get(KEY_SELLER).getAsString());
AssetAmount amountToSell = context.deserialize(jsonObject.get(KEY_AMOUNT_TO_SELL), AssetAmount.class);
AssetAmount minToReceive = context.deserialize(jsonObject.get(KEY_MIN_TO_RECEIVE), AssetAmount.class);
String expiration = jsonObject.get(KEY_EXPIRATION).getAsString();

View File

@ -14,6 +14,7 @@ import java.io.IOException;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.net.ssl.SSLContext;
@ -24,6 +25,7 @@ import cy.agorise.graphenej.interfaces.WitnessResponseListener;
import cy.agorise.graphenej.models.BaseResponse;
import cy.agorise.graphenej.models.WitnessResponse;
import cy.agorise.graphenej.objects.Memo;
import cy.agorise.graphenej.operations.CustomOperation;
import cy.agorise.graphenej.operations.LimitOrderCancelOperation;
import cy.agorise.graphenej.operations.LimitOrderCreateOperation;
import cy.agorise.graphenej.operations.TransferOperation;
@ -53,6 +55,13 @@ public class TransactionTest {
private AssetAmount minToReceive = new AssetAmount(UnsignedLong.valueOf(520), BIT_USD);
private long expiration;
// Custom operation transaction
private final AssetAmount fee = new AssetAmount(UnsignedLong.valueOf(100000), CORE_ASSET);
private final UserAccount payer = bilthon_7;
private final Integer operationId = 61166;
private final List<UserAccount> requiredAuths = Collections.singletonList(payer);
private final String data = "some data";
private final long FEE_AMOUNT = 21851;
// Lock object
@ -322,4 +331,19 @@ public class TransactionTest {
}
}, lockObject);
}
@Test
public void testCustomOperationTransaction(){
ECKey sourcePrivateKey = new BrainKey(BILTHON_7_BRAIN_KEY, 0).getPrivateKey();
// Creating custom operation
CustomOperation customOperation = new CustomOperation(fee, payer, operationId, requiredAuths, data);
// Adding operation to the operation list
ArrayList<BaseOperation> operationList = new ArrayList<>();
operationList.add(customOperation);
// Broadcasting transaction
broadcastTransaction(sourcePrivateKey, operationList, listener, null);
}
}

View File

@ -9,6 +9,7 @@ import cy.agorise.graphenej.interfaces.WitnessResponseListener;
import cy.agorise.graphenej.models.BaseResponse;
import cy.agorise.graphenej.models.Block;
import cy.agorise.graphenej.models.WitnessResponse;
import cy.agorise.graphenej.operations.CustomOperation;
import cy.agorise.graphenej.operations.TransferOperation;
import org.junit.Assert;
import org.junit.Test;
@ -21,29 +22,42 @@ import java.util.concurrent.TimeoutException;
import static org.hamcrest.CoreMatchers.instanceOf;
public class GetBlockTest extends BaseApiTest {
private final static long BLOCK_NUMBER = 15776988L;
// data for the test with transfer operation
private final static long TRANSFER_BLOCK_NUMBER = 15776988L;
private final static String EXPECTED_PREVIOUS = "00f0bcdbe3d9dc66d8597e7b013a16d8dea5f778";
private final static String EXPECTED_TIMESTAMP = "2017-04-18T04:14:51";
private final static String EXPECTED_WITNESS = "1.6.17";
private final static String EXPECTED_TRANSACTION_MERKLE_ROOT = "fb87a3f8af907a2450c327d181b2833b8270a504";
private final static int EXPECTED_EXTENSIONS_SIZE = 0;
private final static String EXPECTED_WITNESS_SIGNATURE = "20160921c8ba0d312dee14bdc780c3d05b27e406e2d014b8a2415e9843bf7075cb7abbc45f5173ffefac69cecf4dd2afa5dce6076bdf24cc577ff49427babe75e1";
private final static int EXPECTED_TRANSACTIONS_SIZE = 1;
private final static int EXPECTED_OPERATIONS_SIZE = 1;
private final static String TRANSFER_EXPECTED_PREVIOUS = "00f0bcdbe3d9dc66d8597e7b013a16d8dea5f778";
private final static String TRANSFER_EXPECTED_TIMESTAMP = "2017-04-18T04:14:51";
private final static String TRANSFER_EXPECTED_WITNESS = "1.6.17";
private final static String TRANSFER_EXPECTED_TRANSACTION_MERKLE_ROOT = "fb87a3f8af907a2450c327d181b2833b8270a504";
private final static int TRANSFER_EXPECTED_EXTENSIONS_SIZE = 0;
private final static String TRANSFER_EXPECTED_WITNESS_SIGNATURE = "20160921c8ba0d312dee14bdc780c3d05b27e406e2d014b8a2415e9843bf7075cb7abbc45f5173ffefac69cecf4dd2afa5dce6076bdf24cc577ff49427babe75e1";
private final static int TRANSFER_EXPECTED_TRANSACTIONS_SIZE = 1;
private final static int TRANSFER_EXPECTED_OPERATIONS_SIZE = 1;
private final static UnsignedLong EXPECTED_FEE_AMOUNT = UnsignedLong.valueOf(2048);
private final static String EXPECTED_FEE_ASSET_ID = "1.3.113";
private final static String EXPECTED_FROM = "1.2.151069";
private final static String EXPECTED_TO = "1.2.116354";
private final static UnsignedLong EXPECTED_AMOUNT = UnsignedLong.valueOf(13700);
private final static String EXPECTED_ASSET_ID = "1.3.113";
private final static UnsignedLong TRANSFER_EXPECTED_FEE_AMOUNT = UnsignedLong.valueOf(2048);
private final static String TRANSFER_EXPECTED_FEE_ASSET_ID = "1.3.113";
private final static String TRANSFER_EXPECTED_FROM = "1.2.151069";
private final static String TRANSFER_EXPECTED_TO = "1.2.116354";
private final static UnsignedLong TRANSFER_EXPECTED_AMOUNT = UnsignedLong.valueOf(13700);
private final static String TRANSFER_EXPECTED_ASSET_ID = "1.3.113";
// data for the test with custom operation
private final static long CUSTOM_BLOCK_NUMBER = 22754473L;
private final static int CUSTOM_TRANSACTION_INDEX = 8;
private final static UnsignedLong CUSTOM_EXPECTED_FEE_AMOUNT = UnsignedLong.valueOf(13798);
private final static String CUSTOM_EXPECTED_FEE_ASSET_ID = "1.3.0";
private final static String CUSTOM_EXPECTED_PAYER = "1.2.140994";
private final static int CUSTOM_EXPECTED_REQUIRED_AUTHS_SIZE = 1;
private final static String CUSTOM_EXPECTED_REQUIRED_AUTH = "1.2.140994";
private final static int CUSTOM_EXPECTED_ID = 61166;
private final static String CUSTOM_EXPECTED_DATA = "some data";
@Test
public void testGetBlock() {
public void testGetBlockWithTransferOperation() {
try {
final Exchanger<Object> responseExchanger = new Exchanger<>();
mWebSocket.addListener(new GetBlock(BLOCK_NUMBER, new WitnessResponseListener() {
mWebSocket.addListener(new GetBlock(TRANSFER_BLOCK_NUMBER, new WitnessResponseListener() {
@Override
public void onSuccess(WitnessResponse response) {
System.out.println("onSuccess");
@ -71,31 +85,88 @@ public class GetBlockTest extends BaseApiTest {
Block block = (Block) responseResult;
Assert.assertNotNull(block);
Assert.assertEquals(EXPECTED_PREVIOUS, block.getPrevious());
Assert.assertEquals(EXPECTED_TIMESTAMP, block.getTimestamp());
Assert.assertEquals(EXPECTED_WITNESS, block.getWitness());
Assert.assertEquals(EXPECTED_TRANSACTION_MERKLE_ROOT, block.getTransaction_merkle_root());
Assert.assertEquals(EXPECTED_EXTENSIONS_SIZE, block.getExtensions().length);
Assert.assertEquals(EXPECTED_WITNESS_SIGNATURE, block.getWitness_signature());
Assert.assertEquals(TRANSFER_EXPECTED_PREVIOUS, block.getPrevious());
Assert.assertEquals(TRANSFER_EXPECTED_TIMESTAMP, block.getTimestamp());
Assert.assertEquals(TRANSFER_EXPECTED_WITNESS, block.getWitness());
Assert.assertEquals(TRANSFER_EXPECTED_TRANSACTION_MERKLE_ROOT, block.getTransaction_merkle_root());
Assert.assertEquals(TRANSFER_EXPECTED_EXTENSIONS_SIZE, block.getExtensions().length);
Assert.assertEquals(TRANSFER_EXPECTED_WITNESS_SIGNATURE, block.getWitness_signature());
List<Transaction> transactions = block.getTransactions();
Assert.assertEquals(EXPECTED_TRANSACTIONS_SIZE, transactions.size());
Assert.assertEquals(TRANSFER_EXPECTED_TRANSACTIONS_SIZE, transactions.size());
List<BaseOperation> operations = transactions.get(0).getOperations();
Assert.assertEquals(EXPECTED_OPERATIONS_SIZE, operations.size());
Assert.assertEquals(TRANSFER_EXPECTED_OPERATIONS_SIZE, operations.size());
BaseOperation operation = operations.get(0);
Assert.assertThat(operation, instanceOf(TransferOperation.class));
TransferOperation transferOperation = (TransferOperation) operation;
AssetAmount fee = transferOperation.getFee();
Assert.assertEquals(EXPECTED_FEE_AMOUNT, fee.getAmount());
Assert.assertEquals(EXPECTED_FEE_ASSET_ID, fee.getAsset().getObjectId());
Assert.assertEquals(EXPECTED_FROM, transferOperation.getFrom().getObjectId());
Assert.assertEquals(EXPECTED_TO, transferOperation.getTo().getObjectId());
Assert.assertEquals(TRANSFER_EXPECTED_FEE_AMOUNT, fee.getAmount());
Assert.assertEquals(TRANSFER_EXPECTED_FEE_ASSET_ID, fee.getAsset().getObjectId());
Assert.assertEquals(TRANSFER_EXPECTED_FROM, transferOperation.getFrom().getObjectId());
Assert.assertEquals(TRANSFER_EXPECTED_TO, transferOperation.getTo().getObjectId());
AssetAmount assetAmount = transferOperation.getAssetAmount();
Assert.assertEquals(EXPECTED_AMOUNT, assetAmount.getAmount());
Assert.assertEquals(EXPECTED_ASSET_ID, assetAmount.getAsset().getObjectId());
Assert.assertEquals(TRANSFER_EXPECTED_AMOUNT, assetAmount.getAmount());
Assert.assertEquals(TRANSFER_EXPECTED_ASSET_ID, assetAmount.getAsset().getObjectId());
} catch (WebSocketException e) {
System.out.println("WebSocketException. Msg: " + e.getMessage());
Assert.fail("Fail because of WebSocketException");
} catch (InterruptedException e) {
System.out.println("InterruptedException. Msg: " + e.getMessage());
Assert.fail("Fail because of InterruptedException");
} catch (TimeoutException e) {
System.out.println("TimeoutException. Msg: " + e.getMessage());
Assert.fail("Fail because of TimeoutException");
}
}
@Test
public void testGetBlockWithCustomOperation() {
try {
final Exchanger<Object> responseExchanger = new Exchanger<>();
mWebSocket.addListener(new GetBlock(CUSTOM_BLOCK_NUMBER, new WitnessResponseListener() {
@Override
public void onSuccess(WitnessResponse response) {
System.out.println("onSuccess");
try {
responseExchanger.exchange(response.result);
} catch (InterruptedException e) {
System.out.println("InterruptedException in success handler. Msg: " + e.getMessage());
}
}
@Override
public void onError(BaseResponse.Error error) {
System.out.println("onError");
try {
responseExchanger.exchange(null);
} catch (InterruptedException e) {
System.out.println("InterruptedException in error handler. Msg: " + e.getMessage());
}
}
}));
mWebSocket.connect();
Object responseResult = responseExchanger.exchange(null, 5, TimeUnit.SECONDS);
Block block = (Block) responseResult;
List<Transaction> transactions = block.getTransactions();
List<BaseOperation> operations = transactions.get(CUSTOM_TRANSACTION_INDEX).getOperations();
BaseOperation operation = operations.get(0);
Assert.assertThat(operation, instanceOf(CustomOperation.class));
CustomOperation customOperation = (CustomOperation) operation;
AssetAmount fee = customOperation.getFee();
Assert.assertEquals(CUSTOM_EXPECTED_FEE_AMOUNT, fee.getAmount());
Assert.assertEquals(CUSTOM_EXPECTED_FEE_ASSET_ID, fee.getAsset().getObjectId());
Assert.assertEquals(CUSTOM_EXPECTED_PAYER, customOperation.getPayer().getObjectId());
Assert.assertEquals(CUSTOM_EXPECTED_REQUIRED_AUTHS_SIZE, customOperation.getRequiredAuths().size());
Assert.assertEquals(CUSTOM_EXPECTED_REQUIRED_AUTH, customOperation.getRequiredAuths().get(0).getObjectId());
Assert.assertEquals(CUSTOM_EXPECTED_ID, customOperation.getOperationId());
Assert.assertEquals(CUSTOM_EXPECTED_DATA, customOperation.getData());
} catch (WebSocketException e) {
System.out.println("WebSocketException. Msg: " + e.getMessage());
Assert.fail("Fail because of WebSocketException");

View File

@ -0,0 +1,39 @@
package cy.agorise.graphenej.operations;
import com.google.common.primitives.UnsignedLong;
import cy.agorise.graphenej.Asset;
import cy.agorise.graphenej.AssetAmount;
import cy.agorise.graphenej.UserAccount;
import cy.agorise.graphenej.Util;
import org.junit.Test;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertArrayEquals;
public class CustomOperationTest {
private final Asset CORE_ASSET = new Asset("1.3.0");
private final AssetAmount fee = new AssetAmount(UnsignedLong.valueOf(100000L), CORE_ASSET);
private final UserAccount payer = new UserAccount("1.2.20");
private final Integer operationId = 61166;
private final List<UserAccount> requiredAuths = Collections.singletonList(payer);
private final String shortData = "some data";
private final String longData = "very long data, very long data, very long data, very long data, very long data, very long data, very long data, very long data, very long data, very long data, very long data, very long data, very long data, very long data...";
private static final byte[] EXPECTED_SERIALIZED_BYTES_1 = Util.hexToBytes("a08601000000000000140114eeee09736f6d652064617461");
private static final byte[] EXPECTED_SERIALIZED_BYTES_2 = Util.hexToBytes("a08601000000000000140114eeeee50176657279206c6f6e6720646174612c2076657279206c6f6e6720646174612c2076657279206c6f6e6720646174612c2076657279206c6f6e6720646174612c2076657279206c6f6e6720646174612c2076657279206c6f6e6720646174612c202076657279206c6f6e6720646174612c2076657279206c6f6e6720646174612c202076657279206c6f6e6720646174612c2076657279206c6f6e6720646174612c202076657279206c6f6e6720646174612c2076657279206c6f6e6720646174612c202076657279206c6f6e6720646174612c2076657279206c6f6e6720646174612e2e2e");
@Test
public void testToBytes() throws Exception {
CustomOperation customOperation1 = new CustomOperation(fee, payer, operationId, requiredAuths, shortData);
byte[] serialized1 = customOperation1.toBytes();
assertArrayEquals(EXPECTED_SERIALIZED_BYTES_1, serialized1);
// test with some long data string to check if data length is serialized correctly
CustomOperation customOperation2 = new CustomOperation(fee, payer, operationId, requiredAuths, longData);
byte[] serialized2 = customOperation2.toBytes();
assertArrayEquals(EXPECTED_SERIALIZED_BYTES_2, serialized2);
}
}