mirror of
https://github.com/mollyim/monero-wallet-sdk.git
synced 2025-01-26 23:06:58 -05:00
lib: use isolated process
This commit is contained in:
parent
70231790ee
commit
f0c425dca9
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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,28 +58,37 @@ class WalletNative private constructor(
|
||||
|
||||
private val handle: Long = nativeCreate(networkId)
|
||||
|
||||
private fun tryWriteState(): Boolean {
|
||||
private suspend fun tryWriteState(): Boolean {
|
||||
requireNotNull(storageAdapter)
|
||||
val pipe = ParcelFileDescriptor.createReliablePipe()
|
||||
return pipe[1].use { writeSide ->
|
||||
val storageIsReady = storageAdapter.writeAsync(pipe[0])
|
||||
if (storageIsReady) {
|
||||
val result = nativeSave(handle, writeSide.fd)
|
||||
if (!result) {
|
||||
logger.e("Wallet data serialization failed")
|
||||
return withContext(ioDispatcher) {
|
||||
val pipe = ParcelFileDescriptor.createReliablePipe()
|
||||
pipe[1].use { writeSide ->
|
||||
val storageIsReady = storageAdapter.writeAsync(pipe[0])
|
||||
if (storageIsReady) {
|
||||
val result = nativeSave(handle, writeSide.fd)
|
||||
if (!result) {
|
||||
logger.e("Wallet data serialization failed")
|
||||
}
|
||||
result
|
||||
} else {
|
||||
logger.i("Unable to save wallet data because WalletDataStore is unset")
|
||||
false
|
||||
}
|
||||
result
|
||||
} else false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun readState() {
|
||||
private suspend fun readState() {
|
||||
requireNotNull(storageAdapter)
|
||||
val pipe = ParcelFileDescriptor.createReliablePipe()
|
||||
return pipe[0].use { readSide ->
|
||||
storageAdapter.readAsync(pipe[1])
|
||||
val result = nativeLoad(handle, readSide.fd)
|
||||
check(result) { "Wallet data deserialization failed" }
|
||||
return withContext(ioDispatcher) {
|
||||
val pipe = ParcelFileDescriptor.createReliablePipe()
|
||||
pipe[0].use { readSide ->
|
||||
pipe[1].use { writeSide ->
|
||||
storageAdapter.readAsync(writeSide)
|
||||
}
|
||||
val result = nativeLoad(handle, readSide.fd)
|
||||
check(result) { "Wallet data deserialization failed" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user