Initial commit

master
Nelson R. Perez 2016-11-21 12:50:30 -05:00
commit 52c2c9db5e
39 changed files with 2603 additions and 0 deletions

77
.gitignore vendored Normal file
View File

@ -0,0 +1,77 @@
# Mac OS X file
.DS_Store
# Gradle
# ------
.gradle
/build
/buildSrc/build
/subprojects/*/build
/subprojects/docs/src/samples/*/*/build
/subprojects/internal-android-performance-testing/build-android-libs
# IDEA
# ----
.idea
.shelf
/*.iml
/*.ipr
/*.iws
/buildSrc/*.iml
/buildSrc/*.ipr
/buildSrc/*.iws
/buildSrc/out
/out
/subprojects/*/*.iml
/subprojects/*/out
# Eclipse
# -------
*.classpath
*.project
*.settings
/bin
/subprojects/*/bin
atlassian-ide-plugin.xml
# NetBeans
# --------
.nb-gradle
.nb-gradle-properties
# Vim
# ---
*.sw[op]
# Emacs
# -----
*~
# Textmate
# --------
.textmate
# Sublime Text
# ------------
*.sublime-*
# jEnv
# ----
.java-version
# OS X
# ----
.DS_Store
# HPROF
# -----
*.hprof
# Work dirs
# ---------
/incoming-distributions
/intTestHomeDir
# Logs
# ----
/*.log

15
build.gradle Normal file
View File

@ -0,0 +1,15 @@
group 'com.luminiasoft'
version '0.1-SNAPSHOT'
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.11'
compile 'com.neovisionaries:nv-websocket-client:1.30'
compile 'org.bitcoinj:bitcoinj-core:0.14.3'
compile group: 'com.google.code.gson', name: 'gson', version: '2.8.0'
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Mon Nov 21 11:33:11 PET 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-all.zip

164
gradlew vendored Executable file
View File

@ -0,0 +1,164 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

90
gradlew.bat vendored Normal file
View File

@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

3
settings.gradle Normal file
View File

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

View File

@ -0,0 +1,13 @@
package com.luminiasoft.bitshares;
import com.luminiasoft.bitshares.interfaces.JsonSerializable;
/**
* Created by nelson on 11/9/16.
*/
public class Asset extends GrapheneObject {
public Asset(String id) {
super(id);
}
}

View File

@ -0,0 +1,88 @@
package com.luminiasoft.bitshares;
import com.google.common.primitives.UnsignedLong;
import com.google.gson.*;
import com.luminiasoft.bitshares.interfaces.ByteSerializable;
import com.luminiasoft.bitshares.interfaces.JsonSerializable;
import java.lang.reflect.Type;
/**
* Created by nelson on 11/7/16.
*/
public class AssetAmount implements ByteSerializable, JsonSerializable{
/**
* Constants used in the JSON serialization procedure.
*/
public static final String KEY_AMOUNT = "amount";
public static final String KEY_ASSET_ID = "asset_id";
private UnsignedLong amount;
private Asset asset;
public AssetAmount(UnsignedLong amount, Asset asset){
this.amount = amount;
this.asset = asset;
}
public void setAmount(UnsignedLong amount){
this.amount = amount;
}
public UnsignedLong getAmount(){
return this.amount;
}
public Asset getAsset(){ return this.asset; }
@Override
public byte[] toBytes() {
byte[] serialized = new byte[8 + 1];
byte[] amountBytes = this.amount.bigIntegerValue().toByteArray();
serialized[serialized.length - 1] = (byte) asset.instance;
for(int i = 0; i < amountBytes.length; i++)
serialized[i] = amountBytes[amountBytes.length - 1 - i];
return serialized;
}
@Override
public String toJsonString() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(AssetAmount.class, new AssetSerializer());
return gsonBuilder.create().toJson(this);
}
@Override
public JsonObject toJsonObject() {
JsonObject jsonAmount = new JsonObject();
jsonAmount.addProperty(KEY_AMOUNT, amount);
jsonAmount.addProperty(KEY_ASSET_ID, asset.getObjectId());
return jsonAmount;
}
/**
* Custom serializer used to translate this object into the JSON-formatted entry we need for a transaction.
*/
public static class AssetSerializer implements JsonSerializer<AssetAmount> {
@Override
public JsonElement serialize(AssetAmount assetAmount, Type type, JsonSerializationContext jsonSerializationContext) {
JsonObject obj = new JsonObject();
obj.addProperty(KEY_AMOUNT, assetAmount.amount);
obj.addProperty(KEY_ASSET_ID, assetAmount.asset.getObjectId());
return obj;
}
}
public static class AssetDeserializer implements JsonDeserializer<AssetAmount> {
@Override
public AssetAmount deserialize(JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
Long amount = json.getAsJsonObject().get(KEY_AMOUNT).getAsLong();
String assetId = json.getAsJsonObject().get(KEY_ASSET_ID).getAsString();
AssetAmount assetAmount = new AssetAmount(UnsignedLong.valueOf(amount), new Asset(assetId));
return assetAmount;
}
}
}

View File

@ -0,0 +1,20 @@
package com.luminiasoft.bitshares;
import com.luminiasoft.bitshares.interfaces.ByteSerializable;
import com.luminiasoft.bitshares.interfaces.JsonSerializable;
/**
* Created by nelson on 11/5/16.
*/
public abstract class BaseOperation implements ByteSerializable, JsonSerializable{
protected OperationType type;
public BaseOperation(OperationType type){
this.type = type;
}
public abstract byte getId();
public abstract byte[] toBytes();
}

View File

@ -0,0 +1,101 @@
package com.luminiasoft.bitshares;
import com.luminiasoft.bitshares.interfaces.ByteSerializable;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
/**
* This class encapsulates all block-related information needed in order to build a valid transaction.
*/
public class BlockData implements ByteSerializable {
private final int REF_BLOCK_NUM_BYTES = 2;
private final int REF_BLOCK_PREFIX_BYTES = 4;
private final int REF_BLOCK_EXPIRATION_BYTES = 4;
private int refBlockNum;
private long refBlockPrefix;
private long relativeExpiration;
/**
* Block data constructor
* @param ref_block_num: Least significant 16 bits from the reference block number.
* If "relative_expiration" is zero, this field must be zero as well.
* @param ref_block_prefix: The first non-block-number 32-bits of the reference block ID.
* Recall that block IDs have 32 bits of block number followed by the
* actual block hash, so this field should be set using the second 32 bits
* in the block_id_type
* @param relative_expiration: This field specifies the number of block intervals after the
* reference block until this transaction becomes invalid. If this field is
* set to zero, the "ref_block_prefix" is interpreted as an absolute timestamp
* of the time the transaction becomes invalid.
*/
public BlockData(int ref_block_num, long ref_block_prefix, long relative_expiration){
this.refBlockNum = ref_block_num;
this.refBlockPrefix = ref_block_prefix;
this.relativeExpiration = relative_expiration;
}
/**
* Block data constructor that takes in raw blockchain information.
* @param head_block_number: The last block number.
* @param head_block_id: The last block apiId.
* @param relative_expiration: The relative expiration
*/
public BlockData(long head_block_number, String head_block_id, long relative_expiration){
String hashData = head_block_id.substring(8, 16);
StringBuilder builder = new StringBuilder();
for(int i = 0; i < 8; i = i + 2){
builder.append(hashData.substring(6 - i, 8 - i));
}
this.refBlockNum = ((int) head_block_number ) & 0xFFFF;
this.refBlockPrefix = Long.parseLong(builder.toString(), 16);
this.relativeExpiration = relative_expiration;
}
public int getRefBlockNum() {
return refBlockNum;
}
public void setRefBlockNum(int refBlockNum) {
this.refBlockNum = refBlockNum;
}
public long getRefBlockPrefix() {
return refBlockPrefix;
}
public void setRefBlockPrefix(long refBlockPrefix) {
this.refBlockPrefix = refBlockPrefix;
}
public long getRelativeExpiration() {
return relativeExpiration;
}
public void setRelativeExpiration(long relativeExpiration) {
this.relativeExpiration = relativeExpiration;
}
@Override
public byte[] toBytes() {
// Allocating a fixed length byte array, since we will always need
// 2 bytes for the ref_block_num value
// 4 bytes for the ref_block_prefix value
// 4 bytes for the relative_expiration
byte[] result = new byte[REF_BLOCK_NUM_BYTES + REF_BLOCK_PREFIX_BYTES + REF_BLOCK_EXPIRATION_BYTES];
for(int i = 0; i < result.length; i++){
if(i < REF_BLOCK_NUM_BYTES){
result[i] = (byte) (this.refBlockNum >> 8 * i);
}else if(i >= REF_BLOCK_NUM_BYTES && i < REF_BLOCK_NUM_BYTES + REF_BLOCK_PREFIX_BYTES){
result[i] = (byte) (this.refBlockPrefix >> 8 * (i - REF_BLOCK_NUM_BYTES));
}else{
result[i] = (byte) (this.relativeExpiration >> 8 * (i - REF_BLOCK_NUM_BYTES + REF_BLOCK_PREFIX_BYTES));
}
}
return result;
}
}

