Rename JniConverter to RustBackend

Turns out JNI classes are hard to mock due to the way sytem libraries
are loaded. So it became easier to put the JniConverter class behind an
interface. Once that refactor was necessary, it was a good time to
update the name of this class because it retained its original PoC name
for far too long.
This commit is contained in:
Kevin Gorham 2019-05-24 00:37:17 -04:00 committed by Jack Grigg
parent 4948617d6d
commit c3f57d5e2f
No known key found for this signature in database
GPG Key ID: 9E8255172BBF9898
12 changed files with 178 additions and 120 deletions

View File

@ -2,7 +2,7 @@ package cash.z.wallet.sdk.sample.memo
import android.content.Context
import cash.z.wallet.sdk.data.*
import cash.z.wallet.sdk.jni.JniConverter
import cash.z.wallet.sdk.jni.RustBackend
import cash.z.wallet.sdk.secure.Wallet
object Injection {
@ -15,7 +15,7 @@ object Injection {
val downloader = CompactBlockStream(host, port)
val wallet = Wallet(
context = appContext,
converter = JniConverter(),
rustBackend = RustBackend(),
dataDbPath = appContext.getDatabasePath(dataDbName).absolutePath,
paramDestinationDir = "${appContext.cacheDir.absolutePath}/params",
seedProvider = SampleSeedProvider("testreferencealice"),

View File

@ -10,8 +10,9 @@ import cash.z.wallet.sdk.dao.CompactBlockDao
import cash.z.wallet.sdk.dao.TransactionDao
import cash.z.wallet.sdk.data.SampleSeedProvider
import cash.z.wallet.sdk.ext.toBlockHeight
import cash.z.wallet.sdk.jni.JniConverter
import cash.z.wallet.sdk.jni.RustBackend
import cash.z.wallet.sdk.entity.CompactBlock
import cash.z.wallet.sdk.jni.RustBackendWelding
import io.grpc.ManagedChannel
import io.grpc.ManagedChannelBuilder
import org.junit.*
@ -55,12 +56,12 @@ class GlueIntegrationTest {
private fun scanData() {
val dbFileName = "/data/user/0/cash.z.wallet.sdk.test/databases/new-data-glue.db"
converter.initDataDb(dbFileName)
converter.initAccountsTable(dbFileName, "dummyseed".toByteArray(), 1)
rustBackend.initDataDb(dbFileName)
rustBackend.initAccountsTable(dbFileName, "dummyseed".toByteArray(), 1)
Log.e("tezt", "scanning blocks...")
val result = converter.scanBlocks(cacheDbPath, dbFileName)
val result = rustBackend.scanBlocks(cacheDbPath, dbFileName)
System.err.println("done.")
}
@ -70,7 +71,7 @@ class GlueIntegrationTest {
companion object {
// jni
val converter: JniConverter = JniConverter()
val rustBackend: RustBackendWelding = RustBackend()
// db
private lateinit var dao: CompactBlockDao
@ -84,7 +85,7 @@ class GlueIntegrationTest {
@BeforeClass
@JvmStatic
fun setup() {
converter.initLogs()
rustBackend.initLogs()
val channel = ManagedChannelBuilder.forAddress("10.0.2.2", 9067).usePlaintext().build()
blockingStub = CompactTxStreamerGrpc.newBlockingStub(channel)

View File

@ -8,9 +8,10 @@ import androidx.test.core.app.ApplicationProvider
import cash.z.wallet.sdk.dao.BlockDao
import cash.z.wallet.sdk.dao.CompactBlockDao
import cash.z.wallet.sdk.dao.TransactionDao
import cash.z.wallet.sdk.ext.toBlockHeight
import cash.z.wallet.sdk.jni.JniConverter
import cash.z.wallet.sdk.entity.CompactBlock
import cash.z.wallet.sdk.ext.toBlockHeight
import cash.z.wallet.sdk.jni.RustBackend
import cash.z.wallet.sdk.jni.RustBackendWelding
import io.grpc.ManagedChannel
import io.grpc.ManagedChannelBuilder
import org.junit.*
@ -55,7 +56,7 @@ class GlueSetupIntegrationTest {
private fun scanData() {
Log.e("tezt", "scanning blocks...")
val result = converter.scanBlocks(cacheDbPath, "/data/user/0/cash.z.wallet.sdk.test/databases/data-glue.db")
val result = rustBackend.scanBlocks(cacheDbPath, "/data/user/0/cash.z.wallet.sdk.test/databases/data-glue.db")
System.err.println("done.")
}
@ -65,7 +66,7 @@ class GlueSetupIntegrationTest {
companion object {
// jni
val converter: JniConverter = JniConverter()
val rustBackend: RustBackendWelding = RustBackend()
// db
private lateinit var dao: CompactBlockDao
@ -79,7 +80,7 @@ class GlueSetupIntegrationTest {
@BeforeClass
@JvmStatic
fun setup() {
converter.initLogs()
rustBackend.initLogs()
val channel = ManagedChannelBuilder.forAddress("10.0.2.2", 9067).usePlaintext().build()
blockingStub = CompactTxStreamerGrpc.newBlockingStub(channel)

View File

@ -3,7 +3,7 @@ package cash.z.wallet.sdk.db
import android.text.format.DateUtils
import androidx.test.platform.app.InstrumentationRegistry
import cash.z.wallet.sdk.data.*
import cash.z.wallet.sdk.jni.JniConverter
import cash.z.wallet.sdk.jni.RustBackend
import cash.z.wallet.sdk.secure.Wallet
import kotlinx.coroutines.runBlocking
import org.junit.AfterClass
@ -40,16 +40,16 @@ class IntegrationTest {
@Test(timeout = 1L * DateUtils.MINUTE_IN_MILLIS/10)
fun testSync() = runBlocking<Unit> {
val converter = JniConverter()
converter.initLogs()
val rustBackend = RustBackend()
rustBackend.initLogs()
val logger = TroubleshootingTwig()
downloader = CompactBlockStream("10.0.2.2", 9067, logger)
processor = CompactBlockProcessor(context, converter, cacheDdName, dataDbName, logger = logger)
processor = CompactBlockProcessor(context, rustBackend, cacheDdName, dataDbName, logger = logger)
repository = PollingTransactionRepository(context, dataDbName, 10_000L)
wallet = Wallet(
context,
converter,
rustBackend,
context.getDatabasePath(dataDbName).absolutePath,
context.cacheDir.absolutePath,
arrayOf(0),

View File

@ -6,29 +6,29 @@ import org.junit.Assert.assertNotNull
import org.junit.BeforeClass
import org.junit.Test
class JniConverterTest {
class RustBackendTest {
private val dbDataFile = "/data/user/0/cash.z.wallet.sdk.test/databases/data2.db"
@Test
fun testGetAddress_exists() {
assertNotNull(converter.getAddress(dbDataFile, 0))
assertNotNull(rustBackend.getAddress(dbDataFile, 0))
}
@Test
fun testGetAddress_valid() {
val address = converter.getAddress(dbDataFile, 0)
val address = rustBackend.getAddress(dbDataFile, 0)
val expectedAddress = "ztestsapling1snmqdnfqnc407pvqw7sld8w5zxx6nd0523kvlj4jf39uvxvh7vn0hs3q38n07806dwwecqwke3t"
assertEquals("Invalid address", expectedAddress, address)
}
@Test
fun testScanBlocks() {
converter.initDataDb(dbDataFile)
converter.initAccountsTable(dbDataFile, "dummyseed".toByteArray(), 1)
rustBackend.initDataDb(dbDataFile)
rustBackend.initAccountsTable(dbDataFile, "dummyseed".toByteArray(), 1)
// note: for this to work, the db file below must be uploaded to the device. Eventually, this test will be self-contained and remove that requirement.
val result = converter.scanBlocks(
val result = rustBackend.scanBlocks(
"/data/user/0/cash.z.wallet.sdk.test/databases/dummy-cache.db",
dbDataFile
)
@ -38,7 +38,7 @@ class JniConverterTest {
@Test
fun testSend() {
converter.sendToAddress(
rustBackend.sendToAddress(
"/data/user/0/cash.z.wallet.sdk.test/databases/data2.db",
0,
"dummykey",
@ -51,12 +51,12 @@ class JniConverterTest {
}
companion object {
val converter: JniConverter = JniConverter()
val rustBackend: RustBackendWelding = RustBackend()
@BeforeClass
@JvmStatic
fun setup() {
converter.initLogs()
rustBackend.initLogs()
}
}

View File

@ -6,7 +6,8 @@ import androidx.room.RoomDatabase
import cash.z.wallet.sdk.dao.CompactBlockDao
import cash.z.wallet.sdk.db.CompactBlockDb
import cash.z.wallet.sdk.exception.CompactBlockProcessorException
import cash.z.wallet.sdk.jni.JniConverter
import cash.z.wallet.sdk.jni.RustBackend
import cash.z.wallet.sdk.jni.RustBackendWelding
import cash.z.wallet.sdk.rpc.CompactFormats
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.channels.ReceiveChannel
@ -21,7 +22,7 @@ import java.io.File
*/
class CompactBlockProcessor(
applicationContext: Context,
val converter: JniConverter = JniConverter(),
val rustBackend: RustBackendWelding = RustBackend(),
cacheDbName: String = DEFAULT_CACHE_DB_NAME,
dataDbName: String = DEFAULT_DATA_DB_NAME
) {
@ -90,7 +91,7 @@ class CompactBlockProcessor(
twigTask("scanning blocks") {
if (isActive) {
try {
converter.scanBlocks(cacheDbPath, dataDbPath)
rustBackend.scanBlocks(cacheDbPath, dataDbPath)
} catch (t: Throwable) {
twig("error while scanning blocks: $t")
}

View File

@ -24,7 +24,7 @@ sealed class SynchronizerException(message: String, cause: Throwable? = null) :
sealed class CompactBlockProcessorException(message: String, cause: Throwable? = null) : RuntimeException(message, cause) {
class DataDbMissing(path: String): CompactBlockProcessorException("No data db file found at path $path. Verify " +
"that the data DB has been initialized via `converter.initDataDb(path)`")
"that the data DB has been initialized via `rustBackend.initDataDb(path)`")
}
sealed class CompactBlockStreamException(message: String, cause: Throwable? = null) : RuntimeException(message, cause) {

View File

@ -1,62 +0,0 @@
package cash.z.wallet.sdk.jni
import cash.z.wallet.sdk.annotation.OpenForTesting
/**
* Serves as the JNI boundary between the Kotlin and Rust layers. Functions in this class should not be called directly
* by code outside of the SDK. Instead, one of the higher-level components should be used such as Wallet.kt or
* CompactBlockProcessor.kt.
*/
@OpenForTesting
class JniConverter {
external fun initDataDb(dbData: String): Boolean
external fun initAccountsTable(
dbData: String,
seed: ByteArray,
accounts: Int): Array<String>
external fun initBlocksTable(
dbData: String,
height: Int,
hash: String,
time: Long,
saplingTree: String): Boolean
external fun getAddress(dbData: String, account: Int): String
external fun getBalance(dbData: String, account: Int): Long
external fun getVerifiedBalance(dbData: String, account: Int): Long
external fun getReceivedMemoAsUtf8(dbData: String, idNote: Long): String
external fun getSentMemoAsUtf8(dbData: String, idNote: Long): String
external fun validateCombinedChain(db_cache: String, db_data: String): Int
external fun rewindToHeight(db_data: String, height: Int): Boolean
external fun scanBlocks(db_cache: String, db_data: String): Boolean
external fun sendToAddress(
dbData: String,
account: Int,
extsk: String,
to: String,
value: Long,
memo: String,
spendParams: String,
outputParams: String
): Long
external fun initLogs()
companion object {
init {
System.loadLibrary("zcashwalletsdk")
}
}
}

View File

@ -0,0 +1,65 @@
package cash.z.wallet.sdk.jni
import cash.z.wallet.sdk.data.twig
/**
* Serves as the JNI boundary between the Kotlin and Rust layers. Functions in this class should not be called directly
* by code outside of the SDK. Instead, one of the higher-level components should be used such as Wallet.kt or
* CompactBlockProcessor.kt.
*/
class RustBackend : RustBackendWelding {
external override fun initDataDb(dbData: String): Boolean
external override fun initAccountsTable(
dbData: String,
seed: ByteArray,
accounts: Int): Array<String>
external override fun initBlocksTable(
dbData: String,
height: Int,
hash: String,
time: Long,
saplingTree: String): Boolean
external override fun getAddress(dbData: String, account: Int): String
external override fun getBalance(dbData: String, account: Int): Long
external override fun getVerifiedBalance(dbData: String, account: Int): Long
external override fun getReceivedMemoAsUtf8(dbData: String, idNote: Long): String
external override fun getSentMemoAsUtf8(dbData: String, idNote: Long): String
external override fun validateCombinedChain(db_cache: String, db_data: String): Int
external override fun rewindToHeight(db_data: String, height: Int): Boolean
external override fun scanBlocks(db_cache: String, db_data: String): Boolean
external override fun sendToAddress(
dbData: String,
account: Int,
extsk: String,
to: String,
value: Long,
memo: String,
spendParams: String,
outputParams: String
): Long
external override fun initLogs()
companion object {
init {
try {
System.loadLibrary("zcashwalletsdk")
} catch (e: Throwable) {
twig("Error while loading native library: ${e.message}")
}
}
}
}

View File

@ -0,0 +1,52 @@
package cash.z.wallet.sdk.jni
/**
* Contract defining the exposed capabilitiies of the Rust backend.
* This is what welds the SDK to the Rust layer.
*/
interface RustBackendWelding {
fun initDataDb(dbData: String): Boolean
fun initAccountsTable(
dbData: String,
seed: ByteArray,
accounts: Int): Array<String>
fun initBlocksTable(
dbData: String,
height: Int,
hash: String,
time: Long,
saplingTree: String): Boolean
fun getAddress(dbData: String, account: Int): String
fun getBalance(dbData: String, account: Int): Long
fun getVerifiedBalance(dbData: String, account: Int): Long
fun getReceivedMemoAsUtf8(dbData: String, idNote: Long): String
fun getSentMemoAsUtf8(dbData: String, idNote: Long): String
fun validateCombinedChain(db_cache: String, db_data: String): Int
fun rewindToHeight(db_data: String, height: Int): Boolean
fun scanBlocks(db_cache: String, db_data: String): Boolean
fun sendToAddress(
dbData: String,
account: Int,
extsk: String,
to: String,
value: Long,
memo: String,
spendParams: String,
outputParams: String
): Long
fun initLogs()
}

View File

@ -8,7 +8,7 @@ import cash.z.wallet.sdk.data.twigTask
import cash.z.wallet.sdk.exception.RustLayerException
import cash.z.wallet.sdk.exception.WalletException
import cash.z.wallet.sdk.ext.masked
import cash.z.wallet.sdk.jni.JniConverter
import cash.z.wallet.sdk.jni.RustBackendWelding
import cash.z.wallet.sdk.secure.Wallet.WalletBirthday
import com.google.gson.Gson
import com.google.gson.stream.JsonReader
@ -26,14 +26,14 @@ import kotlin.properties.ReadWriteProperty
/**
* Wrapper for the converter. This class basically represents all the Rust-wallet capabilities and the supporting data
* required to exercise those abilities.
* Wrapper for the Rust backend. This class basically represents all the Rust-wallet
* capabilities and the supporting data required to exercise those abilities.
*
* @param birthday the birthday of this wallet. See [WalletBirthday] for more info.
*/
class Wallet(
private val birthday: WalletBirthday,
private val converter: JniConverter,
private val rustBackend: RustBackendWelding,
private val dataDbPath: String,
private val paramDestinationDir: String,
/** indexes of accounts ids. In the reference wallet, we only work with account 0 */
@ -43,7 +43,7 @@ class Wallet(
) {
constructor(
context: Context,
converter: JniConverter,
rustBackend: RustBackendWelding,
dataDbPath: String,
paramDestinationDir: String,
accountIds: Array<Int> = arrayOf(0),
@ -51,7 +51,7 @@ class Wallet(
spendingKeyProvider: ReadWriteProperty<Any?, String>
) : this(
birthday = loadBirthdayFromAssets(context),
converter = converter,
rustBackend = rustBackend,
dataDbPath = dataDbPath,
paramDestinationDir = paramDestinationDir,
accountIds = accountIds,
@ -77,13 +77,13 @@ class Wallet(
firstRunStartHeight: Int = SAPLING_ACTIVATION_HEIGHT
): Int {
twig("Initializing wallet for first run")
converter.initDataDb(dataDbPath)
rustBackend.initDataDb(dataDbPath)
twig("seeding the database with sapling tree at height ${birthday.height}")
converter.initBlocksTable(dataDbPath, birthday.height, birthday.hash, birthday.time, birthday.tree)
rustBackend.initBlocksTable(dataDbPath, birthday.height, birthday.hash, birthday.time, birthday.tree)
// store the spendingkey by leveraging the utilities provided during construction
val seed by seedProvider
val accountSpendingKeys = converter.initAccountsTable(dataDbPath, seed, 1)
val accountSpendingKeys = rustBackend.initAccountsTable(dataDbPath, seed, 1)
spendingKeyStore = accountSpendingKeys[0]
return Math.max(firstRunStartHeight, birthday.height)
@ -93,7 +93,7 @@ class Wallet(
* Gets the address for this wallet, defaulting to the first account.
*/
fun getAddress(accountId: Int = accountIds[0]): String {
return converter.getAddress(dataDbPath, accountId)
return rustBackend.getAddress(dataDbPath, accountId)
}
/**
@ -110,7 +110,7 @@ class Wallet(
* @param accountId the account to check for balance info. Defaults to zero.
*/
fun availableBalanceSnapshot(accountId: Int = accountIds[0]): Long {
return converter.getVerifiedBalance(dataDbPath, accountId)
return rustBackend.getVerifiedBalance(dataDbPath, accountId)
}
/**
@ -121,9 +121,9 @@ class Wallet(
suspend fun sendBalanceInfo(accountId: Int = accountIds[0]) = withContext(IO) {
twigTask("checking balance info") {
try {
val balanceTotal = converter.getBalance(dataDbPath, accountId)
val balanceTotal = rustBackend.getBalance(dataDbPath, accountId)
twig("found total balance of: $balanceTotal")
val balanceAvailable = converter.getVerifiedBalance(dataDbPath, accountId)
val balanceAvailable = rustBackend.getVerifiedBalance(dataDbPath, accountId)
twig("found available balance of: $balanceAvailable")
balanceChannel.send(WalletBalance(balanceTotal, balanceAvailable))
} catch (t: Throwable) {
@ -150,7 +150,7 @@ class Wallet(
result = runCatching {
ensureParams(paramDestinationDir)
twig("params exist at $paramDestinationDir! attempting to send...")
converter.sendToAddress(
rustBackend.sendToAddress(
dataDbPath,
fromAccountId,
spendingKeyStore,

View File

@ -46,7 +46,7 @@ use zcash_client_backend::constants::testnet::{
};
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_initLogs(
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_RustBackend_initLogs(
_env: JNIEnv<'_>,
_: JClass<'_>,
) {
@ -61,7 +61,7 @@ pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_initLogs(
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_initDataDb(
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_RustBackend_initDataDb(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -77,7 +77,7 @@ pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_initDataDb(
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_initAccountsTable(
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_RustBackend_initAccountsTable(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -121,7 +121,7 @@ pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_initAccountsTab
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_initBlocksTable(
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_RustBackend_initBlocksTable(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -154,7 +154,7 @@ pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_initBlocksTable
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_getAddress(
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_RustBackend_getAddress(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -180,7 +180,7 @@ pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_getAddress(
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_getBalance(
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_RustBackend_getBalance(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -203,7 +203,7 @@ pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_getBalance(
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_getVerifiedBalance(
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_RustBackend_getVerifiedBalance(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -226,7 +226,7 @@ pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_getVerifiedBala
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_getReceivedMemoAsUtf8(
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_RustBackend_getReceivedMemoAsUtf8(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -247,7 +247,7 @@ pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_getReceivedMemo
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_getSentMemoAsUtf8(
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_RustBackend_getSentMemoAsUtf8(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -268,7 +268,7 @@ pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_getSentMemoAsUt
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_validateCombinedChain(
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_RustBackend_validateCombinedChain(
env: JNIEnv<'_>,
_: JClass<'_>,
db_cache: JString<'_>,
@ -292,7 +292,7 @@ pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_validateCombine
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_rewindToHeight(
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_RustBackend_rewindToHeight(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,
@ -314,7 +314,7 @@ pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_rewindToHeight(
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_scanBlocks(
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_RustBackend_scanBlocks(
env: JNIEnv<'_>,
_: JClass<'_>,
db_cache: JString<'_>,
@ -333,7 +333,7 @@ pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_scanBlocks(
}
#[no_mangle]
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_JniConverter_sendToAddress(
pub unsafe extern "C" fn Java_cash_z_wallet_sdk_jni_RustBackend_sendToAddress(
env: JNIEnv<'_>,
_: JClass<'_>,
db_data: JString<'_>,