mirror of
https://github.com/mollyim/monero-wallet-sdk.git
synced 2025-02-10 11:48:53 -05:00
lib: use isolated process
This commit is contained in:
parent
70231790ee
commit
f0c425dca9
@ -8,7 +8,8 @@
|
|||||||
<application android:usesCleartextTraffic="true">
|
<application android:usesCleartextTraffic="true">
|
||||||
<service
|
<service
|
||||||
android:name=".WalletService"
|
android:name=".WalletService"
|
||||||
android:exported="false" />
|
android:process=":wallet_service"
|
||||||
<!-- android:isolatedProcess="true" />-->
|
android:isolatedProcess="true"
|
||||||
|
/>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
package im.molly.monero;
|
package im.molly.monero;
|
||||||
|
|
||||||
|
import im.molly.monero.IRemoteNodeClient;
|
||||||
|
import im.molly.monero.IStorageAdapter;
|
||||||
import im.molly.monero.IWalletServiceCallbacks;
|
import im.molly.monero.IWalletServiceCallbacks;
|
||||||
import im.molly.monero.IWalletServiceListener;
|
import im.molly.monero.IWalletServiceListener;
|
||||||
import im.molly.monero.SecretKey;
|
import im.molly.monero.SecretKey;
|
||||||
import im.molly.monero.WalletConfig;
|
import im.molly.monero.WalletConfig;
|
||||||
|
|
||||||
interface IWalletService {
|
interface IWalletService {
|
||||||
oneway void createWallet(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 IWalletServiceCallbacks callback, in SecretKey spendSecretKey, long restorePoint);
|
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 IWalletServiceCallbacks callback);
|
oneway void openWallet(in WalletConfig config, in IStorageAdapter storage, in IRemoteNodeClient client, in IWalletServiceCallbacks callback);
|
||||||
void setListener(in IWalletServiceListener listener);
|
void setListener(in IWalletServiceListener listener);
|
||||||
}
|
}
|
||||||
|
@ -10,22 +10,19 @@ import kotlinx.coroutines.sync.withLock
|
|||||||
|
|
||||||
internal class StorageAdapter(var dataStore: WalletDataStore?) : IStorageAdapter.Stub() {
|
internal class StorageAdapter(var dataStore: WalletDataStore?) : IStorageAdapter.Stub() {
|
||||||
|
|
||||||
private val logger = loggerFor<StorageAdapter>()
|
private val ioStorageScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
|
|
||||||
private val storageScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
|
||||||
|
|
||||||
private val mutex = Mutex()
|
private val mutex = Mutex()
|
||||||
|
|
||||||
override fun writeAsync(pfd: ParcelFileDescriptor?): Boolean {
|
override fun writeAsync(pfd: ParcelFileDescriptor?): Boolean {
|
||||||
requireNotNull(pfd)
|
requireNotNull(pfd)
|
||||||
val inputStream = ParcelFileDescriptor.AutoCloseInputStream(pfd)
|
|
||||||
val localDataStore = dataStore
|
val localDataStore = dataStore
|
||||||
if (localDataStore == null) {
|
if (localDataStore == null) {
|
||||||
logger.i("Unable to save wallet data because WalletDataStore is unset")
|
pfd.close()
|
||||||
inputStream.close()
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
storageScope.launch {
|
val inputStream = ParcelFileDescriptor.AutoCloseInputStream(pfd)
|
||||||
|
ioStorageScope.launch {
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
localDataStore.write { output ->
|
localDataStore.write { output ->
|
||||||
inputStream.copyTo(output)
|
inputStream.copyTo(output)
|
||||||
@ -37,13 +34,13 @@ internal class StorageAdapter(var dataStore: WalletDataStore?) : IStorageAdapter
|
|||||||
|
|
||||||
override fun readAsync(pfd: ParcelFileDescriptor?) {
|
override fun readAsync(pfd: ParcelFileDescriptor?) {
|
||||||
requireNotNull(pfd)
|
requireNotNull(pfd)
|
||||||
val outputStream = ParcelFileDescriptor.AutoCloseOutputStream(pfd)
|
|
||||||
val localDataStore = dataStore
|
val localDataStore = dataStore
|
||||||
if (localDataStore == null) {
|
if (localDataStore == null) {
|
||||||
outputStream.close()
|
pfd.close()
|
||||||
throw IllegalArgumentException("WalletDataStore cannot be null")
|
throw IllegalArgumentException("WalletDataStore cannot be null")
|
||||||
}
|
}
|
||||||
storageScope.launch {
|
val outputStream = ParcelFileDescriptor.AutoCloseOutputStream(pfd)
|
||||||
|
ioStorageScope.launch {
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
localDataStore.read().use { input ->
|
localDataStore.read().use { input ->
|
||||||
input.copyTo(outputStream)
|
input.copyTo(outputStream)
|
||||||
|
@ -6,6 +6,4 @@ import kotlinx.parcelize.Parcelize
|
|||||||
@Parcelize
|
@Parcelize
|
||||||
internal data class WalletConfig(
|
internal data class WalletConfig(
|
||||||
val networkId: Int,
|
val networkId: Int,
|
||||||
val storageAdapter: IStorageAdapter.Stub?,
|
|
||||||
val remoteNodeClient: IRemoteNodeClient.Stub?,
|
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
@ -19,7 +19,7 @@ class WalletNative private constructor(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// TODO: Find better name because this is a local synchronization wallet, not a full node wallet
|
// TODO: Find better name because this is a local synchronization wallet, not a full node wallet
|
||||||
fun fullNode(
|
suspend fun fullNode(
|
||||||
networkId: Int,
|
networkId: Int,
|
||||||
storageAdapter: IStorageAdapter? = null,
|
storageAdapter: IStorageAdapter? = null,
|
||||||
remoteNodeClient: IRemoteNodeClient? = null,
|
remoteNodeClient: IRemoteNodeClient? = null,
|
||||||
@ -41,6 +41,7 @@ class WalletNative private constructor(
|
|||||||
nativeRestoreAccount(handle, secretSpendKey.bytes, restorePointOrNow)
|
nativeRestoreAccount(handle, secretSpendKey.bytes, restorePointOrNow)
|
||||||
tryWriteState()
|
tryWriteState()
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
require(restorePoint == null)
|
require(restorePoint == null)
|
||||||
readState()
|
readState()
|
||||||
@ -57,10 +58,11 @@ class WalletNative private constructor(
|
|||||||
|
|
||||||
private val handle: Long = nativeCreate(networkId)
|
private val handle: Long = nativeCreate(networkId)
|
||||||
|
|
||||||
private fun tryWriteState(): Boolean {
|
private suspend fun tryWriteState(): Boolean {
|
||||||
requireNotNull(storageAdapter)
|
requireNotNull(storageAdapter)
|
||||||
|
return withContext(ioDispatcher) {
|
||||||
val pipe = ParcelFileDescriptor.createReliablePipe()
|
val pipe = ParcelFileDescriptor.createReliablePipe()
|
||||||
return pipe[1].use { writeSide ->
|
pipe[1].use { writeSide ->
|
||||||
val storageIsReady = storageAdapter.writeAsync(pipe[0])
|
val storageIsReady = storageAdapter.writeAsync(pipe[0])
|
||||||
if (storageIsReady) {
|
if (storageIsReady) {
|
||||||
val result = nativeSave(handle, writeSide.fd)
|
val result = nativeSave(handle, writeSide.fd)
|
||||||
@ -68,19 +70,27 @@ class WalletNative private constructor(
|
|||||||
logger.e("Wallet data serialization failed")
|
logger.e("Wallet data serialization failed")
|
||||||
}
|
}
|
||||||
result
|
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)
|
requireNotNull(storageAdapter)
|
||||||
|
return withContext(ioDispatcher) {
|
||||||
val pipe = ParcelFileDescriptor.createReliablePipe()
|
val pipe = ParcelFileDescriptor.createReliablePipe()
|
||||||
return pipe[0].use { readSide ->
|
pipe[0].use { readSide ->
|
||||||
storageAdapter.readAsync(pipe[1])
|
pipe[1].use { writeSide ->
|
||||||
|
storageAdapter.readAsync(writeSide)
|
||||||
|
}
|
||||||
val result = nativeLoad(handle, readSide.fd)
|
val result = nativeLoad(handle, readSide.fd)
|
||||||
check(result) { "Wallet data deserialization failed" }
|
check(result) { "Wallet data deserialization failed" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getPrimaryAccountAddress() = nativeGetPrimaryAccountAddress(handle)
|
override fun getPrimaryAccountAddress() = nativeGetPrimaryAccountAddress(handle)
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ import android.content.Intent
|
|||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.time.Instant
|
|
||||||
|
|
||||||
// TODO: Rename to SandboxedWalletProvider and extract interface, add InProcessWalletProvider
|
// TODO: Rename to SandboxedWalletProvider and extract interface, add InProcessWalletProvider
|
||||||
class WalletProvider private constructor(
|
class WalletProvider private constructor(
|
||||||
@ -54,10 +53,11 @@ class WalletProvider private constructor(
|
|||||||
dataStore: WalletDataStore? = null,
|
dataStore: WalletDataStore? = null,
|
||||||
client: RemoteNodeClient? = null,
|
client: RemoteNodeClient? = null,
|
||||||
): MoneroWallet {
|
): MoneroWallet {
|
||||||
|
require(client == null || client.network == network)
|
||||||
val storageAdapter = StorageAdapter(dataStore)
|
val storageAdapter = StorageAdapter(dataStore)
|
||||||
val wallet = suspendCancellableCoroutine { continuation ->
|
val wallet = suspendCancellableCoroutine { continuation ->
|
||||||
service.createWallet(
|
service.createWallet(
|
||||||
buildConfig(network, StorageAdapter(dataStore), client),
|
buildConfig(network), storageAdapter, client,
|
||||||
WalletResultCallback(continuation),
|
WalletResultCallback(continuation),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -71,10 +71,11 @@ class WalletProvider private constructor(
|
|||||||
secretSpendKey: SecretKey,
|
secretSpendKey: SecretKey,
|
||||||
restorePoint: RestorePoint,
|
restorePoint: RestorePoint,
|
||||||
): MoneroWallet {
|
): MoneroWallet {
|
||||||
|
require(client == null || client.network == network)
|
||||||
val storageAdapter = StorageAdapter(dataStore)
|
val storageAdapter = StorageAdapter(dataStore)
|
||||||
val wallet = suspendCancellableCoroutine { continuation ->
|
val wallet = suspendCancellableCoroutine { continuation ->
|
||||||
service.restoreWallet(
|
service.restoreWallet(
|
||||||
buildConfig(network, StorageAdapter(dataStore), client),
|
buildConfig(network), storageAdapter, client,
|
||||||
WalletResultCallback(continuation),
|
WalletResultCallback(continuation),
|
||||||
secretSpendKey,
|
secretSpendKey,
|
||||||
restorePoint.heightOrTimestamp,
|
restorePoint.heightOrTimestamp,
|
||||||
@ -88,23 +89,19 @@ class WalletProvider private constructor(
|
|||||||
dataStore: WalletDataStore,
|
dataStore: WalletDataStore,
|
||||||
client: RemoteNodeClient? = null,
|
client: RemoteNodeClient? = null,
|
||||||
): MoneroWallet {
|
): MoneroWallet {
|
||||||
|
require(client == null || client.network == network)
|
||||||
val storageAdapter = StorageAdapter(dataStore)
|
val storageAdapter = StorageAdapter(dataStore)
|
||||||
val wallet = suspendCancellableCoroutine { continuation ->
|
val wallet = suspendCancellableCoroutine { continuation ->
|
||||||
service.openWallet(
|
service.openWallet(
|
||||||
buildConfig(network, storageAdapter, client),
|
buildConfig(network), storageAdapter, client,
|
||||||
WalletResultCallback(continuation),
|
WalletResultCallback(continuation),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return MoneroWallet(wallet, storageAdapter, client)
|
return MoneroWallet(wallet, storageAdapter, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildConfig(
|
private fun buildConfig(network: MoneroNetwork): WalletConfig {
|
||||||
network: MoneroNetwork,
|
return WalletConfig(network.id)
|
||||||
storageAdapter: StorageAdapter,
|
|
||||||
remoteNodeClient: RemoteNodeClient?,
|
|
||||||
): WalletConfig {
|
|
||||||
require(remoteNodeClient == null || remoteNodeClient.network == network)
|
|
||||||
return WalletConfig(network.id, storageAdapter, remoteNodeClient)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun disconnect() {
|
fun disconnect() {
|
||||||
|
@ -42,12 +42,14 @@ internal class WalletServiceImpl(
|
|||||||
|
|
||||||
override fun createWallet(
|
override fun createWallet(
|
||||||
config: WalletConfig?,
|
config: WalletConfig?,
|
||||||
|
storage: IStorageAdapter?,
|
||||||
|
client: IRemoteNodeClient?,
|
||||||
callback: IWalletServiceCallbacks?,
|
callback: IWalletServiceCallbacks?,
|
||||||
) {
|
) {
|
||||||
serviceScope.launch {
|
serviceScope.launch {
|
||||||
val secretSpendKey = randomSecretKey()
|
val secretSpendKey = randomSecretKey()
|
||||||
val wallet = secretSpendKey.use { secret ->
|
val wallet = secretSpendKey.use { secret ->
|
||||||
createOrRestoreWallet(config, secret)
|
createOrRestoreWallet(config, storage, client, secret)
|
||||||
}
|
}
|
||||||
callback?.onWalletResult(wallet)
|
callback?.onWalletResult(wallet)
|
||||||
}
|
}
|
||||||
@ -55,13 +57,15 @@ internal class WalletServiceImpl(
|
|||||||
|
|
||||||
override fun restoreWallet(
|
override fun restoreWallet(
|
||||||
config: WalletConfig?,
|
config: WalletConfig?,
|
||||||
|
storage: IStorageAdapter?,
|
||||||
|
client: IRemoteNodeClient?,
|
||||||
callback: IWalletServiceCallbacks?,
|
callback: IWalletServiceCallbacks?,
|
||||||
secretSpendKey: SecretKey?,
|
secretSpendKey: SecretKey?,
|
||||||
restorePoint: Long,
|
restorePoint: Long,
|
||||||
) {
|
) {
|
||||||
serviceScope.launch {
|
serviceScope.launch {
|
||||||
val wallet = secretSpendKey.use { secret ->
|
val wallet = secretSpendKey.use { secret ->
|
||||||
createOrRestoreWallet(config, secret, restorePoint)
|
createOrRestoreWallet(config, storage, client, secret, restorePoint)
|
||||||
}
|
}
|
||||||
callback?.onWalletResult(wallet)
|
callback?.onWalletResult(wallet)
|
||||||
}
|
}
|
||||||
@ -69,22 +73,26 @@ internal class WalletServiceImpl(
|
|||||||
|
|
||||||
override fun openWallet(
|
override fun openWallet(
|
||||||
config: WalletConfig?,
|
config: WalletConfig?,
|
||||||
|
storage: IStorageAdapter?,
|
||||||
|
client: IRemoteNodeClient?,
|
||||||
callback: IWalletServiceCallbacks?,
|
callback: IWalletServiceCallbacks?,
|
||||||
) {
|
) {
|
||||||
requireNotNull(config)
|
requireNotNull(config)
|
||||||
serviceScope.launch {
|
serviceScope.launch {
|
||||||
val wallet = WalletNative.fullNode(
|
val wallet = WalletNative.fullNode(
|
||||||
networkId = config.networkId,
|
networkId = config.networkId,
|
||||||
storageAdapter = config.storageAdapter,
|
storageAdapter = storage,
|
||||||
remoteNodeClient = config.remoteNodeClient,
|
remoteNodeClient = client,
|
||||||
coroutineContext = serviceScope.coroutineContext,
|
coroutineContext = serviceScope.coroutineContext,
|
||||||
)
|
)
|
||||||
callback?.onWalletResult(wallet)
|
callback?.onWalletResult(wallet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createOrRestoreWallet(
|
private suspend fun createOrRestoreWallet(
|
||||||
config: WalletConfig?,
|
config: WalletConfig?,
|
||||||
|
storage: IStorageAdapter?,
|
||||||
|
client: IRemoteNodeClient?,
|
||||||
secretSpendKey: SecretKey?,
|
secretSpendKey: SecretKey?,
|
||||||
restorePoint: Long? = null,
|
restorePoint: Long? = null,
|
||||||
): IWallet {
|
): IWallet {
|
||||||
@ -92,8 +100,8 @@ internal class WalletServiceImpl(
|
|||||||
requireNotNull(secretSpendKey)
|
requireNotNull(secretSpendKey)
|
||||||
return WalletNative.fullNode(
|
return WalletNative.fullNode(
|
||||||
networkId = config.networkId,
|
networkId = config.networkId,
|
||||||
storageAdapter = config.storageAdapter,
|
storageAdapter = storage,
|
||||||
remoteNodeClient = config.remoteNodeClient,
|
remoteNodeClient = client,
|
||||||
secretSpendKey = secretSpendKey,
|
secretSpendKey = secretSpendKey,
|
||||||
restorePoint = restorePoint,
|
restorePoint = restorePoint,
|
||||||
coroutineContext = serviceScope.coroutineContext,
|
coroutineContext = serviceScope.coroutineContext,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user