lib: use isolated process

This commit is contained in:
Oscar Mira 2023-05-27 01:34:09 +02:00
parent 70231790ee
commit f0c425dca9
7 changed files with 65 additions and 52 deletions

View File

@ -8,7 +8,8 @@
<application android:usesCleartextTraffic="true">
<service
android:name=".WalletService"
android:exported="false" />
<!-- android:isolatedProcess="true" />-->
android:process=":wallet_service"
android:isolatedProcess="true"
/>
</application>
</manifest>

View File

@ -1,13 +1,15 @@
package im.molly.monero;
import im.molly.monero.IRemoteNodeClient;
import im.molly.monero.IStorageAdapter;
import im.molly.monero.IWalletServiceCallbacks;
import im.molly.monero.IWalletServiceListener;
import im.molly.monero.SecretKey;
import im.molly.monero.WalletConfig;
interface IWalletService {
oneway void createWallet(in WalletConfig config, in IWalletServiceCallbacks callback);
oneway void restoreWallet(in WalletConfig config, in IWalletServiceCallbacks callback, in SecretKey spendSecretKey, long restorePoint);
oneway void openWallet(in WalletConfig config, in IWalletServiceCallbacks callback);
oneway void createWallet(in WalletConfig config, in IStorageAdapter storage, in IRemoteNodeClient client, in IWalletServiceCallbacks callback);
oneway void restoreWallet(in WalletConfig config, in IStorageAdapter storage, in IRemoteNodeClient client, in IWalletServiceCallbacks callback, in SecretKey spendSecretKey, long restorePoint);
oneway void openWallet(in WalletConfig config, in IStorageAdapter storage, in IRemoteNodeClient client, in IWalletServiceCallbacks callback);
void setListener(in IWalletServiceListener listener);
}

View File

@ -10,22 +10,19 @@ import kotlinx.coroutines.sync.withLock
internal class StorageAdapter(var dataStore: WalletDataStore?) : IStorageAdapter.Stub() {
private val logger = loggerFor<StorageAdapter>()
private val storageScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val ioStorageScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val mutex = Mutex()
override fun writeAsync(pfd: ParcelFileDescriptor?): Boolean {
requireNotNull(pfd)
val inputStream = ParcelFileDescriptor.AutoCloseInputStream(pfd)
val localDataStore = dataStore
if (localDataStore == null) {
logger.i("Unable to save wallet data because WalletDataStore is unset")
inputStream.close()
pfd.close()
return false
}
storageScope.launch {
val inputStream = ParcelFileDescriptor.AutoCloseInputStream(pfd)
ioStorageScope.launch {
mutex.withLock {
localDataStore.write { output ->
inputStream.copyTo(output)
@ -37,13 +34,13 @@ internal class StorageAdapter(var dataStore: WalletDataStore?) : IStorageAdapter
override fun readAsync(pfd: ParcelFileDescriptor?) {
requireNotNull(pfd)
val outputStream = ParcelFileDescriptor.AutoCloseOutputStream(pfd)
val localDataStore = dataStore
if (localDataStore == null) {
outputStream.close()
pfd.close()
throw IllegalArgumentException("WalletDataStore cannot be null")
}
storageScope.launch {
val outputStream = ParcelFileDescriptor.AutoCloseOutputStream(pfd)
ioStorageScope.launch {
mutex.withLock {
localDataStore.read().use { input ->
input.copyTo(outputStream)

View File

@ -6,6 +6,4 @@ import kotlinx.parcelize.Parcelize
@Parcelize
internal data class WalletConfig(
val networkId: Int,
val storageAdapter: IStorageAdapter.Stub?,
val remoteNodeClient: IRemoteNodeClient.Stub?,
) : Parcelable

View File

@ -19,7 +19,7 @@ class WalletNative private constructor(
companion object {
// TODO: Find better name because this is a local synchronization wallet, not a full node wallet
fun fullNode(
suspend fun fullNode(
networkId: Int,
storageAdapter: IStorageAdapter? = null,
remoteNodeClient: IRemoteNodeClient? = null,
@ -41,6 +41,7 @@ class WalletNative private constructor(
nativeRestoreAccount(handle, secretSpendKey.bytes, restorePointOrNow)
tryWriteState()
}
else -> {
require(restorePoint == null)
readState()
@ -57,10 +58,11 @@ class WalletNative private constructor(
private val handle: Long = nativeCreate(networkId)
private fun tryWriteState(): Boolean {
private suspend fun tryWriteState(): Boolean {
requireNotNull(storageAdapter)
return withContext(ioDispatcher) {
val pipe = ParcelFileDescriptor.createReliablePipe()
return pipe[1].use { writeSide ->
pipe[1].use { writeSide ->
val storageIsReady = storageAdapter.writeAsync(pipe[0])
if (storageIsReady) {
val result = nativeSave(handle, writeSide.fd)
@ -68,19 +70,27 @@ class WalletNative private constructor(
logger.e("Wallet data serialization failed")
}
result
} else false
} else {
logger.i("Unable to save wallet data because WalletDataStore is unset")
false
}
}
}
}
private fun readState() {
private suspend fun readState() {
requireNotNull(storageAdapter)
return withContext(ioDispatcher) {
val pipe = ParcelFileDescriptor.createReliablePipe()
return pipe[0].use { readSide ->
storageAdapter.readAsync(pipe[1])
pipe[0].use { readSide ->
pipe[1].use { writeSide ->
storageAdapter.readAsync(writeSide)
}
val result = nativeLoad(handle, readSide.fd)
check(result) { "Wallet data deserialization failed" }
}
}
}
override fun getPrimaryAccountAddress() = nativeGetPrimaryAccountAddress(handle)

View File

@ -6,7 +6,6 @@ import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import kotlinx.coroutines.*
import java.time.Instant
// TODO: Rename to SandboxedWalletProvider and extract interface, add InProcessWalletProvider
class WalletProvider private constructor(
@ -54,10 +53,11 @@ class WalletProvider private constructor(
dataStore: WalletDataStore? = null,
client: RemoteNodeClient? = null,
): MoneroWallet {
require(client == null || client.network == network)
val storageAdapter = StorageAdapter(dataStore)
val wallet = suspendCancellableCoroutine { continuation ->
service.createWallet(
buildConfig(network, StorageAdapter(dataStore), client),
buildConfig(network), storageAdapter, client,
WalletResultCallback(continuation),
)
}
@ -71,10 +71,11 @@ class WalletProvider private constructor(
secretSpendKey: SecretKey,
restorePoint: RestorePoint,
): MoneroWallet {
require(client == null || client.network == network)
val storageAdapter = StorageAdapter(dataStore)
val wallet = suspendCancellableCoroutine { continuation ->
service.restoreWallet(
buildConfig(network, StorageAdapter(dataStore), client),
buildConfig(network), storageAdapter, client,
WalletResultCallback(continuation),
secretSpendKey,
restorePoint.heightOrTimestamp,
@ -88,23 +89,19 @@ class WalletProvider private constructor(
dataStore: WalletDataStore,
client: RemoteNodeClient? = null,
): MoneroWallet {
require(client == null || client.network == network)
val storageAdapter = StorageAdapter(dataStore)
val wallet = suspendCancellableCoroutine { continuation ->
service.openWallet(
buildConfig(network, storageAdapter, client),
buildConfig(network), storageAdapter, client,
WalletResultCallback(continuation),
)
}
return MoneroWallet(wallet, storageAdapter, client)
}
private fun buildConfig(
network: MoneroNetwork,
storageAdapter: StorageAdapter,
remoteNodeClient: RemoteNodeClient?,
): WalletConfig {
require(remoteNodeClient == null || remoteNodeClient.network == network)
return WalletConfig(network.id, storageAdapter, remoteNodeClient)
private fun buildConfig(network: MoneroNetwork): WalletConfig {
return WalletConfig(network.id)
}
fun disconnect() {

View File

@ -42,12 +42,14 @@ internal class WalletServiceImpl(
override fun createWallet(
config: WalletConfig?,
storage: IStorageAdapter?,
client: IRemoteNodeClient?,
callback: IWalletServiceCallbacks?,
) {
serviceScope.launch {
val secretSpendKey = randomSecretKey()
val wallet = secretSpendKey.use { secret ->
createOrRestoreWallet(config, secret)
createOrRestoreWallet(config, storage, client, secret)
}
callback?.onWalletResult(wallet)
}
@ -55,13 +57,15 @@ internal class WalletServiceImpl(
override fun restoreWallet(
config: WalletConfig?,
storage: IStorageAdapter?,
client: IRemoteNodeClient?,
callback: IWalletServiceCallbacks?,
secretSpendKey: SecretKey?,
restorePoint: Long,
) {
serviceScope.launch {
val wallet = secretSpendKey.use { secret ->
createOrRestoreWallet(config, secret, restorePoint)
createOrRestoreWallet(config, storage, client, secret, restorePoint)
}
callback?.onWalletResult(wallet)
}
@ -69,22 +73,26 @@ internal class WalletServiceImpl(
override fun openWallet(
config: WalletConfig?,
storage: IStorageAdapter?,
client: IRemoteNodeClient?,
callback: IWalletServiceCallbacks?,
) {
requireNotNull(config)
serviceScope.launch {
val wallet = WalletNative.fullNode(
networkId = config.networkId,
storageAdapter = config.storageAdapter,
remoteNodeClient = config.remoteNodeClient,
storageAdapter = storage,
remoteNodeClient = client,
coroutineContext = serviceScope.coroutineContext,
)
callback?.onWalletResult(wallet)
}
}
private fun createOrRestoreWallet(
private suspend fun createOrRestoreWallet(
config: WalletConfig?,
storage: IStorageAdapter?,
client: IRemoteNodeClient?,
secretSpendKey: SecretKey?,
restorePoint: Long? = null,
): IWallet {
@ -92,8 +100,8 @@ internal class WalletServiceImpl(
requireNotNull(secretSpendKey)
return WalletNative.fullNode(
networkId = config.networkId,
storageAdapter = config.storageAdapter,
remoteNodeClient = config.remoteNodeClient,
storageAdapter = storage,
remoteNodeClient = client,
secretSpendKey = secretSpendKey,
restorePoint = restorePoint,
coroutineContext = serviceScope.coroutineContext,