View File

@ -0,0 +1,31 @@
package com.luminiasoft.bitshares;
import org.bitcoinj.core.ECKey;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* Created by nelson on 11/19/16.
*/
public class BrainKey {
private ECKey mPrivateKey;
public BrainKey(String words, int sequence){
String encoded = String.format("%s %d", words, sequence);
try {
MessageDigest md = MessageDigest.getInstance("SHA-512");
byte[] bytes = md.digest(encoded.getBytes("UTF-8"));
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] result = sha256.digest(bytes);
System.out.println("hash: "+Util.bytesToHex(result));
//TODO: Transform this final result into a ECKey private key (mPrivateKey)
} catch (NoSuchAlgorithmException e) {
System.out.println("NoSuchAlgotithmException. Msg: "+e.getMessage());
} catch (UnsupportedEncodingException e) {
System.out.println("UnsupportedEncodingException. Msg: "+e.getMessage());
}
}
}

View File

@ -0,0 +1,16 @@
package com.luminiasoft.bitshares;
/**
* Created by nelson on 11/8/16.
*/
public class Chains {
public static class BITSHARES {
public static final String CHAIN_ID = "4018d7844c78f6a6c41c6a552b898022310fc5dec06da467ee7905a8dad512c8";
}
public static class GRAPHENE {
public static final String CHAIN_ID = "b8d1603965b3eb1acba27e62ff59f74efa3154d43a4188d381088ac7cdf35539";
}
public static class TEST {
public static final String CHAIN_ID = "39f5e2ede1f8bc1a3a54a7914414e3779e33193f1f5693510e73cb7a87617447";
}
}

View File

@ -0,0 +1,8 @@
package com.luminiasoft.bitshares;
/**
* Created by nelson on 11/9/16.
*/
public class Extension {
//TODO: Give this class a proper implementation
}

View File

@ -0,0 +1,33 @@
package com.luminiasoft.bitshares;
/**
* <p>
* Generic class used to represent a graphene object as defined in
* <a href="http://docs.bitshares.org/development/blockchain/objects.html"></a>
* </p>
* Created by nelson on 11/8/16.
*/
public class GrapheneObject {
protected int space;
protected int type;
protected long instance;
public GrapheneObject(String id){
String[] parts = id.split("\\.");
if(parts.length == 3){
this.space = Integer.parseInt(parts[0]);
this.type = Integer.parseInt(parts[1]);
this.instance = Long.parseLong(parts[2]);
}
}
/**
*
* @return: A String containing the full object apiId in the form {space}.{type}.{instance}
*/
public String getObjectId(){
return String.format("%d.%d.%d", space, type, instance);
}
}

View File

@ -0,0 +1,64 @@
package com.luminiasoft.bitshares;
import org.bitcoinj.core.ECKey;
import java.io.IOException;
public class Main {
// Brain key from Nelson's app referencing the bilthon-83 account
public static final String BRAIN_KEY = "PUMPER ISOTOME SERE STAINER CLINGER MOONLIT CHAETA UPBRIM AEDILIC BERTHER NIT SHAP SAID SHADING JUNCOUS CHOUGH";
// WIF from Nelson's app referencing the bilthon-83 account
public static final String WIF = "5J96pne45qWM1WpektoeazN6k9Mt93jQ7LyueRxFfEMTiy6yxjM";
// WIF from the cli_wallet instance
// public static final String WIF = "5KMzB2GqGhnh7ufhgddmz1eKPHS72uTLeL9hHjSvPb1UywWknF5";
public static final String EXTERNAL_SIGNATURE = "1f36c41acb774fcbc9c231b5895ec9701d6872729098d8ea56d78dda72a6b54252694db85d7591de5751b7aea06871da15d63a1028758421607ffc143e53ef3306";
// 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.testTransactionSerialization();
// test.testLoginSerialization();
// test.testNetworkBroadcastSerialization();
// test.testNetworkBroadcastDeserialization();
// test.testGetDynamicParams();
// test.testGetRequiredFeesSerialization();
// test.testRequiredFeesResponse();
// test.testTransactionBroadcastSequence();
// test.testAccountLookupDeserialization();
// test.testPrivateKeyManipulations();
// test.testGetAccountByName();
// test.testGetRequiredFees();
// test.testRandomNumberGeneration();
test.testBrainKeyOperations();
}
}

View File

@ -0,0 +1,15 @@
package com.luminiasoft.bitshares;
import com.luminiasoft.bitshares.interfaces.ByteSerializable;
/**
* Created by nelson on 11/9/16.
*/
public class Memo implements ByteSerializable {
//TODO: Give this class a proper implementation
@Override
public byte[] toBytes() {
return new byte[1];
}
}

View File

@ -0,0 +1,52 @@
package com.luminiasoft.bitshares;
/**
* Created by nelson on 11/6/16.
*/
public enum OperationType {
transfer_operation,
limit_order_create_operation,
limit_order_cancel_operation,
call_order_update_operation,
fill_order_operation, // VIRTUAL
account_create_operation,
account_update_operation,
account_whitelist_operation,
account_upgrade_operation,
account_transfer_operation,
asset_create_operation,
asset_update_operation,
asset_update_bitasset_operation,
asset_update_feed_producers_operation,
asset_issue_operation,
asset_reserve_operation,
asset_fund_fee_pool_operation,
asset_settle_operation,
asset_global_settle_operation,
asset_publish_feed_operation,
witness_create_operation,
witness_update_operation,
proposal_create_operation,
proposal_update_operation,
proposal_delete_operation,
withdraw_permission_create_operation,
withdraw_permission_update_operation,
withdraw_permission_claim_operation,
withdraw_permission_delete_operation,
committee_member_create_operation,
committee_member_update_operation,
committee_member_update_global_parameters_operation,
vesting_balance_create_operation,
vesting_balance_withdraw_operation,
worker_create_operation,
custom_operation,
assert_operation,
balance_claim_operation,
override_transfer_operation,
transfer_to_blind_operation,
blind_transfer_operation,
transfer_from_blind_operation,
asset_settle_cancel_operation, // VIRTUAL
asset_claim_fees_operation,
fba_distribute_operation // VIRTUAL
}

View File

@ -0,0 +1,13 @@
package com.luminiasoft.bitshares;
/**
* Created by nelson on 11/16/16.
*/
public class RPC {
public static final String CALL_LOGIN = "login";
public static final String CALL_NETWORK_BROADCAST = "network_broadcast";
public static final String CALL_GET_ACCOUNT_BY_NAME = "get_account_by_name";
public static final String CALL_GET_DYNAMIC_GLOBAL_PROPERTIES = "get_dynamic_global_properties";
public static final String CALL_BROADCAST_TRANSACTION = "broadcast_transaction";
public static final String CALL_GET_REQUIRED_FEES = "get_required_fees";
}

View File

