Updated on 2026-08-14

This commit is contained in:
Tangem 2018-12-18 14:11:24 +03:00
commit 2922eab134
24 changed files with 2106 additions and 937 deletions

View file

@ -17,14 +17,14 @@ public class ElectrumRequest {
public static final String METHOD_SendTransaction = "blockchain.transaction.broadcast";
public static final String METHOD_GetFee = "blockchain.estimatefee";
public JSONObject jsRequestData;
public String answerData;
public String error;
public String walletAddress;
private JSONObject jsRequestData;
String answerData;
private String error = null;
private String walletAddress;
public String txHash;
public String TX;
public String host;
public int port;
private String TX;
String host;
int port;
private ElectrumRequest() {
}
@ -142,8 +142,8 @@ public class ElectrumRequest {
return "";
}
public boolean isMethod(String methodName) throws JSONException {
return jsRequestData.getString("method").equals(methodName);
public boolean isMethod(String methodName) {
return getMethod().equals(methodName);
}
public JSONArray getParams() throws JSONException {
@ -154,15 +154,30 @@ public class ElectrumRequest {
return getAnswer().getJSONObject("result");
}
public JSONObject getError() throws JSONException {
JSONObject answer = getAnswer();
if (answer.has("error")) {
return getAnswer().getJSONObject("error");
} else {
return null;
public String getError() {
if( answerData!=null ) {
// answer received - return error from it
JSONObject answer = getAnswer();
if (answer.has("error")) {
try {
return getAnswer().getJSONObject("error").toString();
} catch (JSONException e) {
e.printStackTrace();
return null;
}
} else {
return null;
}
}else{
// no answer received - return saved error reason
return error;
}
}
public void setError(String error) {
this.error = error;
}
public String getResultString() throws JSONException {
if (getAnswer().has("result")) {
return getAnswer().getString("result");

View file

@ -28,7 +28,7 @@ public class ServerApiCommon {
public interface EstimateFeeListener {
void onSuccess(int blockCount, String estimateFeeResponse);
void onFail(String message);
void onFail(int blockCount, String message);
}
public void setEstimateFee(EstimateFeeListener listener) {
@ -63,13 +63,13 @@ public class ServerApiCommon {
estimateFeeListener.onSuccess(blockCount, response.body());
Log.i(TAG, "estimateFee onResponse " + response.code() + " " + response.body());
} else
estimateFeeListener.onFail(response.body());
estimateFeeListener.onFail(blockCount, response.body());
Log.e(TAG, "estimateFee onResponse " + response.code());
}
@Override
public void onFailure(@NonNull Call<String> call, @NonNull Throwable t) {
estimateFeeListener.onFail(t.getMessage());
estimateFeeListener.onFail(blockCount, t.getMessage());
Log.e(TAG, "estimateFee onFailure " + t.getMessage());
}
});

View file

@ -8,6 +8,7 @@ import com.tangem.data.Blockchain;
import com.tangem.domain.wallet.bch.BitcoinCashNode;
import com.tangem.domain.wallet.btc.BitcoinNode;
import com.tangem.domain.wallet.btc.BitcoinNodeTestNet;
import com.tangem.wallet.R;
import java.io.BufferedReader;
import java.io.IOException;
@ -41,6 +42,17 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.observers.DefaultObserver;
import io.reactivex.schedulers.Schedulers;
/**
* Request processor for Electrum Api
* Every request live cycle:
* 1. In application create request and call {@link ServerApiElectrum}.electrumRequestData(..)
* 2. Try send every request for max 4 times,
* 3. If all 4 times fail call DefaultObserver<ElectrumRequest>.onError (defined in .electrumRequestData(..)) and than
* {@link ElectrumRequestDataListener}.onFail(...) callback
* Error can be acquired with {@link ElectrumRequest}.getError() method
* 4. If request network communication finished successfully then call DefaultObserver<ElectrumRequest>.onComplete (defined in .electrumRequestData) and than
* {@link ElectrumRequestDataListener}.onSuccess(...) callback
*/
public class ServerApiElectrum {
private static String TAG = ServerApiElectrum.class.getSimpleName();
@ -52,17 +64,47 @@ public class ServerApiElectrum {
private String host;
private int port;
public interface ElectrumRequestDataListener {
void onSuccess(ElectrumRequest electrumRequest);
private int requestsCount=0;
void onFail(String method);
public boolean isRequestsSequenceCompleted() {
Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount));
return requestsCount <= 0;
}
/**
* Interface for notification every request result
*/
public interface ElectrumRequestDataListener {
/**
* Notify that request processing was successful
* @param electrumRequest - processed request containing received answer {@see electrumRequest.getAnswer() method}
*/
void onSuccess(ElectrumRequest electrumRequest);
/**
* Notify that request processing was successful
* @param electrumRequest - processed request containing occurred error {@see electrumRequest.getError() method}
*/
void onFail(ElectrumRequest electrumRequest);
}
/**
* Set notificaion listener
* @param listener
*/
public void setElectrumRequestData(ElectrumRequestDataListener listener) {
electrumRequestDataListener = listener;
}
/**
* Start process request
* @param ctx
* @param electrumRequest
*/
public void electrumRequestData(TangemContext ctx, ElectrumRequest electrumRequest) {
requestsCount++;
Log.i(TAG, String.format("New request[%d]: %s", requestsCount,electrumRequest.getMethod()));
Observable<ElectrumRequest> checkElectrumDataObserver = Observable.just(electrumRequest)
.doOnNext(electrumRequest1 -> doElectrumRequest(ctx, electrumRequest))
@ -81,32 +123,50 @@ public class ServerApiElectrum {
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
checkElectrumDataObserver.subscribe(new DefaultObserver<ElectrumRequest>() {
//TODO remove onNext
@Override
public void onNext(ElectrumRequest v) {
if (electrumRequest.answerData != null) {
electrumRequestDataListener.onSuccess(electrumRequest);
// Log.i(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onNext != null");
Log.i(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onNext != null");
} else {
electrumRequestDataListener.onFail(electrumRequest.getMethod());
Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onNext == null");
}
}
@Override
public void onError(Throwable e) {
electrumRequestDataListener.onFail(electrumRequest.getMethod());
requestsCount--;
Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onError " + e.getMessage());
Log.e(TAG, String.format("%d requests left in processing",requestsCount));
electrumRequest.setError(ctx.getString(R.string.cannot_obtain_data_from_blockchain));
//setErrorOccurred(e.getMessage());//;
electrumRequestDataListener.onFail(electrumRequest);
}
/**
* Called after completion request processing
*/
@Override
public void onComplete() {
// Log.i(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onComplete");
requestsCount--;
if (electrumRequest.answerData != null) {
Log.i(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onComplete, answerData!=null");
} else {
Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " onComplete, answerData==null");
}
Log.e(TAG, String.format("%d requests left in processing",requestsCount));
if (electrumRequest.answerData != null) {
electrumRequestDataListener.onSuccess(electrumRequest);
} else {
// if( error==null || error.isEmpty() ) setErrorOccurred(ctx.getString(R.string.cannot_obtain_data_from_blockchain));
electrumRequestDataListener.onFail(electrumRequest);
}
}
});
}
private List<ElectrumRequest> doElectrumRequest(TangemContext ctx, ElectrumRequest electrumRequest) {
private void doElectrumRequest(TangemContext ctx, ElectrumRequest electrumRequest) {
String host;
int port;
String proto;
@ -118,8 +178,7 @@ public class ServerApiElectrum {
this.host = host;
this.port = port;
return doElectrumRequestTcp(electrumRequest, host, port);
doElectrumRequestTcp(electrumRequest, host, port);
} else if (ctx.getBlockchain() == Blockchain.BitcoinCash) {
BitcoinCashNode bitcoinCashNode = BitcoinCashNode.values()[new Random().nextInt(BitcoinCashNode.values().length)];
host = bitcoinCashNode.getHost();
@ -130,9 +189,9 @@ public class ServerApiElectrum {
this.port = port;
if (proto.equals("tcp")) {
return doElectrumRequestTcp(electrumRequest, host, port);
doElectrumRequestTcp(electrumRequest, host, port);
} else {
return doElectrumRequestSsl(electrumRequest, host, port);
doElectrumRequestSsl(electrumRequest, host, port);
}
} else if (ctx.getBlockchain() == Blockchain.Bitcoin) {
@ -145,23 +204,19 @@ public class ServerApiElectrum {
this.port = port;
if (proto.equals("tcp")) {
return doElectrumRequestTcp(electrumRequest, host, port);
doElectrumRequestTcp(electrumRequest, host, port);
} else {
return doElectrumRequestSsl(electrumRequest, host, port);
doElectrumRequestSsl(electrumRequest, host, port);
}
}
return null;
}
private List<ElectrumRequest> doElectrumRequestTcp(ElectrumRequest electrumRequest, String host, int port) {
List<ElectrumRequest> result = new ArrayList<>();
Collections.addAll(result, electrumRequest);
private void doElectrumRequestTcp(ElectrumRequest electrumRequest, String host, int port) {
try {
Socket socket = App.getNetworkComponent().getSocket();
socket.setSoTimeout(3000);
Log.i(TAG, "Start process "+electrumRequest.getMethod()+" @ "+host + ":" + port);
socket.connect(new InetSocketAddress(InetAddress.getByName(host), port));
Log.i(TAG, host + " " + port);
try {
OutputStream os = socket.getOutputStream();
OutputStreamWriter out = new OutputStreamWriter(os, "UTF-8");
@ -178,37 +233,38 @@ public class ServerApiElectrum {
if (electrumRequest.answerData != null) {
Log.i(TAG, ">> " + electrumRequest.answerData);
} else {
electrumRequest.error = "No answer from server";
electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain_no_answer));
Log.i(TAG, ">> <NULL>");
}
} catch (ConnectException e) {
e.printStackTrace();
electrumRequestDataListener.onFail(e.getMessage());
Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " ConnectException " + e.getMessage());
//e.printStackTrace();
//electrumRequestDataListener.onFail(e.getMessage());
electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain_no_connection));
Log.e(TAG, "doElectrumRequestTcp " + electrumRequest.getMethod() + " ConnectException " + e.getMessage());
} finally {
Log.i(TAG, "electrumRequestData " + electrumRequest.getMethod() + " CLOSE");
Log.i(TAG, "doElectrumRequestTcp " + electrumRequest.getMethod() + " socket.close");
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
electrumRequestDataListener.onFail(e.getMessage());
Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " IOException " + e.getMessage());
//e.printStackTrace();
//electrumRequestDataListener.onFail(e.getMessage());
electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain_communication_error));
Log.e(TAG, "doElectrumRequestTcp " + electrumRequest.getMethod() + " IOException " + e.getMessage());
}
return result;
}
private List<ElectrumRequest> doElectrumRequestSsl(ElectrumRequest electrumRequest, String host, int port) {
private void doElectrumRequestSsl(ElectrumRequest electrumRequest, String host, int port) {
try {
// create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@ -236,8 +292,8 @@ public class ServerApiElectrum {
Collections.addAll(result, electrumRequest);
try {
sslSocket = (SSLSocket) sf.createSocket(host, port);
Log.i(TAG, host + " " + port);
sslSocket = (SSLSocket) sf.createSocket(host, port);
try {
OutputStream os = sslSocket.getOutputStream();
OutputStreamWriter out = new OutputStreamWriter(os, "UTF-8");
@ -252,34 +308,33 @@ public class ServerApiElectrum {
if (electrumRequest.answerData != null) {
Log.i(TAG, ">> " + electrumRequest.answerData);
} else {
electrumRequest.error = "No answer from server";
electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain_no_answer));
Log.i(TAG, ">> <NULL>");
}
} catch (ConnectException e) {
e.printStackTrace();
Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " ConnectException " + e.getMessage());
electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain_no_connection));
Log.e(TAG, "doElectrumRequestTcp " + electrumRequest.getMethod() + " ConnectException " + e.getMessage());
} finally {
Log.i(TAG, "electrumRequestData " + electrumRequest.getMethod() + " CLOSE");
Log.i(TAG, "doElectrumRequestTcp " + electrumRequest.getMethod() + " socket.close");
sslSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "electrumRequestData " + electrumRequest.getMethod() + " IOException " + e.getMessage());
electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain_communication_error));
Log.e(TAG, "doElectrumRequestTcp " + electrumRequest.getMethod() + " IOException " + e.getMessage());
}
return result;
} catch (NoSuchAlgorithmException | KeyManagementException e) {
electrumRequest.setError(App.getInstance().getString(R.string.cannot_obtain_data_from_blockchain));
Log.e(TAG, e.getMessage());
}
return null;
}
public String getValidationNodeDescription() {
return "Electrum, " + host + ":" + String.valueOf(port);
}
}

View file

@ -31,6 +31,13 @@ public class ServerApiInfura {
public static final String INFURA_ETH_SEND_RAW_TRANSACTION = "eth_sendRawTransaction";
public static final String INFURA_ETH_GAS_PRICE = "eth_gasPrice";
private int requestsCount=0;
public boolean isRequestsSequenceCompleted() {
Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount));
return requestsCount <= 0;
}
private InfuraBodyListener infuraBodyListener;
public interface InfuraBodyListener {
@ -44,6 +51,7 @@ public class ServerApiInfura {
}
public void infura(String method, int id, String wallet, String contract, String tx) {
requestsCount++;
InfuraApi infuraApi = App.getNetworkComponent().getRetrofitInfura().create(InfuraApi.class);
InfuraBody infuraBody;
@ -77,6 +85,7 @@ public class ServerApiInfura {
@Override
public void onResponse(@NonNull Call<InfuraResponse> call, @NonNull Response<InfuraResponse> response) {
if (response.code() == 200) {
requestsCount--;
infuraBodyListener.onSuccess(method, response.body());
Log.i(TAG, "infura " + method + " onResponse " + response.code());
} else {