Merge branch 'develop'
This commit is contained in:
commit
9b915b3d36
158 changed files with 8376 additions and 231 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -103,3 +103,4 @@ release.properties
|
|||
graphenej/build
|
||||
|
||||
local.properties
|
||||
|
||||
|
|
18
build.gradle
18
build.gradle
|
@ -3,11 +3,27 @@ subprojects {
|
|||
mavenCentral()
|
||||
}
|
||||
}
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
maven {
|
||||
url "https://maven.google.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'https://maven.google.com/'
|
||||
name 'Google'
|
||||
}
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:2.3.0'
|
||||
classpath 'com.android.tools.build:gradle:3.4.2'
|
||||
classpath 'com.novoda:bintray-release:0.9.1'
|
||||
}
|
||||
}
|
|
@ -17,8 +17,8 @@
|
|||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
VERSION_NAME=0.4.6
|
||||
VERSION_CODE=9
|
||||
VERSION_NAME=0.4.7-alpha3
|
||||
VERSION_CODE=12
|
||||
GROUP=com.github.bilthon
|
||||
|
||||
POM_DESCRIPTION=A Java library for mobile app Developers; Graphene/Bitshares blockchain.
|
||||
|
|
0
gradlew
vendored
Normal file → Executable file
0
gradlew
vendored
Normal file → Executable file
|
@ -1,34 +1,54 @@
|
|||
group 'cy.agorise'
|
||||
version '0.4.6'
|
||||
|
||||
apply plugin: 'com.novoda.bintray-release'
|
||||
apply plugin: 'com.android.library'
|
||||
apply from: 'maven-push.gradle'
|
||||
|
||||
|
||||
dependencies {
|
||||
testCompile group: 'junit', name: 'junit', version: '4.12'
|
||||
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'
|
||||
compile group: "org.tukaani", name: "xz", version: "1.6"
|
||||
publish {
|
||||
userOrg = 'bilthon'
|
||||
groupId = 'cy.agorise.graphenej'
|
||||
artifactId = 'graphenej'
|
||||
publishVersion = '0.6.0'
|
||||
repoName = 'Graphenej'
|
||||
desc = 'A Java library for mobile app Developers; Graphene/Bitshares blockchain.'
|
||||
website = 'https://github.com/Agorise/graphenej'
|
||||
}
|
||||
|
||||
|
||||
android {
|
||||
compileSdkVersion 24
|
||||
buildToolsVersion "25.0.0"
|
||||
compileSdkVersion 28
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 9
|
||||
targetSdkVersion 24
|
||||
versionCode 9
|
||||
versionName "0.4.6"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 28
|
||||
versionCode 12
|
||||
versionName "0.6.0"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
buildTypes {
|
||||
debug{}
|
||||
preRelease{}
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
}
|
||||
defaultConfig {
|
||||
multiDexEnabled true
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation group: 'junit', name: 'junit', version: '4.12'
|
||||
implementation 'com.neovisionaries:nv-websocket-client:1.30'
|
||||
implementation 'org.bitcoinj:bitcoinj-core:0.14.3'
|
||||
implementation 'com.google.code.gson:gson:2.8.5'
|
||||
implementation 'org.tukaani:xz:1.6'
|
||||
|
||||
androidTestImplementation 'com.android.support:support-annotations:28.0.0'
|
||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||
androidTestImplementation 'com.android.support.test:rules:1.0.2'
|
||||
|
||||
// Rx dependencies
|
||||
api 'io.reactivex.rxjava2:rxandroid:2.1.0'
|
||||
api 'io.reactivex.rxjava2:rxjava:2.2.2'
|
||||
api 'com.jakewharton.rxrelay2:rxrelay:2.1.0'
|
||||
api 'com.squareup.okhttp3:okhttp:3.12.2'
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.util.Log;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.network.FullNode;
|
||||
import cy.agorise.graphenej.network.LatencyNodeProvider;
|
||||
import cy.agorise.graphenej.network.NodeLatencyVerifier;
|
||||
import cy.agorise.graphenej.network.NodeProvider;
|
||||
import io.reactivex.Observer;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class NodeLatencyVerifierTest {
|
||||
private final String TAG = this.getClass().getName();
|
||||
|
||||
String[] nodeURLs = new String[]{
|
||||
"wss://bitshares.openledger.info/ws",
|
||||
"wss://us.nodes.bitshares.ws",
|
||||
"wss://eu.nodes.bitshares.ws",
|
||||
"wss://citadel.li/node",
|
||||
"wss://api.bts.mobi/ws"
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testNodeLatencyTest() throws Exception {
|
||||
ArrayList<FullNode> nodeList = new ArrayList<>();
|
||||
nodeList.add(new FullNode(nodeURLs[0]));
|
||||
nodeList.add(new FullNode(nodeURLs[1]));
|
||||
nodeList.add(new FullNode(nodeURLs[2]));
|
||||
final NodeLatencyVerifier nodeLatencyVerifier = new NodeLatencyVerifier(nodeList);
|
||||
PublishSubject subject = nodeLatencyVerifier.start();
|
||||
final NodeProvider nodeProvider = new LatencyNodeProvider();
|
||||
subject.subscribe(new Observer<FullNode>() {
|
||||
int counter = 0;
|
||||
|
||||
@Override
|
||||
public void onSubscribe(Disposable d) {}
|
||||
|
||||
@Override
|
||||
public void onNext(FullNode fullNode) {
|
||||
Log.i(TAG,String.format("Avg latency: %.2f, url: %s", fullNode.getLatencyValue(), fullNode.getUrl()));
|
||||
|
||||
// Updating node provider
|
||||
nodeProvider.updateNode(fullNode);
|
||||
List<FullNode> sortedNodes = nodeProvider.getSortedNodes();
|
||||
for(FullNode node : sortedNodes){
|
||||
Log.d(TAG,String.format("> %.2f, url: %s", node.getLatencyValue(), node.getUrl()));
|
||||
}
|
||||
|
||||
// Finish test after certain amount of rounds
|
||||
if(counter > 3){
|
||||
synchronized (NodeLatencyVerifierTest.this){
|
||||
nodeLatencyVerifier.stop();
|
||||
NodeLatencyVerifierTest.this.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
counter++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
Log.e(TAG,"onError.Msg: "+e.getMessage());
|
||||
synchronized (NodeLatencyVerifierTest.this){
|
||||
NodeLatencyVerifierTest.this.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
Log.d(TAG,"onComplete");
|
||||
}
|
||||
});
|
||||
try {
|
||||
synchronized(this) {
|
||||
wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="cy.agorise.graphenej"
|
||||
android:versionCode="9"
|
||||
android:versionName="0.4.6" >
|
||||
<uses-sdk android:minSdkVersion="1" />
|
||||
<application/>
|
||||
package="cy.agorise.graphenej">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application>
|
||||
<service
|
||||
android:name=".api.android.NetworkService"
|
||||
android:enabled="true"
|
||||
android:exported="true"/>
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -33,17 +33,51 @@ public class AccountOptions implements GrapheneSerializable {
|
|||
private Vote[] votes;
|
||||
private Extensions extensions;
|
||||
|
||||
/**
|
||||
* Constructor used to instantiate only the following attributes:
|
||||
* <ul>
|
||||
* <li>voting_account</li>
|
||||
* <li>votes</li>
|
||||
* <li>extensions</li>
|
||||
* </ul>
|
||||
*/
|
||||
public AccountOptions(){
|
||||
voting_account = new UserAccount(UserAccount.PROXY_TO_SELF);
|
||||
this.votes = new Vote[0];
|
||||
this.extensions = new Extensions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor used to instantiate only the following attributes:
|
||||
* <ul>
|
||||
* <li>voting_account</li>
|
||||
* <li>votes</li>
|
||||
* <li>memo_key</li>
|
||||
* <li>extensions</li>
|
||||
* </ul>
|
||||
*/
|
||||
public AccountOptions(PublicKey memoKey){
|
||||
this();
|
||||
this.memo_key = memoKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that can be used to instantiate a version of the AccountOptions object
|
||||
* with a null reference in the 'voting_account' attribute. This can be used to prevent
|
||||
* a circular dependency situation when de-serializing the UserAccount instance.
|
||||
*
|
||||
* @param memoKey Memo public key used by this account
|
||||
* @param includeAccount Whether or not to instantiate an UserAccount
|
||||
*/
|
||||
public AccountOptions(PublicKey memoKey, boolean includeAccount){
|
||||
if(includeAccount){
|
||||
voting_account = new UserAccount(UserAccount.PROXY_TO_SELF);
|
||||
}
|
||||
this.memo_key = memoKey;
|
||||
this.votes = new Vote[0];
|
||||
this.extensions = new Extensions();
|
||||
}
|
||||
|
||||
//TODO: Implement constructor that takes a Vote array.
|
||||
|
||||
public PublicKey getMemoKey() {
|
||||
|
@ -149,13 +183,23 @@ public class AccountOptions implements GrapheneSerializable {
|
|||
*/
|
||||
public static class AccountOptionsDeserializer implements JsonDeserializer<AccountOptions> {
|
||||
|
||||
boolean mIncludeUserAccount;
|
||||
|
||||
public AccountOptionsDeserializer(){
|
||||
this.mIncludeUserAccount = true;
|
||||
}
|
||||
|
||||
public AccountOptionsDeserializer(boolean includeUserAccount){
|
||||
this.mIncludeUserAccount = includeUserAccount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountOptions deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonObject baseObject = json.getAsJsonObject();
|
||||
AccountOptions options;
|
||||
try {
|
||||
Address address = new Address(baseObject.get(KEY_MEMO_KEY).getAsString());
|
||||
options = new AccountOptions(address.getPublicKey());
|
||||
options = new AccountOptions(address.getPublicKey(), mIncludeUserAccount);
|
||||
} catch (MalformedAddressException e) {
|
||||
System.out.println("MalformedAddressException. Msg: "+e.getMessage());
|
||||
options = new AccountOptions();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.primitives.Bytes;
|
||||
import cy.agorise.graphenej.errors.MalformedAddressException;
|
||||
import org.bitcoinj.core.Base58;
|
||||
|
@ -61,4 +62,18 @@ public class Address {
|
|||
ripemd160Digest.doFinal(checksum, 0);
|
||||
return Arrays.copyOfRange(checksum, 0, 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Address address = (Address) o;
|
||||
return Objects.equal(publicKey, address.publicKey) &&
|
||||
Objects.equal(prefix, address.prefix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(publicKey, prefix);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -191,6 +191,11 @@ public class AssetAmount implements ByteSerializable, JsonSerializable {
|
|||
return jsonAmount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("(asset=%s, amount=%s)", asset.getObjectId(), amount.toString(10));
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom serializer used to translate this object into the JSON-formatted entry we need for a transaction.
|
||||
*/
|
||||
|
|
|
@ -9,5 +9,10 @@ package cy.agorise.graphenej;
|
|||
public enum AuthorityType {
|
||||
OWNER,
|
||||
ACTIVE,
|
||||
MEMO
|
||||
MEMO;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%d", this.ordinal());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import cy.agorise.graphenej.interfaces.ByteSerializable;
|
||||
import cy.agorise.graphenej.interfaces.JsonSerializable;
|
||||
import cy.agorise.graphenej.operations.TransferOperation;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/5/16.
|
||||
* Base class that represents a generic operation
|
||||
*/
|
||||
public abstract class BaseOperation implements ByteSerializable, JsonSerializable {
|
||||
|
||||
|
@ -32,4 +39,54 @@ public abstract class BaseOperation implements ByteSerializable, JsonSerializabl
|
|||
array.add(this.getId());
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* De-serializer used to unpack data from a generic operation. The general format used in the
|
||||
* JSON-RPC blockchain API is the following:
|
||||
* </p>
|
||||
*
|
||||
* <code>[OPERATION_ID, OPERATION_OBJECT]</code><br>
|
||||
*
|
||||
* <p>
|
||||
* Where <code>OPERATION_ID</code> is one of the operations defined in {@link cy.agorise.graphenej.OperationType}
|
||||
* and <code>OPERATION_OBJECT</code> is the actual operation serialized in the JSON format.
|
||||
* </p>
|
||||
* Here's an example of this serialized form for a transfer operation:<br><br>
|
||||
*<pre>
|
||||
*[
|
||||
* 0,
|
||||
* {
|
||||
* "fee": {
|
||||
* "amount": 264174,
|
||||
* "asset_id": "1.3.0"
|
||||
* },
|
||||
* "from": "1.2.138632",
|
||||
* "to": "1.2.129848",
|
||||
* "amount": {
|
||||
* "amount": 100,
|
||||
* "asset_id": "1.3.0"
|
||||
* },
|
||||
* "extensions": []
|
||||
* }
|
||||
*]
|
||||
*</pre><br>
|
||||
* If this class is used, this serialized data will be translated to a TransferOperation object instance.<br>
|
||||
*
|
||||
* TODO: Add support for operations other than the 'transfer'
|
||||
*/
|
||||
public static class OperationDeserializer implements JsonDeserializer<BaseOperation> {
|
||||
|
||||
@Override
|
||||
public BaseOperation deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
BaseOperation operation = null;
|
||||
if(json.isJsonArray()){
|
||||
JsonArray array = json.getAsJsonArray();
|
||||
if(array.get(0).getAsLong() == OperationType.TRANSFER_OPERATION.ordinal()){
|
||||
operation = context.deserialize(array.get(1), TransferOperation.class);
|
||||
}
|
||||
}
|
||||
return operation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import org.bitcoinj.core.DumpedPrivateKey;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
|
@ -69,7 +71,15 @@ public class BrainKey {
|
|||
public BrainKey(String words, int sequence) {
|
||||
this.mBrainKey = words;
|
||||
this.sequenceNumber = sequence;
|
||||
String encoded = String.format("%s %d", words, sequence);
|
||||
derivePrivateKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the actual private key from the brainkey + sequence number
|
||||
*/
|
||||
private void derivePrivateKey(){
|
||||
@SuppressLint("DefaultLocale")
|
||||
String encoded = String.format("%s %d", this.mBrainKey, this.sequenceNumber);
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-512");
|
||||
byte[] bytes = md.digest(encoded.getBytes("UTF-8"));
|
||||
|
@ -120,19 +130,28 @@ public class BrainKey {
|
|||
}
|
||||
|
||||
/**
|
||||
* Brain key words getter
|
||||
* @return: The word sequence that comprises this brain key
|
||||
* Brain key words getter.
|
||||
* @return The word sequence that comprises this brain key
|
||||
*/
|
||||
public String getBrainKey(){
|
||||
return mBrainKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sequence number getter
|
||||
* @return: The sequence number used alongside with the brain key words in order
|
||||
* Sequence number getter.
|
||||
* @return The sequence number used alongside with the brain key words in order
|
||||
* to derive the private key
|
||||
*/
|
||||
public int getSequenceNumber(){
|
||||
return sequenceNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sequence number setter.
|
||||
* @param sequenceNumber The sequence number used to generate a specific key from this brainkey
|
||||
*/
|
||||
public void setSequenceNumber(int sequenceNumber) {
|
||||
this.sequenceNumber = sequenceNumber;
|
||||
derivePrivateKey();
|
||||
}
|
||||
}
|
|
@ -1,12 +1,17 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import cy.agorise.graphenej.interfaces.ByteSerializable;
|
||||
import cy.agorise.graphenej.interfaces.JsonSerializable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/9/16.
|
||||
*/
|
||||
|
@ -40,4 +45,15 @@ public class Extensions implements JsonSerializable, ByteSerializable {
|
|||
public int size(){
|
||||
return extensions.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom de-serializer used to avoid problems when de-serializing an object that contains
|
||||
* an extension array.
|
||||
*/
|
||||
public static class ExtensionsDeserializer implements JsonDeserializer<Extensions> {
|
||||
@Override
|
||||
public Extensions deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ package cy.agorise.graphenej;
|
|||
|
||||
import com.google.gson.annotations.Expose;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Generic class used to represent a graphene object as defined in
|
||||
|
@ -34,15 +36,15 @@ public class GrapheneObject {
|
|||
|
||||
/**
|
||||
*
|
||||
* @return: A String containing the full object apiId in the form {space}.{type}.{instance}
|
||||
* @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);
|
||||
return String.format(Locale.US, "%d.%d.%d", space, type, instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of this object.
|
||||
* @return: Instance of the ObjectType enum.
|
||||
* @return Instance of the ObjectType enum.
|
||||
*/
|
||||
public ObjectType getObjectType(){
|
||||
switch(space){
|
||||
|
@ -78,6 +80,8 @@ public class GrapheneObject {
|
|||
return ObjectType.WORKER_OBJECT;
|
||||
case 15:
|
||||
return ObjectType.BALANCE_OBJECT;
|
||||
case 16:
|
||||
return ObjectType.HTLC_OBJECT;
|
||||
}
|
||||
case IMPLEMENTATION_SPACE:
|
||||
switch(type){
|
||||
|
|
43
graphenej/src/main/java/cy/agorise/graphenej/Htlc.java
Normal file
43
graphenej/src/main/java/cy/agorise/graphenej/Htlc.java
Normal file
|
@ -0,0 +1,43 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import cy.agorise.graphenej.interfaces.ByteSerializable;
|
||||
import cy.agorise.graphenej.interfaces.JsonSerializable;
|
||||
|
||||
/**
|
||||
* Class used to represent an existing HTLC contract.
|
||||
*/
|
||||
public class Htlc extends GrapheneObject implements ByteSerializable, JsonSerializable {
|
||||
|
||||
public Htlc(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 JsonElement toJsonObject() {
|
||||
return null;
|
||||
}
|
||||
}
|
45
graphenej/src/main/java/cy/agorise/graphenej/HtlcHash.java
Normal file
45
graphenej/src/main/java/cy/agorise/graphenej/HtlcHash.java
Normal file
|
@ -0,0 +1,45 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
import cy.agorise.graphenej.interfaces.ByteSerializable;
|
||||
import cy.agorise.graphenej.interfaces.JsonSerializable;
|
||||
|
||||
/**
|
||||
* Class used to represent a HTLC hash.
|
||||
*/
|
||||
public class HtlcHash implements ByteSerializable, JsonSerializable {
|
||||
private HtlcHashType hashType;
|
||||
private byte[] hash;
|
||||
|
||||
public HtlcHash(HtlcHashType hashType, byte[] hash) {
|
||||
this.hashType = hashType;
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
public HtlcHashType getType(){
|
||||
return this.hashType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
byte[] hashTypeBytes = new byte[] { Util.revertInteger(hashType.ordinal())[3] };
|
||||
return Bytes.concat(hashTypeBytes, hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
JsonElement element = toJsonObject();
|
||||
return element.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJsonObject() {
|
||||
JsonArray array = new JsonArray();
|
||||
array.add(hashType.ordinal());
|
||||
array.add(Util.byteToString(hash));
|
||||
return array;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
/**
|
||||
* Used to enumerate the possible hash algorithms used in HTLCs.
|
||||
* @see <a href="https://github.com/bitshares/bitshares-core/blob/623aea265f2711adade982fc3248e6528dc8ac51/libraries/chain/include/graphene/chain/protocol/htlc.hpp">htlc.hpp</a>
|
||||
*/
|
||||
public enum HtlcHashType {
|
||||
RIPEMD160,
|
||||
SHA1,
|
||||
SHA256
|
||||
}
|
|
@ -1,15 +1,11 @@
|
|||
package cy.agorise.graphenej.objects;
|
||||
package cy.agorise.graphenej;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
|
||||
import com.google.gson.*;
|
||||
import cy.agorise.graphenej.errors.ChecksumException;
|
||||
import cy.agorise.graphenej.errors.MalformedAddressException;
|
||||
import cy.agorise.graphenej.interfaces.ByteSerializable;
|
||||
import cy.agorise.graphenej.interfaces.JsonSerializable;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.spongycastle.math.ec.ECPoint;
|
||||
|
||||
|
@ -19,20 +15,11 @@ import java.security.MessageDigest;
|
|||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import cy.agorise.graphenej.Address;
|
||||
import cy.agorise.graphenej.PublicKey;
|
||||
import cy.agorise.graphenej.Util;
|
||||
import cy.agorise.graphenej.errors.ChecksumException;
|
||||
import cy.agorise.graphenej.errors.MalformedAddressException;
|
||||
import cy.agorise.graphenej.interfaces.ByteSerializable;
|
||||
import cy.agorise.graphenej.interfaces.JsonSerializable;
|
||||
|
||||
/**
|
||||
* Class used to represent a memo data structure
|
||||
* {@url https://bitshares.org/doxygen/structgraphene_1_1chain_1_1memo__data.html}
|
||||
*/
|
||||
public class Memo implements ByteSerializable, JsonSerializable {
|
||||
public final static String TAG = "Memo";
|
||||
public static final String KEY_FROM = "from";
|
||||
public static final String KEY_TO = "to";
|
||||
public static final String KEY_NONCE = "nonce";
|
||||
|
@ -190,10 +177,6 @@ public class Memo implements ByteSerializable, JsonSerializable {
|
|||
|
||||
byte[] seed = Bytes.concat(nonceBytes, Util.hexlify(Util.bytesToHex(ss)));
|
||||
|
||||
// Calculating checksum
|
||||
byte[] sha256Msg = sha256.digest(message);
|
||||
|
||||
|
||||
// Applying decryption
|
||||
byte[] temp = Util.decryptAES(message, seed);
|
||||
byte[] checksum = Arrays.copyOfRange(temp, 0, 4);
|
||||
|
@ -205,7 +188,7 @@ public class Memo implements ByteSerializable, JsonSerializable {
|
|||
throw new ChecksumException("Invalid checksum found while performing decryption");
|
||||
}
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
System.out.println("NoSuchAlgotithmException. Msg:"+ e.getMessage());
|
||||
System.out.println("NoSuchAlgorithmException. Msg:"+ e.getMessage());
|
||||
}
|
||||
return plaintext;
|
||||
}
|
||||
|
@ -291,12 +274,14 @@ public class Memo implements ByteSerializable, JsonSerializable {
|
|||
memoObject.addProperty(KEY_FROM, "");
|
||||
memoObject.addProperty(KEY_TO, "");
|
||||
memoObject.addProperty(KEY_NONCE, "");
|
||||
if(this.message != null)
|
||||
memoObject.addProperty(KEY_MESSAGE, Util.bytesToHex(this.message));
|
||||
return null;
|
||||
}else{
|
||||
memoObject.addProperty(KEY_FROM, this.from.toString());
|
||||
memoObject.addProperty(KEY_TO, this.to.toString());
|
||||
memoObject.addProperty(KEY_NONCE, String.format("%x", this.nonce));
|
||||
memoObject.addProperty(KEY_NONCE, this.nonce.toString());
|
||||
if(this.message != null)
|
||||
memoObject.addProperty(KEY_MESSAGE, Util.bytesToHex(this.message));
|
||||
}
|
||||
return memoObject;
|
||||
|
@ -310,8 +295,9 @@ public class Memo implements ByteSerializable, JsonSerializable {
|
|||
*/
|
||||
public JsonElement toJson(boolean decimal){
|
||||
JsonElement jsonElement = toJsonObject();
|
||||
if(decimal){
|
||||
if(decimal && jsonElement != null){
|
||||
JsonObject jsonObject = (JsonObject) jsonElement;
|
||||
// The nonce is interpreted in base 16, but it is going to be written in base 10
|
||||
BigInteger nonce = new BigInteger(jsonObject.get(KEY_NONCE).getAsString(), 16);
|
||||
jsonObject.addProperty(KEY_NONCE, nonce.toString());
|
||||
}
|
|
@ -20,6 +20,7 @@ public enum ObjectType {
|
|||
VESTING_BALANCE_OBJECT,
|
||||
WORKER_OBJECT,
|
||||
BALANCE_OBJECT,
|
||||
HTLC_OBJECT,
|
||||
GLOBAL_PROPERTY_OBJECT,
|
||||
DYNAMIC_GLOBAL_PROPERTY_OBJECT,
|
||||
ASSET_DYNAMIC_DATA,
|
||||
|
@ -53,6 +54,7 @@ public enum ObjectType {
|
|||
case VESTING_BALANCE_OBJECT:
|
||||
case WORKER_OBJECT:
|
||||
case BALANCE_OBJECT:
|
||||
case HTLC_OBJECT:
|
||||
space = 1;
|
||||
break;
|
||||
case GLOBAL_PROPERTY_OBJECT:
|
||||
|
@ -123,6 +125,8 @@ public enum ObjectType {
|
|||
case BALANCE_OBJECT:
|
||||
type = 15;
|
||||
break;
|
||||
case HTLC_OBJECT:
|
||||
type = 16;
|
||||
case GLOBAL_PROPERTY_OBJECT:
|
||||
type = 0;
|
||||
break;
|
||||
|
|
|
@ -51,5 +51,15 @@ public enum OperationType {
|
|||
BLIND_TRANSFER_OPERATION,
|
||||
TRANSFER_FROM_BLIND_OPERATION,
|
||||
ASSET_SETTLE_CANCEL_OPERATION, // VIRTUAL
|
||||
ASSET_CLAIM_FEES_OPERATION
|
||||
ASSET_CLAIM_FEES_OPERATION,
|
||||
FBA_DISTRIBUTE_OPERATION,
|
||||
BID_COLLATERAL_OPERATION,
|
||||
EXECUTE_BID_OPERATION, // VIRTUAL
|
||||
ASSET_CLAIM_POOL_OPERATION,
|
||||
ASSET_UPDATE_ISSUER_OPERATION,
|
||||
HTLC_CREATE_OPERATION,
|
||||
HTLC_REDEEM_OPERATION,
|
||||
HTLC_REDEEMED_OPERATION, // VIRTUAL
|
||||
HTLC_EXTEND_OPERATION,
|
||||
HTLC_REFUND_OPERATION // VIRTUAL
|
||||
}
|
||||
|
|
|
@ -132,4 +132,8 @@ public class OrderBook {
|
|||
}
|
||||
return obtainedBase;
|
||||
}
|
||||
|
||||
public List<LimitOrder> getLimitOrders(){
|
||||
return limitOrders;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,4 +18,13 @@ package cy.agorise.graphenej;
|
|||
public class Price {
|
||||
public AssetAmount base;
|
||||
public AssetAmount quote;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("base:[%s, %s], quote:[%s, %s]",
|
||||
base.getAsset().getObjectId(),
|
||||
base.getAmount().toString(),
|
||||
quote.getAsset().getObjectId(),
|
||||
quote.getAmount().toString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,4 +53,9 @@ public class PublicKey implements ByteSerializable, Serializable {
|
|||
PublicKey other = (PublicKey) obj;
|
||||
return this.publicKey.equals(other.getKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getAddress();
|
||||
}
|
||||
}
|
|
@ -14,15 +14,19 @@ public class RPC {
|
|||
public static final String CALL_CANCEL_ALL_SUBSCRIPTIONS = "cancel_all_subscriptions";
|
||||
public static final String CALL_GET_ACCOUNT_BY_NAME = "get_account_by_name";
|
||||
public static final String CALL_GET_ACCOUNTS = "get_accounts";
|
||||
public static final String CALL_GET_FULL_ACCOUNTS = "get_full_accounts";
|
||||
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";
|
||||
public static final String CALL_GET_KEY_REFERENCES = "get_key_references";
|
||||
public static final String CALL_GET_RELATIVE_ACCOUNT_HISTORY = "get_relative_account_history";
|
||||
public static final String CALL_GET_ACCOUNT_HISTORY = "get_account_history";
|
||||
public static final String CALL_GET_ACCOUNT_HISTORY_BY_OPERATIONS = "get_account_history_by_operations";
|
||||
public static final String CALL_LOOKUP_ACCOUNTS = "lookup_accounts";
|
||||
public static final String CALL_LIST_ASSETS = "list_assets";
|
||||
public static final String GET_OBJECTS = "get_objects";
|
||||
public static final String GET_ACCOUNT_BALANCES = "get_account_balances";
|
||||
public static final String CALL_GET_ASSETS = "get_assets";
|
||||
public static final String CALL_GET_OBJECTS = "get_objects";
|
||||
public static final String CALL_GET_ACCOUNT_BALANCES = "get_account_balances";
|
||||
public static final String CALL_LOOKUP_ASSET_SYMBOLS = "lookup_asset_symbols";
|
||||
public static final String CALL_GET_BLOCK_HEADER = "get_block_header";
|
||||
public static final String CALL_GET_BLOCK = "get_block";
|
||||
|
@ -30,4 +34,5 @@ public class RPC {
|
|||
public static final String CALL_GET_TRADE_HISTORY = "get_trade_history";
|
||||
public static final String CALL_GET_MARKET_HISTORY = "get_market_history";
|
||||
public static final String CALL_GET_ALL_ASSET_HOLDERS = "get_all_asset_holders";
|
||||
public static final String CALL_GET_TRANSACTION = "get_transaction";
|
||||
}
|
|
@ -20,6 +20,7 @@ import java.lang.reflect.Type;
|
|||
import java.text.ParsePosition;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
@ -97,16 +98,25 @@ public class Transaction implements ByteSerializable, JsonSerializable {
|
|||
public Transaction(BlockData blockData, List<BaseOperation> operationList){
|
||||
this.blockData = blockData;
|
||||
this.operations = operationList;
|
||||
this.extensions = new Extensions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the block data
|
||||
* Block data getter
|
||||
* @param blockData New block data
|
||||
*/
|
||||
public void setBlockData(BlockData blockData){
|
||||
this.blockData = blockData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Block data setter
|
||||
* @return BlockData instance
|
||||
*/
|
||||
public BlockData getBlockData(){
|
||||
return this.blockData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the fees for all operations in this transaction.
|
||||
* @param fees: New fees to apply
|
||||
|
@ -229,7 +239,12 @@ public class Transaction implements ByteSerializable, JsonSerializable {
|
|||
|
||||
// Getting the signature before anything else,
|
||||
// since this might change the transaction expiration data slightly
|
||||
byte[] signature = getGrapheneSignature();
|
||||
byte[] signature = null;
|
||||
try{
|
||||
signature = getGrapheneSignature();
|
||||
}catch(Exception e){
|
||||
System.out.println("Could not generate signature");
|
||||
}
|
||||
|
||||
// Formatting expiration time
|
||||
Date expirationTime = new Date(blockData.getExpiration() * 1000);
|
||||
|
@ -239,10 +254,12 @@ public class Transaction implements ByteSerializable, JsonSerializable {
|
|||
// Adding expiration
|
||||
obj.addProperty(KEY_EXPIRATION, dateFormat.format(expirationTime));
|
||||
|
||||
// Adding signatures
|
||||
if(signature != null){
|
||||
// Adding signature
|
||||
JsonArray signatureArray = new JsonArray();
|
||||
signatureArray.add(Util.bytesToHex(signature));
|
||||
obj.add(KEY_SIGNATURES, signatureArray);
|
||||
}
|
||||
|
||||
JsonArray operationsArray = new JsonArray();
|
||||
for(BaseOperation operation : operations){
|
||||
|
@ -259,7 +276,19 @@ public class Transaction implements ByteSerializable, JsonSerializable {
|
|||
obj.addProperty(KEY_REF_BLOCK_PREFIX, blockData.getRefBlockPrefix());
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that will return a hash of this transaction's data. The hash covers only the transaction
|
||||
* attributes and not the signature or the chain id.
|
||||
*
|
||||
* @return A hash of the serialized transaction.
|
||||
*/
|
||||
public byte[] getHash(){
|
||||
byte[] txBytes = toBytes();
|
||||
byte[] toHash = Arrays.copyOfRange(txBytes, 32, txBytes.length); //Tx data only, without chain id
|
||||
Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(toHash));
|
||||
return Arrays.copyOfRange(hash.getBytes(), 0, 20); // The hash is only the first 20 bytes
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -291,7 +320,8 @@ public class Transaction implements ByteSerializable, JsonSerializable {
|
|||
SimpleDateFormat dateFormat = new SimpleDateFormat(Util.TIME_DATE_FORMAT);
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
Date expirationDate = dateFormat.parse(expiration, new ParsePosition(0));
|
||||
BlockData blockData = new BlockData(refBlockNum, refBlockPrefix, expirationDate.getTime());
|
||||
long relativeExpiration = expirationDate.getTime() / 1000;
|
||||
BlockData blockData = new BlockData(refBlockNum, refBlockPrefix, relativeExpiration);
|
||||
|
||||
// Parsing operation list
|
||||
BaseOperation operation = null;
|
||||
|
@ -387,6 +417,26 @@ public class Transaction implements ByteSerializable, JsonSerializable {
|
|||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ASSET_CLAIM_FEES_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.FBA_DISTRIBUTE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.BID_COLLATERAL_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.EXECUTE_BID_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ASSET_CLAIM_POOL_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.ASSET_UPDATE_ISSUER_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.HTLC_CREATE_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.HTLC_REDEEM_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.HTLC_REDEEMED_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.HTLC_EXTEND_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
} else if (operationId == OperationType.HTLC_REFUND_OPERATION.ordinal()) {
|
||||
//TODO: Add operation deserialization support
|
||||
}
|
||||
if (operation != null) operationList.add(operation);
|
||||
operation = null;
|
||||
|
@ -401,4 +451,9 @@ public class Transaction implements ByteSerializable, JsonSerializable {
|
|||
return new Transaction(blockData, operationList);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.toJsonString();
|
||||
}
|
||||
}
|
|
@ -47,6 +47,7 @@ public class UserAccount extends GrapheneObject implements ByteSerializable, Jso
|
|||
public static final String KEY_OWNER_SPECIAL_AUTHORITY = "owner_special_authority";
|
||||
public static final String KEY_ACTIVE_SPECIAL_AUTHORITY = "active_special_authority";
|
||||
public static final String KEY_N_CONTROL_FLAGS = "top_n_control_flags";
|
||||
public static final String LIFETIME_EXPIRATION_DATE = "1969-12-31T23:59:59";
|
||||
|
||||
@Expose
|
||||
private String name;
|
||||
|
@ -84,6 +85,7 @@ public class UserAccount extends GrapheneObject implements ByteSerializable, Jso
|
|||
@Expose
|
||||
private long referrerRewardsPercentage;
|
||||
|
||||
private boolean isLifeTime;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -248,6 +250,14 @@ public class UserAccount extends GrapheneObject implements ByteSerializable, Jso
|
|||
this.statistics = statistics;
|
||||
}
|
||||
|
||||
public boolean isLifeTime() {
|
||||
return isLifeTime;
|
||||
}
|
||||
|
||||
public void setLifeTime(boolean lifeTime) {
|
||||
isLifeTime = lifeTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializer used to build a UserAccount instance from the full JSON-formatted response obtained
|
||||
* by the 'get_objects' API call.
|
||||
|
@ -274,8 +284,10 @@ public class UserAccount extends GrapheneObject implements ByteSerializable, Jso
|
|||
// Handling the deserialization and assignation of the membership date, which internally
|
||||
// is stored as a long POSIX time value
|
||||
try{
|
||||
Date date = dateFormat.parse(jsonAccount.get(KEY_MEMBERSHIP_EXPIRATION_DATE).getAsString());
|
||||
String expirationDate = jsonAccount.get(KEY_MEMBERSHIP_EXPIRATION_DATE).getAsString();
|
||||
Date date = dateFormat.parse(expirationDate);
|
||||
userAccount.setMembershipExpirationDate(date.getTime());
|
||||
userAccount.setLifeTime(expirationDate.equals(LIFETIME_EXPIRATION_DATE));
|
||||
} catch (ParseException e) {
|
||||
System.out.println("ParseException. Msg: "+e.getMessage());
|
||||
}
|
||||
|
|
|
@ -5,6 +5,10 @@ import com.google.common.primitives.UnsignedLong;
|
|||
|
||||
import org.spongycastle.crypto.DataLengthException;
|
||||
import org.spongycastle.crypto.InvalidCipherTextException;
|
||||
import org.spongycastle.crypto.digests.GeneralDigest;
|
||||
import org.spongycastle.crypto.digests.RIPEMD160Digest;
|
||||
import org.spongycastle.crypto.digests.SHA1Digest;
|
||||
import org.spongycastle.crypto.digests.SHA256Digest;
|
||||
import org.spongycastle.crypto.engines.AESFastEngine;
|
||||
import org.spongycastle.crypto.modes.CBCBlockCipher;
|
||||
import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||
|
@ -320,7 +324,9 @@ public class Util {
|
|||
}
|
||||
|
||||
byte[] temp = new byte[count];
|
||||
System.arraycopy(out, out.length - count, temp, 0, temp.length);
|
||||
int srcPos = out.length - count > 0 ? out.length - count : 0;
|
||||
int length = count < out.length ? count : out.length;
|
||||
System.arraycopy(out, srcPos, temp, 0, length);
|
||||
byte[] temp2 = new byte[count];
|
||||
Arrays.fill(temp2, (byte) count);
|
||||
if (Arrays.equals(temp, temp2)) {
|
||||
|
@ -383,4 +389,36 @@ public class Util {
|
|||
public static long toBase(double value, int precision){
|
||||
return (long) (value * Math.pow(10, precision));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hash for HTLC operations.
|
||||
*
|
||||
* @param preimage The data we want to operate on.
|
||||
* @param hashType The type of hash.
|
||||
* @return The hash.
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
public static byte[] htlcHash(byte[] preimage, HtlcHashType hashType) throws NoSuchAlgorithmException {
|
||||
byte[] out = null;
|
||||
GeneralDigest digest = null;
|
||||
switch(hashType){
|
||||
case RIPEMD160:
|
||||
digest = new RIPEMD160Digest();
|
||||
out = new byte[20];
|
||||
break;
|
||||
case SHA1:
|
||||
digest = new SHA1Digest();
|
||||
out = new byte[20];
|
||||
break;
|
||||
case SHA256:
|
||||
digest = new SHA256Digest();
|
||||
out = new byte[32];
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Not supported hash function!");
|
||||
}
|
||||
digest.update(preimage, 0, preimage.length);
|
||||
digest.doFinal(out, 0);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package cy.agorise.graphenej.api;
|
||||
|
||||
/**
|
||||
* Class used to list all currently supported API accesses
|
||||
*/
|
||||
|
||||
public class ApiAccess {
|
||||
public static final int API_NONE = 0x00;
|
||||
public static final int API_DATABASE = 0x01;
|
||||
public static final int API_HISTORY = 0x02;
|
||||
public static final int API_NETWORK_BROADCAST = 0x04;
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package cy.agorise.graphenej.api;
|
||||
|
||||
/**
|
||||
* Class used to send connection status updates.
|
||||
*
|
||||
* Connection status updates can be any of the following:
|
||||
* - {@link ConnectionStatusUpdate#CONNECTED}
|
||||
* - {@link ConnectionStatusUpdate#AUTHENTICATED}
|
||||
* - {@link ConnectionStatusUpdate#API_UPDATE}
|
||||
* - {@link ConnectionStatusUpdate#DISCONNECTED}
|
||||
*
|
||||
* This is specified by the field called {@link #updateCode}.
|
||||
*
|
||||
* If the updateCode is ConnectionStatusUpdate#API_UPDATE another extra field called
|
||||
* {@link #api} is used to specify which api we're getting access to.
|
||||
*/
|
||||
|
||||
public class ConnectionStatusUpdate {
|
||||
// Constant used to announce that a connection has been established
|
||||
public final static int CONNECTED = 0;
|
||||
// Constant used to announce a successful authentication
|
||||
public final static int AUTHENTICATED = 1;
|
||||
// Constant used to announce an api update
|
||||
public final static int API_UPDATE = 2;
|
||||
// Constant used to announce a disconnection event
|
||||
public final static int DISCONNECTED = 3;
|
||||
|
||||
/**
|
||||
* The update code is the general purpose of the update message. Can be any of the following:
|
||||
* - {@link ConnectionStatusUpdate#CONNECTED}
|
||||
* - {@link ConnectionStatusUpdate#AUTHENTICATED}
|
||||
* - {@link ConnectionStatusUpdate#API_UPDATE}
|
||||
* - {@link ConnectionStatusUpdate#DISCONNECTED}
|
||||
*/
|
||||
private int updateCode;
|
||||
|
||||
/**
|
||||
* This field is used in case the updateCode is {@link ConnectionStatusUpdate#API_UPDATE} and
|
||||
* it serves to specify which API we're getting access to.
|
||||
*
|
||||
* It can be any of the fields defined in {@link ApiAccess}
|
||||
*/
|
||||
private int api;
|
||||
|
||||
public ConnectionStatusUpdate(int updateCode, int api){
|
||||
this.updateCode = updateCode;
|
||||
this.api = api;
|
||||
}
|
||||
|
||||
public int getUpdateCode() {
|
||||
return updateCode;
|
||||
}
|
||||
|
||||
public void setUpdateCode(int updateCode) {
|
||||
this.updateCode = updateCode;
|
||||
}
|
||||
|
||||
public int getApi() {
|
||||
return api;
|
||||
}
|
||||
|
||||
public void setApi(int api) {
|
||||
this.api = api;
|
||||
}
|
||||
}
|
|
@ -79,7 +79,7 @@ public class GetAccountBalances extends BaseGrapheneHandler {
|
|||
}
|
||||
params.add(mUserAccount.getObjectId());
|
||||
params.add(assetList);
|
||||
ApiCall apiCall = new ApiCall(0, RPC.GET_ACCOUNT_BALANCES, params, RPC.VERSION, requestId);
|
||||
ApiCall apiCall = new ApiCall(0, RPC.CALL_GET_ACCOUNT_BALANCES, params, RPC.VERSION, requestId);
|
||||
websocket.sendText(apiCall.toJsonString());
|
||||
}
|
||||
|
||||
|
|
|
@ -5,12 +5,10 @@ import com.google.gson.GsonBuilder;
|
|||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.neovisionaries.ws.client.WebSocket;
|
||||
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;
|
||||
|
@ -24,7 +22,9 @@ import cy.agorise.graphenej.RPC;
|
|||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
import cy.agorise.graphenej.models.AssetFeed;
|
||||
import cy.agorise.graphenej.models.BitAssetData;
|
||||
import cy.agorise.graphenej.models.ReportedAssetFeed;
|
||||
import cy.agorise.graphenej.models.WitnessResponse;
|
||||
|
||||
/**
|
||||
|
@ -75,11 +75,9 @@ public class GetObjects extends BaseGrapheneHandler {
|
|||
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
ArrayList<Serializable> subParams = new ArrayList<>();
|
||||
for(String id : this.ids){
|
||||
subParams.add(id);
|
||||
}
|
||||
subParams.addAll(this.ids);
|
||||
params.add(subParams);
|
||||
ApiCall apiCall = new ApiCall(0, RPC.GET_OBJECTS, params, RPC.VERSION, 0);
|
||||
ApiCall apiCall = new ApiCall(0, RPC.CALL_GET_OBJECTS, params, RPC.VERSION, 0);
|
||||
websocket.sendText(apiCall.toJsonString());
|
||||
}
|
||||
|
||||
|
@ -91,6 +89,9 @@ public class GetObjects extends BaseGrapheneHandler {
|
|||
String response = frame.getPayloadText();
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
|
||||
gsonBuilder.registerTypeAdapter(BitAssetData.class, new BitAssetData.BitAssetDataDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(AssetFeed.class, new AssetFeed.AssetFeedDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(ReportedAssetFeed.class, new ReportedAssetFeed.ReportedAssetFeedDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(Asset.class, new Asset.AssetDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(UserAccount.class, new UserAccount.UserAccountFullDeserializer());
|
||||
|
@ -102,7 +103,8 @@ public class GetObjects extends BaseGrapheneHandler {
|
|||
|
||||
JsonParser parser = new JsonParser();
|
||||
JsonArray resultArray = parser.parse(response).getAsJsonObject().get(WitnessResponse.KEY_RESULT).getAsJsonArray();
|
||||
for(JsonElement element : resultArray){
|
||||
for(int i = 0; i < resultArray.size(); i++){
|
||||
JsonElement element = resultArray.get(i);
|
||||
String id = element.getAsJsonObject().get(GrapheneObject.KEY_ID).getAsString();
|
||||
GrapheneObject grapheneObject = new GrapheneObject(id);
|
||||
switch (grapheneObject.getObjectType()){
|
||||
|
@ -115,10 +117,9 @@ public class GetObjects extends BaseGrapheneHandler {
|
|||
parsedResult.add(account);
|
||||
break;
|
||||
case ASSET_BITASSET_DATA:
|
||||
Type BitAssetDataType = new TypeToken<WitnessResponse<List<BitAssetData>>>(){}.getType();
|
||||
WitnessResponse<List<BitAssetData>> witnessResponse = gsonBuilder.create().fromJson(response, BitAssetDataType);
|
||||
BitAssetData bitAssetData = witnessResponse.result.get(0);
|
||||
BitAssetData bitAssetData = gson.fromJson(element, BitAssetData.class);
|
||||
parsedResult.add(bitAssetData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,9 +18,9 @@ import cy.agorise.graphenej.UserAccount;
|
|||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
import cy.agorise.graphenej.models.BaseResponse;
|
||||
import cy.agorise.graphenej.models.HistoricalTransfer;
|
||||
import cy.agorise.graphenej.models.OperationHistory;
|
||||
import cy.agorise.graphenej.models.WitnessResponse;
|
||||
import cy.agorise.graphenej.objects.Memo;
|
||||
import cy.agorise.graphenej.Memo;
|
||||
import cy.agorise.graphenej.operations.TransferOperation;
|
||||
|
||||
/**
|
||||
|
@ -158,12 +158,13 @@ public class GetRelativeAccountHistory extends BaseGrapheneHandler {
|
|||
|
||||
sendRelativeAccountHistoryRequest();
|
||||
}else if(baseResponse.id >= GET_HISTORY_DATA){
|
||||
Type RelativeAccountHistoryResponse = new TypeToken<WitnessResponse<List<HistoricalTransfer>>>(){}.getType();
|
||||
Type RelativeAccountHistoryResponse = new TypeToken<WitnessResponse<List<OperationHistory>>>(){}.getType();
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
gsonBuilder.registerTypeAdapter(OperationHistory.class, new OperationHistory.OperationHistoryDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(TransferOperation.class, new TransferOperation.TransferDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer());
|
||||
gsonBuilder.registerTypeAdapter(Memo.class, new Memo.MemoDeserializer());
|
||||
WitnessResponse<List<HistoricalTransfer>> transfersResponse = gsonBuilder.create().fromJson(response, RelativeAccountHistoryResponse);
|
||||
WitnessResponse<List<OperationHistory>> transfersResponse = gsonBuilder.create().fromJson(response, RelativeAccountHistoryResponse);
|
||||
mListener.onSuccess(transfersResponse);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,9 +23,10 @@ import cy.agorise.graphenej.interfaces.SubscriptionHub;
|
|||
import cy.agorise.graphenej.interfaces.SubscriptionListener;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
import cy.agorise.graphenej.models.DynamicGlobalProperties;
|
||||
import cy.agorise.graphenej.models.OperationHistory;
|
||||
import cy.agorise.graphenej.models.SubscriptionResponse;
|
||||
import cy.agorise.graphenej.models.WitnessResponse;
|
||||
import cy.agorise.graphenej.objects.Memo;
|
||||
import cy.agorise.graphenej.Memo;
|
||||
import cy.agorise.graphenej.operations.CustomOperation;
|
||||
import cy.agorise.graphenej.operations.LimitOrderCreateOperation;
|
||||
import cy.agorise.graphenej.operations.TransferOperation;
|
||||
|
@ -61,6 +62,7 @@ public class SubscriptionMessagesHub extends BaseGrapheneHandler implements Subs
|
|||
private int subscriptionCounter = 0;
|
||||
private HashMap<Long, BaseGrapheneHandler> mHandlerMap = new HashMap<>();
|
||||
private List<BaseGrapheneHandler> pendingHandlerList = new ArrayList<>();
|
||||
private boolean printLogs = true;
|
||||
|
||||
// State variables
|
||||
private boolean isUnsubscribing;
|
||||
|
@ -95,6 +97,7 @@ public class SubscriptionMessagesHub extends BaseGrapheneHandler implements Subs
|
|||
builder.registerTypeAdapter(UserAccount.class, new UserAccount.UserAccountSimpleDeserializer());
|
||||
builder.registerTypeAdapter(DynamicGlobalProperties.class, new DynamicGlobalProperties.DynamicGlobalPropertiesDeserializer());
|
||||
builder.registerTypeAdapter(Memo.class, new Memo.MemoDeserializer());
|
||||
builder.registerTypeAdapter(OperationHistory.class, new OperationHistory.OperationHistoryDeserializer());
|
||||
this.gson = builder.create();
|
||||
}
|
||||
|
||||
|
@ -141,7 +144,7 @@ public class SubscriptionMessagesHub extends BaseGrapheneHandler implements Subs
|
|||
@Override
|
||||
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
|
||||
String message = frame.getPayloadText();
|
||||
System.out.println("<< "+message);
|
||||
if(printLogs) System.out.println("<< "+message);
|
||||
if(currentId == LOGIN_ID){
|
||||
currentId = GET_DATABASE_ID;
|
||||
ArrayList<Serializable> emptyParams = new ArrayList<>();
|
||||
|
@ -185,7 +188,7 @@ public class SubscriptionMessagesHub extends BaseGrapheneHandler implements Subs
|
|||
}
|
||||
|
||||
payload.add(objects);
|
||||
ApiCall subscribe = new ApiCall(databaseApiId, RPC.GET_OBJECTS, payload, RPC.VERSION, MANUAL_SUBSCRIPTION_ID);
|
||||
ApiCall subscribe = new ApiCall(databaseApiId, RPC.CALL_GET_OBJECTS, payload, RPC.VERSION, MANUAL_SUBSCRIPTION_ID);
|
||||
websocket.sendText(subscribe.toJsonString());
|
||||
subscriptionCounter++;
|
||||
}else{
|
||||
|
@ -316,4 +319,12 @@ public class SubscriptionMessagesHub extends BaseGrapheneHandler implements Subs
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setPrintLogs(boolean printLogs){
|
||||
this.printLogs = printLogs;
|
||||
}
|
||||
|
||||
public boolean isPrintLogs(){
|
||||
return this.printLogs;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,265 @@
|
|||
package cy.agorise.graphenej.api.android;
|
||||
|
||||
import com.google.gson.ExclusionStrategy;
|
||||
import com.google.gson.FieldAttributes;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.AccountOptions;
|
||||
import cy.agorise.graphenej.Asset;
|
||||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.AssetOptions;
|
||||
import cy.agorise.graphenej.Authority;
|
||||
import cy.agorise.graphenej.BaseOperation;
|
||||
import cy.agorise.graphenej.Extensions;
|
||||
import cy.agorise.graphenej.LimitOrder;
|
||||
import cy.agorise.graphenej.Memo;
|
||||
import cy.agorise.graphenej.Transaction;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.api.calls.GetAccountBalances;
|
||||
import cy.agorise.graphenej.api.calls.GetAccountByName;
|
||||
import cy.agorise.graphenej.api.calls.GetAccountHistoryByOperations;
|
||||
import cy.agorise.graphenej.api.calls.GetAccounts;
|
||||
import cy.agorise.graphenej.api.calls.GetAssets;
|
||||
import cy.agorise.graphenej.api.calls.GetBlock;
|
||||
import cy.agorise.graphenej.api.calls.GetBlockHeader;
|
||||
import cy.agorise.graphenej.api.calls.GetDynamicGlobalProperties;
|
||||
import cy.agorise.graphenej.api.calls.GetFullAccounts;
|
||||
import cy.agorise.graphenej.api.calls.GetKeyReferences;
|
||||
import cy.agorise.graphenej.api.calls.GetLimitOrders;
|
||||
import cy.agorise.graphenej.api.calls.GetMarketHistory;
|
||||
import cy.agorise.graphenej.api.calls.GetObjects;
|
||||
import cy.agorise.graphenej.api.calls.GetRelativeAccountHistory;
|
||||
import cy.agorise.graphenej.api.calls.GetRequiredFees;
|
||||
import cy.agorise.graphenej.api.calls.GetTransaction;
|
||||
import cy.agorise.graphenej.api.calls.ListAssets;
|
||||
import cy.agorise.graphenej.api.calls.LookupAssetSymbols;
|
||||
import cy.agorise.graphenej.models.AccountProperties;
|
||||
import cy.agorise.graphenej.models.AssetFeed;
|
||||
import cy.agorise.graphenej.models.BitAssetData;
|
||||
import cy.agorise.graphenej.models.Block;
|
||||
import cy.agorise.graphenej.models.BlockHeader;
|
||||
import cy.agorise.graphenej.models.BucketObject;
|
||||
import cy.agorise.graphenej.models.DynamicGlobalProperties;
|
||||
import cy.agorise.graphenej.models.FullAccountDetails;
|
||||
import cy.agorise.graphenej.models.HistoryOperationDetail;
|
||||
import cy.agorise.graphenej.models.OperationHistory;
|
||||
import cy.agorise.graphenej.models.ReportedAssetFeed;
|
||||
import cy.agorise.graphenej.operations.CustomOperation;
|
||||
import cy.agorise.graphenej.operations.LimitOrderCreateOperation;
|
||||
import cy.agorise.graphenej.operations.TransferOperation;
|
||||
|
||||
/**
|
||||
* Class used to store a mapping of request class to two important things:
|
||||
*
|
||||
* 1- The class to which the corresponding response should be de-serialized to
|
||||
* 2- An instance of the Gson class, with all required type adapters
|
||||
*/
|
||||
public class DeserializationMap {
|
||||
private final String TAG = this.getClass().getName();
|
||||
|
||||
private HashMap<Class, Class> mClassMap = new HashMap<>();
|
||||
|
||||
private HashMap<Class, Gson> mGsonMap = new HashMap<>();
|
||||
|
||||
DeserializationMap(){
|
||||
Gson genericGson = new Gson();
|
||||
|
||||
// GetBlock
|
||||
mClassMap.put(GetBlock.class, Block.class);
|
||||
Gson getBlockGson = new GsonBuilder()
|
||||
.registerTypeAdapter(Transaction.class, new Transaction.TransactionDeserializer())
|
||||
.registerTypeAdapter(TransferOperation.class, new TransferOperation.TransferDeserializer())
|
||||
.registerTypeAdapter(LimitOrderCreateOperation.class, new LimitOrderCreateOperation.LimitOrderCreateDeserializer())
|
||||
.registerTypeAdapter(CustomOperation.class, new CustomOperation.CustomOperationDeserializer())
|
||||
.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer())
|
||||
.create();
|
||||
mGsonMap.put(GetBlock.class, getBlockGson);
|
||||
|
||||
// GetAccounts
|
||||
mClassMap.put(GetAccounts.class, List.class);
|
||||
Gson getAccountsGson = new GsonBuilder()
|
||||
.registerTypeAdapter(Authority.class, new Authority.AuthorityDeserializer())
|
||||
.registerTypeAdapter(AccountOptions.class, new AccountOptions.AccountOptionsDeserializer(false))
|
||||
.create();
|
||||
mGsonMap.put(GetAccounts.class, getAccountsGson);
|
||||
|
||||
// GetRequiredFees
|
||||
mClassMap.put(GetRequiredFees.class, List.class);
|
||||
Gson getRequiredFeesGson = new GsonBuilder()
|
||||
.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer())
|
||||
.create();
|
||||
mGsonMap.put(GetRequiredFees.class, getRequiredFeesGson);
|
||||
|
||||
// GetRelativeAccountHistory
|
||||
mClassMap.put(GetRelativeAccountHistory.class, List.class);
|
||||
Gson getRelativeAcountHistoryGson = new GsonBuilder()
|
||||
.setExclusionStrategies(new SkipAccountOptionsStrategy(), new SkipAssetOptionsStrategy())
|
||||
.registerTypeAdapter(BaseOperation.class, new BaseOperation.OperationDeserializer())
|
||||
.registerTypeAdapter(OperationHistory.class, new OperationHistory.OperationHistoryDeserializer())
|
||||
.registerTypeAdapter(TransferOperation.class, new TransferOperation.TransferDeserializer())
|
||||
.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer())
|
||||
.registerTypeAdapter(Memo.class, new Memo.MemoDeserializer())
|
||||
.create();
|
||||
mGsonMap.put(GetRelativeAccountHistory.class, getRelativeAcountHistoryGson);
|
||||
|
||||
// GetBlockHeader
|
||||
mClassMap.put(GetBlockHeader.class, BlockHeader.class);
|
||||
mGsonMap.put(GetBlockHeader.class, genericGson);
|
||||
|
||||
// GetMarketHistory
|
||||
mClassMap.put(GetMarketHistory.class, List.class);
|
||||
Gson getMarketHistoryGson = new GsonBuilder()
|
||||
.registerTypeAdapter(BucketObject.class, new BucketObject.BucketDeserializer())
|
||||
.create();
|
||||
mGsonMap.put(GetMarketHistory.class, getMarketHistoryGson);
|
||||
|
||||
// LookupAssetSymbols
|
||||
mClassMap.put(LookupAssetSymbols.class, List.class);
|
||||
Gson lookupAssetSymbolGson = new GsonBuilder()
|
||||
.registerTypeAdapter(Asset.class, new Asset.AssetDeserializer())
|
||||
.create();
|
||||
mGsonMap.put(LookupAssetSymbols.class, lookupAssetSymbolGson);
|
||||
|
||||
// GetObjects
|
||||
mClassMap.put(GetObjects.class, List.class);
|
||||
Gson getObjectsGson = new GsonBuilder()
|
||||
.registerTypeAdapter(Asset.class, new Asset.AssetDeserializer())
|
||||
.registerTypeAdapter(BitAssetData.class, new BitAssetData.BitAssetDataDeserializer())
|
||||
.registerTypeAdapter(ReportedAssetFeed.class, new ReportedAssetFeed.ReportedAssetFeedDeserializer())
|
||||
.registerTypeAdapter(AssetFeed.class, new AssetFeed.AssetFeedDeserializer())
|
||||
.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer())
|
||||
.create();
|
||||
mGsonMap.put(GetObjects.class, getObjectsGson);
|
||||
|
||||
// ListAssets
|
||||
mClassMap.put(ListAssets.class, List.class);
|
||||
Gson listAssetsGson = new GsonBuilder()
|
||||
.registerTypeAdapter(Asset.class, new Asset.AssetDeserializer())
|
||||
.create();
|
||||
mGsonMap.put(ListAssets.class, listAssetsGson);
|
||||
|
||||
// GetAccountByName
|
||||
mClassMap.put(GetAccountByName.class, AccountProperties.class);
|
||||
Gson getAccountByNameGson = new GsonBuilder()
|
||||
.registerTypeAdapter(Authority.class, new Authority.AuthorityDeserializer())
|
||||
.registerTypeAdapter(AccountOptions.class, new AccountOptions.AccountOptionsDeserializer())
|
||||
.create();
|
||||
mGsonMap.put(GetAccountByName.class, getAccountByNameGson);
|
||||
|
||||
// GetLimitOrders
|
||||
mClassMap.put(GetLimitOrders.class, List.class);
|
||||
Gson getLimitOrdersGson = new GsonBuilder()
|
||||
.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer())
|
||||
.registerTypeAdapter(UserAccount.class, new UserAccount.UserAccountSimpleDeserializer())
|
||||
.registerTypeAdapter(LimitOrder.class, new LimitOrder.LimitOrderDeserializer())
|
||||
.create();
|
||||
mGsonMap.put(GetLimitOrders.class, getLimitOrdersGson);
|
||||
|
||||
// GetAccountHistoryByOperations
|
||||
mClassMap.put(GetAccountHistoryByOperations.class, HistoryOperationDetail.class);
|
||||
Gson getAccountHistoryByOperationsGson = new GsonBuilder()
|
||||
.setExclusionStrategies(new DeserializationMap.SkipAccountOptionsStrategy(), new DeserializationMap.SkipAssetOptionsStrategy())
|
||||
.registerTypeAdapter(BaseOperation.class, new BaseOperation.OperationDeserializer())
|
||||
.registerTypeAdapter(OperationHistory.class, new OperationHistory.OperationHistoryDeserializer())
|
||||
.registerTypeAdapter(Extensions.class, new Extensions.ExtensionsDeserializer())
|
||||
.registerTypeAdapter(Memo.class, new Memo.MemoDeserializer())
|
||||
.registerTypeAdapter(UserAccount.class, new UserAccount.UserAccountSimpleDeserializer())
|
||||
.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer())
|
||||
.create();
|
||||
mGsonMap.put(GetAccountHistoryByOperations.class, getAccountHistoryByOperationsGson);
|
||||
|
||||
// GetFullAccounts
|
||||
mClassMap.put(GetFullAccounts.class, List.class);
|
||||
Gson getFullAccountsGson = new GsonBuilder()
|
||||
.registerTypeAdapter(FullAccountDetails.class, new FullAccountDetails.FullAccountDeserializer())
|
||||
.registerTypeAdapter(Authority.class, new Authority.AuthorityDeserializer())
|
||||
.registerTypeAdapter(Memo.class, new Memo.MemoDeserializer())
|
||||
.registerTypeAdapter(AccountOptions.class, new AccountOptions.AccountOptionsDeserializer())
|
||||
.create();
|
||||
mGsonMap.put(GetFullAccounts.class, getFullAccountsGson);
|
||||
|
||||
// GetDynamicGlobalProperties
|
||||
mClassMap.put(GetDynamicGlobalProperties.class, DynamicGlobalProperties.class);
|
||||
Gson getDynamicGlobalPropertiesGson = new GsonBuilder()
|
||||
.registerTypeAdapter(DynamicGlobalProperties.class, new DynamicGlobalProperties.DynamicGlobalPropertiesDeserializer())
|
||||
.create();
|
||||
mGsonMap.put(GetDynamicGlobalProperties.class, getDynamicGlobalPropertiesGson);
|
||||
|
||||
// GetKeyReferences
|
||||
mClassMap.put(GetKeyReferences.class, List.class);
|
||||
Gson getKeyReferencesGson = new GsonBuilder()
|
||||
.registerTypeAdapter(UserAccount.class, new UserAccount.UserAccountSimpleDeserializer())
|
||||
.create();
|
||||
mGsonMap.put(GetKeyReferences.class, getKeyReferencesGson);
|
||||
|
||||
// GetAccountBalances
|
||||
mClassMap.put(GetAccountBalances.class, List.class);
|
||||
Gson getAccountBalancesGson = new GsonBuilder()
|
||||
.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer())
|
||||
.create();
|
||||
mGsonMap.put(GetAccountBalances.class, getAccountBalancesGson);
|
||||
|
||||
// GetAssets
|
||||
mClassMap.put(GetAssets.class, List.class);
|
||||
Gson getAssetsGson = new GsonBuilder()
|
||||
.registerTypeAdapter(Asset.class, new Asset.AssetDeserializer())
|
||||
.create();
|
||||
mGsonMap.put(GetAssets.class, getAssetsGson);
|
||||
|
||||
// GetTransaction
|
||||
mClassMap.put(GetTransaction.class, Transaction.class);
|
||||
Gson getTransactionGson = new GsonBuilder()
|
||||
.registerTypeAdapter(Transaction.class, new Transaction.TransactionDeserializer())
|
||||
.registerTypeAdapter(Memo.class, new Memo.MemoDeserializer())
|
||||
.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer())
|
||||
.registerTypeAdapter(TransferOperation.class, new TransferOperation.TransferDeserializer())
|
||||
.create();
|
||||
mGsonMap.put(GetTransaction.class, getTransactionGson);
|
||||
}
|
||||
|
||||
public Class getReceivedClass(Class _class){
|
||||
return mClassMap.get(_class);
|
||||
}
|
||||
|
||||
public Gson getGson(Class aClass) {
|
||||
return mGsonMap.get(aClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is required in order to break a recursion loop when de-serializing the
|
||||
* AccountProperties class instance.
|
||||
*/
|
||||
public static class SkipAccountOptionsStrategy implements ExclusionStrategy {
|
||||
|
||||
@Override
|
||||
public boolean shouldSkipField(FieldAttributes f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSkipClass(Class<?> clazz) {
|
||||
return clazz == AccountOptions.class;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is required in order to break a recursion loop when de-serializing the
|
||||
* AssetAmount instance.
|
||||
*/
|
||||
public static class SkipAssetOptionsStrategy implements ExclusionStrategy {
|
||||
|
||||
@Override
|
||||
public boolean shouldSkipField(FieldAttributes f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSkipClass(Class<?> clazz) {
|
||||
return clazz == AssetOptions.class;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,795 @@
|
|||
package cy.agorise.graphenej.api.android;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import cy.agorise.graphenej.Asset;
|
||||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.BaseOperation;
|
||||
import cy.agorise.graphenej.LimitOrder;
|
||||
import cy.agorise.graphenej.Memo;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.Transaction;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.api.ConnectionStatusUpdate;
|
||||
import cy.agorise.graphenej.api.calls.ApiCallable;
|
||||
import cy.agorise.graphenej.api.calls.GetAccountBalances;
|
||||
import cy.agorise.graphenej.api.calls.GetAccounts;
|
||||
import cy.agorise.graphenej.api.calls.GetAssets;
|
||||
import cy.agorise.graphenej.api.calls.GetFullAccounts;
|
||||
import cy.agorise.graphenej.api.calls.GetKeyReferences;
|
||||
import cy.agorise.graphenej.api.calls.GetLimitOrders;
|
||||
import cy.agorise.graphenej.api.calls.GetMarketHistory;
|
||||
import cy.agorise.graphenej.api.calls.GetObjects;
|
||||
import cy.agorise.graphenej.api.calls.GetRelativeAccountHistory;
|
||||
import cy.agorise.graphenej.api.calls.GetRequiredFees;
|
||||
import cy.agorise.graphenej.api.calls.ListAssets;
|
||||
import cy.agorise.graphenej.models.AccountProperties;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
import cy.agorise.graphenej.models.BitAssetData;
|
||||
import cy.agorise.graphenej.models.Block;
|
||||
import cy.agorise.graphenej.models.BlockHeader;
|
||||
import cy.agorise.graphenej.models.BucketObject;
|
||||
import cy.agorise.graphenej.models.DynamicGlobalProperties;
|
||||
import cy.agorise.graphenej.models.FullAccountDetails;
|
||||
import cy.agorise.graphenej.models.HistoryOperationDetail;
|
||||
import cy.agorise.graphenej.models.JsonRpcNotification;
|
||||
import cy.agorise.graphenej.models.JsonRpcResponse;
|
||||
import cy.agorise.graphenej.models.OperationHistory;
|
||||
import cy.agorise.graphenej.network.FullNode;
|
||||
import cy.agorise.graphenej.network.LatencyNodeProvider;
|
||||
import cy.agorise.graphenej.network.NodeLatencyVerifier;
|
||||
import cy.agorise.graphenej.network.NodeProvider;
|
||||
import cy.agorise.graphenej.operations.CustomOperation;
|
||||
import cy.agorise.graphenej.operations.LimitOrderCreateOperation;
|
||||
import cy.agorise.graphenej.operations.TransferOperation;
|
||||
import cy.agorise.graphenej.stats.ExponentialMovingAverage;
|
||||
import io.reactivex.Observer;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.annotations.Nullable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.WebSocket;
|
||||
import okhttp3.WebSocketListener;
|
||||
|
||||
/**
|
||||
* Service in charge of maintaining a connection to the full node.
|
||||
*/
|
||||
|
||||
public class NetworkService extends Service {
|
||||
private final String TAG = this.getClass().getName();
|
||||
|
||||
public static final int NORMAL_CLOSURE_STATUS = 1000;
|
||||
private static final int GOING_AWAY_STATUS = 1001;
|
||||
|
||||
// Time to wait before retrying a connection attempt
|
||||
private static final int DEFAULT_RETRY_DELAY = 500;
|
||||
|
||||
// Default connection delay when using the node latency verification strategy. This initial
|
||||
// delay is required in order ot make sure we have a fair selection of node latencies from
|
||||
// which we can choose from.
|
||||
private final int DEFAULT_INITIAL_DELAY = 500;
|
||||
|
||||
/**
|
||||
* Constant to be used as a key in order to pass the user name information, in case the
|
||||
* provided API nodes might require this information.
|
||||
*/
|
||||
public static final String KEY_USERNAME = "key_username";
|
||||
|
||||
/**
|
||||
* Constant to be used as a key in order to pass the password information, in case the
|
||||
* provided API nodes might require this information.
|
||||
* <p>
|
||||
* This information should be passed as an intent extra when calling the bindService
|
||||
* or startService methods.
|
||||
*/
|
||||
public static final String KEY_PASSWORD = "key_password";
|
||||
|
||||
/**
|
||||
* Constant used as a key in order to specify which APIs the application will be requiring.
|
||||
* <p>
|
||||
* This information should be passed as an intent extra when calling the bindService
|
||||
* or startService methods.
|
||||
*/
|
||||
public static final String KEY_REQUESTED_APIS = "key_requested_apis";
|
||||
|
||||
/**
|
||||
* Constant used as a key in order to let the NetworkService know whether or not it should
|
||||
* start a recurring node latency verification task.
|
||||
* <p>
|
||||
* This information should be passed as an intent extra when calling the bindService
|
||||
* or startService methods.
|
||||
*/
|
||||
public static final String KEY_ENABLE_LATENCY_VERIFIER = "key_enable_latency_verifier";
|
||||
|
||||
/**
|
||||
* Constant used as a key in order to specify the alpha (or smoothing) factor to be used in
|
||||
* the exponential moving average calculated from the different latency samples. This only
|
||||
* makes sense if the latency verification feature is enabled of course.
|
||||
* <p>
|
||||
* This information should be passed as an intent extra when calling the bindService
|
||||
* or startService methods.
|
||||
*/
|
||||
public static final String KEY_NODE_LATENCY_SMOOTHING_FACTOR = "key_node_latency_smoothing_factor";
|
||||
|
||||
/**
|
||||
* Key used to pass via intent a boolean extra to specify whether the connection should
|
||||
* be automatically established.
|
||||
* <p>
|
||||
* This information should be passed as an intent extra when calling the bindService
|
||||
* or startService methods.
|
||||
*/
|
||||
public static final String KEY_AUTO_CONNECT = "key_auto_connect";
|
||||
|
||||
/**
|
||||
* Key used to pass via intent a list of node URLs. The value passed should be a String
|
||||
* containing a simple comma separated list of URLs.
|
||||
* <p>
|
||||
* For example:
|
||||
*
|
||||
* wss://domain1.com/ws,wss://domain2.com/ws,wss://domain3.com/ws
|
||||
* <p>
|
||||
* This information should be passed as an intent extra when calling the bindService
|
||||
* or startService methods.
|
||||
*/
|
||||
public static final String KEY_NODE_URLS = "key_node_urls";
|
||||
|
||||
private final IBinder mBinder = new LocalBinder();
|
||||
|
||||
private WebSocket mWebSocket;
|
||||
|
||||
// Username and password used to connect to a specific node
|
||||
private String mUsername;
|
||||
private String mPassword;
|
||||
|
||||
private boolean isLoggedIn = false;
|
||||
|
||||
private String mLastCall;
|
||||
private long mCurrentId = 0;
|
||||
|
||||
// Requested APIs passed to this service
|
||||
private int mRequestedApis;
|
||||
|
||||
// Variable used to keep track of the currently obtained API accesses
|
||||
private HashMap<Integer, Integer> mApiIds = new HashMap<Integer, Integer>();
|
||||
|
||||
// Variable used as a source of node information
|
||||
private NodeProvider nodeProvider = new LatencyNodeProvider();
|
||||
|
||||
// Class used to obtain frequent node latency updates
|
||||
private NodeLatencyVerifier nodeLatencyVerifier;
|
||||
|
||||
// PublishSubject used to announce full node latencies updates
|
||||
private PublishSubject<FullNode> fullNodePublishSubject;
|
||||
|
||||
// Counter used to trigger the connection only after we've received enough node latency updates
|
||||
private long latencyUpdateCounter;
|
||||
|
||||
// Property used to keep track of the currently active node
|
||||
private FullNode mSelectedNode;
|
||||
|
||||
private Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
private Gson gson = new GsonBuilder()
|
||||
.registerTypeAdapter(Transaction.class, new Transaction.TransactionDeserializer())
|
||||
.registerTypeAdapter(TransferOperation.class, new TransferOperation.TransferDeserializer())
|
||||
.registerTypeAdapter(LimitOrderCreateOperation.class, new LimitOrderCreateOperation.LimitOrderCreateDeserializer())
|
||||
.registerTypeAdapter(CustomOperation.class, new CustomOperation.CustomOperationDeserializer())
|
||||
.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer())
|
||||
.registerTypeAdapter(UserAccount.class, new UserAccount.UserAccountSimpleDeserializer())
|
||||
.registerTypeAdapter(DynamicGlobalProperties.class, new DynamicGlobalProperties.DynamicGlobalPropertiesDeserializer())
|
||||
.registerTypeAdapter(Memo.class, new Memo.MemoDeserializer())
|
||||
.registerTypeAdapter(BaseOperation.class, new BaseOperation.OperationDeserializer())
|
||||
.registerTypeAdapter(OperationHistory.class, new OperationHistory.OperationHistoryDeserializer())
|
||||
.registerTypeAdapter(JsonRpcNotification.class, new JsonRpcNotification.JsonRpcNotificationDeserializer())
|
||||
.create();
|
||||
|
||||
// Map used to keep track of outgoing request ids and its request types. This is just
|
||||
// one of two required mappings. The second one is implemented by the DeserializationMap
|
||||
// class.
|
||||
private HashMap<Long, Class> mRequestClassMap = new HashMap<>();
|
||||
|
||||
// This class is used to keep track of the mapping between request classes and response
|
||||
// payload classes. It also provides a handy method that returns a Gson deserializer instance
|
||||
// suited for every response type.
|
||||
private DeserializationMap mDeserializationMap = new DeserializationMap();
|
||||
|
||||
/**
|
||||
* Actually establishes a connection from this Service to one of the full nodes.
|
||||
*/
|
||||
public void connect(){
|
||||
OkHttpClient client = new OkHttpClient
|
||||
.Builder()
|
||||
.connectTimeout(2, TimeUnit.SECONDS)
|
||||
.readTimeout(5, TimeUnit.SECONDS)
|
||||
.writeTimeout(5, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
synchronized (mWebSocketListener){
|
||||
mSelectedNode = nodeProvider.getBestNode();
|
||||
if(mSelectedNode != null){
|
||||
Log.d(TAG,"Trying to connect to: "+ mSelectedNode.getUrl());
|
||||
Request request = new Request.Builder().url(mSelectedNode.getUrl()).build();
|
||||
mWebSocket = client.newWebSocket(request, mWebSocketListener);
|
||||
}else{
|
||||
Log.d(TAG,"Could not find best node, reescheduling");
|
||||
// If no node could be found yet, schedule a new attempt in DEFAULT_INITIAL_DELAY ms
|
||||
mHandler.postDelayed(mConnectAttempt, DEFAULT_INITIAL_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long sendMessage(String message){
|
||||
if(mWebSocket != null){
|
||||
if(mWebSocket.send(message)){
|
||||
Log.v(TAG,"-> " + message);
|
||||
return mCurrentId;
|
||||
}
|
||||
}else{
|
||||
throw new RuntimeException("Websocket connection has not yet been established");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that will send a message to the full node, and takes as an argument one of the
|
||||
* API call wrapper classes. This is the preferred method of sending blockchain API calls.
|
||||
*
|
||||
* @param apiCallable The object that will get serialized into a request
|
||||
* @param requiredApi The required APIs for this specific request. Should be one of the
|
||||
* constants specified in the ApiAccess class.
|
||||
* @return The id of the message that was just sent, or -1 if no message was sent.
|
||||
*/
|
||||
public synchronized long sendMessage(ApiCallable apiCallable, int requiredApi){
|
||||
if(requiredApi != -1 && mApiIds.containsKey(requiredApi) || requiredApi == ApiAccess.API_NONE){
|
||||
int apiId = 0;
|
||||
if(requiredApi != ApiAccess.API_NONE)
|
||||
apiId = mApiIds.get(requiredApi);
|
||||
ApiCall call = apiCallable.toApiCall(apiId, ++mCurrentId);
|
||||
mRequestClassMap.put(mCurrentId, apiCallable.getClass());
|
||||
if(mWebSocket != null && mWebSocket.send(call.toJsonString())){
|
||||
Log.v(TAG,"-> "+call.toJsonString());
|
||||
return mCurrentId;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to inform any external party a clue about the current connectivity status
|
||||
* @return True if the service is currently connected and logged in, false otherwise.
|
||||
*/
|
||||
public boolean isConnected(){
|
||||
return mWebSocket != null && isLoggedIn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if(mWebSocket != null)
|
||||
mWebSocket.close(NORMAL_CLOSURE_STATUS, null);
|
||||
|
||||
if(nodeLatencyVerifier != null)
|
||||
nodeLatencyVerifier.stop();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize information and try to connect to a node accordingly. This methods were moved
|
||||
* from onBind to avoid crashes due to components other than {@link NetworkServiceManager}
|
||||
* binding to the service without submitting the proper information.
|
||||
*
|
||||
* @param extras Bundle that contains all required information for a proper initialization
|
||||
*/
|
||||
public void bootstrapService(Bundle extras) {
|
||||
// Retrieving credentials and requested API data from the shared preferences
|
||||
mUsername = extras.getString(NetworkService.KEY_USERNAME, "");
|
||||
mPassword = extras.getString(NetworkService.KEY_PASSWORD, "");
|
||||
mRequestedApis = extras.getInt(NetworkService.KEY_REQUESTED_APIS, 0);
|
||||
boolean mAutoConnect = extras.getBoolean(NetworkService.KEY_AUTO_CONNECT, true);
|
||||
boolean verifyNodeLatency = extras.getBoolean(NetworkService.KEY_ENABLE_LATENCY_VERIFIER, false);
|
||||
|
||||
// If the user of the library desires, a custom list of node URLs can
|
||||
// be passed using the KEY_NODE_URLS constant
|
||||
String nodeURLStr = extras.getString(NetworkService.KEY_NODE_URLS, "");
|
||||
if(nodeURLStr.equals("")){
|
||||
throw new MissingResourceException("A comma-separated list of node URLs must be provided as an intent extra", String.class.getName(), NetworkService.KEY_NODE_URLS);
|
||||
}
|
||||
|
||||
// Adding user-provided list of node URLs
|
||||
String[] urls = nodeURLStr.split(",");
|
||||
|
||||
// Feeding all node information to the NodeProvider instance
|
||||
for(String nodeUrl : urls){
|
||||
nodeProvider.addNode(new FullNode(nodeUrl));
|
||||
}
|
||||
|
||||
if (!mAutoConnect && !verifyNodeLatency) {
|
||||
throw new IllegalArgumentException("NetworkService$bootstrapService: verifyNodeLatency cannot be false when autoConnect is false too.");
|
||||
}
|
||||
|
||||
if (verifyNodeLatency) {
|
||||
double alpha = extras.getDouble(KEY_NODE_LATENCY_SMOOTHING_FACTOR, ExponentialMovingAverage.DEFAULT_ALPHA);
|
||||
ArrayList<FullNode> fullNodes = new ArrayList<>();
|
||||
for(String url : urls){
|
||||
fullNodes.add(new FullNode(url, alpha));
|
||||
}
|
||||
nodeLatencyVerifier = new NodeLatencyVerifier(fullNodes);
|
||||
fullNodePublishSubject = nodeLatencyVerifier.start();
|
||||
fullNodePublishSubject.observeOn(AndroidSchedulers.mainThread()).subscribe(nodeLatencyObserver);
|
||||
}
|
||||
|
||||
if (mAutoConnect)
|
||||
connect();
|
||||
else
|
||||
mHandler.postDelayed(mConnectAttempt, DEFAULT_INITIAL_DELAY);
|
||||
|
||||
// TODO make sure (verifyNodeLatency==false && mAutoConnect==true) is a valid/useful combination, else simplify and use only one of those arguments
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to close the current connection and cause the service to attempt a reconnection.
|
||||
*/
|
||||
public void reconnectNode() {
|
||||
mWebSocket.close(GOING_AWAY_STATUS, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runnable that will perform a connection attempt with the best node after DEFAULT_INITIAL_DELAY
|
||||
* milliseconds. This is used only if the node latency verification is activated.
|
||||
*
|
||||
* The reason to delay the initial connection is that we want to ideally connect to the best node,
|
||||
* meaning the one that offers the lowest latency value. But we have to give some time for the
|
||||
* first node latency measurement round to finish in order to have at least a partial result set
|
||||
* that could be used.
|
||||
*/
|
||||
private Runnable mConnectAttempt = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
FullNode fullNode = nodeProvider.getBestNode();
|
||||
if(fullNode != null){
|
||||
Log.i(TAG, String.format("Connected with %d latency results", latencyUpdateCounter));
|
||||
connect();
|
||||
}else{
|
||||
mHandler.postDelayed(this, DEFAULT_INITIAL_DELAY);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Observer used to be notified about node latency measurement updates.
|
||||
*/
|
||||
private Observer<FullNode> nodeLatencyObserver = new Observer<FullNode>() {
|
||||
@Override
|
||||
public void onSubscribe(Disposable d) { }
|
||||
|
||||
@Override
|
||||
public void onNext(FullNode fullNode) {
|
||||
latencyUpdateCounter++;
|
||||
// Updating the node with the new latency measurement
|
||||
nodeProvider.updateNode(fullNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
Log.e(TAG,"nodeLatencyObserver.onError.Msg: "+e.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() { }
|
||||
};
|
||||
|
||||
/**
|
||||
* Class used for the client Binder. Because we know this service always
|
||||
* runs in the same process as its clients, we don't need to deal with IPC.
|
||||
*/
|
||||
public class LocalBinder extends Binder {
|
||||
public NetworkService getService() {
|
||||
// Return this instance of LocalService so clients can call public methods
|
||||
return NetworkService.this;
|
||||
}
|
||||
}
|
||||
|
||||
private WebSocketListener mWebSocketListener = new WebSocketListener() {
|
||||
|
||||
@Override
|
||||
public synchronized void onOpen(WebSocket webSocket, Response response) {
|
||||
super.onOpen(webSocket, response);
|
||||
|
||||
// Marking the selected node as connected
|
||||
mSelectedNode.setConnected(true);
|
||||
|
||||
// Updating the selected node's 'connected' status on the NodeLatencyVerifier instance
|
||||
if(nodeLatencyVerifier != null)
|
||||
nodeLatencyVerifier.updateActiveNodeInformation(mSelectedNode);
|
||||
|
||||
// Notifying all listeners about the new connection status
|
||||
RxBus.getBusInstance().send(new ConnectionStatusUpdate(ConnectionStatusUpdate.CONNECTED, ApiAccess.API_NONE));
|
||||
|
||||
// If we're not yet logged in, we should do it now
|
||||
if(!isLoggedIn){
|
||||
ArrayList<Serializable> loginParams = new ArrayList<>();
|
||||
loginParams.add(mUsername);
|
||||
loginParams.add(mPassword);
|
||||
ApiCall loginCall = new ApiCall(1, RPC.CALL_LOGIN, loginParams, RPC.VERSION, ++mCurrentId);
|
||||
mLastCall = RPC.CALL_LOGIN;
|
||||
sendMessage(loginCall.toJsonString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onMessage(WebSocket webSocket, String text) {
|
||||
super.onMessage(webSocket, text);
|
||||
Log.v(TAG,"<- "+text);
|
||||
JsonRpcNotification notification = gson.fromJson(text, JsonRpcNotification.class);
|
||||
|
||||
if(notification.method != null){
|
||||
// If we are dealing with a notification
|
||||
handleJsonRpcNotification(notification);
|
||||
}else{
|
||||
// If we are dealing with a response
|
||||
JsonRpcResponse<?> response = gson.fromJson(text, JsonRpcResponse.class);
|
||||
if(response.result != null){
|
||||
// Handling initial handshake with the full node (authentication and API access checks)
|
||||
if(response.result instanceof Double || response.result instanceof Boolean){
|
||||
switch (mLastCall) {
|
||||
case RPC.CALL_LOGIN:
|
||||
isLoggedIn = true;
|
||||
|
||||
// Broadcasting result
|
||||
RxBus.getBusInstance().send(new ConnectionStatusUpdate(ConnectionStatusUpdate.AUTHENTICATED, ApiAccess.API_NONE));
|
||||
|
||||
checkNextRequestedApiAccess();
|
||||
break;
|
||||
case RPC.CALL_DATABASE: {
|
||||
// Deserializing integer response
|
||||
Type IntegerJsonResponse = new TypeToken<JsonRpcResponse<Integer>>() {}.getType();
|
||||
JsonRpcResponse<Integer> apiIdResponse = gson.fromJson(text, IntegerJsonResponse);
|
||||
|
||||
// Storing the "database" api id
|
||||
mApiIds.put(ApiAccess.API_DATABASE, apiIdResponse.result);
|
||||
|
||||
// Broadcasting result
|
||||
RxBus.getBusInstance().send(new ConnectionStatusUpdate(ConnectionStatusUpdate.API_UPDATE, ApiAccess.API_DATABASE));
|
||||
|
||||
checkNextRequestedApiAccess();
|
||||
break;
|
||||
}
|
||||
case RPC.CALL_HISTORY: {
|
||||
// Deserializing integer response
|
||||
Type IntegerJsonResponse = new TypeToken<JsonRpcResponse<Integer>>() {}.getType();
|
||||
JsonRpcResponse<Integer> apiIdResponse = gson.fromJson(text, IntegerJsonResponse);
|
||||
|
||||
// Broadcasting result
|
||||
RxBus.getBusInstance().send(new ConnectionStatusUpdate(ConnectionStatusUpdate.API_UPDATE, ApiAccess.API_HISTORY));
|
||||
|
||||
// Storing the "history" api id
|
||||
mApiIds.put(ApiAccess.API_HISTORY, apiIdResponse.result);
|
||||
|
||||
checkNextRequestedApiAccess();
|
||||
break;
|
||||
}
|
||||
case RPC.CALL_NETWORK_BROADCAST:
|
||||
// Deserializing integer response
|
||||
Type IntegerJsonResponse = new TypeToken<JsonRpcResponse<Integer>>() {}.getType();
|
||||
JsonRpcResponse<Integer> apiIdResponse = gson.fromJson(text, IntegerJsonResponse);
|
||||
|
||||
// Broadcasting result
|
||||
RxBus.getBusInstance().send(new ConnectionStatusUpdate(ConnectionStatusUpdate.API_UPDATE, ApiAccess.API_NETWORK_BROADCAST));
|
||||
|
||||
// Storing the "network_broadcast" api access
|
||||
mApiIds.put(ApiAccess.API_NETWORK_BROADCAST, apiIdResponse.result);
|
||||
|
||||
// All calls have been handled at this point
|
||||
mLastCall = "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(response.error != null && response.error.message != null){
|
||||
// We could not make sense of this incoming message, just log a warning
|
||||
Log.w(TAG,"Error.Msg: "+response.error.message);
|
||||
}
|
||||
// Properly de-serialize all other fields and broadcasts to the event bus
|
||||
handleJsonRpcResponse(response, text);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Private method that will de-serialize all fields of every kind of JSON-RPC response
|
||||
* and broadcast it to the event bus.
|
||||
*
|
||||
* @param response De-serialized response
|
||||
* @param text Raw text, as received
|
||||
*/
|
||||
private void handleJsonRpcResponse(JsonRpcResponse response, String text){
|
||||
JsonRpcResponse parsedResponse = null;
|
||||
Class requestClass = mRequestClassMap.get(response.id);
|
||||
if(requestClass != null){
|
||||
// Removing the class entry in the map
|
||||
mRequestClassMap.remove(response.id);
|
||||
|
||||
// Obtaining the response payload class
|
||||
Class responsePayloadClass = mDeserializationMap.getReceivedClass(requestClass);
|
||||
Gson gson = mDeserializationMap.getGson(requestClass);
|
||||
if(responsePayloadClass == Block.class){
|
||||
// If the response payload is a Block instance, we proceed to de-serialize it
|
||||
Type GetBlockResponse = new TypeToken<JsonRpcResponse<Block>>() {}.getType();
|
||||
parsedResponse = gson.fromJson(text, GetBlockResponse);
|
||||
}else if(responsePayloadClass == BlockHeader.class){
|
||||
// If the response payload is a BlockHeader instance, we proceed to de-serialize it
|
||||
Type GetBlockHeaderResponse = new TypeToken<JsonRpcResponse<BlockHeader>>(){}.getType();
|
||||
parsedResponse = gson.fromJson(text, GetBlockHeaderResponse);
|
||||
} else if(responsePayloadClass == AccountProperties.class){
|
||||
Type GetAccountByNameResponse = new TypeToken<JsonRpcResponse<AccountProperties>>(){}.getType();
|
||||
parsedResponse = gson.fromJson(text, GetAccountByNameResponse);
|
||||
} else if(responsePayloadClass == HistoryOperationDetail.class){
|
||||
Type GetAccountHistoryByOperationsResponse = new TypeToken<JsonRpcResponse<HistoryOperationDetail>>(){}.getType();
|
||||
parsedResponse = gson.fromJson(text, GetAccountHistoryByOperationsResponse);
|
||||
}else if(responsePayloadClass == DynamicGlobalProperties.class){
|
||||
Type GetDynamicGlobalPropertiesResponse = new TypeToken<JsonRpcResponse<DynamicGlobalProperties>>(){}.getType();
|
||||
parsedResponse = gson.fromJson(text, GetDynamicGlobalPropertiesResponse);
|
||||
}else if(responsePayloadClass == Transaction.class){
|
||||
Type GetTransactionClass = new TypeToken<JsonRpcResponse<Transaction>>(){}.getType();
|
||||
parsedResponse = gson.fromJson(text, GetTransactionClass);
|
||||
}else if(responsePayloadClass == List.class){
|
||||
// If the response payload is a List, further inquiry is required in order to
|
||||
// determine a list of what is expected here
|
||||
if(requestClass == GetAccounts.class){
|
||||
// If the request call was the wrapper to the get_accounts API call, we know
|
||||
// the response should be in the form of a JsonRpcResponse<List<AccountProperties>>
|
||||
// so we proceed with that
|
||||
Type GetAccountsResponse = new TypeToken<JsonRpcResponse<List<AccountProperties>>>(){}.getType();
|
||||
parsedResponse = gson.fromJson(text, GetAccountsResponse);
|
||||
}else if(requestClass == GetRequiredFees.class){
|
||||
Type GetRequiredFeesResponse = new TypeToken<JsonRpcResponse<List<AssetAmount>>>(){}.getType();
|
||||
parsedResponse = gson.fromJson(text, GetRequiredFeesResponse);
|
||||
}else if(requestClass == GetRelativeAccountHistory.class){
|
||||
Type RelativeAccountHistoryResponse = new TypeToken<JsonRpcResponse<List<OperationHistory>>>(){}.getType();
|
||||
parsedResponse = gson.fromJson(text, RelativeAccountHistoryResponse);
|
||||
}else if(requestClass == GetMarketHistory.class){
|
||||
Type GetMarketHistoryResponse = new TypeToken<JsonRpcResponse<List<BucketObject>>>(){}.getType();
|
||||
parsedResponse = gson.fromJson(text, GetMarketHistoryResponse);
|
||||
}else if(requestClass == GetObjects.class){
|
||||
parsedResponse = handleGetObject(text);
|
||||
}else if(requestClass == ListAssets.class){
|
||||
Type LisAssetsResponse = new TypeToken<JsonRpcResponse<List<Asset>>>(){}.getType();
|
||||
parsedResponse = gson.fromJson(text, LisAssetsResponse);
|
||||
}else if(requestClass == GetLimitOrders.class){
|
||||
Type GetLimitOrdersResponse = new TypeToken<JsonRpcResponse<List<LimitOrder>>>() {}.getType();
|
||||
parsedResponse = gson.fromJson(text, GetLimitOrdersResponse);
|
||||
} else if (requestClass == GetFullAccounts.class) {
|
||||
Type GetFullAccountsResponse = new TypeToken<JsonRpcResponse<List<FullAccountDetails>>>(){}.getType();
|
||||
parsedResponse = gson.fromJson(text, GetFullAccountsResponse);
|
||||
} else if(requestClass == GetKeyReferences.class){
|
||||
Type GetKeyReferencesResponse = new TypeToken<JsonRpcResponse<List<List<UserAccount>>>>(){}.getType();
|
||||
parsedResponse = gson.fromJson(text, GetKeyReferencesResponse);
|
||||
} else if(requestClass == GetAccountBalances.class){
|
||||
Type GetAccountBalancesResponse = new TypeToken<JsonRpcResponse<List<AssetAmount>>>(){}.getType();
|
||||
parsedResponse = gson.fromJson(text, GetAccountBalancesResponse);
|
||||
} else if(requestClass == GetAssets.class){
|
||||
Type GetAssetsResponse = new TypeToken<JsonRpcResponse<List<Asset>>>(){}.getType();
|
||||
parsedResponse = gson.fromJson(text, GetAssetsResponse);
|
||||
}else {
|
||||
Log.w(TAG,"Unknown request class");
|
||||
}
|
||||
}else{
|
||||
Log.w(TAG,"Unhandled situation");
|
||||
}
|
||||
}
|
||||
|
||||
// In case the parsedResponse instance is null, we fall back to the raw response
|
||||
if(parsedResponse == null){
|
||||
parsedResponse = response;
|
||||
}
|
||||
// Broadcasting the parsed response to all interested listeners
|
||||
RxBus.getBusInstance().send(parsedResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Private method that will just broadcast a de-serialized notification to all interested parties
|
||||
* @param notification De-serialized notification
|
||||
*/
|
||||
private void handleJsonRpcNotification(JsonRpcNotification notification){
|
||||
// Broadcasting the parsed notification to all interested listeners
|
||||
RxBus.getBusInstance().send(notification);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to try to deserialize a 'get_objects' API call. Since this request can be used
|
||||
* for several types of objects, the de-serialization procedure can be a bit more complex.
|
||||
*
|
||||
* @param response Response to a 'get_objects' API call
|
||||
*/
|
||||
private JsonRpcResponse handleGetObject(String response){
|
||||
//TODO: Add support for other types of 'get_objects' request types
|
||||
Gson gson = mDeserializationMap.getGson(GetObjects.class);
|
||||
Type GetBitAssetResponse = new TypeToken<JsonRpcResponse<List<BitAssetData>>>(){}.getType();
|
||||
return gson.fromJson(response, GetBitAssetResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to check all possible API accesses.
|
||||
*
|
||||
* The service will try to obtain sequentially API access ids for the following APIs:
|
||||
*
|
||||
* - Database
|
||||
* - History
|
||||
* - Network broadcast
|
||||
*/
|
||||
private void checkNextRequestedApiAccess(){
|
||||
if( (mRequestedApis & ApiAccess.API_DATABASE) == ApiAccess.API_DATABASE &&
|
||||
mApiIds.get(ApiAccess.API_DATABASE) == null){
|
||||
// If we need the "database" api access and we don't yet have it
|
||||
|
||||
ApiCall apiCall = new ApiCall(1, RPC.CALL_DATABASE, null, RPC.VERSION, ++mCurrentId);
|
||||
mLastCall = RPC.CALL_DATABASE;
|
||||
sendMessage(apiCall.toJsonString());
|
||||
} else if( (mRequestedApis & ApiAccess.API_HISTORY) == ApiAccess.API_HISTORY &&
|
||||
mApiIds.get(ApiAccess.API_HISTORY) == null){
|
||||
// If we need the "history" api access and we don't yet have it
|
||||
|
||||
ApiCall apiCall = new ApiCall(1, RPC.CALL_HISTORY, null, RPC.VERSION, ++mCurrentId);
|
||||
mLastCall = RPC.CALL_HISTORY;
|
||||
sendMessage(apiCall.toJsonString());
|
||||
}else if( (mRequestedApis & ApiAccess.API_NETWORK_BROADCAST) == ApiAccess.API_NETWORK_BROADCAST &&
|
||||
mApiIds.get(ApiAccess.API_NETWORK_BROADCAST) == null){
|
||||
// If we need the "network_broadcast" api access and we don't yet have it
|
||||
|
||||
ApiCall apiCall = new ApiCall(1, RPC.CALL_NETWORK_BROADCAST, null, RPC.VERSION, ++mCurrentId);
|
||||
mLastCall = RPC.CALL_NETWORK_BROADCAST;
|
||||
sendMessage(apiCall.toJsonString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed(WebSocket webSocket, int code, String reason) {
|
||||
super.onClosed(webSocket, code, reason);
|
||||
if(code == GOING_AWAY_STATUS)
|
||||
handleWebSocketDisconnection(true, false);
|
||||
else
|
||||
handleWebSocketDisconnection(false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
||||
super.onFailure(webSocket, t, response);
|
||||
Log.e(TAG,"onFailure. Exception: "+t.getClass().getName()+", Msg: "+t.getMessage());
|
||||
// Logging error stack trace
|
||||
for(StackTraceElement element : t.getStackTrace()){
|
||||
Log.v(TAG,String.format("%s#%s:%s", element.getClassName(), element.getMethodName(), element.getLineNumber()));
|
||||
}
|
||||
|
||||
// If there is a response, we print it
|
||||
if(response != null){
|
||||
Log.e(TAG,"Response: "+response.message());
|
||||
}
|
||||
|
||||
handleWebSocketDisconnection(true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that encapsulates the behavior of handling a disconnection to the current node, and
|
||||
* potentially tries to reconnect to another one.
|
||||
*
|
||||
* @param tryReconnection States if a reconnection to other node should be tried.
|
||||
* @param penalizeNode Whether or not to penalize the current node with a very high latency reading.
|
||||
*/
|
||||
private synchronized void handleWebSocketDisconnection(boolean tryReconnection, boolean penalizeNode) {
|
||||
Log.d(TAG,"handleWebSocketDisconnection. try reconnection: " + tryReconnection + ", penalizeNode: " + penalizeNode);
|
||||
RxBus.getBusInstance().send(new ConnectionStatusUpdate(ConnectionStatusUpdate.DISCONNECTED, ApiAccess.API_NONE));
|
||||
isLoggedIn = false;
|
||||
|
||||
// Clearing previous request id to class mappings
|
||||
mRequestClassMap.clear();
|
||||
|
||||
if(mSelectedNode != null){
|
||||
// Marking the selected node as not connected
|
||||
mSelectedNode.setConnected(false);
|
||||
|
||||
// Updating the selected node's 'connected' status on the NodeLatencyVerifier instance
|
||||
if(nodeLatencyVerifier != null)
|
||||
nodeLatencyVerifier.updateActiveNodeInformation(mSelectedNode);
|
||||
|
||||
if (penalizeNode){
|
||||
// Adding a very high latency value to this node in order to prevent
|
||||
// us from getting it again
|
||||
mSelectedNode.addLatencyValue(Long.MAX_VALUE);
|
||||
nodeProvider.updateNode(mSelectedNode);
|
||||
}
|
||||
}
|
||||
|
||||
if(tryReconnection) {
|
||||
// Registering current status
|
||||
mCurrentId = 0;
|
||||
mApiIds.clear();
|
||||
|
||||
RxBus.getBusInstance().send(new ConnectionStatusUpdate(ConnectionStatusUpdate.DISCONNECTED, ApiAccess.API_NONE));
|
||||
|
||||
if (nodeProvider.getBestNode() == null) {
|
||||
Log.e(TAG, "Giving up on connections");
|
||||
stopSelf();
|
||||
} else {
|
||||
mHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
connect();
|
||||
}
|
||||
}, DEFAULT_RETRY_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
// We have currently no selected node
|
||||
mSelectedNode = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Method used to check whether or not the network service is connected to a node that
|
||||
* offers a specific API.
|
||||
*
|
||||
* @param whichApi The API we want to use.
|
||||
* @return True if the node has got that API enabled, false otherwise
|
||||
*/
|
||||
public boolean hasApiId(int whichApi){
|
||||
return mApiIds.get(whichApi) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the full node details
|
||||
* @param fullNode Updated {@link FullNode} instance
|
||||
*/
|
||||
public void updateNode(FullNode fullNode){
|
||||
nodeProvider.updateNode(fullNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of {@link FullNode} instances
|
||||
* @return List of full nodes
|
||||
*/
|
||||
public List<FullNode> getNodes(){
|
||||
return nodeProvider.getSortedNodes();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the currently selected node
|
||||
*/
|
||||
public FullNode getSelectedNode() { return mSelectedNode; }
|
||||
|
||||
/**
|
||||
* Returns an observable that will notify its observers about node latency updates.
|
||||
* @return Observer of {@link FullNode} instances.
|
||||
*/
|
||||
public PublishSubject<FullNode> getNodeLatencyObservable(){
|
||||
return fullNodePublishSubject;
|
||||
}
|
||||
|
||||
public NodeLatencyVerifier getNodeLatencyVerifier(){ return nodeLatencyVerifier; }
|
||||
}
|
|
@ -0,0 +1,325 @@
|
|||
package cy.agorise.graphenej.api.android;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.stats.ExponentialMovingAverage;
|
||||
|
||||
/**
|
||||
* This class should be instantiated at the application level of the android app.
|
||||
*
|
||||
* It will monitor the interaction between the different activities of an app and help us decide
|
||||
* when the connection to the full node should be interrupted.
|
||||
*/
|
||||
|
||||
public class NetworkServiceManager implements Application.ActivityLifecycleCallbacks {
|
||||
private final String TAG = this.getClass().getName();
|
||||
/**
|
||||
* Constant used to specify how long will the app wait for another activity to go through its starting life
|
||||
* cycle events before running the teardownConnectionTask task.
|
||||
*
|
||||
* This is used as a means to detect whether or not the user has left the app.
|
||||
*/
|
||||
private static final int DISCONNECT_DELAY = 1500;
|
||||
|
||||
/**
|
||||
* Handler instance used to schedule tasks back to the main thread
|
||||
*/
|
||||
private Handler mHandler = new Handler();
|
||||
|
||||
/**
|
||||
* Weak reference to the application context
|
||||
*/
|
||||
private WeakReference<Context> mContextReference;
|
||||
|
||||
// In case we want to interact directly with the service
|
||||
private NetworkService mService;
|
||||
|
||||
// Attributes that might need to be passed to the NetworkService
|
||||
private String mUserName = "";
|
||||
private String mPassword = "";
|
||||
private int mRequestedApis;
|
||||
private List<String> mCustomNodeUrls = new ArrayList<>();
|
||||
private boolean mAutoConnect;
|
||||
private boolean mVerifyLatency;
|
||||
// Flag used to make sure we only call 'bindService' once.
|
||||
// private boolean mStartingService;
|
||||
|
||||
/**
|
||||
* Runnable used to schedule a service disconnection once the app is not visible to the user for
|
||||
* more than DISCONNECT_DELAY milliseconds.
|
||||
*/
|
||||
private final Runnable mDisconnectRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Context context = mContextReference.get();
|
||||
if(mService != null){
|
||||
context.unbindService(mServiceConnection);
|
||||
mService = null;
|
||||
}
|
||||
context.stopService(new Intent(context, NetworkService.class));
|
||||
}
|
||||
};
|
||||
|
||||
private NetworkServiceManager(Context context){
|
||||
mContextReference = new WeakReference<>(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Activity activity, Bundle bundle) { }
|
||||
|
||||
@Override
|
||||
public void onActivityStarted(Activity activity) { }
|
||||
|
||||
@Override
|
||||
public void onActivityResumed(Activity activity) {
|
||||
mHandler.removeCallbacks(mDisconnectRunnable);
|
||||
if(mService == null){
|
||||
// Creating a new Intent that will be used to start the NetworkService
|
||||
Context context = mContextReference.get();
|
||||
Intent intent = new Intent(context, NetworkService.class);
|
||||
context.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method passes all the required information to the NetworkService to properly
|
||||
* initialize itself
|
||||
*/
|
||||
private void passRequiredInfoToConfigureService() {
|
||||
// Adding user-provided node URLs
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
Iterator<String> it = mCustomNodeUrls.iterator();
|
||||
while(it.hasNext()){
|
||||
stringBuilder.append(it.next());
|
||||
if(it.hasNext()) stringBuilder.append(",");
|
||||
}
|
||||
String customNodes = stringBuilder.toString();
|
||||
|
||||
Bundle b = new Bundle();
|
||||
|
||||
// Adding all
|
||||
b.putString(NetworkService.KEY_USERNAME, mUserName);
|
||||
b.putString(NetworkService.KEY_PASSWORD, mPassword);
|
||||
b.putInt(NetworkService.KEY_REQUESTED_APIS, mRequestedApis);
|
||||
b.putString(NetworkService.KEY_NODE_URLS, customNodes);
|
||||
b.putBoolean(NetworkService.KEY_AUTO_CONNECT, mAutoConnect);
|
||||
b.putBoolean(NetworkService.KEY_ENABLE_LATENCY_VERIFIER, mVerifyLatency);
|
||||
|
||||
mService.bootstrapService(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityPaused(Activity activity) {
|
||||
mHandler.postDelayed(mDisconnectRunnable, DISCONNECT_DELAY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStopped(Activity activity) {}
|
||||
|
||||
@Override
|
||||
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroyed(Activity activity) {}
|
||||
|
||||
/** Defines callbacks for backend binding, passed to bindService() */
|
||||
private ServiceConnection mServiceConnection = new ServiceConnection() {
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName className,
|
||||
IBinder service) {
|
||||
// We've bound to LocalService, cast the IBinder and get LocalService instance
|
||||
NetworkService.LocalBinder binder = (NetworkService.LocalBinder) service;
|
||||
boolean passInfo = false;
|
||||
if(mService == null){
|
||||
mService = binder.getService();
|
||||
// We only pass the required information in case this is the first time we get a reference
|
||||
// to the NetworkService instance.
|
||||
passInfo = true;
|
||||
}
|
||||
if(passInfo)
|
||||
passRequiredInfoToConfigureService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName componentName) {
|
||||
}
|
||||
};
|
||||
|
||||
public String getUserName() {
|
||||
return mUserName;
|
||||
}
|
||||
|
||||
public void setUserName(String userName) {
|
||||
this.mUserName = userName;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return mPassword;
|
||||
}
|
||||
|
||||
public void setPassword(String mPassword) {
|
||||
this.mPassword = mPassword;
|
||||
}
|
||||
|
||||
public int getRequestedApis() {
|
||||
return mRequestedApis;
|
||||
}
|
||||
|
||||
public void setRequestedApis(int mRequestedApis) {
|
||||
this.mRequestedApis = mRequestedApis;
|
||||
}
|
||||
|
||||
public List<String> getCustomNodeUrls() {
|
||||
return mCustomNodeUrls;
|
||||
}
|
||||
|
||||
public void setCustomNodeUrls(List<String> mCustomNodeUrls) {
|
||||
this.mCustomNodeUrls = mCustomNodeUrls;
|
||||
}
|
||||
|
||||
public boolean isAutoConnect() {
|
||||
return mAutoConnect;
|
||||
}
|
||||
|
||||
public void setAutoConnect(boolean mAutoConnect) {
|
||||
this.mAutoConnect = mAutoConnect;
|
||||
}
|
||||
|
||||
public boolean isVerifyLatency() {
|
||||
return mVerifyLatency;
|
||||
}
|
||||
|
||||
public void setVerifyLatency(boolean mVerifyLatency) {
|
||||
this.mVerifyLatency = mVerifyLatency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class used to create a {@link NetworkServiceManager} with specific attributes.
|
||||
*/
|
||||
public static class Builder {
|
||||
private String username;
|
||||
private String password;
|
||||
private int requestedApis;
|
||||
private List<String> customNodeUrls;
|
||||
private boolean autoconnect = true;
|
||||
private boolean verifyNodeLatency;
|
||||
private double alpha = ExponentialMovingAverage.DEFAULT_ALPHA;
|
||||
|
||||
/**
|
||||
* Sets the user name, if required to connect to a node.
|
||||
* @param name User name
|
||||
* @return The Builder instance
|
||||
*/
|
||||
public Builder setUserName(String name){
|
||||
this.username = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the password, if required to connect to a node.
|
||||
* @param password Password
|
||||
* @return The Builder instance
|
||||
*/
|
||||
public Builder setPassword(String password){
|
||||
this.password = password;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an integer with the requested APIs encoded as binary flags.
|
||||
* @param apis Integer representing the different APIs we require from the node.
|
||||
* @return The Builder instance
|
||||
*/
|
||||
public Builder setRequestedApis(int apis){
|
||||
this.requestedApis = apis;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a list of custom node URLs.
|
||||
* @param nodeUrls List of custom full node URLs.
|
||||
* @return The Builder instance
|
||||
*/
|
||||
public Builder setCustomNodeUrls(List<String> nodeUrls){
|
||||
this.customNodeUrls = nodeUrls;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a list of custom node URLs.
|
||||
* @param nodeUrls List of custom full node URLs.
|
||||
* @return The Builder instance
|
||||
*/
|
||||
public Builder setCustomNodeUrls(String nodeUrls){
|
||||
String[] urls = nodeUrls.split(",");
|
||||
for(String url : urls){
|
||||
if(customNodeUrls == null) customNodeUrls = new ArrayList<>();
|
||||
customNodeUrls.add(url);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the autoconnect flag. This is true by default.
|
||||
* @param autoConnect True if we want the service to connect automatically, false otherwise.
|
||||
* @return The Builder instance
|
||||
*/
|
||||
public Builder setAutoConnect(boolean autoConnect){
|
||||
this.autoconnect = autoConnect;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the node-verification flag. This is false by default.
|
||||
* @param verifyLatency True if we want the service to perform a latency analysis before connecting.
|
||||
* @return The Builder instance.
|
||||
*/
|
||||
public Builder setNodeLatencyVerification(boolean verifyLatency){
|
||||
this.verifyNodeLatency = verifyLatency;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the node latency verification's exponential moving average alpha parameter.
|
||||
* @param alpha The alpha parameter to use when computing the exponential moving average of the
|
||||
* measured latencies.
|
||||
* @return The Builder instance.
|
||||
*/
|
||||
public Builder setLatencyAverageAlpha(double alpha){
|
||||
this.alpha = alpha;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to build a {@link NetworkServiceManager} instance with all of the characteristics
|
||||
* passed as parameters.
|
||||
* @param context A Context of the application package implementing
|
||||
* this class.
|
||||
* @return Instance of the NetworkServiceManager class.
|
||||
*/
|
||||
public NetworkServiceManager build(Context context){
|
||||
NetworkServiceManager manager = new NetworkServiceManager(context);
|
||||
if(username != null) manager.setUserName(username); else manager.setUserName("");
|
||||
if(password != null) manager.setPassword(password); else manager.setPassword("");
|
||||
if(customNodeUrls != null) manager.setCustomNodeUrls(customNodeUrls);
|
||||
manager.setRequestedApis(requestedApis);
|
||||
manager.setAutoConnect(autoconnect);
|
||||
manager.setVerifyLatency(verifyNodeLatency);
|
||||
return manager;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package cy.agorise.graphenej.api.android;
|
||||
|
||||
import com.jakewharton.rxrelay2.PublishRelay;
|
||||
import com.jakewharton.rxrelay2.Relay;
|
||||
|
||||
import io.reactivex.BackpressureStrategy;
|
||||
import io.reactivex.Flowable;
|
||||
|
||||
/**
|
||||
* Explained here: https://blog.kaush.co/2014/12/24/implementing-an-event-bus-with-rxjava-rxbus/
|
||||
*/
|
||||
public class RxBus {
|
||||
|
||||
private static RxBus rxBus;
|
||||
|
||||
public static final RxBus getBusInstance(){
|
||||
if(rxBus == null){
|
||||
rxBus = new RxBus();
|
||||
}
|
||||
return rxBus;
|
||||
}
|
||||
|
||||
private final Relay<Object> _bus = PublishRelay.create().toSerialized();
|
||||
|
||||
public void send(Object o) {
|
||||
_bus.accept(o);
|
||||
}
|
||||
|
||||
public Flowable<Object> asFlowable() {
|
||||
return _bus.toFlowable(BackpressureStrategy.LATEST);
|
||||
}
|
||||
|
||||
public boolean hasObservers() {
|
||||
return _bus.hasObservers();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
/**
|
||||
* Interface to be implemented by all classes that will produce an ApiCall object instance
|
||||
* as a result.
|
||||
*/
|
||||
|
||||
public interface ApiCallable {
|
||||
|
||||
/**
|
||||
*
|
||||
* @return An instance of the {@link ApiCall} class
|
||||
*/
|
||||
ApiCall toApiCall(int apiId, long sequenceId);
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.Transaction;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
public class BroadcastTransaction implements ApiCallable {
|
||||
public static final int REQUIRED_API = ApiAccess.API_NETWORK_BROADCAST;
|
||||
|
||||
private Transaction mTransaction;
|
||||
|
||||
public BroadcastTransaction(Transaction transaction){
|
||||
if(!transaction.hasPrivateKey()) throw new IllegalStateException("The Transaction instance has to be provided with a private key in order to be broadcasted");
|
||||
mTransaction = transaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCall toApiCall(int apiId, long sequenceId) {
|
||||
ArrayList<Serializable> transactions = new ArrayList<>();
|
||||
transactions.add(mTransaction);
|
||||
return new ApiCall(apiId, RPC.CALL_BROADCAST_TRANSACTION, transactions, RPC.VERSION, sequenceId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
public class CancelAllSubscriptions implements ApiCallable {
|
||||
public static final int REQUIRED_API = ApiAccess.API_DATABASE;
|
||||
|
||||
@Override
|
||||
public ApiCall toApiCall(int apiId, long sequenceId) {
|
||||
return new ApiCall(apiId, RPC.CALL_CANCEL_ALL_SUBSCRIPTIONS, new ArrayList<Serializable>(), RPC.VERSION, sequenceId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.Asset;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
/**
|
||||
* Wrapper around the 'get_account_balances' API call.
|
||||
*
|
||||
* @see <a href="https://goo.gl/faFdey">get_account_balances API doc</a>
|
||||
*/
|
||||
public class GetAccountBalances implements ApiCallable {
|
||||
public static final int REQUIRED_API = ApiAccess.API_NONE;
|
||||
|
||||
private UserAccount mUserAccount;
|
||||
private List<Asset> mAssetList;
|
||||
|
||||
public GetAccountBalances(UserAccount userAccount, List<Asset> assets){
|
||||
mUserAccount = userAccount;
|
||||
mAssetList = assets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCall toApiCall(int apiId, long sequenceId) {
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
ArrayList<Serializable> assetList = new ArrayList<>();
|
||||
if(mAssetList != null){
|
||||
for(Asset asset : mAssetList){
|
||||
assetList.add(asset.getObjectId());
|
||||
}
|
||||
}
|
||||
params.add(mUserAccount.getObjectId());
|
||||
params.add(assetList);
|
||||
return new ApiCall(apiId, RPC.CALL_GET_ACCOUNT_BALANCES, params, RPC.VERSION, sequenceId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
public class GetAccountByName implements ApiCallable {
|
||||
public static final int REQUIRED_API = ApiAccess.API_NONE;
|
||||
|
||||
private String accountName;
|
||||
|
||||
public GetAccountByName(String name){
|
||||
this.accountName = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCall toApiCall(int apiId, long sequenceId) {
|
||||
ArrayList<Serializable> accountParams = new ArrayList<>();
|
||||
accountParams.add(this.accountName);
|
||||
return new ApiCall(apiId, RPC.CALL_GET_ACCOUNT_BY_NAME, accountParams, RPC.VERSION, sequenceId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
public class GetAccountHistory implements ApiCallable {
|
||||
public static final int REQUIRED_API = ApiAccess.API_HISTORY;
|
||||
|
||||
private UserAccount mUserAccount;
|
||||
private String startOperation;
|
||||
private String endOperation;
|
||||
private int limit;
|
||||
|
||||
public GetAccountHistory(UserAccount userAccount, String start, String end, int limit){
|
||||
this.mUserAccount = userAccount;
|
||||
this.startOperation = start;
|
||||
this.endOperation = end;
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
public GetAccountHistory(String userId, String start, String end, int limit){
|
||||
this.mUserAccount = new UserAccount(userId);
|
||||
this.startOperation = start;
|
||||
this.endOperation = end;
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ApiCall toApiCall(int apiId, long sequenceId) {
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
params.add(mUserAccount.getObjectId());
|
||||
params.add(endOperation);
|
||||
params.add(limit);
|
||||
params.add(startOperation);
|
||||
return new ApiCall(apiId, RPC.CALL_GET_ACCOUNT_HISTORY, params, RPC.VERSION, sequenceId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.OperationType;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
public class GetAccountHistoryByOperations implements ApiCallable {
|
||||
|
||||
public static final int REQUIRED_API = ApiAccess.API_HISTORY;
|
||||
|
||||
private UserAccount mUserAccount;
|
||||
private List<OperationType> mOperationTypes;
|
||||
private long mStart;
|
||||
private long mLimit;
|
||||
|
||||
/**
|
||||
* @param userAccount The user account that should be queried
|
||||
* @param operationsTypes The IDs of the operation we want to get operations in the account( 0 = transfer , 1 = limit order create, ...)
|
||||
* @param start The sequence number where to start listing operations
|
||||
* @param limit The max number of entries to return (from start number)
|
||||
*/
|
||||
public GetAccountHistoryByOperations(UserAccount userAccount, List<OperationType> operationsTypes, long start, long limit){
|
||||
this.mUserAccount = userAccount;
|
||||
this.mOperationTypes = operationsTypes;
|
||||
this.mStart = start;
|
||||
this.mLimit = limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param userAccount The user account that should be queried
|
||||
* @param operationsTypes The IDs of the operation we want to get operations in the account( 0 = transfer , 1 = limit order create, ...)
|
||||
* @param start The sequence number where to start listing operations
|
||||
* @param limit The max number of entries to return (from start number)
|
||||
*/
|
||||
public GetAccountHistoryByOperations(String userAccount, List<OperationType> operationsTypes, long start, long limit){
|
||||
if(userAccount.matches("^1\\.2\\.\\d*$")){
|
||||
this.mUserAccount = new UserAccount(userAccount);
|
||||
}else{
|
||||
this.mUserAccount = new UserAccount("", userAccount);
|
||||
}
|
||||
this.mOperationTypes = operationsTypes;
|
||||
this.mStart = start;
|
||||
this.mLimit = limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCall toApiCall(int apiId, long sequenceId) {
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
if(mUserAccount.getName() != null){
|
||||
params.add(mUserAccount.getName());
|
||||
}else{
|
||||
params.add(mUserAccount.getObjectId());
|
||||
}
|
||||
ArrayList<Integer> operationTypes = new ArrayList<>();
|
||||
for(OperationType operationType : mOperationTypes){
|
||||
operationTypes.add(operationType.ordinal());
|
||||
}
|
||||
params.add(operationTypes);
|
||||
params.add(mStart);
|
||||
params.add(mLimit);
|
||||
return new ApiCall(apiId, RPC.CALL_GET_ACCOUNT_HISTORY_BY_OPERATIONS, params, RPC.VERSION, sequenceId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
/**
|
||||
* Wrapper around the "get_accounts" API call.
|
||||
*/
|
||||
public class GetAccounts implements ApiCallable {
|
||||
public static final int REQUIRED_API = ApiAccess.API_NONE;
|
||||
|
||||
private List<UserAccount> mUserAccounts;
|
||||
|
||||
public GetAccounts(List<UserAccount> accountList){
|
||||
mUserAccounts = accountList;
|
||||
}
|
||||
|
||||
public GetAccounts(UserAccount userAccount){
|
||||
mUserAccounts = new ArrayList<>();
|
||||
mUserAccounts.add(userAccount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCall toApiCall(int apiId, long sequenceId) {
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
ArrayList<Serializable> accountIds = new ArrayList<>();
|
||||
for(UserAccount userAccount : mUserAccounts){
|
||||
accountIds.add(userAccount.getObjectId());
|
||||
}
|
||||
params.add(accountIds);
|
||||
return new ApiCall(apiId, RPC.CALL_GET_ACCOUNTS, params, RPC.VERSION, sequenceId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.Asset;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
public class GetAssets implements ApiCallable {
|
||||
public static final int REQUIRED_API = ApiAccess.API_NONE;
|
||||
|
||||
private List<Asset> assetList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Constructor that will receive a List of Asset instances.
|
||||
*
|
||||
* @param assets List of Asset instances.
|
||||
*/
|
||||
public GetAssets(List<Asset> assets){
|
||||
assetList.addAll(assets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that will accept a string containing the asset id.
|
||||
*
|
||||
* @param id String containing the asset id of the desired asset.
|
||||
*/
|
||||
public GetAssets(String id){
|
||||
assetList.add(new Asset(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that accepts an {@link Asset} object instance.
|
||||
*
|
||||
* @param asset Asset class instance.
|
||||
*/
|
||||
public GetAssets(Asset asset){
|
||||
assetList.add(asset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCall toApiCall(int apiId, long sequenceId) {
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
ArrayList<Serializable> assetIds = new ArrayList<>();
|
||||
for(Asset asset : assetList){
|
||||
assetIds.add(asset.getObjectId());
|
||||
}
|
||||
params.add(assetIds);
|
||||
return new ApiCall(apiId, RPC.CALL_GET_ASSETS, params, RPC.VERSION, sequenceId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
/**
|
||||
* Wrapper around the "get_block" API call.
|
||||
*/
|
||||
|
||||
public class GetBlock implements ApiCallable {
|
||||
|
||||
public static final int REQUIRED_API = ApiAccess.API_DATABASE;
|
||||
|
||||
private long blockNumber;
|
||||
|
||||
public GetBlock(long blockNum){
|
||||
this.blockNumber = blockNum;
|
||||
}
|
||||
|
||||
public ApiCall toApiCall(int apiId, long sequenceId){
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
String blockNum = String.format("%d", this.blockNumber);
|
||||
params.add(blockNum);
|
||||
return new ApiCall(apiId, RPC.CALL_GET_BLOCK, params, RPC.VERSION, sequenceId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
/**
|
||||
* Wrapper around the "get_block_header" API call. To be used in the single-connection mode.
|
||||
*/
|
||||
public class GetBlockHeader implements ApiCallable {
|
||||
public static final int REQUIRED_API = ApiAccess.API_DATABASE;
|
||||
|
||||
private long blockNumber;
|
||||
|
||||
public GetBlockHeader(long number){
|
||||
this.blockNumber = number;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCall toApiCall(int apiId, long sequenceId) {
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
String blockNum = String.format("%d", this.blockNumber);
|
||||
params.add(blockNum);
|
||||
|
||||
return new ApiCall(apiId, RPC.CALL_GET_BLOCK_HEADER, params, RPC.VERSION, sequenceId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
public class GetDynamicGlobalProperties implements ApiCallable {
|
||||
public static final int REQUIRED_API = ApiAccess.API_NONE;
|
||||
|
||||
@Override
|
||||
public ApiCall toApiCall(int apiId, long sequenceId) {
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
return new ApiCall(apiId, RPC.CALL_GET_DYNAMIC_GLOBAL_PROPERTIES, params, RPC.VERSION, sequenceId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
/**
|
||||
* Wrapper around the 'get_full_accounts' API call.
|
||||
*/
|
||||
public class GetFullAccounts implements ApiCallable {
|
||||
|
||||
public static final int REQUIRED_API = ApiAccess.API_NONE;
|
||||
|
||||
private List<String> mUserAccounts;
|
||||
private boolean mSubscribe;
|
||||
|
||||
public GetFullAccounts(UserAccount userAccount, boolean subscribe){
|
||||
this.mUserAccounts = new ArrayList<>();
|
||||
if(userAccount.getName() != null && !userAccount.getName().equals("")){
|
||||
this.mUserAccounts.add(userAccount.getName());
|
||||
}else{
|
||||
this.mUserAccounts.add(userAccount.getObjectId());
|
||||
}
|
||||
this.mSubscribe = subscribe;
|
||||
}
|
||||
|
||||
public GetFullAccounts(List<String> accounts, boolean subscribe){
|
||||
this.mUserAccounts = accounts;
|
||||
this.mSubscribe = subscribe;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCall toApiCall(int apiId, long sequenceId) {
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
ArrayList<Serializable> accounts = new ArrayList<Serializable>(mUserAccounts);
|
||||
params.add(accounts);
|
||||
params.add(mSubscribe);
|
||||
return new ApiCall(apiId, RPC.CALL_GET_FULL_ACCOUNTS, params, RPC.VERSION, sequenceId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.Address;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.errors.MalformedAddressException;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
/**
|
||||
* Wrapper around the 'get_key_references' API call.
|
||||
*/
|
||||
public class GetKeyReferences implements ApiCallable {
|
||||
public static final int REQUIRED_API = ApiAccess.API_NONE;
|
||||
|
||||
private List<Address> addresses = new ArrayList<>();
|
||||
|
||||
public GetKeyReferences(String addr) throws MalformedAddressException, IllegalArgumentException {
|
||||
this(new Address(addr));
|
||||
}
|
||||
|
||||
public GetKeyReferences(Address address){
|
||||
addresses.add(address);
|
||||
}
|
||||
|
||||
public GetKeyReferences(List<Address> addressList){
|
||||
addresses.addAll(addressList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCall toApiCall(int apiId, long sequenceId) {
|
||||
ArrayList<Serializable> inner = new ArrayList<Serializable>();
|
||||
for(Address addr : addresses){
|
||||
inner.add(addr.toString());
|
||||
}
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
params.add(inner);
|
||||
return new ApiCall(apiId, RPC.CALL_GET_KEY_REFERENCES, params, RPC.VERSION, sequenceId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
/** Class that implements get_limit_orders request handler.
|
||||
*
|
||||
* Get limit orders in a given market.
|
||||
*
|
||||
* The request returns the limit orders, ordered from least price to greatest
|
||||
*
|
||||
* @see <a href="https://goo.gl/5sRTRq">get_limit_orders API doc</a>
|
||||
*
|
||||
*/
|
||||
public class GetLimitOrders implements ApiCallable {
|
||||
|
||||
public static final int REQUIRED_API = ApiAccess.API_DATABASE;
|
||||
|
||||
private String a;
|
||||
private String b;
|
||||
private int limit;
|
||||
|
||||
public GetLimitOrders(String a, String b, int limit){
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCall toApiCall(int apiId, long sequenceId) {
|
||||
ArrayList<Serializable> parameters = new ArrayList<>();
|
||||
parameters.add(a);
|
||||
parameters.add(b);
|
||||
parameters.add(limit);
|
||||
return new ApiCall(apiId, RPC.CALL_GET_LIMIT_ORDERS, parameters, RPC.VERSION, sequenceId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
import cy.agorise.graphenej.Asset;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
public class GetMarketHistory implements ApiCallable {
|
||||
public static final int REQUIRED_API = ApiAccess.API_HISTORY;
|
||||
|
||||
public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
|
||||
|
||||
// API call parameters
|
||||
private Asset base;
|
||||
private Asset quote;
|
||||
private long bucket;
|
||||
private Date start;
|
||||
private Date end;
|
||||
|
||||
/**
|
||||
* Constructor that receives the start and end time as UNIX timestamp in milliseconds.
|
||||
*
|
||||
* @param base Desired asset history
|
||||
* @param quote Asset to which the base price will be compared to
|
||||
* @param bucket The time interval (in seconds) for each point should be (analog to
|
||||
* candles on a candle stick graph).
|
||||
* @param start Timestamp (POSIX) of of the most recent operation to retrieve
|
||||
* (Note: The name can be counter intuitive, but it follow the original
|
||||
* API parameter name)
|
||||
* @param end Timestamp (POSIX) of the the earliest operation to retrieve
|
||||
*/
|
||||
public GetMarketHistory(Asset base, Asset quote, long bucket, long start, long end){
|
||||
this(base, quote, bucket, fromTimestamp(start), fromTimestamp(end));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that receives the start and end time as Date instance objects.
|
||||
*
|
||||
* @param base Desired asset history
|
||||
* @param quote Asset to which the base price will be compared to
|
||||
* @param bucket The time interval (in seconds) for each point should be (analog to
|
||||
* candles on a candle stick graph).
|
||||
* @param start Date and time of of the most recent operation to retrieve
|
||||
* (Note: The name can be counter intuitive, but it follow the original
|
||||
* API parameter name)
|
||||
* @param end Date and time of the the earliest operation to retrieve
|
||||
*/
|
||||
public GetMarketHistory(Asset base, Asset quote, long bucket, Date start, Date end){
|
||||
this.base = base;
|
||||
this.quote = quote;
|
||||
this.bucket = bucket;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method used to convert a timestamp to a Date.
|
||||
*
|
||||
* @param timestamp POSIX timestamp expressed in milliseconds since 1/1/1970
|
||||
* @return Date instance
|
||||
*/
|
||||
private static Date fromTimestamp(long timestamp){
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTimeInMillis(timestamp);
|
||||
calendar.set(Calendar.MINUTE, 0);
|
||||
calendar.set(Calendar.SECOND, 0);
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCall toApiCall(int apiId, long sequenceId) {
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
params.add(this.base.getObjectId());
|
||||
params.add(this.quote.getObjectId());
|
||||
params.add(this.bucket);
|
||||
params.add(DATE_FORMAT.format(this.start));
|
||||
params.add(DATE_FORMAT.format(this.end));
|
||||
return new ApiCall(apiId, RPC.CALL_GET_MARKET_HISTORY, params, RPC.VERSION, sequenceId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
/**
|
||||
* Wrapper around the "get_objects" API call.
|
||||
*/
|
||||
public class GetObjects implements ApiCallable {
|
||||
public static final int REQUIRED_API = ApiAccess.API_DATABASE;
|
||||
private List<String> ids;
|
||||
|
||||
public GetObjects(List<String> ids){
|
||||
this.ids = ids;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCall toApiCall(int apiId, long sequenceId) {
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
ArrayList<String> subParams = new ArrayList<>(ids);
|
||||
params.add(subParams);
|
||||
return new ApiCall(apiId, RPC.CALL_GET_OBJECTS, params, RPC.VERSION, sequenceId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
/**
|
||||
* Wrapper around the "get_relative_account_history" API call
|
||||
*/
|
||||
public class GetRelativeAccountHistory implements ApiCallable {
|
||||
|
||||
public static final int REQUIRED_API = ApiAccess.API_HISTORY;
|
||||
|
||||
// API call parameters
|
||||
private UserAccount mUserAccount;
|
||||
private int stop;
|
||||
private int limit;
|
||||
private int start;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param userAccount
|
||||
* @param stop
|
||||
* @param limit
|
||||
* @param start
|
||||
*/
|
||||
public GetRelativeAccountHistory(UserAccount userAccount, int stop, int limit, int start){
|
||||
this.mUserAccount = userAccount;
|
||||
this.stop = stop;
|
||||
this.limit = limit;
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCall toApiCall(int apiId, long sequenceId) {
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
params.add(mUserAccount.getObjectId());
|
||||
params.add(this.stop);
|
||||
params.add(this.limit);
|
||||
params.add(this.start);
|
||||
return new ApiCall(apiId, RPC.CALL_GET_RELATIVE_ACCOUNT_HISTORY, params, RPC.VERSION, sequenceId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.Asset;
|
||||
import cy.agorise.graphenej.BaseOperation;
|
||||
import cy.agorise.graphenej.BlockData;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.Transaction;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
/**
|
||||
* Wrapper around the "get_required_fees" API call
|
||||
*/
|
||||
|
||||
public class GetRequiredFees implements ApiCallable {
|
||||
|
||||
public static final int REQUIRED_API = ApiAccess.API_NONE;
|
||||
|
||||
private Transaction mTransaction;
|
||||
private Asset mFeeAsset;
|
||||
|
||||
public GetRequiredFees(Transaction transaction, Asset feeAsset){
|
||||
this.mTransaction = transaction;
|
||||
this.mFeeAsset = feeAsset;
|
||||
}
|
||||
|
||||
public GetRequiredFees(List<BaseOperation> operations, Asset feeAsset){
|
||||
this.mTransaction = new Transaction(new BlockData(0, 0, 0), operations);
|
||||
this.mFeeAsset = feeAsset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCall toApiCall(int apiId, long sequenceId) {
|
||||
// Building a new API call to request fees information
|
||||
ArrayList<Serializable> accountParams = new ArrayList<>();
|
||||
accountParams.add((Serializable) mTransaction.getOperations());
|
||||
accountParams.add(this.mFeeAsset.getObjectId());
|
||||
return new ApiCall(apiId, RPC.CALL_GET_REQUIRED_FEES, accountParams, RPC.VERSION, sequenceId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
public class GetTransaction implements ApiCallable {
|
||||
public static final int REQUIRED_API = ApiAccess.API_NONE;
|
||||
|
||||
private long blockNumber;
|
||||
private long txIndex;
|
||||
|
||||
public GetTransaction(long blockNumber, long txIndex){
|
||||
this.blockNumber = blockNumber;
|
||||
this.txIndex = txIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCall toApiCall(int apiId, long sequenceId) {
|
||||
List<Serializable> params = new ArrayList<>();
|
||||
params.add(blockNumber);
|
||||
params.add(txIndex);
|
||||
return new ApiCall(apiId, RPC.CALL_GET_TRANSACTION, params, RPC.VERSION, sequenceId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
public class ListAssets implements ApiCallable {
|
||||
|
||||
public static final int REQUIRED_API = ApiAccess.API_DATABASE;
|
||||
|
||||
/**
|
||||
* Constant that must be used as argument to the constructor of this class to indicate
|
||||
* that the user wants to get all existing assets.
|
||||
*/
|
||||
public static final int LIST_ALL = -1;
|
||||
|
||||
/**
|
||||
* Internal constant used to represent the maximum limit of assets retrieved in one call.
|
||||
*/
|
||||
public static final int MAX_BATCH_SIZE = 100;
|
||||
|
||||
private String lowerBound;
|
||||
private int limit;
|
||||
|
||||
public ListAssets(String lowerBoundSymbol, int limit){
|
||||
this.lowerBound = lowerBoundSymbol;
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCall toApiCall(int apiId, long sequenceId) {
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
params.add(this.lowerBound);
|
||||
if(limit > MAX_BATCH_SIZE || limit == LIST_ALL){
|
||||
params.add(MAX_BATCH_SIZE);
|
||||
}else{
|
||||
params.add(this.limit);
|
||||
}
|
||||
return new ApiCall(apiId, RPC.CALL_LIST_ASSETS, params, RPC.VERSION, sequenceId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.Asset;
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
public class LookupAssetSymbols implements ApiCallable {
|
||||
public static final int REQUIRED_API = ApiAccess.API_NONE;
|
||||
|
||||
private List<Asset> mAssetList;
|
||||
|
||||
public LookupAssetSymbols(List<Asset> assetList){
|
||||
this.mAssetList = assetList;
|
||||
}
|
||||
|
||||
public LookupAssetSymbols(Asset asset){
|
||||
mAssetList = new ArrayList<Asset>();
|
||||
mAssetList.add(asset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCall toApiCall(int apiId, long sequenceId) {
|
||||
ArrayList<Serializable> params = new ArrayList<>();
|
||||
ArrayList<String> subArray = new ArrayList<>();
|
||||
for(int i = 0; i < mAssetList.size(); i++){
|
||||
Asset asset = mAssetList.get(i);
|
||||
subArray.add(asset.getObjectId());
|
||||
params.add(subArray);
|
||||
}
|
||||
return new ApiCall(apiId, RPC.CALL_LOOKUP_ASSET_SYMBOLS, params, RPC.VERSION, sequenceId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import cy.agorise.graphenej.RPC;
|
||||
import cy.agorise.graphenej.api.ApiAccess;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
public class SetSubscribeCallback implements ApiCallable {
|
||||
public static final int REQUIRED_API = ApiAccess.API_DATABASE;
|
||||
|
||||
private boolean clearFilter;
|
||||
|
||||
public SetSubscribeCallback(boolean clearFilter){
|
||||
this.clearFilter = clearFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCall toApiCall(int apiId, long sequenceId) {
|
||||
ArrayList<Serializable> subscriptionParams = new ArrayList<>();
|
||||
subscriptionParams.add(new Long(sequenceId));
|
||||
subscriptionParams.add(clearFilter);
|
||||
return new ApiCall(apiId, RPC.CALL_SET_SUBSCRIBE_CALLBACK, subscriptionParams, RPC.VERSION, sequenceId);
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ import cy.agorise.graphenej.interfaces.JsonSerializable;
|
|||
* @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";
|
||||
|
@ -65,7 +66,7 @@ public class ApiCall implements JsonSerializable {
|
|||
paramsArray.add(this.apiId);
|
||||
paramsArray.add(this.methodToCall);
|
||||
JsonArray methodParams = new JsonArray();
|
||||
|
||||
if(this.params != null){
|
||||
for(int i = 0; i < this.params.size(); i++){
|
||||
if(this.params.get(i) instanceof JsonSerializable) {
|
||||
// Sometimes the parameters are objects
|
||||
|
@ -86,6 +87,10 @@ public class ApiCall implements JsonSerializable {
|
|||
array.add(((JsonSerializable) element).toJsonObject());
|
||||
else if (element instanceof String) {
|
||||
array.add((String) element);
|
||||
}else if (element instanceof Long){
|
||||
array.add((Long) element);
|
||||
}else if(element instanceof Integer){
|
||||
array.add((Integer) element);
|
||||
}
|
||||
}
|
||||
methodParams.add(array);
|
||||
|
@ -95,6 +100,7 @@ public class ApiCall implements JsonSerializable {
|
|||
System.out.println("Skipping parameter of type: "+this.params.get(i).getClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
paramsArray.add(methodParams);
|
||||
obj.add(KEY_PARAMS, paramsArray);
|
||||
obj.addProperty(KEY_JSON_RPC, this.jsonrpc);
|
||||
|
|
|
@ -1,13 +1,80 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import cy.agorise.graphenej.Price;
|
||||
|
||||
/**
|
||||
* Created by nelson on 1/9/17.
|
||||
* Price feed of a given asset.
|
||||
*/
|
||||
public class AssetFeed {
|
||||
public Price settlement_price;
|
||||
public long maintenance_collateral_ratio;
|
||||
public long maximum_short_squeeze_ratio;
|
||||
public Price core_exchange_rate;
|
||||
public static final String KEY_SETTLEMENT_PRICE = "settlement_price";
|
||||
public static final String KEY_MAINTENANCE_COLLATERAL_RATIO = "maintenance_collateral_ratio";
|
||||
public static final String KEY_MAXIMUM_SHORT_SQUEEZE_RATIO = "maximum_short_squeeze_ratio";
|
||||
public static final String KEY_CORE_EXCHANGE_RATE = "core_exchange_rate";
|
||||
|
||||
private Price settlement_price;
|
||||
private long maintenance_collateral_ratio;
|
||||
private long maximum_short_squeeze_ratio;
|
||||
private Price core_exchange_rate;
|
||||
|
||||
public AssetFeed(Price settlementPrice, long maintenanceCollateralRatio, long maximumShortSqueezeRatio, Price coreExchangeRate){
|
||||
this.settlement_price = settlementPrice;
|
||||
this.maintenance_collateral_ratio = maintenanceCollateralRatio;
|
||||
this.maximum_short_squeeze_ratio = maximumShortSqueezeRatio;
|
||||
this.core_exchange_rate = coreExchangeRate;
|
||||
}
|
||||
|
||||
public Price getSettlementPrice() {
|
||||
return settlement_price;
|
||||
}
|
||||
|
||||
public void setSettlementPrice(Price settlement_price) {
|
||||
this.settlement_price = settlement_price;
|
||||
}
|
||||
|
||||
public long getMaintenanceCollateralRatio() {
|
||||
return maintenance_collateral_ratio;
|
||||
}
|
||||
|
||||
public void setMaintenanceCollateralRatio(long maintenance_collateral_ratio) {
|
||||
this.maintenance_collateral_ratio = maintenance_collateral_ratio;
|
||||
}
|
||||
|
||||
public long getMaximumShortSqueezeRatio() {
|
||||
return maximum_short_squeeze_ratio;
|
||||
}
|
||||
|
||||
public void setMaximumShortSqueezeRatio(long maximum_short_squeeze_ratio) {
|
||||
this.maximum_short_squeeze_ratio = maximum_short_squeeze_ratio;
|
||||
}
|
||||
|
||||
public Price getCoreExchangeRate() {
|
||||
return core_exchange_rate;
|
||||
}
|
||||
|
||||
public void setCoreExchangeRate(Price core_exchange_rate) {
|
||||
this.core_exchange_rate = core_exchange_rate;
|
||||
}
|
||||
|
||||
public static class AssetFeedDeserializer implements JsonDeserializer<AssetFeed> {
|
||||
|
||||
@Override
|
||||
public AssetFeed deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonObject jsonObject = json.getAsJsonObject();
|
||||
Price settlementPrice = context.deserialize(jsonObject.get(KEY_SETTLEMENT_PRICE).getAsJsonObject(), Price.class);
|
||||
long collateralRatio = jsonObject.get(KEY_MAINTENANCE_COLLATERAL_RATIO).getAsLong();
|
||||
long maximumShortSqueezeRatio = jsonObject.get(KEY_MAXIMUM_SHORT_SQUEEZE_RATIO).getAsLong();
|
||||
Price coreExchangeRate = context.deserialize(jsonObject.get(KEY_CORE_EXCHANGE_RATE), Price.class);
|
||||
AssetFeed assetFeed = new AssetFeed(settlementPrice, collateralRatio, maximumShortSqueezeRatio, coreExchangeRate);
|
||||
return assetFeed;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
/**
|
||||
* Created by nelson on 11/12/16.
|
||||
* Base response class
|
||||
* @deprecated Use {@link JsonRpcResponse} instead
|
||||
*/
|
||||
public class BaseResponse {
|
||||
public long id;
|
||||
|
|
|
@ -1,25 +1,158 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
import cy.agorise.graphenej.GrapheneObject;
|
||||
import cy.agorise.graphenej.Price;
|
||||
import cy.agorise.graphenej.Util;
|
||||
|
||||
/**
|
||||
* This is the representation of the response from the 'get_objects' call with
|
||||
* a 2.4.x id, which will retrieve a 'impl_asset_bitasset_data_type'.
|
||||
*
|
||||
* Created by nelson on 1/8/17.
|
||||
*/
|
||||
public class BitAssetData extends GrapheneObject {
|
||||
public Object[] feeds;
|
||||
public AssetFeed current_feed;
|
||||
public String current_feed_publication_time;
|
||||
public Object options;
|
||||
public long force_settled_volume;
|
||||
public boolean is_prediction_market;
|
||||
public Price settlement_price;
|
||||
public long settlement_fund;
|
||||
public static final String KEY_FEEDS = "feeds";
|
||||
public static final String KEY_CURRENT_FEED = "current_feed";
|
||||
public static final String KEY_CURRENT_FEED_PUBLICATION_TIME = "current_feed_publication_time";
|
||||
public static final String KEY_OPERATIONS = "operations";
|
||||
public static final String KEY_FORCE_SETTLED_VOLUME = "force_settled_volume";
|
||||
public static final String KEY_IS_PREDICTION_MARKET = "is_prediction_market";
|
||||
public static final String KEY_SETTLEMENT_PRICE = "settlement_price";
|
||||
public static final String KEY_SETTLEMENT_FUND = "settlement_fund";
|
||||
|
||||
private ReportedAssetFeed[] feeds;
|
||||
private AssetFeed current_feed;
|
||||
private Date current_feed_publication_time;
|
||||
private Options options;
|
||||
private long force_settled_volume;
|
||||
private boolean is_prediction_market;
|
||||
private Price settlement_price;
|
||||
private long settlement_fund;
|
||||
|
||||
public BitAssetData(String id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
public ReportedAssetFeed[] getFeeds() {
|
||||
return feeds;
|
||||
}
|
||||
|
||||
public void setFeeds(ReportedAssetFeed[] feeds) {
|
||||
this.feeds = feeds;
|
||||
}
|
||||
|
||||
public AssetFeed getCurrentFeed() {
|
||||
return current_feed;
|
||||
}
|
||||
|
||||
public void setCurrentFeed(AssetFeed current_feed) {
|
||||
this.current_feed = current_feed;
|
||||
}
|
||||
|
||||
public Date getCurrentFeedPublicationTime() {
|
||||
return current_feed_publication_time;
|
||||
}
|
||||
|
||||
public void setCurrentFeedPublicationTime(String currentFeedPublicationTime) {
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(Util.TIME_DATE_FORMAT);
|
||||
try {
|
||||
this.current_feed_publication_time = simpleDateFormat.parse(currentFeedPublicationTime);
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public Options getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
public void setOptions(Options options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public long getForceSettledVolume() {
|
||||
return force_settled_volume;
|
||||
}
|
||||
|
||||
public void setForceSettledVolume(long force_settled_volume) {
|
||||
this.force_settled_volume = force_settled_volume;
|
||||
}
|
||||
|
||||
public boolean isPredictionMarket() {
|
||||
return is_prediction_market;
|
||||
}
|
||||
|
||||
public void setIsPredictionMarket(boolean is_prediction_market) {
|
||||
this.is_prediction_market = is_prediction_market;
|
||||
}
|
||||
|
||||
public Price getSettlementPrice() {
|
||||
return settlement_price;
|
||||
}
|
||||
|
||||
public void setSettlementPrice(Price settlementPrice) {
|
||||
this.settlement_price = settlementPrice;
|
||||
}
|
||||
|
||||
public long getSettlementFund() {
|
||||
return settlement_fund;
|
||||
}
|
||||
|
||||
public void setSettlementFund(long settlementFund) {
|
||||
this.settlement_fund = settlementFund;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom deserializer used to instantiate the BitAssetData class from the response of the
|
||||
* 'get_objects' API call.
|
||||
*/
|
||||
public static class BitAssetDataDeserializer implements JsonDeserializer<BitAssetData> {
|
||||
|
||||
@Override
|
||||
public BitAssetData deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonObject jsonObject = json.getAsJsonObject();
|
||||
String id = jsonObject.get(GrapheneObject.KEY_ID).getAsString();
|
||||
BitAssetData bitAssetData = new BitAssetData(id);
|
||||
ArrayList<ReportedAssetFeed> reportedAssetFeeds = new ArrayList<>();
|
||||
|
||||
JsonArray jsonAssetFeeds = jsonObject.get(KEY_FEEDS).getAsJsonArray();
|
||||
for(JsonElement jsonFeed : jsonAssetFeeds){
|
||||
ReportedAssetFeed reportedAssetFeed = context.deserialize(jsonFeed, ReportedAssetFeed.class);
|
||||
reportedAssetFeeds.add(reportedAssetFeed);
|
||||
}
|
||||
|
||||
// Deserializing attributes
|
||||
JsonElement jsonCurrentFeed = jsonObject.get(KEY_CURRENT_FEED).getAsJsonObject();
|
||||
AssetFeed assetFeed = context.deserialize(jsonCurrentFeed, AssetFeed.class);
|
||||
String publicationTime = jsonObject.get(KEY_CURRENT_FEED_PUBLICATION_TIME).getAsString();
|
||||
Options options = context.deserialize(jsonObject.get(KEY_OPERATIONS), Options.class);
|
||||
long forceSettledVolume = jsonObject.get(KEY_FORCE_SETTLED_VOLUME).getAsLong();
|
||||
boolean isPredictionMarket = jsonObject.get(KEY_IS_PREDICTION_MARKET).getAsBoolean();
|
||||
Price settlementPrice = context.deserialize(jsonObject.get(KEY_SETTLEMENT_PRICE), Price.class);
|
||||
long settlementFund = jsonObject.get(KEY_SETTLEMENT_FUND).getAsLong();
|
||||
|
||||
// Setting attributes
|
||||
bitAssetData.setFeeds(reportedAssetFeeds.toArray(new ReportedAssetFeed[reportedAssetFeeds.size()]));
|
||||
bitAssetData.setCurrentFeed(assetFeed);
|
||||
bitAssetData.setCurrentFeedPublicationTime(publicationTime);
|
||||
bitAssetData.setOptions(options);
|
||||
bitAssetData.setForceSettledVolume(forceSettledVolume);
|
||||
bitAssetData.setIsPredictionMarket(isPredictionMarket);
|
||||
bitAssetData.setSettlementPrice(settlementPrice);
|
||||
bitAssetData.setSettlementFund(settlementFund);
|
||||
return bitAssetData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
/**
|
||||
* Created by nelson on 12/13/16.
|
||||
* Class used to represent the response to the 'get_block_header' API call.
|
||||
*/
|
||||
public class BlockHeader {
|
||||
public String previous;
|
||||
public String timestamp;
|
||||
public String witness;
|
||||
public String transaction_merkle_root;
|
||||
public Object[] extension;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* Model class used in the de-serialization of the response to the 'get_full_accounts' API call.
|
||||
* @see cy.agorise.graphenej.api.calls.GetFullAccounts
|
||||
*/
|
||||
public class FullAccountDetails {
|
||||
private AccountProperties account;
|
||||
private Statistics statistics;
|
||||
|
||||
public FullAccountDetails(AccountProperties properties, Statistics statistics){
|
||||
this.account = properties;
|
||||
this.statistics = statistics;
|
||||
}
|
||||
|
||||
public AccountProperties getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
public void setAccount(AccountProperties account) {
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
public Statistics getStatistics() {
|
||||
return statistics;
|
||||
}
|
||||
|
||||
public void setStatistics(Statistics statistics) {
|
||||
this.statistics = statistics;
|
||||
}
|
||||
|
||||
public static class Statistics {
|
||||
public String id;
|
||||
public String owner;
|
||||
public String name;
|
||||
public String most_recent_op;
|
||||
public long total_ops;
|
||||
public long removed_ops;
|
||||
public long total_core_in_orders;
|
||||
public String core_in_balance;
|
||||
public boolean has_cashback_vb;
|
||||
public boolean is_voting;
|
||||
public long lifetime_fees_paid;
|
||||
public long pending_fees;
|
||||
public long pending_vested_fees;
|
||||
}
|
||||
|
||||
public static class FullAccountDeserializer implements JsonDeserializer<FullAccountDetails> {
|
||||
|
||||
@Override
|
||||
public FullAccountDetails deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonArray array = (JsonArray) json;
|
||||
JsonObject jsonObject = (JsonObject) array.get(1);
|
||||
AccountProperties properties = context.deserialize(jsonObject.get("account"), AccountProperties.class);
|
||||
Statistics statistics = context.deserialize(jsonObject.get("statistics"), Statistics.class);
|
||||
return new FullAccountDetails(properties, statistics);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
import cy.agorise.graphenej.operations.TransferOperation;
|
||||
|
||||
|
||||
/**
|
||||
* This class offers support to deserialization of transfer operations received by the API
|
||||
* method get_relative_account_history.
|
||||
*
|
||||
* More operations types might be listed in the response of that method, but by using this class
|
||||
* those will be filtered out of the parsed result.
|
||||
*/
|
||||
public class HistoricalTransfer {
|
||||
private String id;
|
||||
private TransferOperation op;
|
||||
public Object[] result;
|
||||
private long block_num;
|
||||
private long trx_in_block;
|
||||
private long op_in_trx;
|
||||
private long virtual_op;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public TransferOperation getOperation() {
|
||||
return op;
|
||||
}
|
||||
|
||||
public void setOperation(TransferOperation op) {
|
||||
this.op = op;
|
||||
}
|
||||
|
||||
public long getBlockNum() {
|
||||
return block_num;
|
||||
}
|
||||
|
||||
public void setBlockNum(long block_num) {
|
||||
this.block_num = block_num;
|
||||
}
|
||||
|
||||
public long getTransactionsInBlock() {
|
||||
return trx_in_block;
|
||||
}
|
||||
|
||||
public void setTransactionsInBlock(long trx_in_block) {
|
||||
this.trx_in_block = trx_in_block;
|
||||
}
|
||||
|
||||
public long getOperationsInTrx() {
|
||||
return op_in_trx;
|
||||
}
|
||||
|
||||
public void setOperationsInTrx(long op_in_trx) {
|
||||
this.op_in_trx = op_in_trx;
|
||||
}
|
||||
|
||||
public long getVirtualOp() {
|
||||
return virtual_op;
|
||||
}
|
||||
|
||||
public void setVirtualOp(long virtual_op) {
|
||||
this.virtual_op = virtual_op;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Model class used to represent the struct defined in graphene::app::history_operation_detail and
|
||||
* returned as response to the 'get_account_history_by_operations' API call.
|
||||
*/
|
||||
public class HistoryOperationDetail {
|
||||
private long total_count;
|
||||
List<OperationHistory> operation_history_objs;
|
||||
|
||||
public long getTotalCount() {
|
||||
return total_count;
|
||||
}
|
||||
|
||||
public void setTotalCount(long total_count) {
|
||||
this.total_count = total_count;
|
||||
}
|
||||
|
||||
public List<OperationHistory> getOperationHistoryObjs() {
|
||||
return operation_history_objs;
|
||||
}
|
||||
|
||||
public void setOperationHistoryObjs(List<OperationHistory> operation_history_objs) {
|
||||
this.operation_history_objs = operation_history_objs;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.GrapheneObject;
|
||||
import cy.agorise.graphenej.ObjectType;
|
||||
import cy.agorise.graphenej.OperationType;
|
||||
import cy.agorise.graphenej.Transaction;
|
||||
|
||||
/**
|
||||
* Class that represents a generic subscription notification.
|
||||
* The template for every subscription response is the following:
|
||||
*
|
||||
* {
|
||||
* "method": "notice"
|
||||
* "params": [
|
||||
* SUBSCRIPTION_ID,
|
||||
* [[
|
||||
* { "id": "2.1.0", ... },
|
||||
* { "id": ... },
|
||||
* { "id": ... },
|
||||
* { "id": ... }
|
||||
* ]]
|
||||
* ],
|
||||
* }
|
||||
*/
|
||||
public class JsonRpcNotification {
|
||||
public static final String KEY_METHOD = "method";
|
||||
public static final String KEY_PARAMS = "params";
|
||||
|
||||
public String method;
|
||||
public List<Serializable> params;
|
||||
|
||||
/**
|
||||
* Inner static class used to parse and deserialize subscription notifications.
|
||||
*/
|
||||
public static class JsonRpcNotificationDeserializer implements JsonDeserializer<JsonRpcNotification> {
|
||||
|
||||
@Override
|
||||
public JsonRpcNotification deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonRpcNotification notification = new JsonRpcNotification();
|
||||
JsonObject responseObject = json.getAsJsonObject();
|
||||
if(!responseObject.has(KEY_METHOD)){
|
||||
return notification;
|
||||
}
|
||||
notification.method = responseObject.get(KEY_METHOD).getAsString();
|
||||
|
||||
JsonArray paramsArray = responseObject.get(KEY_PARAMS).getAsJsonArray();
|
||||
notification.params = new ArrayList<>();
|
||||
notification.params.add(paramsArray.get(0).getAsInt());
|
||||
ArrayList<Serializable> secondArgument = new ArrayList<>();
|
||||
notification.params.add(secondArgument);
|
||||
|
||||
JsonArray subArray = paramsArray.get(1).getAsJsonArray().get(0).getAsJsonArray();
|
||||
for(JsonElement object : subArray){
|
||||
if(object.isJsonObject()){
|
||||
GrapheneObject grapheneObject = new GrapheneObject(object.getAsJsonObject().get(GrapheneObject.KEY_ID).getAsString());
|
||||
|
||||
JsonObject jsonObject = object.getAsJsonObject();
|
||||
if(grapheneObject.getObjectType() == ObjectType.ACCOUNT_BALANCE_OBJECT){
|
||||
AccountBalanceUpdate balanceObject = new AccountBalanceUpdate(grapheneObject.getObjectId());
|
||||
balanceObject.owner = jsonObject.get(AccountBalanceUpdate.KEY_OWNER).getAsString();
|
||||
balanceObject.asset_type = jsonObject.get(AccountBalanceUpdate.KEY_ASSET_TYPE).getAsString();
|
||||
balanceObject.balance = jsonObject.get(AccountBalanceUpdate.KEY_BALANCE).getAsLong();
|
||||
secondArgument.add(balanceObject);
|
||||
}else if(grapheneObject.getObjectType() == ObjectType.DYNAMIC_GLOBAL_PROPERTY_OBJECT){
|
||||
DynamicGlobalProperties dynamicGlobalProperties = context.deserialize(object, DynamicGlobalProperties.class);
|
||||
secondArgument.add(dynamicGlobalProperties);
|
||||
}else if(grapheneObject.getObjectType() == ObjectType.TRANSACTION_OBJECT){
|
||||
BroadcastedTransaction broadcastedTransaction = new BroadcastedTransaction(grapheneObject.getObjectId());
|
||||
broadcastedTransaction.setTransaction((Transaction) context.deserialize(jsonObject.get(BroadcastedTransaction.KEY_TRX), Transaction.class));
|
||||
broadcastedTransaction.setTransactionId(jsonObject.get(BroadcastedTransaction.KEY_TRX_ID).getAsString());
|
||||
secondArgument.add(broadcastedTransaction);
|
||||
}else if(grapheneObject.getObjectType() == ObjectType.OPERATION_HISTORY_OBJECT){
|
||||
if(jsonObject.get(OperationHistory.KEY_OP).getAsJsonArray().get(0).getAsLong() == OperationType.TRANSFER_OPERATION.ordinal()){
|
||||
OperationHistory operationHistory = context.deserialize(jsonObject, OperationHistory.class);
|
||||
secondArgument.add(operationHistory);
|
||||
}else{
|
||||
//TODO: Add support for other operations
|
||||
}
|
||||
}else{
|
||||
//TODO: Add support for other types of objects
|
||||
}
|
||||
}else{
|
||||
secondArgument.add(object.getAsString());
|
||||
}
|
||||
}
|
||||
return notification;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
/**
|
||||
* Used to represent a JSON-RPC response object
|
||||
*/
|
||||
|
||||
public class JsonRpcResponse<T> {
|
||||
public long id;
|
||||
public Error error;
|
||||
public T result;
|
||||
|
||||
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,136 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import cy.agorise.graphenej.BaseOperation;
|
||||
import cy.agorise.graphenej.GrapheneObject;
|
||||
|
||||
|
||||
/**
|
||||
* This class offers support to deserialization of transfer operations received by the API
|
||||
* method get_relative_account_history.
|
||||
*
|
||||
* More operations types might be listed in the response of that method, but by using this class
|
||||
* those will be filtered out of the parsed result.
|
||||
*/
|
||||
public class OperationHistory extends GrapheneObject implements Serializable {
|
||||
public static final String KEY_OP = "op";
|
||||
public static final String KEY_BLOCK_NUM = "block_num";
|
||||
public static final String KEY_TRX_IN_BLOCK = "trx_in_block";
|
||||
public static final String KEY_OP_IN_TRX = "op_in_trx";
|
||||
public static final String KEY_VIRTUAL_OP = "virtual_op";
|
||||
|
||||
private BaseOperation op;
|
||||
public Object[] result;
|
||||
private long block_num;
|
||||
private long trx_in_block;
|
||||
private long op_in_trx;
|
||||
private long virtual_op;
|
||||
|
||||
public OperationHistory(String id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
public BaseOperation getOperation() {
|
||||
return op;
|
||||
}
|
||||
|
||||
public void setOperation(BaseOperation op) {
|
||||
this.op = op;
|
||||
}
|
||||
|
||||
public long getBlockNum() {
|
||||
return block_num;
|
||||
}
|
||||
|
||||
public void setBlockNum(long block_num) {
|
||||
this.block_num = block_num;
|
||||
}
|
||||
|
||||
public long getTransactionsInBlock() {
|
||||
return trx_in_block;
|
||||
}
|
||||
|
||||
public void setTransactionsInBlock(long trx_in_block) {
|
||||
this.trx_in_block = trx_in_block;
|
||||
}
|
||||
|
||||
public long getOperationsInTrx() {
|
||||
return op_in_trx;
|
||||
}
|
||||
|
||||
public void setOperationsInTrx(long op_in_trx) {
|
||||
this.op_in_trx = op_in_trx;
|
||||
}
|
||||
|
||||
public long getVirtualOp() {
|
||||
return virtual_op;
|
||||
}
|
||||
|
||||
public void setVirtualOp(long virtual_op) {
|
||||
this.virtual_op = virtual_op;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializer used to transform a an operation history object from its serialized form to an
|
||||
* OperationHistory instance.
|
||||
*
|
||||
* The serialized form of this object is the following:
|
||||
*
|
||||
* {
|
||||
"id": "1.11.178205535",
|
||||
"op": [
|
||||
14,
|
||||
{
|
||||
"fee": {
|
||||
"amount": 10425,
|
||||
"asset_id": "1.3.0"
|
||||
},
|
||||
"issuer": "1.2.374566",
|
||||
"asset_to_issue": {
|
||||
"amount": 8387660,
|
||||
"asset_id": "1.3.3271"
|
||||
},
|
||||
"issue_to_account": "1.2.797835",
|
||||
"extensions": []
|
||||
}
|
||||
],
|
||||
"result": [
|
||||
0,
|
||||
{}
|
||||
],
|
||||
"block_num": 26473240,
|
||||
"trx_in_block": 11,
|
||||
"op_in_trx": 0,
|
||||
"virtual_op": 660
|
||||
}
|
||||
* //TODO: Expand this deserializer for operation history objects that have an operation other than the transfer operation
|
||||
*/
|
||||
public static class OperationHistoryDeserializer implements JsonDeserializer<OperationHistory> {
|
||||
|
||||
@Override
|
||||
public OperationHistory deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonObject jsonObject = json.getAsJsonObject();
|
||||
String id = jsonObject.get(KEY_ID).getAsString();
|
||||
long blockNum = jsonObject.get(KEY_BLOCK_NUM).getAsLong();
|
||||
long trxInBlock = jsonObject.get(KEY_TRX_IN_BLOCK).getAsLong();
|
||||
long opInTrx = jsonObject.get(KEY_OP_IN_TRX).getAsLong();
|
||||
BaseOperation operation = context.deserialize(jsonObject.get(KEY_OP), BaseOperation.class);
|
||||
long virtualOp = jsonObject.get(KEY_VIRTUAL_OP).getAsLong();
|
||||
OperationHistory operationHistory = new OperationHistory(id);
|
||||
operationHistory.setBlockNum(blockNum);
|
||||
operationHistory.setTransactionsInBlock(trxInBlock);
|
||||
operationHistory.setOperationsInTrx(opInTrx);
|
||||
operationHistory.setOperation(operation);
|
||||
operationHistory.setVirtualOp(virtualOp);
|
||||
return operationHistory;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
/**
|
||||
* Class used to represent the 'options' object returned inside the response obtained after
|
||||
* querying for an object of type 'asset_bitasset_data' (2.4.x)
|
||||
*/
|
||||
|
||||
public class Options {
|
||||
private long feed_lifetime_sec;
|
||||
private long minimum_feeds;
|
||||
private long force_settlement_delay_sec;
|
||||
private long force_settlement_offset_percent;
|
||||
private long maximum_force_settlement_volume;
|
||||
private String short_backing_asset;
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.Util;
|
||||
|
||||
/**
|
||||
* Witness-provided asset price feed
|
||||
*/
|
||||
|
||||
public class ReportedAssetFeed {
|
||||
private UserAccount reporter;
|
||||
private AssetFeed assetFeed;
|
||||
private Date reportedDate;
|
||||
|
||||
public ReportedAssetFeed(UserAccount userAccount, Date date, AssetFeed assetFeed){
|
||||
this.reporter = userAccount;
|
||||
this.reportedDate = date;
|
||||
this.assetFeed = assetFeed;
|
||||
}
|
||||
|
||||
public UserAccount getReporter() {
|
||||
return reporter;
|
||||
}
|
||||
|
||||
public void setReporter(UserAccount reporter) {
|
||||
this.reporter = reporter;
|
||||
}
|
||||
|
||||
public AssetFeed getAssetFeed() {
|
||||
return assetFeed;
|
||||
}
|
||||
|
||||
public void setAssetFeed(AssetFeed assetFeed) {
|
||||
this.assetFeed = assetFeed;
|
||||
}
|
||||
|
||||
public Date getReportedDate() {
|
||||
return reportedDate;
|
||||
}
|
||||
|
||||
public void setReportedDate(Date reportedDate) {
|
||||
this.reportedDate = reportedDate;
|
||||
}
|
||||
|
||||
public static class ReportedAssetFeedDeserializer implements JsonDeserializer<ReportedAssetFeed> {
|
||||
|
||||
@Override
|
||||
public ReportedAssetFeed deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonArray array = json.getAsJsonArray();
|
||||
String userId = array.get(0).getAsString();
|
||||
JsonArray subArray = (JsonArray) array.get(1);
|
||||
String dateString = subArray.get(0).getAsString();
|
||||
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(Util.TIME_DATE_FORMAT);
|
||||
Date reportDate = null;
|
||||
try {
|
||||
reportDate = simpleDateFormat.parse(dateString);
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
AssetFeed assetFeed = context.deserialize(subArray.get(1), AssetFeed.class);
|
||||
UserAccount userAccount = new UserAccount(userId);
|
||||
return new ReportedAssetFeed(userAccount, reportDate, assetFeed);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ import java.util.List;
|
|||
|
||||
import cy.agorise.graphenej.GrapheneObject;
|
||||
import cy.agorise.graphenej.ObjectType;
|
||||
import cy.agorise.graphenej.OperationType;
|
||||
import cy.agorise.graphenej.Transaction;
|
||||
import cy.agorise.graphenej.interfaces.SubscriptionListener;
|
||||
|
||||
|
@ -43,15 +44,12 @@ import cy.agorise.graphenej.interfaces.SubscriptionListener;
|
|||
* To minimize CPU usage, we introduce a scheme of selective parsing, implemented by the static inner class
|
||||
* SubscriptionResponseDeserializer.
|
||||
*
|
||||
* Created by nelson on 1/12/17.
|
||||
*/
|
||||
public class SubscriptionResponse {
|
||||
private static final String TAG = "SubscriptionResponse";
|
||||
public static final String KEY_ID = "id";
|
||||
public static final String KEY_METHOD = "method";
|
||||
public static final String KEY_PARAMS = "params";
|
||||
|
||||
public int id;
|
||||
public String method;
|
||||
public List<Serializable> params;
|
||||
|
||||
|
@ -182,6 +180,14 @@ public class SubscriptionResponse {
|
|||
broadcastedTransaction.setTransactionId(jsonObject.get(BroadcastedTransaction.KEY_TRX_ID).getAsString());
|
||||
objectMap.put(ObjectType.TRANSACTION_OBJECT, true);
|
||||
secondArgument.add(broadcastedTransaction);
|
||||
}else if(grapheneObject.getObjectType() == ObjectType.OPERATION_HISTORY_OBJECT){
|
||||
if(jsonObject.get(OperationHistory.KEY_OP).getAsJsonArray().get(0).getAsLong() == OperationType.TRANSFER_OPERATION.ordinal()){
|
||||
OperationHistory operationHistory = context.deserialize(jsonObject, OperationHistory.class);
|
||||
objectMap.put(ObjectType.OPERATION_HISTORY_OBJECT, true);
|
||||
secondArgument.add(operationHistory);
|
||||
}else{
|
||||
//TODO: Add support for other operations
|
||||
}
|
||||
}else{
|
||||
//TODO: Add support for other types of objects
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package cy.agorise.graphenej.models;
|
|||
|
||||
/**
|
||||
* Generic witness response
|
||||
* @deprecated Use {@link JsonRpcResponse} instead
|
||||
*/
|
||||
public class WitnessResponse<T> extends BaseResponse{
|
||||
public static final String KEY_ID = "id";
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
package cy.agorise.graphenej.network;
|
||||
|
||||
import cy.agorise.graphenej.stats.ExponentialMovingAverage;
|
||||
|
||||
/**
|
||||
* Class that represents a full node and is used to keep track of its round-trip time measured in milliseconds.
|
||||
*/
|
||||
public class FullNode implements Comparable {
|
||||
|
||||
private String mUrl;
|
||||
private ExponentialMovingAverage mLatency;
|
||||
private boolean isConnected;
|
||||
private boolean isRemoved;
|
||||
|
||||
private FullNode(){}
|
||||
|
||||
/**
|
||||
* Constructor used to specify both the node URL and the alpha parameter that one wishes to set the
|
||||
* exponential moving average with.
|
||||
* <p>
|
||||
* The alpha parameter represents the degree of weighting decrease, and can be specified as any value
|
||||
* between 0 and 1. A higher alpha discounts older observations faster.
|
||||
*
|
||||
* @param url The node URL.
|
||||
* @param alpha The alpha parameter used to compute the exponential moving average.
|
||||
*/
|
||||
public FullNode(String url, double alpha){
|
||||
mLatency = new ExponentialMovingAverage(alpha);
|
||||
mUrl = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor used to specify only the node URL.
|
||||
* <p>
|
||||
* The alpha parameter is set to the value specified at {@link ExponentialMovingAverage#DEFAULT_ALPHA}
|
||||
*
|
||||
* @param url The node URL.
|
||||
*/
|
||||
public FullNode(String url){
|
||||
this(url, ExponentialMovingAverage.DEFAULT_ALPHA);
|
||||
}
|
||||
|
||||
/**
|
||||
* Full node URL getter
|
||||
* @return
|
||||
*/
|
||||
public String getUrl() {
|
||||
return mUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Full node URL setter
|
||||
* @param mUrl
|
||||
*/
|
||||
public void setUrl(String mUrl) {
|
||||
this.mUrl = mUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return The latest latency average value. If no measurement has been taken yet, the
|
||||
* maximum allows value of a long primitive, or 2<sup>63</sup>-1 will be returned.
|
||||
*/
|
||||
public double getLatencyValue() {
|
||||
double average = mLatency.getAverage();
|
||||
if(average == 0){
|
||||
return Long.MAX_VALUE;
|
||||
}else{
|
||||
return average;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return isConnected;
|
||||
}
|
||||
|
||||
public void setConnected(boolean connected) {
|
||||
isConnected = connected;
|
||||
}
|
||||
|
||||
public boolean isRemoved() {
|
||||
return isRemoved;
|
||||
}
|
||||
|
||||
public void setRemoved(boolean removed) {
|
||||
isRemoved = removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that updates the mLatency average with a new value.
|
||||
* @param latency Most recent mLatency sample to be added to the exponential average
|
||||
*/
|
||||
public void addLatencyValue(double latency) {
|
||||
this.mLatency.updateValue(latency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Object o) {
|
||||
FullNode node = (FullNode) o;
|
||||
double doubleResult = getLatencyValue() - node.getLatencyValue();
|
||||
if(doubleResult > 0)
|
||||
return 1;
|
||||
else if(doubleResult < 0)
|
||||
return -1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
FullNode fullNode = (FullNode) o;
|
||||
return mUrl.equals(fullNode.getUrl());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mUrl.hashCode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package cy.agorise.graphenej.network;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
|
||||
public class LatencyNodeProvider implements NodeProvider {
|
||||
|
||||
private PriorityBlockingQueue<FullNode> mFullNodeHeap;
|
||||
|
||||
public LatencyNodeProvider(){
|
||||
mFullNodeHeap = new PriorityBlockingQueue<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FullNode getBestNode() {
|
||||
return mFullNodeHeap.peek();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNode(FullNode fullNode) {
|
||||
mFullNodeHeap.add(fullNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateNode(FullNode fullNode) {
|
||||
mFullNodeHeap.remove(fullNode);
|
||||
return mFullNodeHeap.offer(fullNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing node with the new latency value.
|
||||
*
|
||||
* @param fullNode Existing full node instance
|
||||
* @param latency New latency measurement
|
||||
* @return True if the node priority was updated successfully
|
||||
*/
|
||||
public boolean updateNode(FullNode fullNode, int latency){
|
||||
boolean existed = mFullNodeHeap.remove(fullNode);
|
||||
if(existed){
|
||||
fullNode.addLatencyValue(latency);
|
||||
return mFullNodeHeap.add(fullNode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNode(FullNode fullNode) {
|
||||
mFullNodeHeap.remove(fullNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FullNode> getSortedNodes() {
|
||||
FullNode[] nodeArray = mFullNodeHeap.toArray(new FullNode[mFullNodeHeap.size()]);
|
||||
ArrayList<FullNode> nodeList = new ArrayList<>();
|
||||
for(FullNode fullNode : nodeArray){
|
||||
if(fullNode != null){
|
||||
nodeList.add(fullNode);
|
||||
}
|
||||
}
|
||||
Collections.sort(nodeList);
|
||||
return nodeList;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,210 @@
|
|||
package cy.agorise.graphenej.network;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.api.android.NetworkService;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.WebSocket;
|
||||
import okhttp3.WebSocketListener;
|
||||
|
||||
/**
|
||||
* Class that encapsulates the node latency verification task
|
||||
*/
|
||||
public class NodeLatencyVerifier {
|
||||
private final String TAG = this.getClass().getName();
|
||||
|
||||
private static final int DEFAULT_LATENCY_VERIFICATION_PERIOD = 5 * 1000;
|
||||
|
||||
// Variable used to store the list of nodes that should be verified
|
||||
private List<FullNode> mNodeList;
|
||||
|
||||
// Variable used to store the desired verification period
|
||||
private long verificationPeriod;
|
||||
|
||||
// Subject used to publish the result to interested parties
|
||||
private PublishSubject<FullNode> subject = PublishSubject.create();
|
||||
|
||||
private HashMap<HttpUrl, FullNode> nodeURLMap = new HashMap<>();
|
||||
|
||||
// Map used to store the first timestamp required for a RTT (Round Trip Time) measurement.
|
||||
// If:
|
||||
// RTT = t2 - t1
|
||||
// This map will hold the value of t1 for each one of the nodes to be measured.
|
||||
private HashMap<FullNode, Long> timestamps = new HashMap<>();
|
||||
|
||||
private HashMap<String, Request> requestMap = new HashMap<>();
|
||||
|
||||
private Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
private OkHttpClient client;
|
||||
|
||||
public NodeLatencyVerifier(List<FullNode> nodes){
|
||||
this(nodes, DEFAULT_LATENCY_VERIFICATION_PERIOD);
|
||||
}
|
||||
|
||||
public NodeLatencyVerifier(List<FullNode> nodes, long period){
|
||||
mNodeList = nodes;
|
||||
verificationPeriod = period;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to start the latency verification task.
|
||||
* <p>
|
||||
* The returning object can be used for interested parties to receive constant updates
|
||||
* regarding new latency measurements for every full node.
|
||||
* </p>
|
||||
* @return A {@link PublishSubject} class instance.
|
||||
*/
|
||||
public PublishSubject start(){
|
||||
mHandler.post(mVerificationTask);
|
||||
return subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to cancel the verification task.
|
||||
*/
|
||||
public void stop(){
|
||||
mHandler.removeCallbacks(mVerificationTask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Node latency verification task.
|
||||
*/
|
||||
private final Runnable mVerificationTask = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for(FullNode fullNode : mNodeList){
|
||||
long before = System.currentTimeMillis();
|
||||
timestamps.put(fullNode, before);
|
||||
|
||||
// We want to reuse the same OkHttpClient instance if possible
|
||||
if(client == null) client = new OkHttpClient();
|
||||
|
||||
// Same thing with the Request instance, we want to reuse them. But since
|
||||
// we might have one request per node, we keep them in a map.
|
||||
Request request;
|
||||
if(requestMap.containsKey(fullNode.getUrl())){
|
||||
request = requestMap.get(fullNode.getUrl());
|
||||
}else{
|
||||
// If the map had no entry for the request we want, we create one
|
||||
// and add it to the map.
|
||||
request = new Request.Builder().url(fullNode.getUrl()).build();
|
||||
requestMap.put(fullNode.getUrl(), request);
|
||||
}
|
||||
|
||||
String normalURL = fullNode.getUrl().replace("wss://", "https://");
|
||||
HttpUrl key = HttpUrl.parse(normalURL);
|
||||
if(!nodeURLMap.containsKey(key)){
|
||||
nodeURLMap.put(key, fullNode);
|
||||
}
|
||||
client.newWebSocket(request, mWebSocketListener);
|
||||
}
|
||||
mHandler.postDelayed(this, verificationPeriod);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Listener that will be called upon a server response.
|
||||
*/
|
||||
private WebSocketListener mWebSocketListener = new WebSocketListener() {
|
||||
@Override
|
||||
public void onOpen(WebSocket webSocket, Response response) {
|
||||
super.onOpen(webSocket, response);
|
||||
handleResponse(webSocket, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
||||
super.onFailure(webSocket, t, response);
|
||||
handleResponse(webSocket, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to handle the node's first response. The idea here is to obtain
|
||||
* the RTT (Round Trip Time) measurement and publish it using the PublishSubject.
|
||||
*
|
||||
* @param webSocket WebSocket instance
|
||||
* @param response Response instance
|
||||
*/
|
||||
private void handleResponse(WebSocket webSocket, Response response){
|
||||
synchronized (this){
|
||||
// Obtaining the HttpUrl instance that was previously used as a key
|
||||
HttpUrl url = webSocket.request().url();
|
||||
if(nodeURLMap.containsKey(url)){
|
||||
FullNode fullNode = nodeURLMap.get(url);
|
||||
long delay;
|
||||
|
||||
if(response == null) {
|
||||
// There is no internet connection, or the node is unreachable. We are just
|
||||
// putting an artificial delay.
|
||||
delay = Long.MAX_VALUE;
|
||||
} else {
|
||||
long after = System.currentTimeMillis();
|
||||
long before = timestamps.get(fullNode);
|
||||
delay = after - before;
|
||||
}
|
||||
if(fullNode != null){
|
||||
fullNode.addLatencyValue(delay);
|
||||
subject.onNext(fullNode);
|
||||
}else{
|
||||
Log.w(TAG,"Could not extract FullNode instance from the map");
|
||||
}
|
||||
}else{
|
||||
// We cannot properly handle a response to a request whose
|
||||
// URL was not registered at the nodeURLMap. This is because without this,
|
||||
// we cannot know to which node this response corresponds. This should not happen.
|
||||
Log.e(TAG,"nodeURLMap does not contain url: "+url);
|
||||
for(HttpUrl key : nodeURLMap.keySet()){
|
||||
Log.e(TAG,"> "+key);
|
||||
}
|
||||
}
|
||||
webSocket.close(NetworkService.NORMAL_CLOSURE_STATUS, null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the 'isConnected' attribute of a specific node.
|
||||
* @param fullNode The node we want to update.
|
||||
*/
|
||||
public void updateActiveNodeInformation(FullNode fullNode){
|
||||
for(FullNode node : mNodeList){
|
||||
if(node.equals(fullNode)){
|
||||
node.setConnected(fullNode.isConnected());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given node from the nodes list
|
||||
* @param fullNode The node to remove
|
||||
*/
|
||||
public void removeNode(FullNode fullNode){
|
||||
for(FullNode node : mNodeList){
|
||||
if(node.equals(fullNode)){
|
||||
mNodeList.remove(node);
|
||||
|
||||
String normalURL = node.getUrl().replace("wss://", "https://");
|
||||
HttpUrl key = HttpUrl.parse(normalURL);
|
||||
nodeURLMap.remove(key);
|
||||
|
||||
node.setRemoved(true);
|
||||
subject.onNext(node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<FullNode> getNodeList(){
|
||||
return mNodeList;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package cy.agorise.graphenej.network;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface used to describe the high level characteristics of a class that will
|
||||
* hold and manage a list of {@link FullNode} instances.
|
||||
*
|
||||
* The idea is that the class implementing this interface should provide node instances
|
||||
* and thus URLs for the {@link cy.agorise.graphenej.api.android.NetworkService} with
|
||||
* different sorting heuristics.
|
||||
*/
|
||||
public interface NodeProvider {
|
||||
|
||||
/**
|
||||
* Returns the node with the best characteristics. Returns null if there is no {@link FullNode}
|
||||
* @return A FullNode instance
|
||||
*/
|
||||
FullNode getBestNode();
|
||||
|
||||
/**
|
||||
* Adds a new node to the queue
|
||||
* @param fullNode {@link FullNode} instance to add.
|
||||
*/
|
||||
void addNode(FullNode fullNode);
|
||||
|
||||
/**
|
||||
* Updates the rating of a specific node that is already in the NodeProvider
|
||||
* @param fullNode The node tu update
|
||||
*/
|
||||
boolean updateNode(FullNode fullNode);
|
||||
|
||||
/**
|
||||
* Removes the given node from the nodes list
|
||||
* @param fullNode The node to remove
|
||||
*/
|
||||
void removeNode(FullNode fullNode);
|
||||
|
||||
/**
|
||||
* Returns an ordered list of {@link FullNode} instances.
|
||||
* @return The sorted list of nodes.
|
||||
*/
|
||||
List<FullNode> getSortedNodes();
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
package cy.agorise.graphenej.operations;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.BaseOperation;
|
||||
import cy.agorise.graphenej.OperationType;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
|
||||
/**
|
||||
* Created by henry on 19/5/2018.
|
||||
*/
|
||||
|
||||
public class AccountUpgradeOperation extends BaseOperation {
|
||||
|
||||
private static final String KEY_ACCOUNT = "account_to_upgrade";
|
||||
private static final String KEY_UPGRADE = "upgrade_to_lifetime_member";
|
||||
|
||||
private AssetAmount fee;
|
||||
private UserAccount accountToUpgrade;
|
||||
private boolean upgradeToLifeTimeMember;
|
||||
|
||||
public AccountUpgradeOperation(UserAccount accountToUpgrade, boolean upgradeToLifeTimeMember) {
|
||||
super(OperationType.ACCOUNT_UPGRADE_OPERATION);
|
||||
this.accountToUpgrade = accountToUpgrade;
|
||||
this.upgradeToLifeTimeMember = upgradeToLifeTimeMember;
|
||||
}
|
||||
|
||||
public AccountUpgradeOperation(UserAccount accountToUpgrade, boolean upgradeToLifeTimeMember, AssetAmount fee) {
|
||||
super(OperationType.ACCOUNT_UPGRADE_OPERATION);
|
||||
this.accountToUpgrade = accountToUpgrade;
|
||||
this.upgradeToLifeTimeMember = upgradeToLifeTimeMember;
|
||||
this.fee = fee;
|
||||
}
|
||||
|
||||
public AssetAmount getFee() {
|
||||
return fee;
|
||||
}
|
||||
|
||||
public UserAccount getAccountToUpgrade() {
|
||||
return accountToUpgrade;
|
||||
}
|
||||
|
||||
public void setAccountToUpgrade(UserAccount accountToUpgrade) {
|
||||
this.accountToUpgrade = accountToUpgrade;
|
||||
}
|
||||
|
||||
public boolean isUpgradeToLifeTimeMember() {
|
||||
return upgradeToLifeTimeMember;
|
||||
}
|
||||
|
||||
public void setUpgradeToLifeTimeMember(boolean upgradeToLifeTimeMember) {
|
||||
this.upgradeToLifeTimeMember = upgradeToLifeTimeMember;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFee(AssetAmount assetAmount) {
|
||||
this.fee = assetAmount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
byte[] feeBytes = fee.toBytes();
|
||||
byte[] accountBytes = accountToUpgrade.toBytes();
|
||||
byte[] upgradeToLifeTimeMemberBytes = this.upgradeToLifeTimeMember ? new byte[]{ 0x1 } : new byte[]{ 0x0 };
|
||||
byte[] extensions = this.extensions.toBytes();
|
||||
return Bytes.concat(feeBytes, accountBytes, upgradeToLifeTimeMemberBytes, extensions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
gsonBuilder.registerTypeAdapter(AccountUpgradeOperation.class, new AccountUpgradeSerializer());
|
||||
return gsonBuilder.create().toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJsonObject() {
|
||||
JsonArray array = new JsonArray();
|
||||
array.add(this.getId());
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
if(fee != null)
|
||||
jsonObject.add(KEY_FEE, fee.toJsonObject());
|
||||
jsonObject.addProperty(KEY_ACCOUNT, accountToUpgrade.getObjectId());
|
||||
jsonObject.addProperty(KEY_UPGRADE, this.upgradeToLifeTimeMember ? "true" : "false");
|
||||
jsonObject.add(KEY_EXTENSIONS, new JsonArray());
|
||||
array.add(jsonObject);
|
||||
return array;
|
||||
}
|
||||
|
||||
public static class AccountUpgradeSerializer implements JsonSerializer<AccountUpgradeOperation> {
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(AccountUpgradeOperation accountUpgrade, Type type, JsonSerializationContext jsonSerializationContext) {
|
||||
return accountUpgrade.toJsonObject();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class AccountUpgradeDeserializer implements JsonDeserializer<AccountUpgradeOperation> {
|
||||
|
||||
@Override
|
||||
public AccountUpgradeOperation deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
if(json.isJsonArray()){
|
||||
// This block is used just to check if we are in the first step of the deserialization
|
||||
// when we are dealing with an array.
|
||||
JsonArray serializedAccountUpgrade = json.getAsJsonArray();
|
||||
if(serializedAccountUpgrade.get(0).getAsInt() != OperationType.ACCOUNT_UPGRADE_OPERATION.ordinal()){
|
||||
// If the operation type does not correspond to a transfer operation, we return null
|
||||
return null;
|
||||
}else{
|
||||
// Calling itself recursively, this is only done once, so there will be no problems.
|
||||
return context.deserialize(serializedAccountUpgrade.get(1), AccountUpgradeOperation.class);
|
||||
}
|
||||
}else{
|
||||
JsonObject jsonObject = json.getAsJsonObject();
|
||||
|
||||
// Deserializing AssetAmount objects
|
||||
AssetAmount fee = context.deserialize(jsonObject.get(KEY_FEE), AssetAmount.class);
|
||||
|
||||
// Deserializing UserAccount objects
|
||||
UserAccount accountToUpgrade = new UserAccount(jsonObject.get(KEY_ACCOUNT).getAsString());
|
||||
|
||||
boolean upgradeToLifeTime = jsonObject.get(KEY_UPGRADE).getAsBoolean();
|
||||
AccountUpgradeOperation upgrade = new AccountUpgradeOperation(accountToUpgrade, upgradeToLifeTime, fee);
|
||||
|
||||
return upgrade;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package cy.agorise.graphenej.operations;
|
||||
|
||||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.errors.MalformedOperationException;
|
||||
|
||||
/**
|
||||
* Created by henry on 19/5/2018.
|
||||
*/
|
||||
|
||||
public class AccountUpgradeOperationBuilder extends BaseOperationBuilder {
|
||||
|
||||
private UserAccount accountToUpgrade;
|
||||
private AssetAmount fee;
|
||||
private boolean isUpgrade = true;
|
||||
|
||||
public AccountUpgradeOperationBuilder setAccountToUpgrade(UserAccount accountToUpgrade) {
|
||||
this.accountToUpgrade = accountToUpgrade;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountUpgradeOperationBuilder setFee(AssetAmount fee) {
|
||||
this.fee = fee;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountUpgradeOperationBuilder setIsUpgrade(Boolean isUpgrade) {
|
||||
this.isUpgrade = isUpgrade;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountUpgradeOperation build(){
|
||||
AccountUpgradeOperation accountUpgrade;
|
||||
if(accountToUpgrade == null ){
|
||||
throw new MalformedOperationException("Missing account to upgrade information");
|
||||
}
|
||||
|
||||
if(fee != null){
|
||||
accountUpgrade = new AccountUpgradeOperation(accountToUpgrade, isUpgrade, fee);
|
||||
}else{
|
||||
accountUpgrade = new AccountUpgradeOperation(accountToUpgrade, isUpgrade);
|
||||
}
|
||||
return accountUpgrade;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
package cy.agorise.graphenej.operations;
|
||||
|
||||
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 cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.BaseOperation;
|
||||
import cy.agorise.graphenej.HtlcHash;
|
||||
import cy.agorise.graphenej.OperationType;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.Util;
|
||||
|
||||
public class CreateHtlcOperation extends BaseOperation {
|
||||
static final String KEY_FROM = "from";
|
||||
static final String KEY_TO = "to";
|
||||
static final String KEY_AMOUNT = "amount";
|
||||
static final String KEY_PREIMAGE_HASH = "preimage_hash";
|
||||
static final String KEY_PREIMAGE_SIZE = "preimage_size";
|
||||
static final String KEY_CLAIM_PERIOD_SECONDS = "claim_period_seconds";
|
||||
|
||||
private AssetAmount fee;
|
||||
private UserAccount from;
|
||||
private UserAccount to;
|
||||
private AssetAmount amount;
|
||||
private HtlcHash preimageHash;
|
||||
private short preimageSize;
|
||||
private int claimPeriodSeconds;
|
||||
|
||||
/**
|
||||
* Public constructor
|
||||
*
|
||||
* @param fee The operation fee.
|
||||
* @param from The source account.
|
||||
* @param to The destination account.
|
||||
* @param amount The amount to be traded.
|
||||
* @param hash The pre-image hash.
|
||||
* @param preimageSize The pre-image size.
|
||||
* @param claimPeriodSeconds The claim period, in seconds.
|
||||
*/
|
||||
public CreateHtlcOperation(AssetAmount fee, UserAccount from, UserAccount to, AssetAmount amount, HtlcHash hash, short preimageSize, int claimPeriodSeconds) {
|
||||
super(OperationType.HTLC_CREATE_OPERATION);
|
||||
this.fee = fee;
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.amount = amount;
|
||||
this.preimageHash = hash;
|
||||
this.preimageSize = preimageSize;
|
||||
this.claimPeriodSeconds = claimPeriodSeconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFee(AssetAmount newFee){
|
||||
this.fee = newFee;
|
||||
}
|
||||
|
||||
public AssetAmount getFee() {
|
||||
return fee;
|
||||
}
|
||||
|
||||
public UserAccount getFrom() {
|
||||
return from;
|
||||
}
|
||||
|
||||
public void setFrom(UserAccount from) {
|
||||
this.from = from;
|
||||
}
|
||||
|
||||
public UserAccount getTo() {
|
||||
return to;
|
||||
}
|
||||
|
||||
public void setTo(UserAccount to) {
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
public AssetAmount getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void setAmount(AssetAmount amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public HtlcHash getPreimageHash() {
|
||||
return preimageHash;
|
||||
}
|
||||
|
||||
public void setPreimageHash(HtlcHash preimageHash) {
|
||||
this.preimageHash = preimageHash;
|
||||
}
|
||||
|
||||
public short getPreimageSize() {
|
||||
return preimageSize;
|
||||
}
|
||||
|
||||
public void setPreimageSize(short preimageSize) {
|
||||
this.preimageSize = preimageSize;
|
||||
}
|
||||
|
||||
public int getClaimPeriodSeconds() {
|
||||
return claimPeriodSeconds;
|
||||
}
|
||||
|
||||
public void setClaimPeriodSeconds(int claimPeriodSeconds) {
|
||||
this.claimPeriodSeconds = claimPeriodSeconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
byte[] feeBytes = fee.toBytes();
|
||||
byte[] fromBytes = from.toBytes();
|
||||
byte[] toBytes = to.toBytes();
|
||||
byte[] amountBytes = amount.toBytes();
|
||||
byte[] htlcHashBytes = preimageHash.toBytes();
|
||||
byte[] preimageSizeBytes = Util.revertShort(preimageSize);
|
||||
byte[] claimPeriodBytes = Util.revertInteger(claimPeriodSeconds);
|
||||
byte[] extensionsBytes = extensions.toBytes();
|
||||
return Bytes.concat(feeBytes, fromBytes, toBytes, amountBytes, htlcHashBytes, preimageSizeBytes, claimPeriodBytes, extensionsBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJsonObject() {
|
||||
JsonArray array = new JsonArray();
|
||||
array.add(this.getId());
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.add(KEY_FEE, fee.toJsonObject());
|
||||
jsonObject.addProperty(KEY_FROM, from.getObjectId());
|
||||
jsonObject.addProperty(KEY_TO, to.getObjectId());
|
||||
jsonObject.add(KEY_AMOUNT, amount.toJsonObject());
|
||||
jsonObject.add(KEY_PREIMAGE_HASH, preimageHash.toJsonObject());
|
||||
jsonObject.addProperty(KEY_PREIMAGE_SIZE, preimageSize);
|
||||
jsonObject.addProperty(KEY_CLAIM_PERIOD_SECONDS, claimPeriodSeconds);
|
||||
jsonObject.add(KEY_EXTENSIONS, new JsonArray());
|
||||
array.add(jsonObject);
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
return gsonBuilder.create().toJson(this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
package cy.agorise.graphenej.operations;
|
||||
|
||||
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 java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.BaseOperation;
|
||||
import cy.agorise.graphenej.Htlc;
|
||||
import cy.agorise.graphenej.OperationType;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.Util;
|
||||
import cy.agorise.graphenej.Varint;
|
||||
|
||||
/**
|
||||
* Class used to encapsulate the redeem_htlc operation.
|
||||
*/
|
||||
public class RedeemHtlcOperation extends BaseOperation {
|
||||
static final String KEY_REDEEMER = "redeemer";
|
||||
static final String KEY_PREIMAGE = "preimage";
|
||||
static final String KEY_HTLC_ID = "htlc_id";
|
||||
|
||||
private AssetAmount fee;
|
||||
private UserAccount redeemer;
|
||||
private Htlc htlc;
|
||||
private byte[] preimage;
|
||||
|
||||
/**
|
||||
* Public constructor
|
||||
*
|
||||
* @param fee The fee associated with this operation.
|
||||
* @param redeemer The user account that will redeem the HTLC.
|
||||
* @param htlc The existing HTLC operation.
|
||||
*/
|
||||
public RedeemHtlcOperation(AssetAmount fee, UserAccount redeemer, Htlc htlc, byte[] preimage) {
|
||||
super(OperationType.HTLC_REDEEM_OPERATION);
|
||||
this.fee = fee;
|
||||
this.redeemer = redeemer;
|
||||
this.htlc = htlc;
|
||||
this.preimage = preimage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFee(AssetAmount fee) {
|
||||
this.fee = fee;
|
||||
}
|
||||
|
||||
public AssetAmount getFee(){
|
||||
return this.fee;
|
||||
}
|
||||
|
||||
public UserAccount getRedeemer() {
|
||||
return redeemer;
|
||||
}
|
||||
|
||||
public void setRedeemer(UserAccount redeemer) {
|
||||
this.redeemer = redeemer;
|
||||
}
|
||||
|
||||
public Htlc getHtlc() {
|
||||
return htlc;
|
||||
}
|
||||
|
||||
public void setHtlc(Htlc htlc) {
|
||||
this.htlc = htlc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
byte[] feeBytes = this.fee.toBytes();
|
||||
byte[] htlcBytes = this.htlc.toBytes();
|
||||
byte[] redeemerBytes = this.redeemer.toBytes();
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
DataOutput out = new DataOutputStream(byteArrayOutputStream);
|
||||
try{
|
||||
Varint.writeUnsignedVarLong(this.preimage.length, out);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
byte[] preimageLength = byteArrayOutputStream.toByteArray();
|
||||
byte[] extensionsBytes = extensions.toBytes();
|
||||
return Bytes.concat(feeBytes, htlcBytes, redeemerBytes, preimageLength, this.preimage, extensionsBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement toJsonObject() {
|
||||
JsonArray array = new JsonArray();
|
||||
array.add(this.getId());
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.add(KEY_FEE, fee.toJsonObject());
|
||||
jsonObject.addProperty(KEY_REDEEMER, this.redeemer.getObjectId());
|
||||
jsonObject.addProperty(KEY_PREIMAGE, Util.bytesToHex(this.preimage));
|
||||
jsonObject.addProperty(KEY_HTLC_ID, this.htlc.getObjectId());
|
||||
jsonObject.add(KEY_EXTENSIONS, new JsonArray());
|
||||
array.add(jsonObject);
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toJsonString() {
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
return gsonBuilder.create().toJson(this);
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@ import cy.agorise.graphenej.AssetAmount;
|
|||
import cy.agorise.graphenej.BaseOperation;
|
||||
import cy.agorise.graphenej.OperationType;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.objects.Memo;
|
||||
import cy.agorise.graphenej.Memo;
|
||||
|
||||
/**
|
||||
* Class used to encapsulate the TransferOperation operation related functionalities.
|
||||
|
|
|
@ -3,7 +3,7 @@ package cy.agorise.graphenej.operations;
|
|||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.errors.MalformedOperationException;
|
||||
import cy.agorise.graphenej.objects.Memo;
|
||||
import cy.agorise.graphenej.Memo;
|
||||
|
||||
/**
|
||||
* Factory class used to build a transfer operation
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
package cy.agorise.graphenej.stats;
|
||||
|
||||
/**
|
||||
* Class used to compute the Exponential Moving Average of a sequence of values.
|
||||
* For more details see <a href="https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average">here</a>.
|
||||
*/
|
||||
public class ExponentialMovingAverage {
|
||||
public static final double DEFAULT_ALPHA = 0.5;
|
||||
private double alpha;
|
||||
private Double accumulatedValue;
|
||||
|
||||
/**
|
||||
* Constructor, which takes only the alpha parameter as an argument.
|
||||
*
|
||||
* @param alpha The coefficient alpha represents the degree of weighting decrease, a constant
|
||||
* smoothing factor between 0 and 1. A higher alpha discounts older observations faster.
|
||||
*/
|
||||
public ExponentialMovingAverage(double alpha) {
|
||||
this.alpha = alpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that updates the average with a new sample
|
||||
* @param value New value
|
||||
* @return The updated average value
|
||||
*/
|
||||
public double updateValue(double value) {
|
||||
if (accumulatedValue == null) {
|
||||
accumulatedValue = value;
|
||||
return value;
|
||||
}
|
||||
double newValue = accumulatedValue + alpha * (value - accumulatedValue);
|
||||
accumulatedValue = newValue;
|
||||
return newValue;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return Returns the current average value
|
||||
*/
|
||||
public double getAverage(){
|
||||
return accumulatedValue == null ? 0 : accumulatedValue;
|
||||
}
|
||||
|
||||
public void setAlpha(double alpha){
|
||||
this.alpha = alpha;
|
||||
this.accumulatedValue = null;
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ import org.junit.Test;
|
|||
import java.util.HashMap;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
|
||||
/**
|
||||
* Created by nelson on 12/16/16.
|
||||
|
@ -48,7 +49,7 @@ public class AuthorityTest {
|
|||
@Test
|
||||
public void equals() throws Exception {
|
||||
assertEquals("Equal authorities", authority, sameAuthority);
|
||||
assertEquals("Different authorities ", authority, differentAuthority);
|
||||
assertNotEquals("Different authorities ", authority, differentAuthority);
|
||||
assertEquals("Two public keys with the same public key should be equal", keyAuthority1, keyAuthority2);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,9 @@ import org.junit.Test;
|
|||
*/
|
||||
public class BrainKeyTest {
|
||||
public final String TEST_BRAINKEY = "BARIC BICKERN LITZ TIPFUL JINGLED POOL TUMBAK PURIST APOPYLE DURAIN SATLIJK FAUCAL";
|
||||
|
||||
public final String TEST_BRAINKEY_OPENLEDGER = "ona refan abscise neebor battik terbia bandit sundra gasser debar phytol frat hauler accede primy garland";
|
||||
|
||||
private BrainKey mBrainKey;
|
||||
|
||||
@Before
|
||||
|
@ -17,6 +20,9 @@ public class BrainKeyTest {
|
|||
mBrainKey = new BrainKey(TEST_BRAINKEY, BrainKey.DEFAULT_SEQUENCE_NUMBER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test making sure that a simple brainkey can successfully generate the expected public address
|
||||
*/
|
||||
@Test
|
||||
public void testAddress(){
|
||||
Address address = mBrainKey.getPublicAddress(Address.BITSHARES_PREFIX);
|
||||
|
@ -24,4 +30,25 @@ public class BrainKeyTest {
|
|||
"BTS61UqqgE3ARuTGcckzARsdQm4EMFdBEwYyi1pbwyHrZZWrCDhT2",
|
||||
address.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test making sure that a OpenLedger's brainkey can successfully generate the given
|
||||
* 'owner' and 'active' keys.
|
||||
*/
|
||||
@Test
|
||||
public void testOpenledgerAddress(){
|
||||
BrainKey brainKey1 = new BrainKey(TEST_BRAINKEY_OPENLEDGER, 0);
|
||||
BrainKey brainKey2 = new BrainKey(TEST_BRAINKEY_OPENLEDGER, 1);
|
||||
|
||||
Address ownerAddress = brainKey1.getPublicAddress(Address.BITSHARES_PREFIX);
|
||||
Address activeAddress = brainKey2.getPublicAddress(Address.BITSHARES_PREFIX);
|
||||
|
||||
Assert.assertEquals("Owner address matches",
|
||||
"BTS6dqT3J7tUcZP6xHo2mHkL8tq8zw5TQgGd6ntRMXH1EoNsCWTzm",
|
||||
ownerAddress.toString());
|
||||
|
||||
Assert.assertEquals("Active address matches",
|
||||
"BTS6DKvgY3yPyN7wKrhBGYhrnghhLSVCYz3ugUdi9pDPkicS6B7N2",
|
||||
activeAddress.toString());
|
||||
}
|
||||
}
|
26
graphenej/src/test/java/cy/agorise/graphenej/HtlcTest.java
Normal file
26
graphenej/src/test/java/cy/agorise/graphenej/HtlcTest.java
Normal file
|
@ -0,0 +1,26 @@
|
|||
package cy.agorise.graphenej;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class HtlcTest {
|
||||
private final Htlc htlc = new Htlc("1.16.124");
|
||||
|
||||
@Test
|
||||
public void testByteSerialization(){
|
||||
Htlc htlc1 = new Htlc("1.16.1");
|
||||
Htlc htlc2 = new Htlc("1.16.100");
|
||||
Htlc htlc3 = new Htlc("1.16.500");
|
||||
Htlc htlc4 = new Htlc("1.16.1000");
|
||||
|
||||
byte[] expected_1 = Util.hexToBytes("01");
|
||||
byte[] expected_2 = Util.hexToBytes("64");
|
||||
byte[] expected_3 = Util.hexToBytes("f403");
|
||||
byte[] expected_4 = Util.hexToBytes("e807");
|
||||
|
||||
Assert.assertArrayEquals(expected_1, htlc1.toBytes());
|
||||
Assert.assertArrayEquals(expected_2, htlc2.toBytes());
|
||||
Assert.assertArrayEquals(expected_3, htlc3.toBytes());
|
||||
Assert.assertArrayEquals(expected_4, htlc4.toBytes());
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ import org.junit.Test;
|
|||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
@ -24,7 +25,6 @@ import cy.agorise.graphenej.api.TransactionBroadcastSequence;
|
|||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.BaseResponse;
|
||||
import cy.agorise.graphenej.models.WitnessResponse;
|
||||
import cy.agorise.graphenej.objects.Memo;
|
||||
import cy.agorise.graphenej.operations.CustomOperation;
|
||||
import cy.agorise.graphenej.operations.LimitOrderCancelOperation;
|
||||
import cy.agorise.graphenej.operations.LimitOrderCreateOperation;
|
||||
|
@ -153,7 +153,8 @@ public class TransactionTest {
|
|||
PublicKey to2 = new PublicKey(ECKey.fromPublicOnly(new BrainKey(BILTHON_16_BRAIN_KEY, 0).getPublicKey()));
|
||||
|
||||
// Creating memo
|
||||
BigInteger nonce = BigInteger.ONE;
|
||||
SecureRandom random = new SecureRandom();
|
||||
BigInteger nonce = BigInteger.valueOf(random.nextLong());
|
||||
byte[] encryptedMessage = Memo.encryptMessage(sourcePrivateKey, to1, nonce, "another message");
|
||||
Memo memo = new Memo(new Address(ECKey.fromPublicOnly(sourcePrivateKey.getPubKey())), new Address(to1.getKey()), nonce, encryptedMessage);
|
||||
|
||||
|
@ -346,4 +347,21 @@ public class TransactionTest {
|
|||
// Broadcasting transaction
|
||||
broadcastTransaction(sourcePrivateKey, operationList, listener, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactionHash(){
|
||||
ArrayList<BaseOperation> operations = new ArrayList<>();
|
||||
TransferOperation transferOperation = new TransferOperationBuilder()
|
||||
.setTransferAmount(new AssetAmount(UnsignedLong.valueOf("363"), new Asset("1.3.0")))
|
||||
.setFee(new AssetAmount(UnsignedLong.valueOf("10420"), new Asset("1.3.0")))
|
||||
.setSource(new UserAccount("1.2.1029856"))
|
||||
.setDestination(new UserAccount("1.2.390320"))
|
||||
.build();
|
||||
BlockData blockData = new BlockData(50885, 2948192884L, 1543548351);
|
||||
operations.add(transferOperation);
|
||||
Transaction transaction = new Transaction(blockData, operations);
|
||||
byte[] testHash = transaction.getHash();
|
||||
// Making sure the generated hash matches the one we expect from the block explorer
|
||||
Assert.assertArrayEquals(Util.hexToBytes("4fec588ccdd04daaf80666a3646a48b5189df041"), testHash);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package cy.agorise.graphenej.api;
|
||||
|
||||
import com.neovisionaries.ws.client.WebSocketException;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.AccountProperties;
|
||||
import cy.agorise.graphenej.models.BaseResponse;
|
||||
import cy.agorise.graphenej.models.WitnessResponse;
|
||||
|
||||
public class GetAccountsTest extends BaseApiTest {
|
||||
private UserAccount ltmAccount = new UserAccount("1.2.99700");
|
||||
private UserAccount nonLtmAccount = new UserAccount("1.2.140994");
|
||||
|
||||
@Test
|
||||
public void testGetAccount(){
|
||||
ArrayList<UserAccount> userAccounts = new ArrayList<>();
|
||||
userAccounts.add(ltmAccount);
|
||||
userAccounts.add(nonLtmAccount);
|
||||
mWebSocket.addListener(new GetAccounts(userAccounts, true, new WitnessResponseListener(){
|
||||
|
||||
@Override
|
||||
public void onSuccess(WitnessResponse response) {
|
||||
System.out.println("onSuccess.");
|
||||
List<AccountProperties> accounts = (List<AccountProperties>) response.result;
|
||||
System.out.println(String.format("Got %d accounts", accounts.size()));
|
||||
for(AccountProperties accountProperties : accounts){
|
||||
System.out.println("account name....: "+accountProperties.name);
|
||||
System.out.println("expiration date.: "+accountProperties.membership_expiration_date);
|
||||
}
|
||||
AccountProperties ltmAccountProperties = accounts.get(0);
|
||||
AccountProperties nonLtmAccountProperties = accounts.get(1);
|
||||
Assert.assertEquals(ltmAccountProperties.membership_expiration_date, UserAccount.LIFETIME_EXPIRATION_DATE);
|
||||
Assert.assertFalse(nonLtmAccountProperties.membership_expiration_date.equals(UserAccount.LIFETIME_EXPIRATION_DATE));
|
||||
synchronized (GetAccountsTest.this){
|
||||
GetAccountsTest.this.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(BaseResponse.Error error) {
|
||||
System.out.println("onError. Msg: "+error.message);
|
||||
synchronized (GetAccountsTest.this){
|
||||
GetAccountsTest.this.notifyAll();
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
try{
|
||||
mWebSocket.connect();
|
||||
synchronized (this){
|
||||
wait();
|
||||
}
|
||||
}catch (WebSocketException e) {
|
||||
System.out.println("WebSocketException. Msg: " + e.getMessage());
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println("InterruptedException. Msg: "+e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ import java.util.List;
|
|||
|
||||
import cy.agorise.graphenej.Asset;
|
||||
import cy.agorise.graphenej.GrapheneObject;
|
||||
import cy.agorise.graphenej.Price;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.BaseResponse;
|
||||
|
@ -26,7 +27,8 @@ public class GetObjectsTest extends BaseApiTest{
|
|||
private final Asset asset = new Asset("1.3.0", "BTS", 5);
|
||||
private final UserAccount account = new UserAccount("1.2.116354");
|
||||
private final UserAccount bilthon_25 = new UserAccount("1.2.151069");
|
||||
private final String bitAssetId = "2.4.13";
|
||||
private UserAccount ltmAccount = new UserAccount("1.2.99700");
|
||||
private final String[] bitAssetIds = new String[]{"2.4.21", "2.4.83"};
|
||||
|
||||
@Test
|
||||
public void testGetAsset(){
|
||||
|
@ -109,23 +111,86 @@ public class GetObjectsTest extends BaseApiTest{
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testBitAssetData(){
|
||||
try{
|
||||
public void testGetLtmAccount(){
|
||||
ArrayList<String> ids = new ArrayList<>();
|
||||
ids.add(bitAssetId);
|
||||
ids.add(ltmAccount.getObjectId());
|
||||
mWebSocket.addListener(new GetObjects(ids, new WitnessResponseListener() {
|
||||
|
||||
@Override
|
||||
public void onSuccess(WitnessResponse response) {
|
||||
System.out.println("onSuccess");
|
||||
List<GrapheneObject> list = (List<GrapheneObject>) response.result;
|
||||
BitAssetData bitAssetData = (BitAssetData) list.get(0);
|
||||
System.out.println("feed time: " + bitAssetData.current_feed_publication_time);
|
||||
List<GrapheneObject> result = (List<GrapheneObject>) response.result;
|
||||
UserAccount userAccount = (UserAccount) result.get(0);
|
||||
System.out.println("Account name.....: "+userAccount.getName());
|
||||
System.out.println("Is LTM...........: "+userAccount.isLifeTime());
|
||||
System.out.println("json string......: "+userAccount.toJsonString());
|
||||
System.out.println("owner............: "+userAccount.getOwner().getKeyAuthList().get(0).getAddress());
|
||||
System.out.println("active key.......: "+userAccount.getActive().getKeyAuthList().get(0).getAddress());
|
||||
System.out.println("memo: "+userAccount.getOptions().getMemoKey().getAddress());
|
||||
Assert.assertEquals("We expect this account to be LTM",true, userAccount.isLifeTime());
|
||||
synchronized (GetObjectsTest.this){
|
||||
GetObjectsTest.this.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(BaseResponse.Error error) {
|
||||
System.out.println("onError");
|
||||
synchronized (GetObjectsTest.this){
|
||||
GetObjectsTest.this.notifyAll();
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
try {
|
||||
mWebSocket.connect();
|
||||
synchronized (this){
|
||||
wait();
|
||||
}
|
||||
}catch (WebSocketException e) {
|
||||
System.out.println("WebSocketException. Msg: " + e.getMessage());
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println("InterruptedException. Msg: "+e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBitAssetData(){
|
||||
try{
|
||||
ArrayList<String> ids = new ArrayList<>();
|
||||
for(String bitAssetId : bitAssetIds){
|
||||
ids.add(bitAssetId);
|
||||
}
|
||||
mWebSocket.addListener(new GetObjects(ids, new WitnessResponseListener() {
|
||||
|
||||
@Override
|
||||
public void onSuccess(WitnessResponse response) {
|
||||
System.out.println("onSuccess");
|
||||
List<BitAssetData> list = (List<BitAssetData>) response.result;
|
||||
System.out.println("Response array length: "+list.size());
|
||||
BitAssetData bitAssetData1 = list.get(0);
|
||||
BitAssetData bitAssetData2 = list.get(1);
|
||||
|
||||
Price price1 = bitAssetData1.getCurrentFeed().getSettlementPrice();
|
||||
Price price2 = bitAssetData2.getCurrentFeed().getSettlementPrice();
|
||||
|
||||
System.out.println("Bitasset data 1");
|
||||
System.out.println("Price 1: "+price1.toString());
|
||||
|
||||
System.out.println("Bitasset data 2");
|
||||
System.out.println("Price 1: "+price2.toString());
|
||||
|
||||
synchronized (GetObjectsTest.this){
|
||||
GetObjectsTest.this.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(BaseResponse.Error error) {
|
||||
System.out.println("onError");
|
||||
synchronized (GetObjectsTest.this){
|
||||
GetObjectsTest.this.notifyAll();
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import java.util.List;
|
|||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.interfaces.WitnessResponseListener;
|
||||
import cy.agorise.graphenej.models.BaseResponse;
|
||||
import cy.agorise.graphenej.models.HistoricalTransfer;
|
||||
import cy.agorise.graphenej.models.OperationHistory;
|
||||
import cy.agorise.graphenej.models.WitnessResponse;
|
||||
import cy.agorise.graphenej.operations.TransferOperation;
|
||||
|
||||
|
@ -51,11 +51,11 @@ public class GetRelativeAccountHistoryTest extends BaseApiTest {
|
|||
public void onSuccess(WitnessResponse response) {
|
||||
System.out.println("mTransferHistoryListener.onSuccess");
|
||||
historicalTransferCount++;
|
||||
WitnessResponse<List<HistoricalTransfer>> resp = response;
|
||||
for(HistoricalTransfer historicalTransfer : resp.result){
|
||||
WitnessResponse<List<OperationHistory>> resp = response;
|
||||
for(OperationHistory historicalTransfer : resp.result){
|
||||
if(historicalTransfer.getOperation() != null){
|
||||
System.out.println("Got transfer operation!");
|
||||
TransferOperation transferOperation = historicalTransfer.getOperation();
|
||||
TransferOperation transferOperation = (TransferOperation) historicalTransfer.getOperation();
|
||||
System.out.println(String.format("%s - > %s, memo: %s",
|
||||
transferOperation.getFrom().getObjectId(),
|
||||
transferOperation.getTo().getObjectId(),
|
||||
|
|
|
@ -10,13 +10,14 @@ import java.util.Timer;
|
|||
import java.util.TimerTask;
|
||||
|
||||
import cy.agorise.graphenej.ObjectType;
|
||||
import cy.agorise.graphenej.Transaction;
|
||||
import cy.agorise.graphenej.interfaces.NodeErrorListener;
|
||||
import cy.agorise.graphenej.interfaces.SubscriptionListener;
|
||||
import cy.agorise.graphenej.models.BaseResponse;
|
||||
import cy.agorise.graphenej.models.BroadcastedTransaction;
|
||||
import cy.agorise.graphenej.models.DynamicGlobalProperties;
|
||||
import cy.agorise.graphenej.models.OperationHistory;
|
||||
import cy.agorise.graphenej.models.SubscriptionResponse;
|
||||
import cy.agorise.graphenej.Transaction;
|
||||
|
||||
/**
|
||||
* Class used to encapsulate all tests that relate to the {@see SubscriptionMessagesHub} class.
|
||||
|
@ -178,7 +179,7 @@ public class SubscriptionMessagesHubTest extends BaseApiTest {
|
|||
@Test
|
||||
public void testBroadcastedTransactionDeserializer(){
|
||||
try{
|
||||
mMessagesHub = new SubscriptionMessagesHub("", "", mErrorListener);
|
||||
mMessagesHub = new SubscriptionMessagesHub("", "", true, mErrorListener);
|
||||
mMessagesHub.addSubscriptionListener(new SubscriptionListener() {
|
||||
private int MAX_MESSAGES = 15;
|
||||
private int messageCounter = 0;
|
||||
|
@ -197,7 +198,7 @@ public class SubscriptionMessagesHubTest extends BaseApiTest {
|
|||
if(item instanceof BroadcastedTransaction){
|
||||
BroadcastedTransaction broadcastedTransaction = (BroadcastedTransaction) item;
|
||||
Transaction tx = broadcastedTransaction.getTransaction();
|
||||
System.out.println(String.format("Got %d operations", tx.getOperations().size()));
|
||||
// System.out.println(String.format("Got %d operations", tx.getOperations().size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -213,6 +214,30 @@ public class SubscriptionMessagesHubTest extends BaseApiTest {
|
|||
}
|
||||
});
|
||||
|
||||
mMessagesHub.addSubscriptionListener(new SubscriptionListener() {
|
||||
|
||||
@Override
|
||||
public ObjectType getInterestObjectType() {
|
||||
return ObjectType.OPERATION_HISTORY_OBJECT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubscriptionUpdate(SubscriptionResponse response) {
|
||||
System.out.println("onSubscriptionUpdate. response.params.size: "+response.params.size());
|
||||
if(response.params.size() == 2){
|
||||
List<Serializable> payload = (List) response.params.get(1);
|
||||
if(payload.size() > 0){
|
||||
for(Serializable item : payload){
|
||||
if(item instanceof OperationHistory){
|
||||
OperationHistory operationHistory = (OperationHistory) item;
|
||||
System.out.println("Operation history: <id:"+operationHistory.getObjectId()+", op: "+operationHistory.getOperation().toJsonString()+">");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mWebSocket.addListener(mMessagesHub);
|
||||
mWebSocket.connect();
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package cy.agorise.graphenej.api.calls;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.models.ApiCall;
|
||||
|
||||
public class GetAccountHistoryTest {
|
||||
|
||||
@Test
|
||||
public void testSerialization(){
|
||||
UserAccount userAccount = new UserAccount("1.2.139293");
|
||||
String end = "1.11.225030218";
|
||||
String start = "1.11.225487973";
|
||||
int limit = 20;
|
||||
GetAccountHistory getAccountHistory = new GetAccountHistory(userAccount, start, end, limit);
|
||||
ApiCall apiCall = getAccountHistory.toApiCall(2, 3);
|
||||
String serialized = apiCall.toJsonString();
|
||||
System.out.println("> "+serialized);
|
||||
String expected = "{\"id\":3,\"method\":\"call\",\"params\":[2,\"get_account_history\",[\"1.2.139293\",\"1.11.225030218\",20,\"1.11.225487973\"]],\"jsonrpc\":\"2.0\"}";
|
||||
Assert.assertEquals("Serialized is as expected", expected, serialized);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
|
||||
import cy.agorise.graphenej.AccountOptions;
|
||||
import cy.agorise.graphenej.Authority;
|
||||
import cy.agorise.graphenej.Memo;
|
||||
|
||||
public class FullAccountDetailsTest {
|
||||
|
||||
@Test
|
||||
public void testDeserialization(){
|
||||
String serialized = "{\"id\":0,\"jsonrpc\":\"2.0\",\"result\":[[\"bilthon-1\",{\"account\":{\"id\":\"1.2.139205\",\"membership_expiration_date\":\"1970-01-01T00:00:00\",\"registrar\":\"1.2.117600\",\"referrer\":\"1.2.90200\",\"lifetime_referrer\":\"1.2.90200\",\"network_fee_percentage\":2000,\"lifetime_referrer_fee_percentage\":3000,\"referrer_rewards_percentage\":9000,\"name\":\"bilthon-1\",\"owner\":{\"weight_threshold\":1,\"account_auths\":[],\"key_auths\":[[\"BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY\",1]],\"address_auths\":[]},\"active\":{\"weight_threshold\":1,\"account_auths\":[],\"key_auths\":[[\"BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY\",1]],\"address_auths\":[]},\"options\":{\"memo_key\":\"BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY\",\"voting_account\":\"1.2.5\",\"num_witness\":0,\"num_committee\":0,\"votes\":[],\"extensions\":[]},\"statistics\":\"2.6.139205\",\"whitelisting_accounts\":[],\"blacklisting_accounts\":[],\"whitelisted_accounts\":[],\"blacklisted_accounts\":[],\"owner_special_authority\":[0,{}],\"active_special_authority\":[0,{}],\"top_n_control_flags\":0},\"statistics\":{\"id\":\"2.6.139205\",\"owner\":\"1.2.139205\",\"name\":\"bilthon-1\",\"most_recent_op\":\"2.9.6668024\",\"total_ops\":3,\"removed_ops\":0,\"total_core_in_orders\":0,\"core_in_balance\":71279,\"has_cashback_vb\":false,\"is_voting\":false,\"lifetime_fees_paid\":28721,\"pending_fees\":0,\"pending_vested_fees\":0},\"registrar_name\":\"bitshares-munich-faucet\",\"referrer_name\":\"bitshares-munich\",\"lifetime_referrer_name\":\"bitshares-munich\",\"votes\":[],\"balances\":[{\"id\":\"2.5.44951\",\"owner\":\"1.2.139205\",\"asset_type\":\"1.3.0\",\"balance\":71279,\"maintenance_flag\":false}],\"vesting_balances\":[],\"limit_orders\":[],\"call_orders\":[],\"settle_orders\":[],\"proposals\":[],\"assets\":[],\"withdraws\":[]}],[\"bilthon-2\",{\"account\":{\"id\":\"1.2.139207\",\"membership_expiration_date\":\"1970-01-01T00:00:00\",\"registrar\":\"1.2.117600\",\"referrer\":\"1.2.90200\",\"lifetime_referrer\":\"1.2.90200\",\"network_fee_percentage\":2000,\"lifetime_referrer_fee_percentage\":3000,\"referrer_rewards_percentage\":9000,\"name\":\"bilthon-2\",\"owner\":{\"weight_threshold\":1,\"account_auths\":[],\"key_auths\":[[\"BTS7gD2wtSauXpSCBin1rYctBcPWeZieX7YrVk1DuQpg9peczSqTv\",1]],\"address_auths\":[]},\"active\":{\"weight_threshold\":1,\"account_auths\":[],\"key_auths\":[[\"BTS7gD2wtSauXpSCBin1rYctBcPWeZieX7YrVk1DuQpg9peczSqTv\",1]],\"address_auths\":[]},\"options\":{\"memo_key\":\"BTS7gD2wtSauXpSCBin1rYctBcPWeZieX7YrVk1DuQpg9peczSqTv\",\"voting_account\":\"1.2.5\",\"num_witness\":0,\"num_committee\":0,\"votes\":[],\"extensions\":[]},\"statistics\":\"2.6.139207\",\"whitelisting_accounts\":[],\"blacklisting_accounts\":[],\"whitelisted_accounts\":[],\"blacklisted_accounts\":[],\"owner_special_authority\":[0,{}],\"active_special_authority\":[0,{}],\"top_n_control_flags\":0},\"statistics\":{\"id\":\"2.6.139207\",\"owner\":\"1.2.139207\",\"name\":\"bilthon-2\",\"most_recent_op\":\"2.9.6159244\",\"total_ops\":1,\"removed_ops\":0,\"total_core_in_orders\":0,\"core_in_balance\":0,\"has_cashback_vb\":false,\"is_voting\":false,\"lifetime_fees_paid\":0,\"pending_fees\":0,\"pending_vested_fees\":0},\"registrar_name\":\"bitshares-munich-faucet\",\"referrer_name\":\"bitshares-munich\",\"lifetime_referrer_name\":\"bitshares-munich\",\"votes\":[],\"balances\":[],\"vesting_balances\":[],\"limit_orders\":[],\"call_orders\":[],\"settle_orders\":[],\"proposals\":[],\"assets\":[],\"withdraws\":[]}]]}";
|
||||
Gson gson = new GsonBuilder()
|
||||
.registerTypeAdapter(FullAccountDetails.class, new FullAccountDetails.FullAccountDeserializer())
|
||||
.registerTypeAdapter(Authority.class, new Authority.AuthorityDeserializer())
|
||||
.registerTypeAdapter(Memo.class, new Memo.MemoDeserializer())
|
||||
.registerTypeAdapter(AccountOptions.class, new AccountOptions.AccountOptionsDeserializer())
|
||||
.create();
|
||||
Type FullAccountDetailsResponse = new TypeToken<JsonRpcResponse<List<FullAccountDetails>>>() {}.getType();
|
||||
JsonRpcResponse<List<FullAccountDetails>> response = gson.fromJson(serialized, FullAccountDetailsResponse);
|
||||
Assert.assertNotNull(response.result);
|
||||
Assert.assertNull(response.error);
|
||||
List<FullAccountDetails> fullAccountDetailsList = response.result;
|
||||
Assert.assertNotNull(fullAccountDetailsList);
|
||||
Assert.assertEquals(2, fullAccountDetailsList.size());
|
||||
Assert.assertNotNull(fullAccountDetailsList.get(0).getAccount());
|
||||
Assert.assertEquals("bilthon-1", fullAccountDetailsList.get(0).getAccount().name);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.BaseOperation;
|
||||
import cy.agorise.graphenej.Extensions;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.api.android.DeserializationMap;
|
||||
import cy.agorise.graphenej.Memo;
|
||||
|
||||
public class HistoryOperationDetailsTest {
|
||||
|
||||
@Test
|
||||
public void testDeserialization(){
|
||||
String text = "{\"id\":5,\"jsonrpc\":\"2.0\",\"result\":{\"total_count\":2,\"operation_history_objs\":[{\"id\":\"1.11.5701809\",\"op\":[0,{\"fee\":{\"amount\":264174,\"asset_id\":\"1.3.0\"},\"from\":\"1.2.99700\",\"to\":\"1.2.138632\",\"amount\":{\"amount\":20000,\"asset_id\":\"1.3.120\"},\"extensions\":[]}],\"result\":[0,{}],\"block_num\":11094607,\"trx_in_block\":0,\"op_in_trx\":0,\"virtual_op\":31767},{\"id\":\"1.11.5701759\",\"op\":[0,{\"fee\":{\"amount\":264174,\"asset_id\":\"1.3.0\"},\"from\":\"1.2.99700\",\"to\":\"1.2.138632\",\"amount\":{\"amount\":10000000,\"asset_id\":\"1.3.0\"},\"extensions\":[]}],\"result\":[0,{}],\"block_num\":11094501,\"trx_in_block\":0,\"op_in_trx\":0,\"virtual_op\":31717}]}}\n";
|
||||
Gson gson = new GsonBuilder()
|
||||
.setExclusionStrategies(new DeserializationMap.SkipAccountOptionsStrategy(), new DeserializationMap.SkipAssetOptionsStrategy())
|
||||
.registerTypeAdapter(BaseOperation.class, new BaseOperation.OperationDeserializer())
|
||||
.registerTypeAdapter(OperationHistory.class, new OperationHistory.OperationHistoryDeserializer())
|
||||
.registerTypeAdapter(Memo.class, new Memo.MemoSerializer())
|
||||
.registerTypeAdapter(Extensions.class, new Extensions.ExtensionsDeserializer())
|
||||
.registerTypeAdapter(UserAccount.class, new UserAccount.UserAccountSimpleDeserializer())
|
||||
.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer())
|
||||
.create();
|
||||
|
||||
Type GetAccountHistoryByOperationsResponse = new TypeToken<JsonRpcResponse<HistoryOperationDetail>>(){}.getType();
|
||||
JsonRpcResponse<HistoryOperationDetail> response = gson.fromJson(text, GetAccountHistoryByOperationsResponse);
|
||||
Assert.assertNotNull(response.result);
|
||||
Assert.assertNotNull(response.result.operation_history_objs);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import cy.agorise.graphenej.AssetAmount;
|
||||
import cy.agorise.graphenej.Transaction;
|
||||
import cy.agorise.graphenej.UserAccount;
|
||||
import cy.agorise.graphenej.Memo;
|
||||
import cy.agorise.graphenej.operations.CustomOperation;
|
||||
import cy.agorise.graphenej.operations.LimitOrderCreateOperation;
|
||||
import cy.agorise.graphenej.operations.TransferOperation;
|
||||
|
||||
public class JsonRpcNotificationTest {
|
||||
|
||||
private String text = "{\"method\":\"notice\",\"params\":[3,[[{\"id\":\"2.1.0\",\"head_block_number\":30071834,\"head_block_id\":\"01cadc1a5f3f517e2eba9588111aef3af3c59916\",\"time\":\"2018-08-30T18:19:45\",\"current_witness\":\"1.6.74\",\"next_maintenance_time\":\"2018-08-30T19:00:00\",\"last_budget_time\":\"2018-08-30T18:00:00\",\"witness_budget\":80800000,\"accounts_registered_this_interval\":9,\"recently_missed_count\":0,\"current_aslot\":30228263,\"recent_slots_filled\":\"340282366920938463463374607431768211455\",\"dynamic_flags\":0,\"last_irreversible_block_num\":30071813}]]]}";
|
||||
|
||||
@Test
|
||||
public void failResponseDeserialization(){
|
||||
Gson gson = new Gson();
|
||||
JsonRpcResponse<?> response = gson.fromJson(text, JsonRpcResponse.class);
|
||||
// The result field of this de-serialized object should be null
|
||||
Assert.assertNull(response.result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void succeedNotificationDeserialization(){
|
||||
Gson gson = new GsonBuilder()
|
||||
.registerTypeAdapter(Transaction.class, new Transaction.TransactionDeserializer())
|
||||
.registerTypeAdapter(TransferOperation.class, new TransferOperation.TransferDeserializer())
|
||||
.registerTypeAdapter(LimitOrderCreateOperation.class, new LimitOrderCreateOperation.LimitOrderCreateDeserializer())
|
||||
.registerTypeAdapter(CustomOperation.class, new CustomOperation.CustomOperationDeserializer())
|
||||
.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer())
|
||||
.registerTypeAdapter(UserAccount.class, new UserAccount.UserAccountSimpleDeserializer())
|
||||
.registerTypeAdapter(DynamicGlobalProperties.class, new DynamicGlobalProperties.DynamicGlobalPropertiesDeserializer())
|
||||
.registerTypeAdapter(Memo.class, new Memo.MemoDeserializer())
|
||||
.registerTypeAdapter(OperationHistory.class, new OperationHistory.OperationHistoryDeserializer())
|
||||
.registerTypeAdapter(JsonRpcNotification.class, new JsonRpcNotification.JsonRpcNotificationDeserializer())
|
||||
.create();
|
||||
JsonRpcNotification notification = gson.fromJson(text, JsonRpcNotification.class);
|
||||
// Should deserialize a 'params' array with 2 elements
|
||||
Assert.assertEquals(2, notification.params.size());
|
||||
// The first element should be the number 3
|
||||
Assert.assertEquals(3, notification.params.get(0));
|
||||
ArrayList<Serializable> secondArgument = (ArrayList<Serializable>) notification.params.get(1);
|
||||
// The second element should be an array of length 1
|
||||
Assert.assertEquals(1, secondArgument.size());
|
||||
// Extracting the payload, which should be in itself another array
|
||||
DynamicGlobalProperties payload = (DynamicGlobalProperties) secondArgument.get(0);
|
||||
// Dynamic global properties head_block_number should match
|
||||
Assert.assertEquals(30071834, payload.head_block_number);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package cy.agorise.graphenej.models;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class JsonRpcResponseTest {
|
||||
|
||||
@Test
|
||||
public void deserializeJsonRpcResponse(){
|
||||
String text = "{\"id\":4,\"jsonrpc\":\"2.0\",\"result\":[{\"id\":\"2.1.0\",\"head_block_number\":30071833,\"head_block_id\":\"01cadc1964cb04ab551463e26033ab0f159bc8e1\",\"time\":\"2018-08-30T18:19:42\",\"current_witness\":\"1.6.71\",\"next_maintenance_time\":\"2018-08-30T19:00:00\",\"last_budget_time\":\"2018-08-30T18:00:00\",\"witness_budget\":80900000,\"accounts_registered_this_interval\":9,\"recently_missed_count\":0,\"current_aslot\":30228262,\"recent_slots_filled\":\"340282366920938463463374607431768211455\",\"dynamic_flags\":0,\"last_irreversible_block_num\":30071813}]}";
|
||||
Gson gson = new Gson();
|
||||
JsonRpcResponse<?> response = gson.fromJson(text, JsonRpcResponse.class);
|
||||
System.out.println("response: "+response.result);
|
||||
Assert.assertNotNull(response);
|
||||
Assert.assertNotNull(response.result);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue