Initial commit
This commit is contained in:
commit
52c2c9db5e
39 changed files with 2603 additions and 0 deletions
77
.gitignore
vendored
Normal file
77
.gitignore
vendored
Normal 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
15
build.gradle
Normal 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
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
164
gradlew
vendored
Executable 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
90
gradlew.bat
vendored
Normal 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
3
settings.gradle
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
rootProject.name = 'fullerene'
|
||||||
|
include 'application'
|
||||||
|
|
13
src/main/java/com/luminiasoft/bitshares/Asset.java
Normal file
13
src/main/java/com/luminiasoft/bitshares/Asset.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
88
src/main/java/com/luminiasoft/bitshares/AssetAmount.java
Normal file
88
src/main/java/com/luminiasoft/bitshares/AssetAmount.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
src/main/java/com/luminiasoft/bitshares/BaseOperation.java
Normal file
20
src/main/java/com/luminiasoft/bitshares/BaseOperation.java
Normal 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();
|
||||||
|
}
|
101
src/main/java/com/luminiasoft/bitshares/BlockData.java
Normal file
101
src/main/java/com/luminiasoft/bitshares/BlockData.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
31
src/main/java/com/luminiasoft/bitshares/BrainKey.java
Normal file
31
src/main/java/com/luminiasoft/bitshares/BrainKey.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
src/main/java/com/luminiasoft/bitshares/Chains.java
Normal file
16
src/main/java/com/luminiasoft/bitshares/Chains.java
Normal 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";
|
||||||
|
}
|
||||||
|
}
|
8
src/main/java/com/luminiasoft/bitshares/Extension.java
Normal file
8
src/main/java/com/luminiasoft/bitshares/Extension.java
Normal 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
|
||||||
|
}
|
33
src/main/java/com/luminiasoft/bitshares/GrapheneObject.java
Normal file
33
src/main/java/com/luminiasoft/bitshares/GrapheneObject.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
64
src/main/java/com/luminiasoft/bitshares/Main.java
Normal file
64
src/main/java/com/luminiasoft/bitshares/Main.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
15
src/main/java/com/luminiasoft/bitshares/Memo.java
Normal file
15
src/main/java/com/luminiasoft/bitshares/Memo.java
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
52
src/main/java/com/luminiasoft/bitshares/OperationType.java
Normal file
52
src/main/java/com/luminiasoft/bitshares/OperationType.java
Normal 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
|
||||||
|
}
|
13
src/main/java/com/luminiasoft/bitshares/RPC.java
Normal file
13
src/main/java/com/luminiasoft/bitshares/RPC.java
Normal 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";
|
||||||
|
}
|
509
src/main/java/com/luminiasoft/bitshares/Test.java
Normal file
509
src/main/java/com/luminiasoft/bitshares/Test.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
197
src/main/java/com/luminiasoft/bitshares/Transaction.java
Normal file
197
src/main/java/com/luminiasoft/bitshares/Transaction.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
113
src/main/java/com/luminiasoft/bitshares/Transfer.java
Normal file
113
src/main/java/com/luminiasoft/bitshares/Transfer.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
48
src/main/java/com/luminiasoft/bitshares/UserAccount.java
Normal file
48
src/main/java/com/luminiasoft/bitshares/UserAccount.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
28
src/main/java/com/luminiasoft/bitshares/Util.java
Normal file
28
src/main/java/com/luminiasoft/bitshares/Util.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
224
src/main/java/com/luminiasoft/bitshares/Varint.java
Normal file
224
src/main/java/com/luminiasoft/bitshares/Varint.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
99
src/main/java/com/luminiasoft/bitshares/models/ApiCall.java
Normal file
99
src/main/java/com/luminiasoft/bitshares/models/ApiCall.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.luminiasoft.bitshares.models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic witness response
|
||||||
|
*/
|
||||||
|
public class WitnessResponse<T> extends BaseResponse{
|
||||||
|
public T result;
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue