Merge branch 'develop'

This commit is contained in:
Nelson R. Perez 2017-03-23 21:20:57 -05:00
commit 80ff09d42f
28 changed files with 924 additions and 1657 deletions

24
.gitignore vendored
View file

@ -7,6 +7,9 @@
# Gradle
# ------
.gradle
gradle
gradlew
gradlew.bat
/build
/buildSrc/build
/subprojects/*/build
@ -79,7 +82,24 @@ atlassian-ide-plugin.xml
# ----
/*.log
src/main/java/com/luminiasoft/bitshares/mycelium/*
# Ignore bin backups
*.bin
# [Maven]
# -------
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
# Package Files #
*.jar
*.war
*.ear
# Build dir
graphenej/build
local.properties

View file

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View file

@ -1,3 +0,0 @@
dependencies {
compile project(':graphenej')
}

Binary file not shown.

View file

@ -1,2 +0,0 @@
Manifest-Version: 1.0

View file

@ -1,99 +0,0 @@
package de.bitsharesmunich.graphenej;
public class Main {
// Brain key from Nelson's app referencing the bilthon-83 account
public static final String BILTHON_83_BRAIN_KEY = System.getenv("BILTHON_83_BRAIN_KEY");
public static final String BILTHON_83_ORIGINAL_BRAIN_KEY = System.getenv("BILTHON_83_ORIGINAL_BRAIN_KEY");
public static final String BILTHON_1_BRAIN_KEY = System.getenv("BILTHON_1_BRAIN_KEY");
public static final String BILTHON_5_BRAIN_KEY = System.getenv("BILTHON_5_BRAIN_KEY");
public static final String BILTHON_7_BRAIN_KEY = System.getenv("BILTHON_7_BRAIN_KEY");
public static final String BIP39_KEY = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
public static final String WIF = "5KMzB2GqGhnh7ufhgddmz1eKPHS72uTLeL9hHjSvPb1UywWknF5";
public static final String BILTHON_83_PASSWORD = System.getenv("BILTHON_83_PASSWORD");
public static final String BILTHON_25_PASSWORD = System.getenv("BILTHON_25_PASSWORD");
public static final String BILTHON_11_BRAIN_KEY = System.getenv("BILTHON_11_BRAINKEY");
public static final String BILTHON_15_BRAIN_KEY = System.getenv("BILTHON_15_BRAINKEY");
public static final String BILTHON_16_BRAIN_KEY = System.getenv("BILTHON_16_BRAINKEY");
public static final String BILTHON_36_BRAIN_KEY = System.getenv("BILTHON_36_BRAINKEY");
public static final String GENERIC_PASSWORD = System.getenv("GENERIC_PASSWORD");
public static final String DISCLOSABLE_PASSWORD = System.getenv("DISCLOSABLE_PASSWORD");
// Static block information used for transaction serialization tests
public static int REF_BLOCK_NUM = 56204;
public static int REF_BLOCK_PREFIX = 1614747814;
public static int RELATIVE_EXPIRATION = 1478385607;
public static void main(String[] args) {
Test test = new Test();
// test.testTransactionSerialization();
// ECKey.ECDSASignature signature = test.testSigning();
// try {
// test.testWebSocketTransfer();
// } catch (IOException e) {
// e.printStackTrace();
// }
// test.testCustomSerializer();
// test.testUserAccountSerialization();
// test.testTransactionSerialization();
// test.testLoginSerialization();
// test.testNetworkBroadcastSerialization();
// test.testNetworkBroadcastDeserialization();
// test.testGetDynamicParams();
// test.testGetRequiredFeesSerialization();
// test.testRequiredFeesResponse();
// test.testTransactionBroadcastSequence();
// test.testAccountLookupDeserialization();
// test.testPrivateKeyManipulations();
// test.testPublicKeyManipulations();
// test.testGetAccountByName();
// test.testGetRequiredFees();
// test.testRandomNumberGeneration();
// test.testBrainKeyOperations(false);
// test.testBip39Opertion();
// test.testAccountNamebyAddress();
// test.testAccountNameById();
// test.testRelativeAccountHistory();
// test.testingInvoiceGeneration();
// test.testCompression();
// test.testAccountUpdateSerialization();
// test.testAccountUpdateOperationBroadcast();
// test.testCreateBinFile();
// test.testImportBinFile();
test.testExportBinFile();
// test.testLzmaCompression();
// test.testLzmaDecompression();
// test.testSimpleDecompression();
// test.testLookupAccounts();
// test.testLookupAccounts();
// test.testDecodeMemo();
// test.testGetRelativeAccountHistory();
// test.testLookupAssetSymbols();
// test.testListAssets();
// test.testGetObjects();
// test.testGetBlockHeader();
// test.testGetLimitOrders();
// test.testGetTradeHistory();
// test.testAssetSerialization();
// test.testGetMarketHistory();
// test.testGetAccountBalances();
// test.testGetAssetHoldersCount();
// test.testSubscription(null);
}
}

File diff suppressed because it is too large Load diff

View file

@ -3,8 +3,15 @@ allprojects {
}
subprojects {
apply plugin: "java"
repositories {
mavenCentral()
}
}
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0'
}
}

33
gradle.properties Normal file
View file

@ -0,0 +1,33 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
VERSION_NAME=0.4.1
VERSION_CODE=3
GROUP=com.github.kenCode-de
POM_DESCRIPTION=A Java library for mobile app Developers; Graphene/Bitshares blockchain.
POM_URL=https://github.com/kenCode-de/graphenej
POM_SCM_URL=https://github.com/kenCode-de/graphenej
POM_SCM_CONNECTION=scm:git@github.com:kenCode-de/graphenej.git
POM_SCM_DEV_CONNECTION=scm:git@github.com:kenCode-de/graphenej.git
POM_LICENCE_NAME=MIT License
POM_LICENCE_URL=https://github.com/kenCode-de/graphenej/blob/master/LICENSE
POM_LICENCE_DIST=repo
POM_DEVELOPER_ID=bilthon
POM_DEVELOPER_NAME=bilthon

View file

@ -1,17 +1,9 @@
group 'de.bitsharesmunich'
version '0.1-SNAPSHOT'
version '0.4.0-SNAPSHOT'
apply plugin: 'com.android.library'
apply from: 'maven-push.gradle'
//apply plugin: 'java'
//
//model {
// components {
// main(JvmLibrarySpec)
// }
//}
//
//repositories {
// mavenCentral()
//}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
@ -20,3 +12,24 @@ dependencies {
compile group: 'com.google.code.gson', name: 'gson', version: '2.8.0'
compile group: "org.tukaani", name: "xz", version: "1.6"
}
android {
compileSdkVersion 24
buildToolsVersion "25.0.0"
defaultConfig {
minSdkVersion 3
targetSdkVersion 24
versionCode 3
versionName "0.4.1"
vectorDrawables.useSupportLibrary = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

View file

@ -0,0 +1,3 @@
POM_NAME=Graphenej
POM_ARTIFACT_ID=graphenej
POM_PACKAGING=aar

112
graphenej/maven-push.gradle Normal file
View file

@ -0,0 +1,112 @@
/*
* Copyright 2013 Chris Banes
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
apply plugin: 'maven'
apply plugin: 'signing'
def isReleaseBuild() {
return VERSION_NAME.contains("SNAPSHOT") == false
}
def getReleaseRepositoryUrl() {
return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
: "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
}
def getSnapshotRepositoryUrl() {
return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
: "https://oss.sonatype.org/content/repositories/snapshots/"
}
def getRepositoryUsername() {
return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : ""
}
def getRepositoryPassword() {
return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ""
}
afterEvaluate { project ->
uploadArchives {
repositories {
mavenDeployer {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
pom.groupId = GROUP
pom.artifactId = POM_ARTIFACT_ID
pom.version = VERSION_NAME
repository(url: getReleaseRepositoryUrl()) {
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}
snapshotRepository(url: getSnapshotRepositoryUrl()) {
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}
pom.project {
name POM_NAME
packaging POM_PACKAGING
description POM_DESCRIPTION
url POM_URL
scm {
url POM_SCM_URL
connection POM_SCM_CONNECTION
developerConnection POM_SCM_DEV_CONNECTION
}
licenses {
license {
name POM_LICENCE_NAME
url POM_LICENCE_URL
distribution POM_LICENCE_DIST
}
}
developers {
developer {
id POM_DEVELOPER_ID
name POM_DEVELOPER_NAME
}
}
}
}
}
}
signing {
required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
sign configurations.archives
}
//task androidJavadocs(type: Javadoc) {
//source = android.sourceSets.main.allJava
//}
//task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
//classifier = 'javadoc'
//from androidJavadocs.destinationDir
//}
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.sourceFiles
}
artifacts {
archives androidSourcesJar
}
}

View file

@ -1,3 +0,0 @@
rootProject.name = 'graphenej'
include 'application'

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.bitsharesmunich.graphenej"
android:versionCode="1"
android:versionName="0.4.0" >
<uses-sdk android:minSdkVersion="1" />
<application/>
</manifest>

View file

@ -1,67 +0,0 @@
package de.bitsharesmunich.graphenej;
import de.bitsharesmunich.graphenej.errors.MalformedTransactionException;
import de.bitsharesmunich.graphenej.operations.AccountUpdateOperation;
import org.bitcoinj.core.ECKey;
import java.util.ArrayList;
import java.util.List;
/**
* Class used to build a transaction containing an account update operation.
*/
public class AccountUpdateTransactionBuilder extends TransactionBuilder {
private List<BaseOperation> operations;
private AssetAmount fee;
private UserAccount account;
private Authority owner;
private Authority active;
private AccountOptions new_options;
public AccountUpdateTransactionBuilder(ECKey privKey) {
super(privKey);
}
public AccountUpdateTransactionBuilder setAccont(UserAccount account){
this.account = account;
return this;
}
public AccountUpdateTransactionBuilder setOwner(Authority owner){
this.owner = owner;
return this;
}
public AccountUpdateTransactionBuilder setActive(Authority active){
this.active = active;
return this;
}
public AccountUpdateTransactionBuilder setOptions(AccountOptions options){
this.new_options = options;
return this;
}
public AccountUpdateTransactionBuilder setFee(AssetAmount fee){
this.fee = fee;
return this;
}
@Override
public Transaction build() throws MalformedTransactionException {
if(account == null){
throw new MalformedTransactionException("Missing required account information");
}else{
operations = new ArrayList<>();
AccountUpdateOperation operation;
if(fee == null){
operation = new AccountUpdateOperation(account, owner, active, new_options);
}else{
operation = new AccountUpdateOperation(account, owner, active, new_options, fee);
}
operations.add(operation);
}
return new Transaction(privateKey, blockData, operations);
}
}

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;
@ -8,6 +10,9 @@ import de.bitsharesmunich.graphenej.interfaces.JsonSerializable;
*/
public abstract class BaseOperation implements ByteSerializable, JsonSerializable {
public static final String KEY_FEE = "fee";
public static final String KEY_EXTENSIONS = "extensions";
protected OperationType type;
protected Extensions extensions;
@ -22,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

@ -1,26 +0,0 @@
package de.bitsharesmunich.graphenej;
import de.bitsharesmunich.graphenej.errors.MalformedTransactionException;
import org.bitcoinj.core.ECKey;
/**
* Created by nelson on 11/14/16.
*/
public abstract class TransactionBuilder {
protected ECKey privateKey;
protected BlockData blockData;
public TransactionBuilder(){}
public TransactionBuilder(ECKey privKey){
this.privateKey = privKey;
}
public TransactionBuilder setBlockData(BlockData blockData){
this.blockData = blockData;
return this;
}
public abstract Transaction build() throws MalformedTransactionException;
}

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

@ -133,7 +133,7 @@ public class TransactionBroadcastSequence extends WebSocketAdapter {
websocket.sendText(call.toJsonString());
}else if(baseResponse.id >= BROADCAST_TRANSACTION){
Type WitnessResponseType = new TypeToken<WitnessResponse<String>>(){}.getType();
WitnessResponse<WitnessResponse<String>> witnessResponse = gson.fromJson(response, WitnessResponseType);
WitnessResponse<String> witnessResponse = gson.fromJson(response, WitnessResponseType);
mListener.onSuccess(witnessResponse);
websocket.disconnect();
}

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

@ -0,0 +1,107 @@
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.*;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
/**
* Operation used to denote the creation of a limit order on the blockchain.
*
* The blockchain will atempt to sell amount_to_sell.asset_id for as much min_to_receive.asset_id as possible.
* The fee will be paid by the seller's account. Market fees will apply as specified by the issuer of both the
* selling asset and the receiving asset as a percentage of the amount exchanged.
*
* If either the selling asset or the receiving asset is white list restricted, the order will only be created
* if the seller is on the white list of the restricted asset type.
*
* Market orders are matched in the order they are included in the block chain.
*/
public class LimitOrderCreateOperation extends BaseOperation {
// Number of bytes used for the expiration field.
private final int EXPIRATION_BYTE_LENGTH = 4;
// Constants used in the JSON representation
public static final String KEY_SELLER = "seller";
public static final String KEY_AMOUNT_TO_SELL = "amount_to_sell";
public static final String KEY_MIN_TO_RECEIVE = "min_to_receive";
public static final String KEY_EXPIRATION = "expiration";
public static final String KEY_FILL_OR_KILL = "fill_or_kill";
// Inner fields of a limit order
private AssetAmount fee;
private UserAccount seller;
private AssetAmount amountToSell;
private AssetAmount minToReceive;
private int expiration;
private boolean fillOrKill;
/**
* @param seller: Id of the seller
* @param toSell: Id of the asset to sell
* @param minToReceive: The minimum amount of the asset to receive
* @param expiration: Expiration in seconds
* @param fillOrKill: If this flag is set the entire order must be filled or the operation is rejected.
*/
public LimitOrderCreateOperation(UserAccount seller, AssetAmount toSell, AssetAmount minToReceive, int expiration, boolean fillOrKill){
super(OperationType.LIMIT_ORDER_CREATE_OPERATION);
this.seller = seller;
this.amountToSell = toSell;
this.minToReceive = minToReceive;
this.expiration = expiration;
this.fillOrKill = fillOrKill;
}
@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_SELLER, seller.toJsonString());
jsonObject.add(KEY_AMOUNT_TO_SELL, amountToSell.toJsonObject());
jsonObject.add(KEY_MIN_TO_RECEIVE, minToReceive.toJsonObject());
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(Util.TIME_DATE_FORMAT);
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
jsonObject.addProperty(KEY_EXPIRATION, simpleDateFormat.format(new Date(((long) expiration) * 1000)));
jsonObject.addProperty(KEY_FILL_OR_KILL, this.fillOrKill ? "true" : "false");
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[] sellerBytes = this.seller.toBytes();
byte[] amountBytes = this.amountToSell.toBytes();
byte[] minAmountBytes = this.minToReceive.toBytes();
ByteBuffer buffer = ByteBuffer.allocate(EXPIRATION_BYTE_LENGTH);
buffer.putInt(this.expiration);
byte[] expirationBytes = Util.revertBytes(buffer.array());
byte[] fillOrKill = this.fillOrKill ? new byte[]{ 0x1 } : new byte[]{ 0x0 };
byte[] extensions = this.extensions.toBytes();
return Bytes.concat(feeBytes, sellerBytes, amountBytes, minAmountBytes, expirationBytes, fillOrKill, extensions);
}
}

View file

@ -14,9 +14,7 @@ import java.lang.reflect.Type;
* Class used to encapsulate the TransferOperation operation related functionalities.
*/
public class TransferOperation extends BaseOperation {
public static final String KEY_FEE = "fee";
public static final String KEY_AMOUNT = "amount";
public static final String KEY_EXTENSIONS = "extensions";
public static final String KEY_FROM = "from";
public static final String KEY_TO = "to";
public static final String KEY_MEMO = "memo";

View file

@ -0,0 +1,316 @@
package de.bitsharesmunich.graphenej;
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.*;
import de.bitsharesmunich.graphenej.test.NaiveSSLContext;
import org.bitcoinj.core.ECKey;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* Created by nelson on 3/6/17.
*/
public class TransactionTest {
private final String BILTHON_15_BRAIN_KEY = System.getenv("BILTHON_15_BRAINKEY");
private final String BILTHON_5_BRAIN_KEY = System.getenv("BILTHON_5_BRAINKEY");
private final String BILTHON_16_BRAIN_KEY = System.getenv("BILTHON_16_BRAINKEY");
private final String BLOCK_PAY_DE = System.getenv("BLOCKPAY_DE");
private final String BLOCK_PAY_FR = System.getenv("BLOCKPAY_FR");
// Transfer operation transaction
private final Asset CORE_ASSET = new Asset("1.3.0");
private final UserAccount bilthon_15 = new UserAccount("1.2.143563");
private final UserAccount bilthon_5 = new UserAccount("1.2.139313");
private final UserAccount bilthon_16 = new UserAccount("1.2.143569");
// Limit order create transaction
private final Asset BIT_USD = new Asset("1.3.121");
private UserAccount seller = bilthon_15;
private AssetAmount amountToSell = new AssetAmount(UnsignedLong.valueOf(100000), CORE_ASSET);
private AssetAmount minToReceive = new AssetAmount(UnsignedLong.valueOf(520), BIT_USD);
private long expiration;
// Lock object
private static final class Lock { }
private final Object lockObject = new Lock();
// Response
private BaseResponse baseResponse;
/**
* 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");
baseResponse = response;
synchronized (this){
this.notifyAll();
}
}
@Override
public void onError(BaseResponse.Error error) {
System.out.println("onError. Msg: "+error.data.message);
synchronized (this){
notifyAll();
}
}
};
@Before
public void setup(){
}
/**
* Receives the elements required for building a transaction, puts them together and broadcasts it.
* @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, Object lockObject) {
try{
Transaction transaction = new Transaction(privateKey, null, operationList);
SSLContext context = null;
context = NaiveSSLContext.getInstance("TLS");
WebSocketFactory factory = new WebSocketFactory();
// Set the custom SSL context.
factory.setSSLContext(context);
WebSocket mWebSocket = factory.createSocket(BLOCK_PAY_DE);
mWebSocket.addListener(new TransactionBroadcastSequence(transaction, CORE_ASSET, responseListener));
mWebSocket.connect();
// 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 (responseListener){
responseListener.wait();
}
}
Assert.assertNotNull(baseResponse);
Assert.assertNull(baseResponse.error);
}catch(NoSuchAlgorithmException e){
System.out.println("NoSuchAlgoritmException. Msg: " + e.getMessage());
} catch (InterruptedException e) {
System.out.println("InterruptedException. Msg: "+e.getMessage());
} catch (IOException e) {
System.out.println("IOException. Msg: " + e.getMessage());
} catch (WebSocketException e) {
System.out.println("WebSocketException. Msg: " + e.getMessage());
}
}
@Test
public void testTransferTransaction(){
ECKey sourcePrivateKey = new BrainKey(BILTHON_15_BRAIN_KEY, 0).getPrivateKey();
PublicKey to1 = new PublicKey(ECKey.fromPublicOnly(new BrainKey(BILTHON_5_BRAIN_KEY, 0).getPublicKey()));
PublicKey to2 = new PublicKey(ECKey.fromPublicOnly(new BrainKey(BILTHON_16_BRAIN_KEY, 0).getPublicKey()));
// Creating memo
long nonce = 1;
byte[] encryptedMessage = Memo.encryptMessage(sourcePrivateKey, to1, nonce, "another message");
Memo memo = new Memo(new Address(ECKey.fromPublicOnly(sourcePrivateKey.getPubKey())), new Address(to1.getKey()), nonce, encryptedMessage);
// Creating operation 1
TransferOperation transferOperation1 = new TransferOperationBuilder()
.setTransferAmount(new AssetAmount(UnsignedLong.valueOf(1), CORE_ASSET))
.setSource(bilthon_15)
.setDestination(bilthon_5) // bilthon-5
.setFee(new AssetAmount(UnsignedLong.valueOf(264174), CORE_ASSET))
.build();
// Creating operation 2
TransferOperation transferOperation2 = new TransferOperationBuilder()
.setTransferAmount(new AssetAmount(UnsignedLong.valueOf(1), CORE_ASSET))
.setSource(bilthon_15) // bilthon-15
.setDestination(bilthon_16) // bilthon-16
.setFee(new AssetAmount(UnsignedLong.valueOf(264174), CORE_ASSET))
.build();
// Adding operations to the operation list
ArrayList<BaseOperation> operationList = new ArrayList<>();
operationList.add(transferOperation1);
operationList.add(transferOperation2);
// Broadcasting transaction
broadcastTransaction(sourcePrivateKey, operationList, listener, null);
}
@Test
public void testLimitOrderCreateTransaction(){
ECKey privateKey = new BrainKey(BILTHON_15_BRAIN_KEY, 0).getPrivateKey();
expiration = (System.currentTimeMillis() / 1000) + 60 * 60;
// 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
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");
baseResponse = response;
synchronized (this){
notifyAll();
}
synchronized (lockObject){
lockObject.notifyAll();
}
}
@Override
public void onError(BaseResponse.Error error) {
System.out.println("onError.2");
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);
}
}

View file

@ -0,0 +1,56 @@
package de.bitsharesmunich.graphenej.operations;
import com.google.common.primitives.UnsignedLong;
import de.bitsharesmunich.graphenej.Asset;
import de.bitsharesmunich.graphenej.AssetAmount;
import de.bitsharesmunich.graphenej.UserAccount;
import de.bitsharesmunich.graphenej.Util;
import org.hamcrest.core.IsEqual;
import org.hamcrest.core.IsNot;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Created by nelson on 3/6/17.
*/
public class LimitOrderCreateOperationTest {
private static final int AMOUNT_TO_SELL = 25000000;
private static final int MIN_TO_RECEIVE = 1;
private static final Asset CORE_ASSET = new Asset("1.3.0");
private static final Asset BIT_USD = new Asset("1.3.121");
private static final int DEFAULT_EXPIRATION = 1488831620; // 2017-03-06T20:20:20
private UserAccount seller;
private AssetAmount amountToSell;
private AssetAmount minToReceive;
private int expiration;
private boolean fillOrKill;
@Before
public void setup(){
seller = new UserAccount("1.2.143563");
amountToSell = new AssetAmount(UnsignedLong.valueOf(AMOUNT_TO_SELL), CORE_ASSET);
minToReceive = new AssetAmount(UnsignedLong.valueOf(MIN_TO_RECEIVE), BIT_USD);
expiration = DEFAULT_EXPIRATION;
}
@Test
public void toBytes() throws Exception {
// Testing serialization of operation with fillOrKill parameter == true
LimitOrderCreateOperation operation = new LimitOrderCreateOperation(seller, amountToSell, minToReceive, expiration, true);
operation.setFee(new AssetAmount(UnsignedLong.valueOf(2), CORE_ASSET));
byte[] serialized = operation.toBytes();
Assert.assertArrayEquals("Correct serialization", serialized, Util.hexToBytes("020000000000000000cbe10840787d01000000000001000000000000007984c4bd580100"));
Assert.assertThat("Incorrect serialization", serialized, IsNot.not(IsEqual.equalTo("020000000000000000cbe10840787d01000000000001000000000000007984c4bd580000")));
// Testing serialization of operation with fillOrKill parameter == false
operation = new LimitOrderCreateOperation(seller, amountToSell, minToReceive, expiration, false);
operation.setFee(new AssetAmount(UnsignedLong.valueOf(2), CORE_ASSET));
serialized = operation.toBytes();
Assert.assertArrayEquals("Correct serialization", serialized, Util.hexToBytes("020000000000000000cbe10840787d01000000000001000000000000007984c4bd580000"));
Assert.assertThat("Incorrect serialization", serialized, IsNot.not(IsEqual.equalTo("020000000000000000cbe10840787d01000000000001000000000000007984c4bd580100")));
}
}

View file

@ -1,3 +1,3 @@
rootProject.name = "Graphenej"
include ":graphenej", ":app"
include ":graphenej"