Skip to content
This repository has been archived by the owner on Nov 17, 2023. It is now read-only.

Useful error messages on connecting, unlock wallet #45

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

testOptions {
unitTests.returnDefaultValues = true
}
}


Expand Down
67 changes: 62 additions & 5 deletions app/src/main/java/zapsolutions/zap/HomeActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import androidx.fragment.app.Fragment;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
Expand All @@ -14,8 +15,13 @@
import com.android.volley.toolbox.JsonObjectRequest;
import com.google.android.material.bottomnavigation.BottomNavigationView;

import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.Toast;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
Expand Down Expand Up @@ -50,6 +56,7 @@ public class HomeActivity extends BaseAppCompatActivity implements LifecycleObse
private static final String LOG_TAG = "Main Activity";

private UserGuardian mUG;
private InputMethodManager mInputMethodManager;
private ScheduledExecutorService mExchangeRateScheduler;
private ScheduledExecutorService mLNDInfoScheduler;
private NetworkChangeReceiver mNetworkChangeReceiver;
Expand All @@ -61,6 +68,7 @@ public class HomeActivity extends BaseAppCompatActivity implements LifecycleObse
private boolean mInfoChangeListenerRegistered;
private boolean mWalletLoadedListenerRegistered;
private boolean mMainnetWarningShownOnce;
private boolean mIsFirstUnlockAttempt = true;


@Override
Expand All @@ -69,6 +77,7 @@ protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);

mUG = new UserGuardian(this, this);
mInputMethodManager = (InputMethodManager) this.getSystemService(Context.INPUT_METHOD_SERVICE);

// Register observer to detect if app goes to background
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
Expand Down Expand Up @@ -135,7 +144,7 @@ private void setupExchangeRateSchedule() {
mExchangeRateScheduler.scheduleAtFixedRate
(new Runnable() {
public void run() {
if(!MonetaryUtil.getInstance().getSecondCurrency().isBitcoin()) {
if (!MonetaryUtil.getInstance().getSecondCurrency().isBitcoin()) {
ZapLog.debug(LOG_TAG, "Fiat exchange rate request initiated");
// Adding request to request queue
HttpClient.getInstance().addToRequestQueue(request, "rateRequest");
Expand Down Expand Up @@ -215,7 +224,7 @@ public void onMoveToForeground() {
ZapLog.debug(LOG_TAG, "Starting to establish connections...");
LndConnection.getInstance().restartBackgroundTasks();

Wallet.getInstance().isLNDReachable();
Wallet.getInstance().checkIfLndIsReachableAndTriggerWalletLoadedInterface();

}
}
Expand Down Expand Up @@ -326,7 +335,7 @@ public void guardianDialogConfirmed(String DialogName) {
}

@Override
public void onWalletLoadedUpdated(boolean success, String error) {
public void onWalletLoadedUpdated(boolean success, int error) {
if (success) {
// We managed to establish a connection to LND.
// Now we can start to fetch all information needed from LND
Expand All @@ -344,10 +353,58 @@ public void onWalletLoadedUpdated(boolean success, String error) {

Wallet.getInstance().subscribeToTransactions();
Wallet.getInstance().subscribeToInvoices();
Wallet.getInstance().subscribeToChannelEvents();
Wallet.getInstance().subscribeToChannelBackup();

// ToDo: subscribeToChannelEvents() causes LND to hang, if it is called short after unlocking.
//Wallet.getInstance().subscribeToChannelEvents();
raphBTC marked this conversation as resolved.
Show resolved Hide resolved
//Wallet.getInstance().subscribeToChannelBackup();

ZapLog.debug(LOG_TAG, "Wallet loaded");
} else {
if (error == Wallet.WalletLoadedListener.ERROR_LOCKED) {


// Show unlock dialog
AlertDialog.Builder adb = new AlertDialog.Builder(this);
adb.setTitle(R.string.unlock_wallet);
adb.setCancelable(false);
View viewInflated = LayoutInflater.from(this).inflate(R.layout.dialog_input_password, null, false);

final EditText input = viewInflated.findViewById(R.id.input);
input.setShowSoftInputOnFocus(true);
input.requestFocus();

mInputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);

adb.setView(viewInflated);

adb.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
((WalletFragment) mCurrentFragment).showLoadingForWalletUnlock();
Wallet.getInstance().unlockWallet(input.getText().toString());
mInputMethodManager.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);
mIsFirstUnlockAttempt = false;
dialog.dismiss();
}
});
adb.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
InputMethodManager inputMethodManager = (InputMethodManager) HomeActivity.this.getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);
((WalletFragment) mCurrentFragment).showErrorAfterNotUnlocked();
mIsFirstUnlockAttempt = true;
dialog.cancel();
}
});

adb.show();
((WalletFragment) mCurrentFragment).showBackgroundForWalletUnlock();

if(!mIsFirstUnlockAttempt) {
Toast.makeText(HomeActivity.this, R.string.error_wrong_password, Toast.LENGTH_LONG).show();
}
}
}
}

Expand Down
13 changes: 9 additions & 4 deletions app/src/main/java/zapsolutions/zap/connection/LndConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@