@ -0,0 +1,509 @@
package com.luminiasoft.bitshares;
import com.google.common.primitives.UnsignedLong;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.luminiasoft.bitshares.errors.MalformedTransactionException;
import com.luminiasoft.bitshares.interfaces.WitnessResponseListener;
import com.luminiasoft.bitshares.models.*;
import com.luminiasoft.bitshares.ws.GetAccountByName;
import com.luminiasoft.bitshares.ws.GetRequiredFees;
import com.luminiasoft.bitshares.ws.TransactionBroadcastSequence;
import com.neovisionaries.ws.client.*;
import org.bitcoinj.core.*;
import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.digests.SHA512Digest;
import org.spongycastle.crypto.prng.DigestRandomGenerator;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* Created by nelson on 11/9/16.
*/
public class Test {
public static final String WITNESS_URL = "ws://api.devling.xyz:8088";
private Transaction transaction;
public Transaction getTransaction() {
return transaction;
}
private WitnessResponseListener mListener = new WitnessResponseListener() {
@Override
public void onSuccess(WitnessResponse response) {
if(response.result.getClass() == AccountProperties.class){
AccountProperties accountProperties = (AccountProperties) response.result;
System.out.println("Got account properties");
System.out.println("id: "+accountProperties.id);
}else if(response.result.getClass() == ArrayList.class){
List l = (List) response.result;
if(l.size() > 0){
if(l.get(0).getClass() == AssetAmount.class){
AssetAmount assetAmount = (AssetAmount) l.get(0);
System.out.println("Got fee");
System.out.println("amount: "+assetAmount.getAmount()+", asset id: "+assetAmount.getAsset().getObjectId());
}
}else{
System.out.println("Got empty list!");
}
}else{
System.out.println("Got other: "+response.result.getClass());
}
}
@Override
public void onError(BaseResponse.Error error) {
System.out.println("onError. message: "+error.message);
}
};
public ECKey.ECDSASignature testSigning() {
byte[] serializedTransaction = this.transaction.toBytes();
Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction));
byte[] bytesDigest = hash.getBytes();
ECKey sk = transaction.getPrivateKey();
ECKey.ECDSASignature signature = sk.sign(hash);
return signature;
}
public String testSigningMessage() {
byte[] serializedTransaction = this.transaction.toBytes();
Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction));
ECKey sk = transaction.getPrivateKey();
return sk.signMessage(hash.toString());
}
public byte[] signMessage() {
byte[] serializedTransaction = this.transaction.toBytes();
Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction));
System.out.println(">> digest <<");
System.out.println(Util.bytesToHex(hash.getBytes()));
ECKey sk = transaction.getPrivateKey();
System.out.println("Private key bytes");
System.out.println(Util.bytesToHex(sk.getPrivKeyBytes()));
boolean isCanonical = false;
int recId = -1;
ECKey.ECDSASignature sig = null;
while (!isCanonical) {
sig = sk.sign(hash);
if (!sig.isCanonical()) {
System.out.println("Signature was not canonical, retrying");
continue;
} else {
System.out.println("Signature is canonical");
isCanonical = true;
}
// Now we have to work backwards to figure out the recId needed to recover the signature.
for (int i = 0; i < 4; i++) {
ECKey k = ECKey.recoverFromSignature(i, sig, hash, sk.isCompressed());
if (k != null && k.getPubKeyPoint().equals(sk.getPubKeyPoint())) {
recId = i;
break;
} else {
if (k == null) {
System.out.println("Recovered key was null");
}
if (k.getPubKeyPoint().equals(sk.getPubKeyPoint())) {
System.out.println("Recovered pub point is not equal to sk pub point");
}
}
}
if (recId == -1)
throw new RuntimeException("Could not construct a recoverable key. This should never happen.");
}
int headerByte = recId + 27 + (sk.isCompressed() ? 4 : 0);
byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 bytes for S
sigData[0] = (byte) headerByte;
System.arraycopy(Utils.bigIntegerToBytes(sig.r, 32), 0, sigData, 1, 32);
System.arraycopy(Utils.bigIntegerToBytes(sig.s, 32), 0, sigData, 33, 32);
System.out.println("recId: " + recId);
System.out.println("r: " + Util.bytesToHex(sig.r.toByteArray()));
System.out.println("s: " + Util.bytesToHex(sig.s.toByteArray()));
return sigData;
// return new String(Base64.encode(sigData), Charset.forName("UTF-8"));
}
public byte[] testTransactionSerialization(long head_block_number, String head_block_id, long relative_expiration) {
BlockData blockData = new BlockData(head_block_number, head_block_id, relative_expiration);
ArrayList<BaseOperation> operations = new ArrayList<BaseOperation>();
UserAccount from = new UserAccount("1.2.138632");
UserAccount to = new UserAccount("1.2.129848");
AssetAmount amount = new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120"));
AssetAmount fee = new AssetAmount(UnsignedLong.valueOf(264174), new Asset("1.3.0"));
operations.add(new Transfer(from, to, amount, fee));
this.transaction = new Transaction(Main.WIF, blockData, operations);
byte[] serializedTransaction = this.transaction.toBytes();
System.out.println("Serialized transaction");
System.out.println(Util.bytesToHex(serializedTransaction));
return serializedTransaction;
}
public void testWebSocketTransfer() throws IOException {
String login = "{\"id\":%d,\"method\":\"call\",\"params\":[1,\"login\",[\"\",\"\"]]}";
String getDatabaseId = "{\"method\": \"call\", \"params\": [1, \"database\", []], \"jsonrpc\": \"2.0\", \"id\": %d}";
String getHistoryId = "{\"method\": \"call\", \"params\": [1, \"history\", []], \"jsonrpc\": \"2.0\", \"id\": %d}";
String getNetworkBroadcastId = "{\"method\": \"call\", \"params\": [1, \"network_broadcast\", []], \"jsonrpc\": \"2.0\", \"id\": %d}";
String getDynamicParameters = "{\"method\": \"call\", \"params\": [0, \"get_dynamic_global_properties\", []], \"jsonrpc\": \"2.0\", \"id\": %d}";
String rawPayload = "{\"method\": \"call\", \"params\": [%d, \"broadcast_transaction\", [{\"expiration\": \"%s\", \"signatures\": [\"%s\"], \"operations\": [[0, {\"fee\": {\"amount\": 264174, \"asset_id\": \"1.3.0\"}, \"amount\": {\"amount\": 100, \"asset_id\": \"1.3.120\"}, \"to\": \"1.2.129848\", \"extensions\": [], \"from\": \"1.2.138632\"}]], \"ref_block_num\": %d, \"extensions\": [], \"ref_block_prefix\": %d}]], \"jsonrpc\": \"2.0\", \"id\": %d}";
// String url = "wss://bitshares.openledger.info/ws";
String url = "ws://api.devling.xyz:8088";
WebSocketFactory factory = new WebSocketFactory().setConnectionTimeout(5000);
// Create a WebSocket. The timeout value set above is used.
WebSocket ws = factory.createSocket(url);
ws.addListener(new WebSocketAdapter() {
private DynamicGlobalProperties dynProperties;
private int networkBroadcastApiId;
@Override
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
System.out.println("onConnected");
String payload = String.format(login, 1);
System.out.println(">>");
System.out.println(payload);
websocket.sendText(payload);
}
@Override
public void onDisconnected(WebSocket websocket, WebSocketFrame serverCloseFrame, WebSocketFrame clientCloseFrame, boolean closedByServer) throws Exception {
System.out.println("onDisconnected");
}
@Override
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
System.out.println("<<");
String response = frame.getPayloadText();
System.out.println(response);
Gson gson = new Gson();
BaseResponse baseResponse = gson.fromJson(response, BaseResponse.class);
// if(baseResponse.id.equals("1")){
// String payload = String.format(getDatabaseId, 2);
// System.out.println(">>");
// System.out.println(payload);
// websocket.sendText(payload);
// }else if(baseResponse.id.equals("2")){
// String payload = String.format(getHistoryId, 3);
// System.out.println(">>");
// System.out.println(payload);
// websocket.sendText(payload);
// }else if(baseResponse.id.equals("3")){
if (baseResponse.id == 1) {
String payload = String.format(getNetworkBroadcastId, 2);
System.out.println(">>");
System.out.println(payload);
websocket.sendText(payload);
// }else if(baseResponse.id.equals("4")){
}
if (baseResponse.id == 2) {
String payload = String.format(getDynamicParameters, 3);
Type ApiIdResponse = new TypeToken<WitnessResponse<Integer>>() {}.getType();
WitnessResponse<Integer> witnessResponse = gson.fromJson(response, ApiIdResponse);
networkBroadcastApiId = witnessResponse.result.intValue();
System.out.println(">>");
System.out.println(payload);
websocket.sendText(payload);
} else if (baseResponse.id == 3) {
// Got dynamic properties
Type DynamicGlobalPropertiesResponse = new TypeToken<WitnessResponse<DynamicGlobalProperties>>() {
}.getType();
WitnessResponse<DynamicGlobalProperties> witnessResponse = gson.fromJson(response, DynamicGlobalPropertiesResponse);
dynProperties = witnessResponse.result;
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
Date date = dateFormat.parse(dynProperties.time);
long expirationTime = (date.getTime() / 1000) + 30;
testTransactionSerialization(dynProperties.head_block_number, dynProperties.head_block_id, expirationTime);
BlockData blockData = new BlockData(dynProperties.head_block_number, dynProperties.head_block_id, expirationTime);
byte[] signatureBytes = signMessage();
String payload = String.format(
rawPayload,
networkBroadcastApiId,
dateFormat.format(new Date(expirationTime * 1000)),
Util.bytesToHex(signatureBytes),
blockData.getRefBlockNum(),
blockData.getRefBlockPrefix(),
4);
System.out.println(">>");
System.out.println(payload);
websocket.sendText(payload);
}
}
@Override
public void onError(WebSocket websocket, WebSocketException cause) throws Exception {
System.out.println("onError");
}
@Override
public void onUnexpectedError(WebSocket websocket, WebSocketException cause) throws Exception {
System.out.println("onUnexpectedError");
}
@Override
public void handleCallbackError(WebSocket websocket, Throwable cause) throws Exception {
System.out.println("handleCallbackError. Msg: " + cause.getMessage());
StackTraceElement[] stackTrace = cause.getStackTrace();
for (StackTraceElement line : stackTrace) {
System.out.println(line.toString());
}
}
});
try {
// Connect to the server and perform an opening handshake.
// This method blocks until the opening handshake is finished.
ws.connect();
} catch (OpeningHandshakeException e) {
// A violation against the WebSocket protocol was detected
// during the opening handshake.
System.out.println("OpeningHandshakeException");
} catch (WebSocketException e) {
// Failed to establish a WebSocket connection.
System.out.println("WebSocketException. Msg: " + e.getMessage());
}
}
public void testCustomSerializer() {
AssetAmount amount = new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120"));
String jsonAmount = amount.toJsonString();
System.out.println("JSON amount");
System.out.println(jsonAmount);
}
public void testTransactionSerialization() {
try {
Transaction transaction = new TransferTransactionBuilder()
.setSource(new UserAccount("1.2.138632"))
.setDestination(new UserAccount("1.2.129848"))
.setAmount(new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120")))
.setFee(new AssetAmount(UnsignedLong.valueOf(264174), new Asset("1.3.0")))
.setBlockData(new BlockData(43408, 1430521623, 1479231969))
.setPrivateKey(DumpedPrivateKey.fromBase58(null, Main.WIF).getKey())
.build();
ArrayList<Serializable> transactionList = new ArrayList<>();
transactionList.add(transaction);
ApiCall call = new ApiCall(4, "call", "broadcast_transaction", transactionList, "2.0", 1);
String jsonCall = call.toJsonString();
System.out.println("json call");
System.out.println(jsonCall);
} catch (MalformedTransactionException e) {
System.out.println("MalformedTransactionException. Msg: " + e.getMessage());
}
}
public void testLoginSerialization() {
ArrayList<Serializable> loginParams = new ArrayList<>();
// loginParams.add("nelson");
// loginParams.add("supersecret");
loginParams.add(null);
loginParams.add(null);
ApiCall loginCall = new ApiCall(1, "login", loginParams, "2.0", 1);
String jsonLoginCall = loginCall.toJsonString();
System.out.println("login call");
System.out.println(jsonLoginCall);
}
public void testNetworkBroadcastSerialization() {
ArrayList<Serializable> params = new ArrayList<>();
ApiCall networkParamsCall = new ApiCall(3, "network_broadcast", params, "2.0", 1);
String call = networkParamsCall.toJsonString();
System.out.println("network broadcast");
System.out.println(call);
}
public void testNetworkBroadcastDeserialization(){
String response = "{\"id\":2,\"result\":2}";
Gson gson = new Gson();
Type ApiIdResponse = new TypeToken<WitnessResponse<Integer>>() {}.getType();
WitnessResponse<Integer> witnessResponse = gson.fromJson(response, ApiIdResponse);
}
public void testGetDynamicParams() {
ArrayList<Serializable> emptyParams = new ArrayList<>();
ApiCall getDynamicParametersCall = new ApiCall(0, "get_dynamic_global_properties", emptyParams, "2.0", 0);
System.out.println(getDynamicParametersCall.toJsonString());
}
public void testRequiredFeesResponse() {
String response = "{\"id\":1,\"result\":[{\"amount\":264174,\"asset_id\":\"1.3.0\"}]}";
Type AccountLookupResponse = new TypeToken<WitnessResponse<List<AssetAmount>>>() {}.getType();
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetDeserializer());
WitnessResponse<List<AssetAmount>> witnessResponse = gsonBuilder.create().fromJson(response, AccountLookupResponse);
for(AssetAmount assetAmount : witnessResponse.result){
System.out.println("asset : "+assetAmount.toJsonString());
}
}
public void testTransactionBroadcastSequence(){
String url = "ws://api.devling.xyz:8088";
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");
System.out.println(error.data.message);
}
};
try{
Transaction transaction = new TransferTransactionBuilder()
.setSource(new UserAccount("1.2.138632"))
.setDestination(new UserAccount("1.2.129848"))
.setAmount(new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120")))
.setFee(new AssetAmount(UnsignedLong.valueOf(264174), new Asset("1.3.0")))
.setBlockData(new BlockData(43408, 1430521623, 1479231969))
.setPrivateKey(DumpedPrivateKey.fromBase58(null, Main.WIF).getKey())
.build();
ArrayList<Serializable> transactionList = new ArrayList<>();
transactionList.add(transaction);
ApiCall call = new ApiCall(4, "call", "broadcast_transaction", transactionList, "2.0", 1);
WebSocketFactory factory = new WebSocketFactory().setConnectionTimeout(5000);
try {
WebSocket mWebSocket = factory.createSocket(url);
mWebSocket.addListener(new TransactionBroadcastSequence(transaction, listener));
mWebSocket.connect();
} catch (IOException e) {
System.out.println("IOException. Msg: "+e.getMessage());
} catch (WebSocketException e) {
System.out.println("WebSocketException. Msg: "+e.getMessage());
}
}catch(MalformedTransactionException e){
System.out.println("MalformedTransactionException. Msg: "+e.getMessage());
}
}
public void testAccountLookupDeserialization(){
String response = "{\"id\":1,\"result\":[[\"ken\",\"1.2.3111\"],[\"ken-1\",\"1.2.101491\"],[\"ken-k\",\"1.2.108646\"]]}";
Type AccountLookupResponse = new TypeToken<WitnessResponse<List<List<String>>>>() {}.getType();
Gson gson = new Gson();
WitnessResponse<List<List<String>>> witnessResponse = gson.fromJson(response, AccountLookupResponse);
for(int i = 0; i < witnessResponse.result.size(); i++){
System.out.println("suggested name: "+witnessResponse.result.get(i).get(0));
}
}
public void testPrivateKeyManipulations(){
ECKey privateKey = DumpedPrivateKey.fromBase58(null, Main.WIF).getKey();
System.out.println("private key..............: "+Util.bytesToHex(privateKey.getSecretBytes()));
System.out.println("public key uncompressed..: "+Util.bytesToHex(privateKey.getPubKey()));
System.out.println("public key compressed....: "+Util.bytesToHex(privateKey.getPubKeyPoint().getEncoded(true)));
System.out.println("base58...................: "+Base58.encode(privateKey.getPubKeyPoint().getEncoded(true)));
System.out.println("base58...................: "+Base58.encode(privateKey.getPubKey()));
String brainKeyWords = "PUMPER ISOTOME SERE STAINER CLINGER MOONLIT CHAETA UPBRIM AEDILIC BERTHER NIT SHAP SAID SHADING JUNCOUS CHOUGH";
BrainKey brainKey = new BrainKey(brainKeyWords, 0);
}
public void testGetAccountByName(){
try {
WebSocketFactory factory = new WebSocketFactory().setConnectionTimeout(5000);
WebSocket mWebSocket = factory.createSocket(WITNESS_URL);
mWebSocket.addListener(new GetAccountByName("bilthon-83", mListener));
mWebSocket.connect();
} catch (IOException e) {
System.out.println("IOException. Msg: "+e.getMessage());
} catch (WebSocketException e) {
System.out.println("WebSocketException. Msg: "+e.getMessage());
}
}
public void testGetRequiredFees() {
ArrayList<Serializable> accountParams = new ArrayList<>();
Asset asset = new Asset("1.3.0");
UserAccount from = new UserAccount("1.2.138632");
UserAccount to = new UserAccount("1.2.129848");
AssetAmount amount = new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120"));
AssetAmount fee = new AssetAmount(UnsignedLong.valueOf(264174), new Asset("1.3.0"));
Transfer transfer = new Transfer(from, to, amount, fee);
ArrayList<BaseOperation> operations = new ArrayList<>();
operations.add(transfer);
accountParams.add(operations);
accountParams.add(asset.getObjectId());
try {
WebSocketFactory factory = new WebSocketFactory().setConnectionTimeout(5000);
WebSocket mWebSocket = factory.createSocket(WITNESS_URL);
mWebSocket.addListener(new GetRequiredFees(operations, asset, mListener));
mWebSocket.connect();
} catch (IOException e) {
System.out.println("IOException. Msg: "+e.getMessage());
} catch (WebSocketException e) {
System.out.println("WebSocketException. Msg: "+e.getMessage());
}
}
public void testRandomNumberGeneration(){
byte[] seed = new byte[] { new Long(System.nanoTime()).byteValue() };
doCountTest(new SHA512Digest(), seed);
}
private void doCountTest(Digest digest, byte[] seed)//, byte[] expectedXors)
{
DigestRandomGenerator generator = new DigestRandomGenerator(digest);
byte[] output = new byte[digest.getDigestSize()];
int[] averages = new int[digest.getDigestSize()];
byte[] ands = new byte[digest.getDigestSize()];
byte[] xors = new byte[digest.getDigestSize()];
byte[] ors = new byte[digest.getDigestSize()];
generator.addSeedMaterial(seed);
for (int i = 0; i != 1000000; i++)
{
generator.nextBytes(output);
for (int j = 0; j != output.length; j++)
{
averages[j] += output[j] & 0xff;
ands[j] &= output[j];
xors[j] ^= output[j];
ors[j] |= output[j];
}
}
for (int i = 0; i != output.length; i++) {
if ((averages[i] / 1000000) != 127) {
System.out.println("average test failed for " + digest.getAlgorithmName());
}
System.out.println("averages["+i+"] / 1000000: "+averages[i] / 1000000);
if (ands[i] != 0) {
System.out.println("and test failed for " + digest.getAlgorithmName());
}
if ((ors[i] & 0xff) != 0xff) {
System.out.println("or test failed for " + digest.getAlgorithmName());
}
// if (xors[i] != expectedXors[i]) {
// System.out.println("xor test failed for " + digest.getAlgorithmName());
// }
}
}
/**
* The final purpose of this test is to convert the plain brainkey at Main.BRAIN_KEY
* into the WIF at Main.WIF
*/
public void testBrainKeyOperations(){
BrainKey brainKey = new BrainKey(Main.BRAIN_KEY, 0);
}
}

View File

@ -0,0 +1,197 @@
package com.luminiasoft.bitshares;
import com.google.common.primitives.Bytes;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.luminiasoft.bitshares.interfaces.ByteSerializable;
import com.luminiasoft.bitshares.interfaces.JsonSerializable;
import org.bitcoinj.core.DumpedPrivateKey;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Utils;
import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
/**
* Class used to represent a generic graphene transaction.
*/
public class Transaction implements ByteSerializable, JsonSerializable {
private final String TAG = this.getClass().getName();
public static final String KEY_EXPIRATION = "expiration";
public static final String KEY_SIGNATURES = "signatures";
public static final String KEY_OPERATIONS = "operations";
public static final String KEY_EXTENSIONS = "extensions";
public static final String KEY_REF_BLOCK_NUM = "ref_block_num";
public static final String KEY_REF_BLOCK_PREFIX = "ref_block_prefix";
private ECKey privateKey;
private BlockData blockData;
private List<BaseOperation> operations;
private List<Extension> extensions;
/**
* Transaction constructor.
* @param wif: The user's private key in the base58 format.
* @param block_data: Block data containing important information used to sign a transaction.
* @param operation_list: List of operations to include in the transaction.
*/
public Transaction(String wif, BlockData block_data, List<BaseOperation> operation_list){
this.privateKey = DumpedPrivateKey.fromBase58(null, wif).getKey();
this.blockData = block_data;
this.operations = operation_list;
this.extensions = new ArrayList<Extension>();
}
/**
* Transaction constructor.
* @param privateKey : Instance of a ECKey containing the private key that will be used to sign this transaction.
* @param blockData : Block data containing important information used to sign a transaction.
* @param operationList : List of operations to include in the transaction.
*/
public Transaction(ECKey privateKey, BlockData blockData, List<BaseOperation> operationList){
this.privateKey = privateKey;
this.blockData = blockData;
this.operations = operationList;
this.extensions = new ArrayList<Extension>();
}
public ECKey getPrivateKey(){
return this.privateKey;
}
public List<BaseOperation> getOperations(){ return this.operations; }
/**
* Obtains a signature of this transaction.
* @return: A valid signature of the current transaction.
*/
public byte[] getSignature(){
byte[] serializedTransaction = this.toBytes();
Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction));
boolean isCanonical = false;
int recId = -1;
ECKey.ECDSASignature sig = null;
while(!isCanonical) {
sig = privateKey.sign(hash);
if(!sig.isCanonical()){
// Signature was not canonical, retrying
continue;
}else{
// Signature is canonical
isCanonical = true;
}
// Now we have to work backwards to figure out the recId needed to recover the signature.
for (int i = 0; i < 4; i++) {
ECKey k = ECKey.recoverFromSignature(i, sig, hash, privateKey.isCompressed());
if (k != null && k.getPubKeyPoint().equals(privateKey.getPubKeyPoint())) {
recId = i;
break;
}
}
}
int headerByte = recId + 27 + (privateKey.isCompressed() ? 4 : 0);
byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 bytes for S
sigData[0] = (byte)headerByte;
System.arraycopy(Utils.bigIntegerToBytes(sig.r, 32), 0, sigData, 1, 32);
System.arraycopy(Utils.bigIntegerToBytes(sig.s, 32), 0, sigData, 33, 32);
return sigData;
}
/**
* Method that creates a serialized byte array with compact information about this transaction
* that is needed for the creation of a signature.
* @return: byte array with serialized information about this transaction.
*/
public byte[] toBytes(){
// Creating a List of Bytes and adding the first bytes from the chain apiId
List<Byte> byteArray = new ArrayList<Byte>();
byteArray.addAll(Bytes.asList(Util.hexToBytes(Chains.BITSHARES.CHAIN_ID)));
// Adding the block data
byteArray.addAll(Bytes.asList(this.blockData.toBytes()));
// Adding the number of operations
byteArray.add((byte) this.operations.size());
// Adding all the operations
for(BaseOperation operation : operations){
byteArray.add(operation.getId());
byteArray.addAll(Bytes.asList(operation.toBytes()));
}
//Adding the number of extensions
byteArray.add((byte) this.extensions.size());
for(Extension extension : extensions){
//TODO: Implement the extension serialization
}
// Adding a last zero byte to match the result obtained by the python-graphenelib code
// I'm not exactly sure what's the meaning of this last zero byte, but for now I'll just
// leave it here and work on signing the transaction.
//TODO: Investigate the origin and meaning of this last byte.
byteArray.add((byte) 0 );
return Bytes.toArray(byteArray);
}
@Override
public String toJsonString() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Transaction.class, new TransactionSerializer());
return gsonBuilder.create().toJson(this);
}
@Override
public JsonObject toJsonObject() {
JsonObject obj = new JsonObject();
// Formatting expiration time
Date expirationTime = new Date(blockData.getRelativeExpiration() * 1000);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
// Adding expiration
obj.addProperty(KEY_EXPIRATION, dateFormat.format(expirationTime));
// Adding signatures
JsonArray signatureArray = new JsonArray();
signatureArray.add(Util.bytesToHex(getSignature()));
obj.add(KEY_SIGNATURES, signatureArray);
JsonArray operationsArray = new JsonArray();
for(BaseOperation operation : operations){
operationsArray.add(operation.toJsonObject());
}
// Adding operations
obj.add(KEY_OPERATIONS, operationsArray);
// Adding extensions
obj.add(KEY_EXTENSIONS, new JsonArray());
// Adding block data
obj.addProperty(KEY_REF_BLOCK_NUM, blockData.getRefBlockNum());
obj.addProperty(KEY_REF_BLOCK_PREFIX, blockData.getRefBlockPrefix());
return obj;
}
class TransactionSerializer implements JsonSerializer<Transaction> {
@Override
public JsonElement serialize(Transaction transaction, Type type, JsonSerializationContext jsonSerializationContext) {
return transaction.toJsonObject();
}
}
}

View File

@ -0,0 +1,15 @@
package com.luminiasoft.bitshares;
import com.luminiasoft.bitshares.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 abstract Transaction build() throws MalformedTransactionException;
}

View File

@ -0,0 +1,113 @@
package com.luminiasoft.bitshares;
import com.google.common.primitives.Bytes;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.lang.reflect.Type;
/**
* Class used to encapsulate the Transfer operation related functionalities.
*/
public class Transfer extends BaseOperation {
public static final String KEY_FEE = "fee";
public static final String KEY_AMOUNT = "amount";
public static final String KEY_EXTENSIONS = "extensions";
public static final String KEY_FROM = "from";
public static final String KEY_TO = "to";
private AssetAmount fee;
private AssetAmount amount;
private UserAccount from;
private UserAccount to;
private Memo memo;
private String[] extensions;
public Transfer(UserAccount from, UserAccount to, AssetAmount transferAmount, AssetAmount fee){
super(OperationType.transfer_operation);
this.from = from;
this.to = to;
this.amount = transferAmount;
this.fee = fee;
this.memo = new Memo();
}
public Transfer(UserAccount from, UserAccount to, AssetAmount transferAmount){
super(OperationType.transfer_operation);
this.from = from;
this.to = to;
this.amount = transferAmount;
this.memo = new Memo();
}
public void setFee(AssetAmount newFee){
this.fee = newFee;
}
@Override
public byte getId() {
return (byte) this.type.ordinal();
}
public UserAccount getFrom(){
return this.from;
}
public UserAccount getTo(){
return this.to;
}
public AssetAmount getAmount(){
return this.amount;
}
public AssetAmount getFee(){
return this.fee;
}
@Override
public byte[] toBytes() {
byte[] feeBytes = fee.toBytes();
byte[] fromBytes = from.toBytes();
byte[] toBytes = to.toBytes();
byte[] amountBytes = amount.toBytes();
byte[] memoBytes = memo.toBytes();
return Bytes.concat(feeBytes, fromBytes, toBytes, amountBytes, memoBytes);
}
@Override
public String toJsonString() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Transfer.class, new TransferSerializer());
return gsonBuilder.create().toJson(this);
}
@Override
public JsonElement toJsonObject() {
JsonArray array = new JsonArray();
array.add(this.getId());
JsonObject jsonObject = new JsonObject();
jsonObject.add(KEY_FEE, fee.toJsonObject());
jsonObject.add(KEY_AMOUNT, amount.toJsonObject());
jsonObject.add(KEY_EXTENSIONS, new JsonArray());
jsonObject.addProperty(KEY_FROM, from.toJsonString());
jsonObject.addProperty(KEY_TO, to.toJsonString());
array.add(jsonObject);
return array;
}
class TransferSerializer implements JsonSerializer<Transfer> {
@Override
public JsonElement serialize(Transfer transfer, Type type, JsonSerializationContext jsonSerializationContext) {
JsonArray arrayRep = new JsonArray();
arrayRep.add(transfer.getId());
arrayRep.add(toJsonObject());
return arrayRep;
}
}
}

