diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index df1e33c2..3a355bf3 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -6,10 +6,21 @@ follow [https://changelog.md/](https://changelog.md/) guidelines. ## [Unreleased] +## [49.4] - 2022-05-23 + +### CHANGED +- Receiving Node cell behavior in Operation Detail. Instead of opening 1ML site (private nodes +return a 404 not found), we copy node public key to clipboard. + +### FIXED +- A bug that triggered LN invoice regeneration multiple times when a payment was received +- Added a workaround for a strange BadPaddingException coming from Android's Keystore + ## [49.3] - 2022-04-26 ### ADDED - Better error reporting and extra metadata for MoneyDecoration (MuunAmountInput) crash +- Show LN alias for outgoing payments in payment history and payment Detail ### FIXED - Added missing error metadata to some crashlytics errors (e.g for background task of anon users) diff --git a/android/Dockerfile b/android/Dockerfile index 994618a7..9660ece9 100644 --- a/android/Dockerfile +++ b/android/Dockerfile @@ -3,7 +3,7 @@ FROM openjdk:17-jdk-buster@sha256:9217da81dcff19e60861791511ce57c019e47eaf5ca40d ENV NDK_VERSION 22.0.7026061 ENV ANDROID_PLATFORM_VERSION 28 ENV ANDROID_BUILD_TOOLS_VERSION 28.0.3 -ENV GO_VERSION 1.16.8 +ENV GO_VERSION 1.18.1 RUN apt-get update \ && apt-get install --yes --no-install-recommends \ diff --git a/android/apollo/build.gradle b/android/apollo/build.gradle index 2bab1fbb..58204408 100644 --- a/android/apollo/build.gradle +++ b/android/apollo/build.gradle @@ -20,20 +20,6 @@ apply from: "${project.rootDir}/linters/pmd/check-android.gradle" // https://github.com/spotbugs/spotbugs-gradle-plugin/issues/90 //apply from: "${project.rootDir}/linters/findbugs/check-android.gradle" -/** - * Returns the ip of the local network specified by the TESTING_LOCAL_NETWORK env var. - */ -def testingLocalIp() { - def interfaceName = System.getenv("TESTING_LOCAL_NETWORK")?.trim() - - return NetworkInterface.getNetworkInterfaces(). - findAll { interfaceName && interfaceName.equalsIgnoreCase(it.displayName) }. - collect { Collections.list it.getInetAddresses() }. - flatten(). - find()?. // first or null - getHostAddress() -} - android { compileSdkVersion 30 @@ -43,9 +29,6 @@ android { versionCode 1 versionName "1.0" - // circle - buildConfigField("String", "TESTING_LOCAL_IP", "\"${testingLocalIp()}\"") - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/android/apollo/src/main/AndroidManifest.xml b/android/apollo/src/main/AndroidManifest.xml index 55d86c80..ca99cd26 100644 --- a/android/apollo/src/main/AndroidManifest.xml +++ b/android/apollo/src/main/AndroidManifest.xml @@ -2,4 +2,6 @@ xmlns:android="http://schemas.android.com/apk/res/android"> + + diff --git a/android/apollo/src/main/java/io/muun/apollo/data/logging/Crashlytics.kt b/android/apollo/src/main/java/io/muun/apollo/data/logging/Crashlytics.kt new file mode 100644 index 00000000..90427c3e --- /dev/null +++ b/android/apollo/src/main/java/io/muun/apollo/data/logging/Crashlytics.kt @@ -0,0 +1,120 @@ +package io.muun.apollo.data.logging + +import com.google.firebase.crashlytics.FirebaseCrashlytics +import io.muun.apollo.domain.action.debug.ForceCrashReportAction +import io.muun.apollo.domain.errors.CyclicalSwapError +import io.muun.apollo.domain.errors.FcmTokenNotAvailableError +import io.muun.apollo.domain.errors.InvoiceAlreadyUsedException +import io.muun.apollo.domain.errors.InvoiceExpiredException +import io.muun.apollo.domain.errors.InvoiceExpiresTooSoonException +import io.muun.apollo.domain.errors.InvoiceMissingAmountException +import io.muun.apollo.domain.errors.NoPaymentRouteException +import io.muun.apollo.domain.errors.UnreachableNodeException +import io.muun.apollo.domain.model.report.CrashReport +import io.muun.apollo.domain.utils.isInstanceOrIsCausedByError + +object Crashlytics { + + private val crashlytics = FirebaseCrashlytics.getInstance() + + /** + * Set up Crashlytics metadata. + */ + @JvmStatic + fun configure(email: String?, userId: String) { + crashlytics.setUserId(userId) + crashlytics.setCustomKey("email", email ?: "unknown") + + // TODO: use setUserEmail, and grab Houston session UUID to attach it + } + + /** + * Add custom log event, to be displayed under Logs tab. Also queryable via Bigquery. See: + * https://firebase.google.com/docs/crashlytics/customize-crash-reports?platform=android#add-logs + */ + @JvmStatic + fun logBreadcrumb(breadcrumb: String) { + crashlytics.log(breadcrumb); + } + + /** + * Send the error to Crashlytics, attaching metadata as key-values with their SDK. + */ + fun reportError(report: CrashReport) { + + // Silence some common "nothing to worry about" errors + if (isOnCrashlyticsBlacklist(report.originalError)) { + return + } + + crashlytics.setCustomKey("tag", report.tag) + crashlytics.setCustomKey("message", report.message) + crashlytics.setCustomKey("locale", LoggingContext.locale) + + for (entry in report.metadata.entries) { + crashlytics.setCustomKey(entry.key, entry.value.toString()) + } + + crashlytics.recordException(report.error) + } + + /** + * Send a "fallback" reporting error to Crashlytics. This means that there was an error while + * doing our usual error report processing. Hence we try to report the original error data (tag, + * message, error) and the error that happened while reporting. + */ + fun reportReportingError( + tag: String?, + message: String?, + originalError: Throwable?, + crashReportingError: Throwable, + ) { + + tag?.let { crashlytics.setCustomKey("tag", it) } + message?.let { crashlytics.setCustomKey("message", it) } + + if (originalError != null) { + crashlytics.recordException(originalError) + } + + crashlytics.recordException(crashReportingError) + } + + /** + * Forcefully report an exception, bypassing our usual error processing. Meant to only be used + * by ForceCrashReportAction. + * + * @see ForceCrashReportAction + */ + fun forceReport(error: ForceCrashReportAction.ForcedCrashlyticsCall) { + crashlytics.recordException(error) + } + + /** + * There are certain errors that are expected and/or there's nothing we can do about it (besides + * properly informing the user about the situation), so let's try to reduce crashlytics noise by + * silencing some common "nothing to worry about" errors. + */ + private fun isOnCrashlyticsBlacklist(error: Throwable?): Boolean { + + // If root error has no throwable cause then there's nothing to blacklist + // This is an ugly signature and behaviour to have but makes life easier for caller + if (error == null) { + return false + } + + return when { + error.isInstanceOrIsCausedByError() -> true + error.isInstanceOrIsCausedByError() -> true + error.isInstanceOrIsCausedByError() -> true + error.isInstanceOrIsCausedByError() -> true + error.isInstanceOrIsCausedByError() -> true + error.isInstanceOrIsCausedByError() -> true + error.isInstanceOrIsCausedByError() -> true + + error.isInstanceOrIsCausedByError() -> true + + else -> false + } + } +} \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/data/logging/LoggingContext.kt b/android/apollo/src/main/java/io/muun/apollo/data/logging/LoggingContext.kt index eb7ef831..d40cf12c 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/logging/LoggingContext.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/logging/LoggingContext.kt @@ -1,25 +1,19 @@ package io.muun.apollo.data.logging -import com.google.firebase.crashlytics.FirebaseCrashlytics - - object LoggingContext { + /** + * Log errors to the Crashlytics. + */ @JvmStatic var sendToCrashlytics = true // default for production + /** + * Log errors to the system logs. + */ @JvmStatic var sendToLogcat = false // default for production @JvmStatic var locale: String = "UNSET" // easily track and attach user's locale to muun errors and reports - - @JvmStatic - fun configure(email: String?, userId: String) { - val crashlytics = FirebaseCrashlytics.getInstance() - crashlytics.setUserId(userId) - crashlytics.setCustomKey("email", email ?: "unknown") - - // TODO: use setUserEmail, and grab Houston session UUID to attach it - } } \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/data/logging/MuunTree.kt b/android/apollo/src/main/java/io/muun/apollo/data/logging/MuunTree.kt index 74f8c095..87b74eb3 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/logging/MuunTree.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/logging/MuunTree.kt @@ -1,14 +1,10 @@ package io.muun.apollo.data.logging import android.util.Log -import com.google.firebase.crashlytics.FirebaseCrashlytics -import io.muun.apollo.domain.errors.* -import io.muun.apollo.domain.model.report.CrashReport import io.muun.apollo.domain.model.report.CrashReportBuilder -import io.muun.apollo.domain.utils.isInstanceOrIsCausedByError import timber.log.Timber -class MuunTree: Timber.DebugTree() { +class MuunTree : Timber.DebugTree() { /** * Log a message, taking steps to enrich and report errors. @@ -35,19 +31,21 @@ class MuunTree: Timber.DebugTree() { private fun sendPreparedCrashReport(tag: String?, message: String?, error: Throwable?) { val report = CrashReportBuilder.build(tag, message, error) - if (LoggingContext.sendToCrashlytics && !isOnCrashlyticsBlacklist(error)) { - sendToCrashlytics(report) + if (LoggingContext.sendToCrashlytics) { + Crashlytics.reportError(report) } if (LoggingContext.sendToLogcat) { - sendToLogcat(report) + Log.e(report.tag, "${report.message} ${report.metadata}", report.error) } } - private fun sendFallbackCrashReport(tag: String?, - message: String?, - error: Throwable?, - crashReportingError: Throwable) { + private fun sendFallbackCrashReport( + tag: String?, + message: String?, + error: Throwable?, + crashReportingError: Throwable, + ) { if (LoggingContext.sendToLogcat) { Log.e("CrashReport:$tag", "During error processing", crashReportingError) @@ -55,65 +53,7 @@ class MuunTree: Timber.DebugTree() { } if (LoggingContext.sendToCrashlytics) { - - val crashlytics = FirebaseCrashlytics.getInstance() - - if (error != null) { - crashlytics.recordException(error) - } - crashlytics.recordException(crashReportingError) - } - } - - /** - * Send the error to the system logs. - */ - private fun sendToLogcat(report: CrashReport) { - Log.e(report.tag, "${report.message} ${report.metadata}", report.error) - } - - /** - * Send the error to Crashlytics, attaching metadata as key-values with their SDK. - */ - private fun sendToCrashlytics(report: CrashReport) { - val crashlytics = FirebaseCrashlytics.getInstance() - - crashlytics.setCustomKey("tag", report.tag) - crashlytics.setCustomKey("message", report.message) - crashlytics.setCustomKey("locale", LoggingContext.locale) - - for (entry in report.metadata.entries) { - crashlytics.setCustomKey(entry.key, entry.value.toString()) - } - - crashlytics.recordException(report.error) - } - - /** - * There are certain errors that are expected and/or there's nothing we can do about it (besides - * properly informing the user about the situation), so let's try to reduce crashlytics noise by - * silencing some common "nothing to worry about" errors. - */ - private fun isOnCrashlyticsBlacklist(error: Throwable?): Boolean { - - // If root error has no throwable cause then there's nothing to blacklist - // This is an ugly signature and behaviour to have but makes life easier for caller - if (error == null) { - return false - } - - return when { - error.isInstanceOrIsCausedByError() -> true - error.isInstanceOrIsCausedByError() -> true - error.isInstanceOrIsCausedByError() -> true - error.isInstanceOrIsCausedByError() -> true - error.isInstanceOrIsCausedByError() -> true - error.isInstanceOrIsCausedByError() -> true - error.isInstanceOrIsCausedByError() -> true - - error.isInstanceOrIsCausedByError() -> true - - else -> false + Crashlytics.reportReportingError(tag, message, error, crashReportingError) } } } \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/secure_storage/SecureStorageProvider.java b/android/apollo/src/main/java/io/muun/apollo/data/os/secure_storage/SecureStorageProvider.java index 5be891c2..52880889 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/os/secure_storage/SecureStorageProvider.java +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/secure_storage/SecureStorageProvider.java @@ -1,6 +1,7 @@ package io.muun.apollo.data.os.secure_storage; import io.muun.apollo.domain.errors.SecureStorageError; +import io.muun.common.utils.Encodings; import io.muun.common.utils.Preconditions; import rx.Observable; @@ -39,7 +40,9 @@ public byte[] get(String key) { return retrieveDecrypted(key); } catch (Throwable e) { - throw new SecureStorageError(e, debugSnapshot()); + final SecureStorageError ssError = new SecureStorageError(e, debugSnapshot()); + enhanceError(ssError, key); + throw ssError; } } @@ -60,7 +63,9 @@ public void put(String key, byte[] value) { storeEncrypted(key, value); } catch (Throwable e) { - throw new SecureStorageError(e, debugSnapshot()); + final SecureStorageError ssError = new SecureStorageError(e, debugSnapshot()); + enhanceError(ssError, key); + throw ssError; } preferences.recordAuditTrail("PUT", key); @@ -147,11 +152,17 @@ private void storeEncrypted(String key, byte[] input) { preferences.saveBytes(keyStore.encryptData(input, key, preferences.getAesIv(key)), key); } + private void enhanceError(SecureStorageError ssError, String key) { + ssError.addMetadata("key", key); + ssError.addMetadata("cypherText", Encodings.bytesToHex(preferences.getBytes(key))); + ssError.addMetadata("aesIV", Encodings.bytesToHex(preferences.getAesIv(key))); + } + /** * Take a debug snapshot of the current state of the secure storage. This is safe to * report without compromising any user data. */ - public DebugSnapshot debugSnapshot() { + private DebugSnapshot debugSnapshot() { // NEVER ever return any values from the keystore itself, only labels should get out. Set keystoreLabels = null; diff --git a/android/apollo/src/main/java/io/muun/apollo/data/preferences/KeysRepository.java b/android/apollo/src/main/java/io/muun/apollo/data/preferences/KeysRepository.java index 62f4e366..4bfbf5f0 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/preferences/KeysRepository.java +++ b/android/apollo/src/main/java/io/muun/apollo/data/preferences/KeysRepository.java @@ -391,7 +391,7 @@ public void storePublicChallengeKey(ChallengePublicKey publicKey, ChallengeType } secureStorageProvider.put( - KEY_CHALLENGE_PUBLIC_KEY + type.toString(), + KEY_CHALLENGE_PUBLIC_KEY + type, publicKey.serialize() ); diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/ApplicationLockManager.java b/android/apollo/src/main/java/io/muun/apollo/domain/ApplicationLockManager.java index 31aa7ac7..bdfcfc3b 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/ApplicationLockManager.java +++ b/android/apollo/src/main/java/io/muun/apollo/domain/ApplicationLockManager.java @@ -1,15 +1,20 @@ package io.muun.apollo.domain; +import io.muun.apollo.data.logging.Crashlytics; import io.muun.apollo.data.os.authentication.PinManager; import io.muun.apollo.data.os.secure_storage.SecureStorageProvider; +import io.muun.apollo.domain.errors.SecureStorageError; import io.muun.apollo.domain.selector.ChallengePublicKeySelector; +import io.muun.apollo.domain.utils.ExtensionsKt; import io.muun.common.utils.Encodings; import io.muun.common.utils.Preconditions; +import android.content.Context; import androidx.annotation.VisibleForTesting; import rx.Observable; import rx.Subscription; +import timber.log.Timber; import java.util.NoSuchElementException; import java.util.concurrent.TimeUnit; @@ -20,6 +25,9 @@ public class ApplicationLockManager { public interface UnlockListener { + /** + * Called when lock screen has successfully been unlocked. + */ void onUnlock(); } @@ -34,6 +42,7 @@ public interface UnlockListener { private final PinManager pinManager; private final SecureStorageProvider secureStorageProvider; private final ChallengePublicKeySelector challengePublicKeySel; + private final Context context; /** * Constructor. @@ -41,11 +50,13 @@ public interface UnlockListener { @Inject public ApplicationLockManager(PinManager pinManager, SecureStorageProvider secureStorageProvider, - ChallengePublicKeySelector challengePublicKeySel1) { + ChallengePublicKeySelector challengePublicKeySel1, + Context context) { this.pinManager = pinManager; this.secureStorageProvider = secureStorageProvider; this.challengePublicKeySel = challengePublicKeySel1; + this.context = context; } public synchronized boolean isLockConfigured() { @@ -176,6 +187,18 @@ private synchronized int fetchIncorrectAttempts() { } catch (NoSuchElementException error) { storeIncorrectAttempts(0); return 0; + + } catch (SecureStorageError error) { + if (ExtensionsKt.isCauseByBadPaddingException(error)) { + // If this error is caused by a BadPadding Exception coming from the Android + // Keystore, we try continue execution hoping this is the only piece of data + // affected by this data corruption. + error.addMetadata("hasBackup", challengePublicKeySel.existsAnyType()); + Crashlytics.logBreadcrumb("bad_padding_exception_workaround"); + Timber.e(error, "WORKAROUND for BadPaddingException in fetchIncorrectAttempts"); + return 0; + } + throw error; } } } diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/LoggingContextManager.kt b/android/apollo/src/main/java/io/muun/apollo/domain/LoggingContextManager.kt index a32fbc0e..0ef3894c 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/LoggingContextManager.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/LoggingContextManager.kt @@ -1,6 +1,7 @@ package io.muun.apollo.domain import android.content.Context +import io.muun.apollo.data.logging.Crashlytics import io.muun.apollo.data.logging.LoggingContext import io.muun.apollo.data.preferences.UserRepository import io.muun.apollo.domain.model.user.User @@ -10,13 +11,13 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class LoggingContextManager @Inject constructor( +class LoggingContextManager @Inject constructor( private val userRepository: UserRepository, - private val context: Context + private val context: Context, ) { /** - * Setups Crashlytics metadata. + * Set up Crashlytics metadata. */ fun setupCrashlytics() { val maybeUser: Optional = userRepository.fetchOneOptional() @@ -26,7 +27,7 @@ class LoggingContextManager @Inject constructor( } val user = maybeUser.get() - LoggingContext.configure(user.email.orElse(null), user.hid.toString()) + Crashlytics.configure(user.email.orElse(null), user.hid.toString()) LoggingContext.locale = context.locale().toString() } diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/action/challenge_keys/password_setup/StartEmailSetupAction.kt b/android/apollo/src/main/java/io/muun/apollo/domain/action/challenge_keys/password_setup/StartEmailSetupAction.kt index fc824d70..a67825f9 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/action/challenge_keys/password_setup/StartEmailSetupAction.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/action/challenge_keys/password_setup/StartEmailSetupAction.kt @@ -1,5 +1,6 @@ package io.muun.apollo.domain.action.challenge_keys.password_setup +import io.muun.apollo.data.logging.Crashlytics import io.muun.apollo.data.logging.LoggingContext import io.muun.apollo.data.net.HoustonClient import io.muun.apollo.data.preferences.UserRepository @@ -36,7 +37,7 @@ class StartEmailSetupAction @Inject constructor( user.email = Optional.of(email) user.isEmailVerified = false - LoggingContext.configure(email, user.hid.toString()) + Crashlytics.configure(email, user.hid.toString()) userRepository.store(user) } diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/action/debug/ForceCrashReportAction.kt b/android/apollo/src/main/java/io/muun/apollo/domain/action/debug/ForceCrashReportAction.kt index a98d3fc1..d492dcdd 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/action/debug/ForceCrashReportAction.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/action/debug/ForceCrashReportAction.kt @@ -1,7 +1,7 @@ package io.muun.apollo.domain.action.debug -import com.google.firebase.crashlytics.FirebaseCrashlytics import io.muun.apollo.data.external.Globals +import io.muun.apollo.data.logging.Crashlytics import io.muun.apollo.domain.action.base.BaseAsyncAction1 import rx.Observable import timber.log.Timber @@ -9,15 +9,15 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class ForceCrashReportAction @Inject constructor(): BaseAsyncAction1() { +class ForceCrashReportAction @Inject constructor() : BaseAsyncAction1() { - class ForcedCrashlyticsCall(origin: String): + class ForcedCrashlyticsCall(origin: String) : RuntimeException("Forced logException $origin v${Globals.INSTANCE.versionCode}") - class ForcedTimberErrorCall(origin: String): + class ForcedTimberErrorCall(origin: String) : RuntimeException("Forced Timber.e $origin v${Globals.INSTANCE.versionCode}") - class ForcedBackgroundException(origin: String): + class ForcedBackgroundException(origin: String) : RuntimeException("Forced throw $origin v${Globals.INSTANCE.versionCode}") /** @@ -26,10 +26,8 @@ class ForceCrashReportAction @Inject constructor(): BaseAsyncAction1 = Observable.defer { Timber.e(ForcedTimberErrorCall(origin)) - FirebaseCrashlytics.getInstance().recordException(ForcedCrashlyticsCall(origin)) + Crashlytics.forceReport(ForcedCrashlyticsCall(origin)) throw ForcedBackgroundException(origin) - - Observable.just(null) } } diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/action/session/CreateFirstSessionAction.kt b/android/apollo/src/main/java/io/muun/apollo/domain/action/session/CreateFirstSessionAction.kt index 0241f923..582261eb 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/action/session/CreateFirstSessionAction.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/action/session/CreateFirstSessionAction.kt @@ -1,5 +1,6 @@ package io.muun.apollo.domain.action.session +import io.muun.apollo.data.logging.Crashlytics import io.muun.apollo.data.logging.LoggingContext import io.muun.apollo.data.net.HoustonClient import io.muun.apollo.data.preferences.FirebaseInstalationIdRepository @@ -58,7 +59,7 @@ class CreateFirstSessionAction @Inject constructor( keysRepository.storeBaseMuunPublicKey(it.cosigningPublicKey) keysRepository.storeSwapServerPublicKey(it.swapServerPublicKey) - LoggingContext.configure(null, it.user.hid.toString()) + Crashlytics.configure(null, it.user.hid.toString()) } } } diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/action/session/CreateLoginSessionAction.kt b/android/apollo/src/main/java/io/muun/apollo/domain/action/session/CreateLoginSessionAction.kt index a5c0bfe9..930edca6 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/action/session/CreateLoginSessionAction.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/action/session/CreateLoginSessionAction.kt @@ -1,6 +1,6 @@ package io.muun.apollo.domain.action.session -import io.muun.apollo.data.logging.LoggingContext +import io.muun.apollo.data.logging.Crashlytics import io.muun.apollo.data.net.HoustonClient import io.muun.apollo.data.preferences.FirebaseInstalationIdRepository import io.muun.apollo.domain.action.LogoutActions @@ -40,6 +40,6 @@ class CreateLoginSessionAction @Inject constructor( isRootedDeviceAction.actionNow() ) } - .doOnNext { LoggingContext.configure(email, "NotLoggedYet") } + .doOnNext { Crashlytics.configure(email, "NotLoggedYet") } } } diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/errors/MuunError.kt b/android/apollo/src/main/java/io/muun/apollo/domain/errors/MuunError.kt index 14aea73f..e9f8b6b5 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/errors/MuunError.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/errors/MuunError.kt @@ -1,6 +1,5 @@ package io.muun.apollo.domain.errors -import io.muun.apollo.data.os.secure_storage.SecureStorageProvider import java.io.Serializable open class MuunError: RuntimeException { @@ -23,4 +22,18 @@ open class MuunError: RuntimeException { val mapKeys = metadata.mapKeys { entry -> "${javaClass.canonicalName}.${entry.key}" } return mapKeys as MutableMap } + + /** + * Add extra metadata to this error. + */ + fun addMetadata(key: String, value: Serializable) { + metadata[key] = value + } + + /** + * Add extra metadata to this error. + */ + fun addMetadata(newMetadata: Map) { + metadata.putAll(newMetadata) + } } \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/errors/SecureStorageError.kt b/android/apollo/src/main/java/io/muun/apollo/domain/errors/SecureStorageError.kt index ba36a3b3..742c4553 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/errors/SecureStorageError.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/errors/SecureStorageError.kt @@ -2,13 +2,13 @@ package io.muun.apollo.domain.errors import io.muun.apollo.data.os.secure_storage.SecureStorageProvider -open class SecureStorageError: MuunError { +open class SecureStorageError : MuunError { constructor(debugSnapshot: SecureStorageProvider.DebugSnapshot) { attachDebugSnapshotMetadata(debugSnapshot) } - constructor(t: Throwable, debugSnapshot: SecureStorageProvider.DebugSnapshot): super(t) { + constructor(t: Throwable, debugSnapshot: SecureStorageProvider.DebugSnapshot) : super(t) { attachDebugSnapshotMetadata(debugSnapshot) } diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/model/report/CrashReport.kt b/android/apollo/src/main/java/io/muun/apollo/domain/model/report/CrashReport.kt index 64e38535..e250f3a8 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/model/report/CrashReport.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/model/report/CrashReport.kt @@ -7,6 +7,7 @@ data class CrashReport( val tag: String, val message: String, val error: Throwable, + val originalError: Throwable?, val metadata: MutableMap ) { diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/model/report/CrashReportBuilder.kt b/android/apollo/src/main/java/io/muun/apollo/domain/model/report/CrashReportBuilder.kt index 3c54def6..4c9676ac 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/model/report/CrashReportBuilder.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/model/report/CrashReportBuilder.kt @@ -75,7 +75,7 @@ object CrashReportBuilder { error = summarize(error) // Done! - return CrashReport(tag ?: "Apollo", message, error, metadata) + return CrashReport(tag ?: "Apollo", message, error, origError, metadata) } /** Craft a summarized Throwable */ @@ -105,5 +105,5 @@ object CrashReportBuilder { /** Remove the Stack trace from the message, if present */ private fun removeRedundantStackTrace(timberMessage: String?, error: Throwable) = - (timberMessage ?: "").split(error.javaClass.canonicalName!!, limit=2)[0] + (timberMessage ?: "").split(error.javaClass.canonicalName!!, limit = 2)[0] } diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/selector/LatestOperationSelector.kt b/android/apollo/src/main/java/io/muun/apollo/domain/selector/LatestOperationSelector.kt index 77212d0e..35d3f76b 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/selector/LatestOperationSelector.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/selector/LatestOperationSelector.kt @@ -10,6 +10,7 @@ class LatestOperationSelector @Inject constructor(private val operationDao: Oper fun watch(): Observable> { return operationDao.fetchMaybeLatest() + .distinct { maybeOp -> maybeOp.map { it.hid }.orElse(null) } } fun get(): Optional { diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/utils/Extensions.kt b/android/apollo/src/main/java/io/muun/apollo/domain/utils/Extensions.kt index a6059439..c1b916d1 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/utils/Extensions.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/utils/Extensions.kt @@ -19,6 +19,7 @@ import libwallet.StringList import rx.Observable import java.io.Serializable import java.util.* +import javax.crypto.BadPaddingException import javax.money.Monetary import javax.money.MonetaryException @@ -60,6 +61,13 @@ fun Throwable.isInstanceOrIsCausedByNetworkError() = fun Throwable.isInstanceOrIsCausedBySecureStorageError() = isInstanceOrIsCausedByError() +/** + * Needed as inline reified functions can't be called from Java. + */ +fun Throwable.isCauseByBadPaddingException() = + isInstanceOrIsCausedByError() + + inline fun Throwable.isInstanceOrIsCausedByError() = this is T || isCausedByError() diff --git a/android/apollo/src/test/java/io/muun/apollo/application_lock/ApplicationLockTest.java b/android/apollo/src/test/java/io/muun/apollo/application_lock/ApplicationLockTest.java index f2668294..38f16083 100644 --- a/android/apollo/src/test/java/io/muun/apollo/application_lock/ApplicationLockTest.java +++ b/android/apollo/src/test/java/io/muun/apollo/application_lock/ApplicationLockTest.java @@ -9,6 +9,7 @@ import io.muun.apollo.domain.ApplicationLockManager; import io.muun.apollo.domain.selector.ChallengePublicKeySelector; +import android.content.Context; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -30,6 +31,9 @@ public class ApplicationLockTest extends BaseTest { @Mock private ChallengePublicKeySelector challengePublicKeySel; + @Mock + private Context context; // Not really used in this tests + private ApplicationLockManager lockManager; @Before @@ -42,7 +46,8 @@ public void setUp() { lockManager = new ApplicationLockManager( pinManager, secureStorageProvider, - challengePublicKeySel + challengePublicKeySel, + context ); when(pinManager.verifyPin(CORRECT_PIN)).thenReturn(true); diff --git a/android/apolloui/build.gradle b/android/apolloui/build.gradle index 45b68a90..52cb2346 100644 --- a/android/apolloui/build.gradle +++ b/android/apolloui/build.gradle @@ -36,14 +36,14 @@ check.dependsOn 'lint' /** * Insert quotes around string fields that are injected literally into code, like a C macro. */ -def quote(string) { +static def quote(string) { return "\"" + string + "\"" } /** * Returns a prefix of the current commit hash. */ -def commitTag() { +static def commitTag() { return 'git rev-parse --short HEAD'.execute().text.trim().substring(0, 7) } @@ -51,7 +51,7 @@ def commitTag() { /** * Configure external links for a given product flavor. */ -def configExternalLinks(productFlavor, String host) { +static def configExternalLinks(productFlavor, String host) { String verifyPath = "/link/verify-v2/index.html" String authorizePath = "/link/authorize/index.html" String changePasswdPath = "/link/confirm/index.html" @@ -80,8 +80,8 @@ android { applicationId "io.muun.apollo" minSdkVersion 19 targetSdkVersion 30 - versionCode 903 - versionName "49.3" + versionCode 904 + versionName "49.4" // Needed to make sure these classes are available in the main DEX file for API 19 // See: https://spin.atomicobject.com/2018/07/16/support-kitkat-multidex/ diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/model/UiOperation.kt b/android/apolloui/src/main/java/io/muun/apollo/presentation/model/UiOperation.kt index c6419d58..093c667d 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/model/UiOperation.kt +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/model/UiOperation.kt @@ -187,13 +187,13 @@ abstract class UiOperation( get() = operation.description /** - * Get the receiving LN node data, in a clickable link to an explorer. + * Get the receiving LN node data. */ - val swapReceiverLink: CharSequence + val swapReceiverNodeData: String get() = if (operation.swap == null) { "" } else { - linkBuilder.lightningNodeLink(operation.swap!!.receiver) + operation.swap!!.receiver.formattedDestination } /** diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/new_operation/NewOperationActivity.kt b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/new_operation/NewOperationActivity.kt index bb2d32a6..9f00fd89 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/new_operation/NewOperationActivity.kt +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/new_operation/NewOperationActivity.kt @@ -221,7 +221,11 @@ class NewOperationActivity : SingleFragmentActivity(), Ne val newOpOrigin = getOrigin(intent) val uri = getValidIntentUri(intent) - val operationUri = try { OperationUri.fromString(uri) } catch (e: Exception) { null } + val operationUri = try { + OperationUri.fromString(uri) + } catch (e: Exception) { + null + } if (operationUri != null) { when { @@ -639,7 +643,7 @@ class NewOperationActivity : SingleFragmentActivity(), Ne } - private fun getFormattedDestinationData(receiver: SubmarineSwapReceiver): CharSequence { + private fun getFormattedDestinationData(receiver: SubmarineSwapReceiver): CharSequence { val publicKeyText: CharSequence = Html.fromHtml( getString( R.string.new_operation_receiving_node_public_key, @@ -747,7 +751,7 @@ class NewOperationActivity : SingleFragmentActivity(), Ne override fun onTickSeconds(remainingSeconds: Long) { val context = this@NewOperationActivity - val timeText= NewOperationInvoiceFormatter(context).formatSeconds(remainingSeconds) + val timeText = NewOperationInvoiceFormatter(context).formatSeconds(remainingSeconds) val prefixText = getString(R.string.new_operation_invoice_exp_prefix) val text = TextUtils.concat(prefixText, " ", timeText) diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/new_operation/NewOperationPresenter.kt b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/new_operation/NewOperationPresenter.kt index 16c133d8..c963de3f 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/new_operation/NewOperationPresenter.kt +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/new_operation/NewOperationPresenter.kt @@ -26,7 +26,7 @@ import io.muun.apollo.presentation.ui.base.BasePresenter import io.muun.apollo.presentation.ui.base.di.PerActivity import io.muun.apollo.presentation.ui.fragments.manual_fee.ManualFeeParentPresenter import io.muun.apollo.presentation.ui.fragments.new_op_error.NewOperationErrorParentPresenter -import io.muun.apollo.presentation.ui.fragments.new_op_error.NewOperationErrorParentPresenter.* +import io.muun.apollo.presentation.ui.fragments.new_op_error.NewOperationErrorParentPresenter.ErrorMetadata import io.muun.apollo.presentation.ui.fragments.recommended_fee.RecommendedFeeParentPresenter import io.muun.common.Rules import io.muun.common.utils.Preconditions diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/operation_detail/OperationDetailActivity.java b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/operation_detail/OperationDetailActivity.java index 059bb4a4..3c27af95 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/operation_detail/OperationDetailActivity.java +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/operation_detail/OperationDetailActivity.java @@ -115,7 +115,7 @@ protected void initializeUi() { } @Override - public void setOperation(UiOperation operation) { + public void setOperation(final UiOperation operation) { final Context context = getViewContext(); // Header: @@ -198,7 +198,7 @@ public void setOperation(UiOperation operation) { } } - private void setSwapOperation(UiOperation operation) { + private void setSwapOperation(final UiOperation operation) { // Sections involved: swapSection.setVisibility(View.VISIBLE); feeItem.setVisibility(View.VISIBLE); @@ -216,7 +216,11 @@ private void setSwapOperation(UiOperation operation) { swapInvoiceItem.setVisibility(View.VISIBLE); swapReceiverPubkeyItem.setVisibility(View.VISIBLE); - swapReceiverPubkeyItem.setDescription(operation.getSwapReceiverLink()); + swapReceiverPubkeyItem.setDescription(operation.getSwapReceiverNodeData()); + final String swapReceiverNodeData = operation.getSwapReceiverNodeData(); + swapReceiverPubkeyItem.setOnIconClickListener(view -> + onCopyReceivingNodeToClipboard(swapReceiverNodeData) + ); // We no longer show fundingTxId to hide the submarine swap impl detail of our ln payments @@ -226,7 +230,7 @@ private void setSwapOperation(UiOperation operation) { swapPreimageItem.setOnIconClickListener(view -> onCopyPreimageToClipboard(preimage)); } - private void setNormalOperation(Context context, UiOperation operation) { + private void setNormalOperation(final Context context, final UiOperation operation) { // Sections involved: normalSection.setVisibility(View.VISIBLE); @@ -298,7 +302,7 @@ private void setIncomingSwapOperation(final UiOperation operation) { } } - private CharSequence getStatusDescription(UiOperation operation) { + private CharSequence getStatusDescription(final UiOperation operation) { switch (operation.getOperationStatus()) { @@ -318,7 +322,7 @@ private CharSequence getStatusDescription(UiOperation operation) { } @NonNull - private CharSequence getPendingStatusDescription(UiOperation operation) { + private CharSequence getPendingStatusDescription(final UiOperation operation) { if (operation.is0ConfSwap()) { return getString(R.string.operation_swap_pending_0conf_desc); @@ -331,7 +335,7 @@ private CharSequence getPendingStatusDescription(UiOperation operation) { ); } - private Unit onWhyThisClick(String linkId) { + private Unit onWhyThisClick(final String linkId) { final TitleAndDescriptionDrawer dialog = new TitleAndDescriptionDrawer(); dialog.setTitle(R.string.operation_swap_pending_confirmations_explanation_title); dialog.setDescription(getString(R.string.operation_swap_pending_explanation_desc)); @@ -341,42 +345,47 @@ private Unit onWhyThisClick(String linkId) { return null; } - private void onCopyInvoiceToClipboard(String invoice) { + private void onCopyInvoiceToClipboard(final String invoice) { presenter.copyLnInvoiceToClipboard(invoice); showTextToast(getString(R.string.operation_detail_invoice_copied)); } - private void onCopyPreimageToClipboard(String preimage) { + private void onCopyPreimageToClipboard(final String preimage) { presenter.copySwapPreimageToClipboard(preimage); showTextToast(getString(R.string.operation_detail_preimage_copied)); } - private void onCopyPaymentHashToClipboard(String paymentHash) { + private void onCopyPaymentHashToClipboard(final String paymentHash) { presenter.copySwapPreimageToClipboard(paymentHash); showTextToast(getString(R.string.operation_detail_preimage_copied)); } - private void onCopyTransactionIdToClipboard(String transactionId) { + private void onCopyTransactionIdToClipboard(final String transactionId) { presenter.copyTransactionIdToClipboard(transactionId); showTextToast(getString(R.string.operation_detail_txid_copied)); } - private void onShareTransactionId(String transactionId) { + private void onShareTransactionId(final String transactionId) { presenter.shareTransactionId(transactionId); } - private void onCopyAmountToClipboard(String amount) { + private void onCopyAmountToClipboard(final String amount) { presenter.copyAmountToClipboard(amount); showTextToast(getString(R.string.operation_detail_amount_copied)); } - private void onCopyNetworkFeeToClipboard(String fee) { + private void onCopyNetworkFeeToClipboard(final String fee) { presenter.copyNetworkFeeToClipboard(fee); showTextToast(getString(R.string.operation_detail_fee_copied)); } - private void onCopyAddressToClipboard(String address) { + private void onCopyAddressToClipboard(final String address) { presenter.copyNetworkFeeToClipboard(address); showTextToast(getString(R.string.operation_detail_address_copied)); } + + private void onCopyReceivingNodeToClipboard(final String receivingNode) { + presenter.copyReceivingNodeToClipboard(receivingNode); + showTextToast(getString(R.string.operation_detail_node_copied)); + } } diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/operation_detail/OperationDetailPresenter.java b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/operation_detail/OperationDetailPresenter.java index cc19a9aa..eaff5908 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/operation_detail/OperationDetailPresenter.java +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/operation_detail/OperationDetailPresenter.java @@ -104,6 +104,13 @@ public void copyTransactionIdToClipboard(String transactionId) { clipboardManager.copy("Transaction ID", transactionId); } + /** + * Copy receiving node to clipboard. + */ + public void copyReceivingNodeToClipboard(String node) { + clipboardManager.copy("Receiving Node", node); + } + /** * Copy fee amount to the clipboard. */ diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/utils/LinkBuilder.java b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/utils/LinkBuilder.java index 3f096765..8878df29 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/utils/LinkBuilder.java +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/utils/LinkBuilder.java @@ -57,13 +57,6 @@ public RichText addressLink(String address) { return createLink(address, urlRoot + address, "block_explorer_address"); } - /** - * Create a RichText containing a clickable link to a lightning node in an explorer. - */ - public RichText lightningNodeLink(SubmarineSwapReceiver receiver) { - return lightningNodeLink(receiver, getLnLinkText(receiver)); - } - /** * Create a RichText containing a clickable link to a lightning node in an explorer. */ @@ -100,7 +93,6 @@ private RichText createLink(String text, String url, String trackingName) { return new RichText(text).setLink(() -> { ExtensionsKt.openInBrowser(context, url); analytics.report(new AnalyticsEvent.E_OPEN_WEB(trackingName, url)); - }); } diff --git a/android/apolloui/src/main/res/layout/operation_detail_activity.xml b/android/apolloui/src/main/res/layout/operation_detail_activity.xml index bf3996bf..0d30a9ae 100644 --- a/android/apolloui/src/main/res/layout/operation_detail_activity.xml +++ b/android/apolloui/src/main/res/layout/operation_detail_activity.xml @@ -172,7 +172,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="gone" - muun:title="@string/operation_detail_receiving_pubkey" /> + muun:title="@string/operation_detail_receiving_pubkey" + muun:icon="@drawable/ic_copy" /> diff --git a/android/apolloui/src/main/res/values-es/strings.xml b/android/apolloui/src/main/res/values-es/strings.xml index d99d91e2..3b468c81 100644 --- a/android/apolloui/src/main/res/values-es/strings.xml +++ b/android/apolloui/src/main/res/values-es/strings.xml @@ -305,6 +305,7 @@ Monto copiado al portapapeles Comisión de la red copiada al portapapeles Dirección copiada al portapapeles + Nodo destinatario copiado al portapapeles Pagos diff --git a/android/apolloui/src/main/res/values/strings.xml b/android/apolloui/src/main/res/values/strings.xml index 82246966..9f5d4229 100644 --- a/android/apolloui/src/main/res/values/strings.xml +++ b/android/apolloui/src/main/res/values/strings.xml @@ -296,6 +296,7 @@ Amount copied to clipboard Network fee copied to clipboard Address copied to clipboard + Receiving node copied to clipboard Payments diff --git a/libwallet/go.mod b/libwallet/go.mod index 16ba4d4e..79239808 100644 --- a/libwallet/go.mod +++ b/libwallet/go.mod @@ -16,12 +16,14 @@ require ( github.com/pdfcpu/pdfcpu v0.3.11 github.com/pkg/errors v0.9.1 github.com/shopspring/decimal v1.2.0 - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 - golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08 // indirect + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 + golang.org/x/mobile v0.0.0-20220414153400-ce6a79cf6a13 // indirect + golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect + golang.org/x/tools v0.1.10 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/protobuf v1.25.0 gopkg.in/gormigrate.v1 v1.6.0 ) // Fork that includes the -cache flag for quicker builds -replace golang.org/x/mobile => github.com/champo/mobile v0.0.0-20210412201235-a784c99e2a62 +replace golang.org/x/mobile => github.com/champo/mobile v0.0.0-20220505154254-6a5f99bae305 diff --git a/libwallet/go.sum b/libwallet/go.sum index be50686f..45b42254 100644 --- a/libwallet/go.sum +++ b/libwallet/go.sum @@ -82,6 +82,10 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/champo/mobile v0.0.0-20210412201235-a784c99e2a62 h1:6CturfaAc1IXi5udu7IMLekMFx6uB81XE7w9AGOqpyc= github.com/champo/mobile v0.0.0-20210412201235-a784c99e2a62/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= +github.com/champo/mobile v0.0.0-20220503145505-51a7737dc434 h1:7KHSIWZ0Lc70SI4dTuF/8ZDBZWUKJfewCi3FpYHaqwk= +github.com/champo/mobile v0.0.0-20220503145505-51a7737dc434/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= +github.com/champo/mobile v0.0.0-20220505154254-6a5f99bae305 h1:YgqwiwLKFqs1/d9BNXUduBA7YZ+uzfrjsKPzXBYAHsw= +github.com/champo/mobile v0.0.0-20220505154254-6a5f99bae305/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY= @@ -322,6 +326,8 @@ github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S0 github.com/urfave/cli v1.18.0 h1:m9MfmZWX7bwr9kUcs/Asr95j0IVXzGNNc+/5ku2m26Q= github.com/urfave/cli v1.18.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50 h1:ASw9n1EHMftwnP3Az4XW6e308+gNsrHzmdhd0Olz9Hs= go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= @@ -340,6 +346,8 @@ golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= @@ -359,6 +367,8 @@ golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd h1:ePuNC7PZ6O5BzgPn9bZayER golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -378,8 +388,13 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -408,14 +423,25 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750 h1:ZBu6861dZq7xBnG1bn5SRU0vA8nx42at4+kP07FMTog= golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -430,6 +456,9 @@ golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk= golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/libwallet/lnurl/lnurl.go b/libwallet/lnurl/lnurl.go index 5144ba73..e935141a 100644 --- a/libwallet/lnurl/lnurl.go +++ b/libwallet/lnurl/lnurl.go @@ -135,7 +135,10 @@ func Withdraw(qr string, createInvoiceFunc CreateInvoiceFunction, allowUnsafe bo // add request id to enhance error reports and troubleshooting with LNURL service providers requestId := uuid.New().String() - qrUrl.Query().Add("requestId", requestId) + // Mutate the query params so we keep those the original URL had + query := qrUrl.Query() + query.Add("requestId", requestId) + qrUrl.RawQuery = query.Encode() notifier.SetRequestId(requestId) // start withdraw with service @@ -188,11 +191,12 @@ func Withdraw(qr string, createInvoiceFunc CreateInvoiceFunction, allowUnsafe bo notifier.Status(StatusInvoiceCreated) // Mutate the query params so we keep those the original URL had - query := callbackURL.Query() - query.Add("k1", wr.K1) - query.Add("pr", invoice) + callbackQuery := callbackURL.Query() + callbackQuery.Add("requestId", requestId) + callbackQuery.Add("k1", wr.K1) + callbackQuery.Add("pr", invoice) + callbackURL.RawQuery = callbackQuery.Encode() - callbackURL.RawQuery = query.Encode() // Confirm withdraw with service // Use an httpClient with a higher timeout for reliability with slow LNURL services withdrawClient := http.Client{Timeout: 3 * time.Minute} @@ -328,10 +332,10 @@ func decode(qr string) (*url.URL, error) { } if len(uri.Opaque) > 0 { - // This catches scheme:LNURL + // This catches lightning:LNURL toParse = uri.Opaque } else { - // And this catches scheme://LNURL which is needed for iOS + // And this catches lightning://LNURL which is needed for iOS toParse = uri.Host } } diff --git a/libwallet/lnurl/lnurl_test.go b/libwallet/lnurl/lnurl_test.go index 0b490708..ddd082f6 100644 --- a/libwallet/lnurl/lnurl_test.go +++ b/libwallet/lnurl/lnurl_test.go @@ -388,6 +388,9 @@ func TestNoRouteCheck(t *testing.T) { func TestExtraQueryParams(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/withdraw/", func(w http.ResponseWriter, r *http.Request) { + if r.URL.Query().Get("requestId") == "" { + t.Fatalf("Expected non-empty requestId in query params. Got URL: %v", r.URL.String()) + } json.NewEncoder(w).Encode(&WithdrawResponse{ K1: "foobar", Callback: "http://" + r.Host + "/withdraw/complete?foo=bar", @@ -397,6 +400,9 @@ func TestExtraQueryParams(t *testing.T) { }) }) mux.HandleFunc("/withdraw/complete", func(w http.ResponseWriter, r *http.Request) { + if r.URL.Query().Get("requestId") == "" { + t.Fatalf("Expected non-empty requestId in query params. Got URL: %v", r.URL.String()) + } if r.URL.Query().Get("foo") != "bar" { t.Fatalf("Expected foo=bar in query params. Got URL: %v", r.URL.String()) } diff --git a/linters/checkstyle/linter.py b/linters/checkstyle/linter.py index ac529f8b..d30df14c 100755 --- a/linters/checkstyle/linter.py +++ b/linters/checkstyle/linter.py @@ -1,11 +1,11 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 import subprocess import sys def get_git_root(): - return subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).\ + return subprocess.check_output(["git", "rev-parse", "--show-toplevel"], text = True).\ strip() @@ -23,7 +23,7 @@ def run_checkstyle(jar_path, checks_path, suppressions_path, xpath_suppressions_ command.extend(files) process = subprocess.Popen(command, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + stderr=subprocess.PIPE, text=True) status = process.wait() if status == 0: @@ -50,7 +50,7 @@ def lint(files): ) for result in results: - print result + print(result) return len(results) == 0