/**
* Singleton to handle the connection to lnd
* <p>
* Please note:
* IP, Certificate and Macaroon are placeholders right now.
*/
public class LndConnection {

Expand Down Expand Up @@ -73,7 +70,11 @@ private void readSavedConnectionInfo() {
String certificateBase64UrlString = mConnectionInfo[2];
byte[] certificateBytes = BaseEncoding.base64Url().decode(certificateBase64UrlString);

mSSLFactory = CustomSSLSocketFactory.create(certificateBytes);
try {
raphBTC marked this conversation as resolved.
Show resolved Hide resolved
mSSLFactory = CustomSSLSocketFactory.create(certificateBytes);
} catch (RuntimeException e) {
ZapLog.debug(LOG_TAG, "Error on Certificate");
}

generateChannelAndStubs();

Expand Down Expand Up @@ -141,4 +142,8 @@ public LightningGrpc.LightningBlockingStub getBlockingClient() {
return mBlockingClient;
}

public String[] getConnectionInfo() {
return mConnectionInfo;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package zapsolutions.zap.connection;

public class LndConnectionConfig {

private String host;
private int port;
private String cert;
private String macaroon;


public String getHost() {
return host;
}

public void setHost(String host) {
this.host = host;
}

public int getPort() {
return port;
}

public void setPort(int port) {
this.port = port;
}

public String getCert() {
return cert;
}

public void setCert(String cert) {
this.cert = cert;
}

public String getMacaroon() {
return macaroon;
}

public void setMacaroon(String macaroon) {
this.macaroon = macaroon;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package zapsolutions.zap.connection.lndConnect;

import com.google.common.io.BaseEncoding;

import java.net.URI;
import java.net.URISyntaxException;

import zapsolutions.zap.connection.CustomSSLSocketFactory;
import zapsolutions.zap.connection.LndConnectionConfig;
import zapsolutions.zap.util.ZapLog;

/**
* This class parses a lndconnect which is defined in this project:
* https://github.com/LN-Zap/lndconnect
* <p>
* A lndconnect string consists of the following parts:
* lndconnect://<HOST>:<PORT>?cert=<certificate_encoded_as_base64url>&macaroon=<macaroon_encoded_as_base64url>
* <p>
* Note: The certificate is not mandatory. For cases like BTCPay server where another certificate is used, this can be omitted.
* <p>
* The parser returns an object containing the desired data or an descriptive error.
*/
public class LndConnectStringParser {

private static final String LOG_TAG = "LND connect string parser";

public static final int ERROR_INVALID_CONNECT_STRING = 0;
public static final int ERROR_NO_MACAROON = 1;
public static final int ERROR_INVALID_CERTIFICATE = 2;
public static final int ERROR_INVALID_MACAROON = 3;
public static final int ERROR_INVALID_HOST_OR_PORT = 4;

private int mError = -1;
private String mConnectString;

private LndConnectionConfig mConnectionConfig;

public LndConnectStringParser(String connectString){
mConnectString = connectString;
mConnectionConfig = new LndConnectionConfig();
}

public LndConnectStringParser parse() {

// validate not null
if (mConnectString == null) {
mError = ERROR_INVALID_CONNECT_STRING;
return this;
}

// validate scheme
if (!mConnectString.toLowerCase().startsWith("lndconnect://")) {
mError = ERROR_INVALID_CONNECT_STRING;
return this;
}

URI connectURI = null;
try {
connectURI = new URI(mConnectString);

// validate host and port
if (connectURI.getPort() == -1) {
mError = ERROR_INVALID_HOST_OR_PORT;
return this;
}

String cert = null;
String macaroon = null;

// fetch params
if (connectURI.getQuery() != null) {
String[] valuePairs = connectURI.getQuery().split("&");

for (String pair : valuePairs) {
String[] param = pair.split("=");
if (param.length > 1) {
if (param[0].equals("cert")) {
cert = param[1];
}
if (param[0].equals("macaroon")) {
macaroon = param[1];
}
}
}

// validate cert (Certificate is not mandatory for BTCPay server for example, therefore null is valid)
if (cert != null) {
try {
byte[] certificateBytes = BaseEncoding.base64Url().decode(cert);
try {
CustomSSLSocketFactory.create(certificateBytes);
} catch (RuntimeException e) {

ZapLog.debug(LOG_TAG, "certificate creation failed");
mError = ERROR_INVALID_CERTIFICATE;
return this;
}
} catch (IllegalArgumentException e) {
ZapLog.debug(LOG_TAG, "cert decoding failed");
mError = ERROR_INVALID_CERTIFICATE;
return this;
}
}

// validate macaroon if everything was valid so far
if (macaroon == null) {
ZapLog.debug(LOG_TAG, "lnd connect string does not include a macaroon");
mError = ERROR_NO_MACAROON;
return this;
} else {
try {
BaseEncoding.base64Url().decode(macaroon);
} catch (IllegalArgumentException e) {
ZapLog.debug(LOG_TAG, "macaroon decoding failed");

mError = ERROR_INVALID_MACAROON;
return this;
}
}

// everything is ok, initiate connection
mConnectionConfig.setHost(connectURI.getHost());
mConnectionConfig.setPort(connectURI.getPort());
mConnectionConfig.setCert(cert);
mConnectionConfig.setMacaroon(macaroon);

return this;

} else {
ZapLog.debug(LOG_TAG, "Connect URI has no parameters");
mError = ERROR_INVALID_CONNECT_STRING;
return this;
}

} catch (URISyntaxException e) {
ZapLog.debug(LOG_TAG, "URI could not be parsed");
mError = ERROR_INVALID_CONNECT_STRING;
return this;
}
}

public boolean hasError() {
if (mError > -1) {
return true;
} else {
return false;
}
}

public int getError(){
return mError;
}

public LndConnectionConfig getConnectionConfig() {
return mConnectionConfig;
}
}
Loading