Adding support for the get_relative_account_history method

master
Nelson R. Perez 2016-11-25 15:08:07 -05:00
parent e9c71b5145
commit 2b2195754a
8 changed files with 402 additions and 16 deletions

View File

@ -71,6 +71,9 @@ public class Main {
// test.testBrainKeyOperations(false);
// test.testBip39Opertion();
test.testAccountNamebyAddress();
// test.testAccountNamebyAddress();
test.testRelativeAccountHistory();
}
}

View File

@ -4,12 +4,15 @@ package com.luminiasoft.bitshares;
* Created by nelson on 11/16/16.
*/
public class RPC {
public static final String VERSION = "2.0";
public static final String CALL_LOGIN = "login";
public static final String CALL_NETWORK_BROADCAST = "network_broadcast";
public static final String CALL_HISTORY = "history";
public static final String CALL_GET_ACCOUNT_BY_NAME = "get_account_by_name";
public static final String CALL_GET_DYNAMIC_GLOBAL_PROPERTIES = "get_dynamic_global_properties";
public static final String CALL_BROADCAST_TRANSACTION = "broadcast_transaction";
public static final String CALL_GET_REQUIRED_FEES = "get_required_fees";
public static final String CALL_GET_ACCOUNTS = "get_accounts";
public static final String CALL_GET_KEY_REFERENCES = "get_key_references";
public static final String CALL_GET_RELATIVE_ACCOUNT_HISTORY = "get_relative_account_history";
}

View File