View File

@ -0,0 +1,86 @@
package com.luminiasoft.bitshares;
import com.luminiasoft.bitshares.errors.MalformedTransactionException;
import org.bitcoinj.core.ECKey;
import java.util.ArrayList;
import java.util.List;
/**
* Class used to build a transaction containing a transfer operation.
*/
public class TransferTransactionBuilder extends TransactionBuilder {
private List<BaseOperation> operations;
private UserAccount sourceAccount;
private UserAccount destinationAccount;
private AssetAmount transferAmount;
private AssetAmount feeAmount;
public TransferTransactionBuilder setPrivateKey(ECKey key){
this.privateKey = key;
return this;
}
public TransferTransactionBuilder setBlockData(BlockData blockData){
this.blockData = blockData;
return this;
}
public TransferTransactionBuilder setSource(UserAccount source){
this.sourceAccount = source;
return this;
}
public TransferTransactionBuilder setDestination(UserAccount destination){
this.destinationAccount = destination;
return this;
}
public TransferTransactionBuilder setAmount(AssetAmount amount){
this.transferAmount = amount;
return this;
}
public TransferTransactionBuilder setFee(AssetAmount amount){
this.feeAmount = amount;
return this;
}
public TransferTransactionBuilder addOperation(Transfer transferOperation){
if(operations == null){
operations = new ArrayList<BaseOperation>();
}
return this;
}
@Override
public Transaction build() throws MalformedTransactionException {
if(privateKey == null){
throw new MalformedTransactionException("Missing private key information");
}else if(blockData == null){
throw new MalformedTransactionException("Missing block data information");
}else if(operations == null){
// 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
// source, destination and transfer amount data.
operations = new ArrayList<>();
if(sourceAccount == null){
throw new MalformedTransactionException("Missing source account information");
}
if(destinationAccount == null){
throw new MalformedTransactionException("Missing destination account information");
}
if(transferAmount == null){
throw new MalformedTransactionException("Missing transfer amount information");
}
Transfer transferOperation;
if(feeAmount == null){
transferOperation = new Transfer(sourceAccount, destinationAccount, transferAmount);
}else{
transferOperation = new Transfer(sourceAccount, destinationAccount, transferAmount, feeAmount);
}
operations.add(transferOperation);
}
return new Transaction(privateKey, blockData, operations);
}
}

View File

@ -0,0 +1,48 @@
package com.luminiasoft.bitshares;
import com.google.gson.JsonObject;
import com.luminiasoft.bitshares.interfaces.ByteSerializable;
import com.luminiasoft.bitshares.interfaces.JsonSerializable;
import java.io.ByteArrayOutputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
/**
* Class tha represents a graphene user account.
* Created by nelson on 11/8/16.
*/
public class UserAccount extends GrapheneObject implements ByteSerializable, JsonSerializable {
/**
* Constructor that expects a user account in the string representation.
* That is in the 1.2.x format.
* @param id: The string representing the account apiId.
*/
public UserAccount(String id) {
super(id);
}
@Override
public byte[] toBytes() {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
DataOutput out = new DataOutputStream(byteArrayOutputStream);
try {
Varint.writeUnsignedVarLong(this.instance, out);
} catch (IOException e) {
e.printStackTrace();
}
return byteArrayOutputStream.toByteArray();
}
@Override
public String toJsonString() {
return this.getObjectId();
}
@Override
public JsonObject toJsonObject() {
return null;
}
}

View File

@ -0,0 +1,28 @@
package com.luminiasoft.bitshares;
/**
* Created by nelson on 11/8/16.
*/
public class Util {
final private static char[] hexArray = "0123456789abcdef".toCharArray();
public static byte[] hexToBytes(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j < bytes.length; j++ ) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
}

View File

@ -0,0 +1,224 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package com.luminiasoft.bitshares;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**
* <p>Encodes signed and unsigned values using a common variable-length
* scheme, found for example in
* <a href="http://code.google.com/apis/protocolbuffers/docs/encoding.html">
* Google's Protocol Buffers</a>. It uses fewer bytes to encode smaller values,
* but will use slightly more bytes to encode large values.</p>
* <p/>
* <p>Signed values are further encoded using so-called zig-zag encoding
* in order to make them "compatible" with variable-length encoding.</p>
*/
public final class Varint {
private Varint() {
}
/**
* Encodes a value using the variable-length encoding from
* <a href="http://code.google.com/apis/protocolbuffers/docs/encoding.html">
* Google Protocol Buffers</a>. It uses zig-zag encoding to efficiently
* encode signed values. If values are known to be nonnegative,
* {@link #writeUnsignedVarLong(long, DataOutput)} should be used.
*
* @param value value to encode
* @param out to write bytes to
* @throws IOException if {@link DataOutput} throws {@link IOException}
*/
public static void writeSignedVarLong(long value, DataOutput out) throws IOException {
// Great trick from http://code.google.com/apis/protocolbuffers/docs/encoding.html#types
writeUnsignedVarLong((value << 1) ^ (value >> 63), out);
}
/**
* Encodes a value using the variable-length encoding from
* <a href="http://code.google.com/apis/protocolbuffers/docs/encoding.html">
* Google Protocol Buffers</a>. Zig-zag is not used, so input must not be negative.
* If values can be negative, use {@link #writeSignedVarLong(long, DataOutput)}
* instead. This method treats negative input as like a large unsigned value.
*
* @param value value to encode
* @param out to write bytes to
* @throws IOException if {@link DataOutput} throws {@link IOException}
*/
public static void writeUnsignedVarLong(long value, DataOutput out) throws IOException {
while ((value & 0xFFFFFFFFFFFFFF80L) != 0L) {
out.writeByte(((int) value & 0x7F) | 0x80);
value >>>= 7;
}
out.writeByte((int) value & 0x7F);
}
/**
* @see #writeSignedVarLong(long, DataOutput)
*/
public static void writeSignedVarInt(int value, DataOutput out) throws IOException {
// Great trick from http://code.google.com/apis/protocolbuffers/docs/encoding.html#types
writeUnsignedVarInt((value << 1) ^ (value >> 31), out);
}
/**
* @see #writeUnsignedVarLong(long, DataOutput)
*/
public static void writeUnsignedVarInt(int value, DataOutput out) throws IOException {
while ((value & 0xFFFFFF80) != 0L) {
out.writeByte((value & 0x7F) | 0x80);
value >>>= 7;
}
out.writeByte(value & 0x7F);
}
public static byte[] writeSignedVarInt(int value) {
// Great trick from http://code.google.com/apis/protocolbuffers/docs/encoding.html#types
return writeUnsignedVarInt((value << 1) ^ (value >> 31));
}
/**
* @see #writeUnsignedVarLong(long, DataOutput)
* <p/>
* This one does not use streams and is much faster.
* Makes a single object each time, and that object is a primitive array.
*/
public static byte[] writeUnsignedVarInt(int value) {
byte[] byteArrayList = new byte[10];
int i = 0;
while ((value & 0xFFFFFF80) != 0L) {
byteArrayList[i++] = ((byte) ((value & 0x7F) | 0x80));
value >>>= 7;
}
byteArrayList[i] = ((byte) (value & 0x7F));
byte[] out = new byte[i + 1];
for (; i >= 0; i--) {
out[i] = byteArrayList[i];
}
return out;
}
/**
* @param in to read bytes from
* @return decode value
* @throws IOException if {@link DataInput} throws {@link IOException}
* @throws IllegalArgumentException if variable-length value does not terminate
* after 9 bytes have been read
* @see #writeSignedVarLong(long, DataOutput)
*/
public static long readSignedVarLong(DataInput in) throws IOException {
long raw = readUnsignedVarLong(in);
// This undoes the trick in writeSignedVarLong()
long temp = (((raw << 63) >> 63) ^ raw) >> 1;
// This extra step lets us deal with the largest signed values by treating
// negative results from read unsigned methods as like unsigned values
// Must re-flip the top bit if the original read value had it set.
return temp ^ (raw & (1L << 63));
}
/**
* @param in to read bytes from
* @return decode value
* @throws IOException if {@link DataInput} throws {@link IOException}
* @throws IllegalArgumentException if variable-length value does not terminate
* after 9 bytes have been read
* @see #writeUnsignedVarLong(long, DataOutput)
*/
public static long readUnsignedVarLong(DataInput in) throws IOException {
long value = 0L;
int i = 0;
long b;
while (((b = in.readByte()) & 0x80L) != 0) {
value |= (b & 0x7F) << i;
i += 7;
if (i > 63) {
throw new IllegalArgumentException("Variable length quantity is too long");
}
}
return value | (b << i);
}
/**
* @throws IllegalArgumentException if variable-length value does not terminate
* after 5 bytes have been read
* @throws IOException if {@link DataInput} throws {@link IOException}
* @see #readSignedVarLong(DataInput)
*/
public static int readSignedVarInt(DataInput in) throws IOException {
int raw = readUnsignedVarInt(in);
// This undoes the trick in writeSignedVarInt()
int temp = (((raw << 31) >> 31) ^ raw) >> 1;
// This extra step lets us deal with the largest signed values by treating
// negative results from read unsigned methods as like unsigned values.
// Must re-flip the top bit if the original read value had it set.
return temp ^ (raw & (1 << 31));
}
/**
* @throws IllegalArgumentException if variable-length value does not terminate
* after 5 bytes have been read
* @throws IOException if {@link DataInput} throws {@link IOException}
* @see #readUnsignedVarLong(DataInput)
*/
public static int readUnsignedVarInt(DataInput in) throws IOException {
int value = 0;
int i = 0;
int b;
while (((b = in.readByte()) & 0x80) != 0) {
value |= (b & 0x7F) << i;
i += 7;
if (i > 35) {
throw new IllegalArgumentException("Variable length quantity is too long");
}
}
return value | (b << i);
}
public static int readSignedVarInt(byte[] bytes) {
int raw = readUnsignedVarInt(bytes);
// This undoes the trick in writeSignedVarInt()
int temp = (((raw << 31) >> 31) ^ raw) >> 1;
// This extra step lets us deal with the largest signed values by treating
// negative results from read unsigned methods as like unsigned values.
// Must re-flip the top bit if the original read value had it set.
return temp ^ (raw & (1 << 31));
}
public static int readUnsignedVarInt(byte[] bytes) {
int value = 0;
int i = 0;
byte rb = Byte.MIN_VALUE;
for (byte b : bytes) {
rb = b;
if ((b & 0x80) == 0) {
break;
}
value |= (b & 0x7f) << i;
i += 7;
if (i > 35) {
throw new IllegalArgumentException("Variable length quantity is too long");
}
}
return value | (rb << i);
}
}

View File

@ -0,0 +1,11 @@
package com.luminiasoft.bitshares.errors;
/**
* Created by nelson on 11/14/16.
*/
public class MalformedTransactionException extends Exception {
public MalformedTransactionException(String message){
super(message);
}
}

View File

@ -0,0 +1,9 @@
package com.luminiasoft.bitshares.interfaces;
/**
* Interface implemented by all entities for which makes sense to have a byte-array representation.
*/
public interface ByteSerializable {
byte[] toBytes();
}

View File

@ -0,0 +1,15 @@
package com.luminiasoft.bitshares.interfaces;
import com.google.gson.JsonElement;
import java.io.Serializable;
/**
* Interface to be implemented by any entity for which makes sense to have a JSON-formatted string representation.
*/
public interface JsonSerializable extends Serializable {
String toJsonString();
JsonElement toJsonObject();
}

View File

@ -0,0 +1,14 @@
package com.luminiasoft.bitshares.interfaces;
import com.luminiasoft.bitshares.models.BaseResponse;
import com.luminiasoft.bitshares.models.WitnessResponse;
/**
* Class used to represent any listener to network requests.
*/
public interface WitnessResponseListener {
void onSuccess(WitnessResponse response);
void onError(BaseResponse.Error error);
}

View File

@ -0,0 +1,43 @@
package com.luminiasoft.bitshares.models;
/**
* Created by nelson on 11/15/16.
*/
public class AccountProperties {
public String id;
public String membership_expiration_date;
public String registrar;
public String referrer;
public String lifetime_referrer;
public long network_fee_percentage;
public long lifetime_referrer_fee_percentage;
public long referrer_rewards_percentage;
public String name;
public User owner;
public User active;
public Options options;
public String statistics;
public String[] whitelisting_accounts;
public String[] blacklisting_accounts;
public String[] whitelisted_accounts;
public String[] blacklisted_accounts;
public Object[] owner_special_authority;
public Object[] active_special_authority;
public long top_n_control_flags;
class User {
public long weight_threshold;
public String[] account_auths; //TODO: Check this type
public String[][] key_auths; //TODO: Check how to deserialize this
public String[] address_auths;
}
class Options {
public String memo_key;
public String voting_account;
public long num_witness;
public long num_committee;
public String[] votes; //TODO: Check this type
public String[] extensions; //TODO: Check this type
}
}

View File

@ -0,0 +1,99 @@
package com.luminiasoft.bitshares.models;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.luminiasoft.bitshares.interfaces.JsonSerializable;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
/**
* Class used to build a Graphene websocket API call.
* @see <a href="http://docs.bitshares.org/api/websocket.html">Websocket Calls & Notifications</a>
*/
public class ApiCall implements JsonSerializable {
public static final String KEY_SEQUENCE_ID = "id";
public static final String KEY_METHOD = "method";
public static final String KEY_PARAMS = "params";
public static final String KEY_JSON_RPC = "jsonrpc";
public String method;
public String methodToCall;
public String jsonrpc;
public List<Serializable> params;
public int apiId;
public int sequenceId;
public ApiCall(int apiId, String methodToCall, List<Serializable> params, String jsonrpc, int sequenceId){
this.apiId = apiId;
this.method = "call";
this.methodToCall = methodToCall;
this.jsonrpc = jsonrpc;
this.params = params;
this.sequenceId = sequenceId;
}
public ApiCall(int apiId, String method, String methodToCall, List<Serializable> params, String jsonrpc, int sequenceId){
this.apiId = apiId;
this.method = method;
this.methodToCall = methodToCall;
this.jsonrpc = jsonrpc;
this.params = params;
this.sequenceId = sequenceId;
}
@Override
public String toJsonString() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(ApiCall.class, new ApiCallSerializer());
return gsonBuilder.create().toJson(this);
}
@Override
public JsonElement toJsonObject() {
JsonObject obj = new JsonObject();
obj.addProperty(KEY_SEQUENCE_ID, this.sequenceId);
obj.addProperty(KEY_METHOD, this.method);
JsonArray paramsArray = new JsonArray();
paramsArray.add(this.apiId);
paramsArray.add(this.methodToCall);
JsonArray methodParams = new JsonArray();
for(int i = 0; i < this.params.size(); i++){
if(this.params.get(i) instanceof JsonSerializable){
// Sometimes the parameters are objects
methodParams.add(((JsonSerializable) this.params.get(i)).toJsonObject());
}else if(this.params.get(i) instanceof String || this.params.get(i) == null){
// Other times they are plain strings
methodParams.add((String) this.params.get(i));
}else if(this.params.get(i) instanceof ArrayList){
// Other times it might be an array
JsonArray array = new JsonArray();
ArrayList<JsonSerializable> listArgument = (ArrayList<JsonSerializable>) this.params.get(i);
for(int l = 0; l < listArgument.size(); l++){
JsonSerializable s = listArgument.get(l);
array.add(s.toJsonObject());
}
methodParams.add(array);
}
}
paramsArray.add(methodParams);
obj.add(KEY_PARAMS, paramsArray);
obj.addProperty(KEY_JSON_RPC, this.jsonrpc);
return obj;
}
class ApiCallSerializer implements JsonSerializer<ApiCall> {
@Override
public JsonElement serialize(ApiCall apiCall, Type type, JsonSerializationContext jsonSerializationContext) {
return toJsonObject();
}
}
}

View File

@ -0,0 +1,29 @@
package com.luminiasoft.bitshares.models;
/**
* Created by nelson on 11/12/16.
*/
public class BaseResponse {
public int id;
public Error error;
public static class Error {
public ErrorData data;
public int code;
public String message;
public Error(String message){
this.message = message;
}
}
public static class ErrorData {
public int code;
public String name;
public String message;
//TODO: Include stack data
public ErrorData(String message){
this.message = message;
}
}
}

View File

@ -0,0 +1,22 @@
package com.luminiasoft.bitshares.models;
/**
* Class used to deserialize the 'result' field returned by the full node after making a call
* to the 'get_dynamic_global_properties' RPC.
*/
public class DynamicGlobalProperties {
public String id;
public long head_block_number;
public String head_block_id;
public String time;
public String current_witness;
public String next_maintenance_time;
public String last_budget_time;
public long witness_budget;
public long accounts_registered_this_interval;
public long recently_missed_count;
public long current_aslot;
public String recent_slots_filled;
public long dynamic_flags;
public long last_irreversible_block_num;
}

View File

@ -0,0 +1,8 @@
package com.luminiasoft.bitshares.models;
/**
* Generic witness response
*/
public class WitnessResponse<T> extends BaseResponse{
public T result;
}

View File

@ -0,0 +1,71 @@
package com.luminiasoft.bitshares.ws;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.luminiasoft.bitshares.RPC;
import com.luminiasoft.bitshares.interfaces.WitnessResponseListener;
import com.luminiasoft.bitshares.models.AccountProperties;
import com.luminiasoft.bitshares.models.ApiCall;
import com.luminiasoft.bitshares.models.BaseResponse;
import com.luminiasoft.bitshares.models.WitnessResponse;
import com.neovisionaries.ws.client.WebSocket;
import com.neovisionaries.ws.client.WebSocketAdapter;
import com.neovisionaries.ws.client.WebSocketException;
import com.neovisionaries.ws.client.WebSocketFrame;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Created by nelson on 11/15/16.
*/
public class GetAccountByName extends WebSocketAdapter {
private String accountName;
private WitnessResponseListener mListener;
public GetAccountByName(String accountName, WitnessResponseListener listener){
this.accountName = accountName;
this.mListener = listener;
}
@Override
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
ArrayList<Serializable> accountParams = new ArrayList<>();
accountParams.add(this.accountName);
ApiCall getAccountByName = new ApiCall(0, RPC.CALL_GET_ACCOUNT_BY_NAME, accountParams, "2.0", 1);
websocket.sendText(getAccountByName.toJsonString());
}
@Override
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
String response = frame.getPayloadText();
Gson gson = new Gson();
Type GetAccountByNameResponse = new TypeToken<WitnessResponse<AccountProperties>>(){}.getType();
WitnessResponse<WitnessResponse<AccountProperties>> witnessResponse = gson.fromJson(response, GetAccountByNameResponse);
if(witnessResponse.error != null){
this.mListener.onError(witnessResponse.error);
}else{
this.mListener.onSuccess(witnessResponse);
}
websocket.disconnect();
}
@Override
public void onError(WebSocket websocket, WebSocketException cause) throws Exception {
mListener.onError(new BaseResponse.Error(cause.getMessage()));
websocket.disconnect();
}
@Override
public void handleCallbackError(WebSocket websocket, Throwable cause) throws Exception {
mListener.onError(new BaseResponse.Error(cause.getMessage()));
websocket.disconnect();
}
}

View File

@ -0,0 +1,77 @@
package com.luminiasoft.bitshares.ws;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.luminiasoft.bitshares.Asset;
import com.luminiasoft.bitshares.AssetAmount;
import com.luminiasoft.bitshares.BaseOperation;
import com.luminiasoft.bitshares.RPC;
import com.luminiasoft.bitshares.interfaces.WitnessResponseListener;
import com.luminiasoft.bitshares.models.ApiCall;
import com.luminiasoft.bitshares.models.BaseResponse;
import com.luminiasoft.bitshares.models.WitnessResponse;
import com.neovisionaries.ws.client.WebSocket;
import com.neovisionaries.ws.client.WebSocketAdapter;
import com.neovisionaries.ws.client.WebSocketException;
import com.neovisionaries.ws.client.WebSocketFrame;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Created by nelson on 11/15/16.
*/
public class GetRequiredFees extends WebSocketAdapter {
private WitnessResponseListener mListener;
private List<BaseOperation> operations;
private Asset asset;
public GetRequiredFees(List<BaseOperation> operations, Asset asset, WitnessResponseListener listener){
this.operations = operations;
this.asset = asset;
this.mListener = listener;
}
@Override
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
ArrayList<Serializable> accountParams = new ArrayList<>();
accountParams.add((Serializable) this.operations);
accountParams.add(this.asset.getObjectId());
ApiCall getRequiredFees = new ApiCall(0, RPC.CALL_GET_REQUIRED_FEES, accountParams, "2.0", 1);
websocket.sendText(getRequiredFees.toJsonString());
}
@Override
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
String response = frame.getPayloadText();
Gson gson = new Gson();
Type GetRequiredFeesResponse = new TypeToken<WitnessResponse<List<AssetAmount>>>(){}.getType();
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetDeserializer());
WitnessResponse<List<AssetAmount>> witnessResponse = gsonBuilder.create().fromJson(response, GetRequiredFeesResponse);
if(witnessResponse.error != null){
mListener.onError(witnessResponse.error);
}else{
mListener.onSuccess(witnessResponse);
}
}
@Override
public void onError(WebSocket websocket, WebSocketException cause) throws Exception {
mListener.onError(new BaseResponse.Error(cause.getMessage()));
websocket.disconnect();
}
@Override
public void handleCallbackError(WebSocket websocket, Throwable cause) throws Exception {
mListener.onError(new BaseResponse.Error(cause.getMessage()));
websocket.disconnect();
}
}

View File

@ -0,0 +1,176 @@
package com.luminiasoft.bitshares.ws;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.luminiasoft.bitshares.BaseOperation;
import com.luminiasoft.bitshares.BlockData;
import com.luminiasoft.bitshares.RPC;
import com.luminiasoft.bitshares.Transaction;
import com.luminiasoft.bitshares.Transfer;
import com.luminiasoft.bitshares.TransferTransactionBuilder;
import com.luminiasoft.bitshares.interfaces.WitnessResponseListener;
import com.luminiasoft.bitshares.models.ApiCall;
import com.luminiasoft.bitshares.models.BaseResponse;
import com.luminiasoft.bitshares.models.DynamicGlobalProperties;
import com.luminiasoft.bitshares.models.WitnessResponse;
import com.neovisionaries.ws.client.WebSocket;
import com.neovisionaries.ws.client.WebSocketAdapter;
import com.neovisionaries.ws.client.WebSocketException;
import com.neovisionaries.ws.client.WebSocketFrame;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
/**
* Class that will handle the transaction publication procedure.
*/
public class TransactionBroadcastSequence extends WebSocketAdapter {
private final String TAG = this.getClass().getName();
private final static int LOGIN_ID = 1;
private final static int GET_NETWORK_BROADCAST_ID = 2;
private final static int GET_NETWORK_DYNAMIC_PARAMETERS = 3;
private final static int BROADCAST_TRANSACTION = 4;
public final static int EXPIRATION_TIME = 30;
private Transaction transaction;
private long expirationTime;
private String headBlockId;
private long headBlockNumber;
private WitnessResponseListener mListener;
private int currentId = 1;
private int broadcastApiId = -1;
private int retries = 0;
/**
* Constructor of this class. The ids required
* @param transaction: The transaction to be broadcasted.
* @param listener: A class implementing the WitnessResponseListener interface. This should
* be implemented by the party interested in being notified about the success/failure
* of the transaction broadcast operation.
*/
public TransactionBroadcastSequence(Transaction transaction, WitnessResponseListener listener){
this.transaction = transaction;
this.mListener = listener;
}
@Override
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
ArrayList<Serializable> loginParams = new ArrayList<>();
loginParams.add(null);
loginParams.add(null);
ApiCall loginCall = new ApiCall(1, RPC.CALL_LOGIN, loginParams, "2.0", currentId);
websocket.sendText(loginCall.toJsonString());
}
@Override
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
String response = frame.getPayloadText();
Gson gson = new Gson();
BaseResponse baseResponse = gson.fromJson(response, BaseResponse.class);
if(baseResponse.error != null && baseResponse.error.message.indexOf("is_canonical") == -1){
mListener.onError(baseResponse.error);
websocket.disconnect();
}else{
currentId++;
ArrayList<Serializable> emptyParams = new ArrayList<>();
if(baseResponse.id == LOGIN_ID){
ApiCall networkApiIdCall = new ApiCall(1, RPC.CALL_NETWORK_BROADCAST, emptyParams, "2.0", currentId);
websocket.sendText(networkApiIdCall.toJsonString());
}else if(baseResponse.id == GET_NETWORK_BROADCAST_ID){
Type ApiIdResponse = new TypeToken<WitnessResponse<Integer>>() {}.getType();
WitnessResponse<Integer> witnessResponse = gson.fromJson(response, ApiIdResponse);
broadcastApiId = witnessResponse.result;
ApiCall getDynamicParametersCall = new ApiCall(0, RPC.CALL_GET_DYNAMIC_GLOBAL_PROPERTIES, emptyParams, "2.0", currentId);
websocket.sendText(getDynamicParametersCall.toJsonString());
}else if(baseResponse.id == GET_NETWORK_DYNAMIC_PARAMETERS){
Type DynamicGlobalPropertiesResponse = new TypeToken<WitnessResponse<DynamicGlobalProperties>>(){}.getType();
WitnessResponse<DynamicGlobalProperties> witnessResponse = gson.fromJson(response, DynamicGlobalPropertiesResponse);
DynamicGlobalProperties dynamicProperties = witnessResponse.result;
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
Date date = dateFormat.parse(dynamicProperties.time);
// Obtained block data
expirationTime = (date.getTime() / 1000) + EXPIRATION_TIME;
headBlockId = dynamicProperties.head_block_id;
headBlockNumber = dynamicProperties.head_block_number;
ArrayList<Serializable> transactionList = new ArrayList<>();
transactionList.add(transaction);
ApiCall call = new ApiCall(broadcastApiId,
RPC.CALL_BROADCAST_TRANSACTION,
transactionList,
"2.0",
currentId);
// Finally sending transaction
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);
if(witnessResponse.error == null){
mListener.onSuccess(witnessResponse);
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();
Transfer transfer = (Transfer) 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 onError(WebSocket websocket, WebSocketException cause) throws Exception {
mListener.onError(new BaseResponse.Error(cause.getMessage()));
websocket.disconnect();
}
@Override
public void handleCallbackError(WebSocket websocket, Throwable cause) throws Exception {
mListener.onError(new BaseResponse.Error(cause.getMessage()));
websocket.disconnect();
}
}