@ -7,10 +7,8 @@ import com.google.gson.reflect.TypeToken;
import com.luminiasoft.bitshares.errors.MalformedTransactionException;
import com.luminiasoft.bitshares.interfaces.WitnessResponseListener;
import com.luminiasoft.bitshares.models.*;
import com.luminiasoft.bitshares.ws.GetAccountByName;
import com.luminiasoft.bitshares.ws.GetAccountsByAddress;
import com.luminiasoft.bitshares.ws.GetRequiredFees;
import com.luminiasoft.bitshares.ws.TransactionBroadcastSequence;
import com.luminiasoft.bitshares.test.NaiveSSLContext;
import com.luminiasoft.bitshares.ws.*;
import com.neovisionaries.ws.client.*;
import org.bitcoinj.core.*;
import org.spongycastle.crypto.Digest;
@ -19,10 +17,14 @@ import org.spongycastle.crypto.digests.RIPEMD160Digest;
import org.spongycastle.crypto.digests.SHA512Digest;
import org.spongycastle.crypto.prng.DigestRandomGenerator;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.io.*;
import java.lang.reflect.Type;
import java.net.URL;
import java.nio.file.Paths;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;
@ -32,6 +34,9 @@ import java.util.*;
public class Test {
public static final String WITNESS_URL = "ws://api.devling.xyz:8088";
public static final String OPENLEDGER_WITNESS_URL = "wss://bitshares.openledger.info/ws";
// public static final String WITNESS_URL = "wss://fr.blockpay.ch:8089";
private Transaction transaction;
public Transaction getTransaction() {
@ -573,10 +578,10 @@ public class Test {
BrainKey brainKey = new BrainKey(Main.BRAIN_KEY, 0);
Address address = new Address(brainKey.getPrivateKey());
try {
WebSocketFactory factory = new WebSocketFactory().setConnectionTimeout(5000);
WebSocket mWebSocket = factory.createSocket(WITNESS_URL);
WebSocket mWebSocket = new WebSocketFactory().createSocket(WITNESS_URL);
byte[] key = brainKey.getPrivateKey().getPubKey();
mWebSocket.addListener(new GetAccountsByAddress(key, mListener));
System.out.println("Before connecting");
mWebSocket.connect();
} catch (IOException e) {
System.out.println("IOException. Msg: " + e.getMessage());
@ -584,4 +589,27 @@ public class Test {
System.out.println("WebSocketException. Msg: " + e.getMessage());
}
}
public void testRelativeAccountHistory(){
GetRelativeAccountHistory relativeAccountHistory = new GetRelativeAccountHistory(new UserAccount("1.2.138632"), mListener);
try {
// Create a custom SSL context.
SSLContext context = null;
context = NaiveSSLContext.getInstance("TLS");
WebSocketFactory factory = new WebSocketFactory();
// Set the custom SSL context.
factory.setSSLContext(context);
WebSocket mWebSocket = factory.createSocket(OPENLEDGER_WITNESS_URL);
mWebSocket.addListener(relativeAccountHistory);
mWebSocket.connect();
} catch (IOException e) {
System.out.println("IOException. Msg: "+e.getMessage());
} catch (WebSocketException e) {
System.out.println("WebSocketException. Msg: "+e.getMessage());
} catch (NoSuchAlgorithmException e) {
System.out.println("NoSuchAlgorithmException. Msg: "+e.getMessage());
}
}
}

View File

@ -1,17 +1,14 @@
package com.luminiasoft.bitshares;
import com.google.common.primitives.Bytes;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.common.primitives.UnsignedLong;
import com.google.gson.*;
import java.lang.reflect.Type;
/**
* Class used to encapsulate the Transfer operation related functionalities.
* TODO: Add extensions support
*/
public class Transfer extends BaseOperation {
public static final String KEY_FEE = "fee";
@ -100,14 +97,72 @@ public class Transfer extends BaseOperation {
return array;
}
class TransferSerializer implements JsonSerializer<Transfer> {
public static class TransferSerializer implements JsonSerializer<Transfer> {
@Override
public JsonElement serialize(Transfer transfer, Type type, JsonSerializationContext jsonSerializationContext) {
JsonArray arrayRep = new JsonArray();
arrayRep.add(transfer.getId());
arrayRep.add(toJsonObject());
arrayRep.add(transfer.toJsonObject());
return arrayRep;
}
}
/**
* This deserializer will work on any transfer operation serialized in the 'array form' used a lot in
* the Graphene Blockchain API.
*
* An example of this serialized form is the following:
*
* [
* 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": []
* }
* ]
*
* It will convert this data into a nice Transfer object.
*/
public static class TransferDeserializer implements JsonDeserializer<Transfer> {
@Override
public Transfer 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 serializedTransfer = json.getAsJsonArray();
if(serializedTransfer.get(0).getAsInt() != OperationType.transfer_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(serializedTransfer.get(1), Transfer.class);
}
}else{
// This block is called in the second recursion and takes care of deserializing the
// transfer data itself.
JsonObject jsonObject = json.getAsJsonObject();
// Deserializing AssetAmount objects
AssetAmount amount = context.deserialize(jsonObject.get("amount"), AssetAmount.class);
AssetAmount fee = context.deserialize(jsonObject.get("fee"), AssetAmount.class);
// Deserializing UserAccount objects
UserAccount from = new UserAccount(jsonObject.get("from").getAsString());
UserAccount to = new UserAccount(jsonObject.get("to").getAsString());
Transfer transfer = new Transfer(from, to, amount, fee);
return transfer;
}
}
}
}

View File

@ -66,9 +66,12 @@ public class ApiCall implements JsonSerializable {
JsonArray methodParams = new JsonArray();
for(int i = 0; i < this.params.size(); i++){
if(this.params.get(i) instanceof JsonSerializable){
if(this.params.get(i) instanceof JsonSerializable) {
// Sometimes the parameters are objects
methodParams.add(((JsonSerializable) this.params.get(i)).toJsonObject());
}else if (Number.class.isInstance(this.params.get(i))){
// Other times they are numbers
methodParams.add( (Number) this.params.get(i));
}else if(this.params.get(i) instanceof String || this.params.get(i) == null){
// Other times they are plain strings
methodParams.add((String) this.params.get(i));

View File

@ -0,0 +1,22 @@
package com.luminiasoft.bitshares.models;
import com.luminiasoft.bitshares.BaseOperation;
import com.luminiasoft.bitshares.GrapheneObject;
import com.luminiasoft.bitshares.Transfer;
/**
* 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 {
public String id;
public Transfer op;
public Object[] result;
public long block_num;
public long trx_in_block;
public long op_in_trx;
public long virtual_op;
}

View File

@ -0,0 +1,126 @@
package com.luminiasoft.bitshares.test;
/*
* Copyright (C) 2015 Neo Visionaries Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the
* License.
*/
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
/**
* A factory class which creates an {@link SSLContext} that
* naively accepts all certificates without verification.
*
* <pre>
* // Create an SSL context that naively accepts all certificates.
* SSLContext context = NaiveSSLContext.getInstance("TLS");
*
* // Create a socket factory from the SSL context.
* SSLSocketFactory factory = context.getSocketFactory();
*
* // Create a socket from the socket factory.
* SSLSocket socket = factory.createSocket("www.example.com", 443);
* </pre>
*
* @author Takahiko Kawasaki
*/
public class NaiveSSLContext
{
private NaiveSSLContext()
{
}
/**
* Get an SSLContext that implements the specified secure
* socket protocol and naively accepts all certificates
* without verification.
*/
public static SSLContext getInstance(String protocol) throws NoSuchAlgorithmException
{
return init(SSLContext.getInstance(protocol));
}
/**
* Get an SSLContext that implements the specified secure
* socket protocol and naively accepts all certificates
* without verification.
*/
public static SSLContext getInstance(String protocol, Provider provider) throws NoSuchAlgorithmException
{
return init(SSLContext.getInstance(protocol, provider));
}
/**
* Get an SSLContext that implements the specified secure
* socket protocol and naively accepts all certificates
* without verification.
*/
public static SSLContext getInstance(String protocol, String provider) throws NoSuchAlgorithmException, NoSuchProviderException
{
return init(SSLContext.getInstance(protocol, provider));
}
/**
* Set NaiveTrustManager to the given context.
*/
private static SSLContext init(SSLContext context)
{
try
{
// Set NaiveTrustManager.
context.init(null, new TrustManager[] { new NaiveTrustManager() }, null);
}
catch (KeyManagementException e)
{
throw new RuntimeException("Failed to initialize an SSLContext.", e);
}
return context;
}
/**
* A {@link TrustManager} which trusts all certificates naively.
*/
private static class NaiveTrustManager implements X509TrustManager
{
@Override
public X509Certificate[] getAcceptedIssuers()
{
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType)
{
}
public void checkServerTrusted(X509Certificate[] certs, String authType)
{
}
}
}

View File

@ -0,0 +1,146 @@
package com.luminiasoft.bitshares.ws;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.luminiasoft.bitshares.AssetAmount;
import com.luminiasoft.bitshares.RPC;
import com.luminiasoft.bitshares.Transfer;
import com.luminiasoft.bitshares.UserAccount;
import com.luminiasoft.bitshares.interfaces.WitnessResponseListener;
import com.luminiasoft.bitshares.models.ApiCall;
import com.luminiasoft.bitshares.models.BaseResponse;
import com.luminiasoft.bitshares.models.HistoricalTransfer;
import com.luminiasoft.bitshares.models.WitnessResponse;
import com.neovisionaries.ws.client.WebSocket;
import com.neovisionaries.ws.client.WebSocketAdapter;
import com.neovisionaries.ws.client.WebSocketException;
import com.neovisionaries.ws.client.WebSocketFrame;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Class used to encapsulate the communication sequence used to retrieve the transaction history of
* a given user.
*/
public class GetRelativeAccountHistory extends WebSocketAdapter {
// Sequence of message ids
private final static int LOGIN_ID = 1;
private final static int GET_HISTORY_ID = 2;
private final static int GET_HISTORY_DATA = 3;
// Default value constants
public static final int DEFAULT_STOP = 0;
public static final int DEFAULT_START = 0;
public static final int MAX_LIMIT = 100;
// API call parameters
private UserAccount mUserAccount;
private int stop;
private int limit;
private int start;
private WitnessResponseListener mListener;
private int currentId = 1;
private int apiId = -1;
/**
* Constructor that takes all possible parameters.
* @param userAccount The user account to be queried
* @param stop Sequence number of earliest operation
* @param limit Maximum number of operations to retrieve (must not exceed 100)
* @param start Sequence number of the most recent operation to retrieve
* @param listener Listener to be notified with the result of this query
*/
public GetRelativeAccountHistory(UserAccount userAccount, int stop, int limit, int start, WitnessResponseListener listener){
if(limit > MAX_LIMIT) limit = MAX_LIMIT;
this.mUserAccount = userAccount;
this.stop = stop;
this.limit = limit;
this.start = start;
this.mListener = listener;
}
/**
* Constructor that uses the default values, and sets the limit to its maximum possible value.
* @param userAccount The user account to be queried
* @param listener Listener to be notified with the result of this query
*/
public GetRelativeAccountHistory(UserAccount userAccount, WitnessResponseListener listener){
this.mUserAccount = userAccount;
this.stop = DEFAULT_STOP;
this.limit = MAX_LIMIT;
this.start = DEFAULT_START;
this.mListener = listener;
}
@Override
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
ArrayList<Serializable> loginParams = new ArrayList<>();
loginParams.add(null);
loginParams.add(null);
ApiCall loginCall = new ApiCall(1, RPC.CALL_LOGIN, loginParams, "2.0", currentId);
websocket.sendText(loginCall.toJsonString());
}
@Override
public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
String response = frame.getPayloadText();
System.out.println("<<< "+response);
Gson gson = new Gson();
BaseResponse baseResponse = gson.fromJson(response, BaseResponse.class);
if(baseResponse.error != null){
mListener.onError(baseResponse.error);
websocket.disconnect();
}else{
currentId++;
ArrayList<Serializable> emptyParams = new ArrayList<>();
if(baseResponse.id == LOGIN_ID){
ApiCall getRelativeAccountHistoryId = new ApiCall(1, RPC.CALL_HISTORY, emptyParams, RPC.VERSION, currentId);
websocket.sendText(getRelativeAccountHistoryId.toJsonString());
}else if(baseResponse.id == GET_HISTORY_ID){
Type ApiIdResponse = new TypeToken<WitnessResponse<Integer>>() {}.getType();
WitnessResponse<Integer> witnessResponse = gson.fromJson(response, ApiIdResponse);
apiId = witnessResponse.result.intValue();
ArrayList<Serializable> params = new ArrayList<>();
params.add(mUserAccount.toJsonString());
params.add(this.stop);
params.add(this.limit);
params.add(this.start);
ApiCall getRelativeAccountHistoryCall = new ApiCall(apiId, RPC.CALL_GET_RELATIVE_ACCOUNT_HISTORY, params, RPC.VERSION, currentId);
websocket.sendText(getRelativeAccountHistoryCall.toJsonString());
}else if(baseResponse.id == GET_HISTORY_DATA){
System.out.println(frame.getPayloadText());
Type RelativeAccountHistoryResponse = new TypeToken<WitnessResponse<List<HistoricalTransfer>>>(){}.getType();
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Transfer.class, new Transfer.TransferDeserializer());
gsonBuilder.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetDeserializer());
WitnessResponse<List<HistoricalTransfer>> transfersResponse = gsonBuilder.create().fromJson(response, RelativeAccountHistoryResponse);
mListener.onSuccess(transfersResponse);
websocket.disconnect();
}
}
}
@Override
public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception {
if(frame.isTextFrame())
System.out.println(">>> "+frame.getPayloadText());
}
@Override
public void onError(WebSocket websocket, WebSocketException cause) throws Exception {
System.out.println("onError. Msg: "+cause.getMessage());
}
@Override
public void handleCallbackError(WebSocket websocket, Throwable cause) throws Exception {
System.out.println("handleCallbackError. Msg: "+cause.getMessage());
}
}