- Enhanced implementation of SaplingParamTool component. It got sapling files move from legacy folder to the preferred one functionality. It downloads the sapling files atomically now (through the temporary file names). It contains all related constants now. It works with SaplingParamToolProperties, which allows us to easily test the SaplingParamTool functionalities. It also now checks file hashes. Removed unnecessary clear function from the component. Changed valid function. - Moved related constants from ZcashSdk class to SaplingParamTool class - Changed Initializer, WalletTransactionEncoder and RustBackend classes to work with File instead of path String - Minor changes in comments in other classes - Added getSha1Hash() extension function to the FileExt class * Related tests - Two new test fixtures to simplify our tests - Test for getSha1Hash() extension function - Minor changes in existing tests - Created new SaplingParamToolBasicTest, which covers non-integration functionality of SaplingParamTool - Moved integration tests of the SaplingParamTool to the new SaplingParamToolIntegrationTest and added some new * Related manual tests - Created Download sapling files manual test - Created Move sapling files to no_backup manual test - Update existing Move database files to no_bakcup manual test Co-authored-by: Carter Jernigan <git@carterjernigan.com>
This commit is contained in:
parent
b7df183634
commit
35e38ddb19
|
@ -4,12 +4,14 @@
|
|||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="detektAll" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list />
|
||||
<list>
|
||||
<option value="detektAll" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
|
|
|
@ -4,14 +4,19 @@
|
|||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="assembleAndroidTest assembleDebug assembleZcashmainnetDebug assembleZcashtestnetDebug" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list />
|
||||
<list>
|
||||
<option value="assembleAndroidTest" />
|
||||
<option value="assembleDebug" />
|
||||
<option value="assembleZcashmainnetDebug" />
|
||||
<option value="assembleZcashtestnetDebug" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
<option name="vmOptions" value="" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
|
|
|
@ -4,12 +4,14 @@
|
|||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="ktlintFormat" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list />
|
||||
<list>
|
||||
<option value="ktlintFormat" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
Change Log
|
||||
==========
|
||||
|
||||
Version 1.9.0-beta04
|
||||
------------------------------------
|
||||
- The SDK now stores sapling param files in `no_backup/co.electricoin.zcash` folder instead of the `cache/params`
|
||||
folder. Besides that, `SaplingParamTool` also does validation of downloaded sapling param file hash and size.
|
||||
**No action required from client app**.
|
||||
|
||||
Version 1.9.0-beta03
|
||||
------------------------------------
|
||||
- No changes; this release is a test of a new deployment process
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
# About
|
||||
This manual test case provides information on how to manually test an implemented action of downloading both of our
|
||||
sapling params files (`sapling-spend.params`, `sapling-output.params`) to the preferred location
|
||||
`/no_backup/co.electricoin.zcash/`. The benefit of this approach is that the content of `no_backup` folder is not part
|
||||
of automatic user data backup to user's cloud storage. Our sapling files are quite big (up to 50MB).
|
||||
|
||||
# Prerequisite
|
||||
- Installed [Android Studio](https://developer.android.com/studio)
|
||||
- Ideally two emulators with min and max supported API level
|
||||
- A working git client
|
||||
- Cloned [Zcash Android SDK repository](https://github.com/zcash/zcash-android-wallet-sdk)
|
||||
- A wallet seed phrase with available funds
|
||||
|
||||
# Download files steps
|
||||
1. Remove a previous version of the demo-app from the emulator, if there is any
|
||||
2. Install the latest version of the demo-app from the latest commit on the **Main** branch
|
||||
3. Run the demo-app on selected emulator
|
||||
4. Once it's opened on the Home screen, change the wallet seed phrase to your preferred one to have some funds
|
||||
available, which can be spent for the purpose of this test
|
||||
5. Go to the Send screen and wait for Downloading and Syncing processes to finish
|
||||
6. Then type the ZEC amount you want to send and the Address to which you want the Zec amount sent
|
||||
7. Wait for send confirmation
|
||||
8. Sapling params files should be now downloaded in the preferred location. Open Device File Explorer from Android
|
||||
Studio bottom-left corner, select the same emulator device from the top. Go to
|
||||
`/data/data/cash.z.ecc.android.sdk.demoapp.mainnet/no_backup/co.electricoin.zcash`, which should be created
|
||||
automatically
|
||||
9. Now verify there both of our sapling params files (`sapling-spend.params`, `sapling-output.params`) placed in the
|
||||
`no_backup/co.electricoin.zcash` folder
|
||||
|
||||
# Check result
|
||||
Ideally run this test for both emulators (min and max supported API level) to ensure the correct functionality on both
|
||||
Android version. There is a difference in implementation for these Android versions, but the result should be the same.
|
|
@ -1,37 +1,55 @@
|
|||
# About
|
||||
This manual test case provides information on how to manually test an implemented action of moving all of our databases files from default `/databases/` to preferred `/no_backup/co.electricoin.zcash` directory. The benefit of this approach is that the content `no_backup` folder is not part of automatic user data backup to user's cloud storage. Our databases can contain potentially big and sensitive data.
|
||||
This manual test case provides information on how to manually test an implemented action of moving all of our databases
|
||||
files from default `/databases/` to preferred `/no_backup/co.electricoin.zcash` directory. The benefit of this approach
|
||||
is that the content `no_backup` folder is not part of automatic user data backup to user's cloud storage. Our databases
|
||||
can contain potentially big and sensitive data.
|
||||
|
||||
The move feature takes all related files (database file itself as well as `journal` and `wal` rollback files) and moves them only once on app start (before first database access) when a client app uses an updated version of this SDK.
|
||||
The move feature takes all related files (database file itself as well as `journal` and `wal` rollback files) and moves
|
||||
them only once on app start (before first database access) when a client app uses an updated version of this SDK.
|
||||
|
||||
# Prerequisite
|
||||
- Installed Android Studio
|
||||
- Installed [Android Studio](https://developer.android.com/studio)
|
||||
- Ideally two emulators with min and max supported API level
|
||||
- A working git client
|
||||
- Cloned Zcash Android SDK repository
|
||||
- Cloned [Zcash Android SDK repository](https://github.com/zcash/zcash-android-wallet-sdk)
|
||||
|
||||
# Prepare steps
|
||||
1. Install a previous version of the SDK and its demo-app to create database files in the original `database` folder
|
||||
1. Switch back to commit **Bump version to 1.8.0-beta01 [3fda6da]** from Jul 11 2022 on the **Main** branch in your git client, or with this git command `git checkout 3fda6da1cae5b83174e5b1e020c91dfe95d93458`
|
||||
2. Update dependencies lock (if needed) and sync Gradle files
|
||||
3. Run the demo-app on selected emulator
|
||||
4. Once it's opened go through the app to let the SDK create all the database files. Visit these screens step by step from the side menu:
|
||||
2. Switch back to commit **Bump version to 1.8.0-beta01 [3fda6da]** from Jul 11 2022 on the **Main** branch in your
|
||||
git client, or with this git command `git checkout 3fda6da1cae5b83174e5b1e020c91dfe95d93458`
|
||||
3. Update dependencies lock (if needed) and sync Gradle files
|
||||
4. Run the demo-app on selected emulator
|
||||
5. Once it's opened go through the app to let the SDK create all the database files. Visit these screens step by step
|
||||
from the side menu:
|
||||
1. Get Balance
|
||||
2. List Transactions
|
||||
3. List UTXOs
|
||||
2. Open Device File Explorer from Android Studio bottom-left corner, select the same emulator device from the top drop-down menu
|
||||
3. Go to `/data/data/cash.z.ecc.android.sdk.demoapp.mainnet/databases`
|
||||
4. Verify there are `data.db`, `cache.db` and `utxos.db` files (their names can vary, depends on the current build variant). There can be several rollback files created.
|
||||
6. Open Device File Explorer from Android Studio bottom-left corner, select the same emulator device from the top
|
||||
drop-down menu
|
||||
7. Go to `/data/data/cash.z.ecc.android.sdk.demoapp.mainnet/databases`
|
||||
8. Verify there are `data.db`, `cache.db` and `utxos.db` files (their names can vary, depends on the current build
|
||||
variant). There can be several rollback files created.
|
||||
|
||||
# Move steps
|
||||
1. Install the newer version of the SDK and its demo-app to the same device to check the database files move operation result
|
||||
1. Install the newer version of the SDK and its demo-app to the same device to check the database files move operation
|
||||
result
|
||||
1. Switch to the latest commit on the **Main** branch in your git client
|
||||
2. Update dependencies lock (if needed) and sync Gradle files
|
||||
3. Run the demo-app on the same emulator device as previously
|
||||
2. Once the app is opened, go to the Device File Explorer from Android Studio bottom-left corner again
|
||||
3. Go to `/data/data/cash.z.ecc.android.sdk.demoapp.mainnet/databases` again, now there shouldn't be any files placed in the `database` folder
|
||||
4. Go to `/data/data/cash.z.ecc.android.sdk.demoapp.mainnet/no_backup/co.electricoin.zcash`, which should be created automatically
|
||||
5. Now verify there are the same files placed in the `no_backup/co.electricoin.zcash` folder as in `databases` were
|
||||
6. To be sure everything is alright, just visit several screens from the side-menu and see no unexpected behavior
|
||||
2. Once the app is opened go through the same steps as previously to let the SDK apply the move mechanisms to all our
|
||||
database files. Visit these screens step by step from the side menu:
|
||||
1. Get Balance
|
||||
2. List Transactions
|
||||
3. List UTXOs
|
||||
3. Go to the Device File Explorer from Android Studio bottom-left corner again
|
||||
4. Go to `/data/data/cash.z.ecc.android.sdk.demoapp.mainnet/databases` again, now there shouldn't be any files placed
|
||||
in the `database` folder
|
||||
5. Go to `/data/data/cash.z.ecc.android.sdk.demoapp.mainnet/no_backup/co.electricoin.zcash`, which should be created
|
||||
automatically
|
||||
6. Now verify there are the same files placed in the `no_backup/co.electricoin.zcash` folder as in `databases` were
|
||||
7. To be sure everything is alright, just visit several screens from the side-menu and see no unexpected behavior
|
||||
|
||||
# Check result
|
||||
Ideally run this test (Prepare and Move steps) for both emulators (Android SDK 21 and 31) to ensure the correct functionality on both Android version. There is a difference in implementation for these Android versions, but the result should be the same.
|
||||
Ideally run this test (Prepare and Move steps) for both emulators (min and max supported API level) to ensure the
|
||||
correct functionality on both Android version. There is a difference in implementation for these Android versions, but
|
||||
the result should be the same.
|
|
@ -0,0 +1,47 @@
|
|||
# About
|
||||
This manual test case provides information on how to manually test an implemented action of moving both of our
|
||||
sapling params files (`sapling-spend.params`, `sapling-output.params`) from legacy location `/cache/params/` to
|
||||
the preferred location `/no_backup/co.electricoin.zcash/`. The benefit of this approach is that the content of
|
||||
`no_backup` folder is not part of automatic user data backup to user's cloud storage. Our sapling files are quite big
|
||||
(up to 50MB).
|
||||
|
||||
# Prerequisite
|
||||
- Installed [Android Studio](https://developer.android.com/studio)
|
||||
- Ideally two emulators with min and max supported API level
|
||||
- A working git client
|
||||
- Cloned [Zcash Android SDK repository](https://github.com/zcash/zcash-android-wallet-sdk)
|
||||
- A wallet seed phrase with available funds
|
||||
|
||||
# Prepare steps
|
||||
1. Install a previous version of the SDK and its demo-app to create sapling files in the original `cache/params` folder
|
||||
2. Switch back to commit **Check sapling files size [12c23dd0]** from Aug 26 2022 on the **Main** branch in your
|
||||
git client, or with this git command `git checkout 12c23dd054c687431aaf51bfc5f67d5dbc08625b`
|
||||
3. Update dependencies lock (if needed) and sync Gradle files
|
||||
4. Run the demo-app on selected emulator
|
||||
5. Once it's opened on the Home screen, change the wallet seed phrase to your preferred one to have some funds
|
||||
available, which can be spent for the purpose of this test
|
||||
6. Go to the Send screen and wait for Downloading and Syncing processes to finish
|
||||
7. Then type the ZEC amount you want to send and the Address to which you want the Zec amount sent
|
||||
8. Wait for send confirmation
|
||||
9. Sapling params files should be now moved to the original location. Open Device File
|
||||
Explorer from Android Studio bottom-left corner, select the same emulator device from the top
|
||||
drop-down menu. Go to `/data/data/cash.z.ecc.android.sdk.demoapp.mainnet/cache/params`
|
||||
10. Verify there are `sapling-spend.params` and `sapling-output.params`
|
||||
|
||||
# Move steps
|
||||
1. Install the newer version of the SDK and its demo-app to the same device to check the database files move operation
|
||||
result
|
||||
1. Switch to the latest commit on the **Main** branch in your git client
|
||||
2. Update dependencies lock (if needed) and sync Gradle files
|
||||
3. Run the demo-app on the same emulator device as previously
|
||||
2. Once the app is opened, go to the Device File Explorer from Android Studio bottom-left corner again
|
||||
3. Go to `/data/data/cash.z.ecc.android.sdk.demoapp.mainnet/cache/params` again, now there shouldn't be our sapling
|
||||
params files placed in the folder and the folder `/params/` should be missing
|
||||
4. Go to `/data/data/cash.z.ecc.android.sdk.demoapp.mainnet/no_backup/co.electricoin.zcash`, which should be created
|
||||
automatically
|
||||
5. Now verify there are the same files placed in the `no_backup/co.electricoin.zcash` folder as in `cache/params` were
|
||||
|
||||
# Check result
|
||||
Ideally run this test (Prepare and Move steps) for both emulators (min and max supported API level) to ensure the
|
||||
correct functionality on both Android version. There is a difference in implementation for these Android versions,
|
||||
but the result should be the same.
|
|
@ -3,6 +3,8 @@ package cash.z.ecc.android.sdk.db
|
|||
import androidx.test.filters.FlakyTest
|
||||
import androidx.test.filters.MediumTest
|
||||
import androidx.test.filters.SmallTest
|
||||
import cash.z.ecc.android.sdk.internal.ext.createNewFileSuspend
|
||||
import cash.z.ecc.android.sdk.internal.ext.existsSuspend
|
||||
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||
import cash.z.ecc.android.sdk.test.getAppContext
|
||||
import cash.z.ecc.fixture.DatabaseNameFixture
|
||||
|
@ -165,39 +167,39 @@ class DatabaseCoordinatorTest {
|
|||
DatabaseNameFixture.newDbWal(name = DatabaseCoordinator.DB_CACHE_NAME)
|
||||
)
|
||||
|
||||
assertTrue(originalDbFile.exists())
|
||||
assertTrue(originalDbJournalFile.exists())
|
||||
assertTrue(originalDbWalFile.exists())
|
||||
assertTrue(originalDbFile.existsSuspend())
|
||||
assertTrue(originalDbJournalFile.existsSuspend())
|
||||
assertTrue(originalDbWalFile.existsSuspend())
|
||||
|
||||
assertFalse(expectedDbFile.exists())
|
||||
assertFalse(expectedDbJournalFile.exists())
|
||||
assertFalse(expectedDbWalFile.exists())
|
||||
assertFalse(expectedDbFile.existsSuspend())
|
||||
assertFalse(expectedDbJournalFile.existsSuspend())
|
||||
assertFalse(expectedDbWalFile.existsSuspend())
|
||||
|
||||
dbCoordinator.cacheDbFile(
|
||||
DatabaseNameFixture.TEST_DB_NETWORK,
|
||||
DatabaseNameFixture.TEST_DB_ALIAS
|
||||
).also { resultFile ->
|
||||
assertTrue(resultFile.exists())
|
||||
assertTrue(resultFile.existsSuspend())
|
||||
assertEquals(expectedDbFile.absolutePath, resultFile.absolutePath)
|
||||
|
||||
assertTrue(expectedDbFile.exists())
|
||||
assertTrue(expectedDbJournalFile.exists())
|
||||
assertTrue(expectedDbWalFile.exists())
|
||||
assertTrue(expectedDbFile.existsSuspend())
|
||||
assertTrue(expectedDbJournalFile.existsSuspend())
|
||||
assertTrue(expectedDbWalFile.existsSuspend())
|
||||
|
||||
assertFalse(originalDbFile.exists())
|
||||
assertFalse(originalDbJournalFile.exists())
|
||||
assertFalse(originalDbWalFile.exists())
|
||||
assertFalse(originalDbFile.existsSuspend())
|
||||
assertFalse(originalDbJournalFile.existsSuspend())
|
||||
assertFalse(originalDbWalFile.existsSuspend())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getEmptyFile(parent: File, fileName: String): File {
|
||||
private suspend fun getEmptyFile(parent: File, fileName: String): File {
|
||||
return File(parent, fileName).apply {
|
||||
assertTrue(parentFile != null)
|
||||
parentFile!!.mkdirs()
|
||||
assertTrue(parentFile!!.exists())
|
||||
assertTrue(parentFile!!.existsSuspend())
|
||||
|
||||
createNewFile()
|
||||
assertTrue(exists())
|
||||
createNewFileSuspend()
|
||||
assertTrue(existsSuspend())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,14 +228,14 @@ class DatabaseCoordinatorTest {
|
|||
fileName = DatabaseNameFixture.newDbWal(name = DatabaseCoordinator.DB_CACHE_NAME)
|
||||
)
|
||||
|
||||
assertTrue(dbFile.exists())
|
||||
assertTrue(dbJournalFile.exists())
|
||||
assertTrue(dbWalFile.exists())
|
||||
assertTrue(dbFile.existsSuspend())
|
||||
assertTrue(dbJournalFile.existsSuspend())
|
||||
assertTrue(dbWalFile.existsSuspend())
|
||||
|
||||
dbCoordinator.deleteDatabases(DatabaseNameFixture.TEST_DB_NETWORK, DatabaseNameFixture.TEST_DB_ALIAS).also {
|
||||
assertFalse(dbFile.exists())
|
||||
assertFalse(dbJournalFile.exists())
|
||||
assertFalse(dbWalFile.exists())
|
||||
assertFalse(dbFile.existsSuspend())
|
||||
assertFalse(dbJournalFile.existsSuspend())
|
||||
assertFalse(dbWalFile.existsSuspend())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
package cash.z.ecc.android.sdk.ext
|
||||
|
||||
import androidx.test.filters.SmallTest
|
||||
import cash.z.ecc.android.sdk.internal.ext.createNewFileSuspend
|
||||
import cash.z.ecc.android.sdk.internal.ext.existsSuspend
|
||||
import cash.z.ecc.android.sdk.internal.ext.getSha1Hash
|
||||
import cash.z.ecc.android.sdk.test.getAppContext
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class FileExtTest {
|
||||
|
||||
private val testFile = File(getAppContext().filesDir, "test_file")
|
||||
|
||||
@Before
|
||||
@After
|
||||
fun remove_test_files() {
|
||||
testFile.delete()
|
||||
}
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
fun check_empty_file_sha1_result() = runTest {
|
||||
testFile.apply {
|
||||
createNewFileSuspend()
|
||||
assertTrue(existsSuspend())
|
||||
assertEquals(
|
||||
expected = "da39a3ee5e6b4b0d3255bfef95601890afd80709",
|
||||
actual = getSha1Hash(),
|
||||
message = "SHA1 hashes are not the same."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
fun check_not_empty_file_sha1_result() = runTest {
|
||||
testFile.apply {
|
||||
createNewFileSuspend()
|
||||
assertTrue(existsSuspend())
|
||||
writeText("Hey! It compiles! Ship it!")
|
||||
assertTrue(length() > 0)
|
||||
assertEquals(
|
||||
expected = "28756ec5d3a73f1e8993bdd46de74b79453ff21c",
|
||||
actual = getSha1Hash(),
|
||||
message = "SHA1 hashes are not the same."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ import kotlin.test.DefaultAsserter.assertEquals
|
|||
import kotlin.test.DefaultAsserter.assertTrue
|
||||
|
||||
// TODO [#650]: https://github.com/zcash/zcash-android-wallet-sdk/issues/650
|
||||
// TODO [#650]: Move integration tests to separate module
|
||||
|
||||
/**
|
||||
* This test is intended to run to make sure that basic things are functional and pinpoint what is
|
||||
|
@ -51,8 +52,8 @@ class SanityTest(
|
|||
)
|
||||
assertTrue(
|
||||
"$name has invalid CacheDB params dir",
|
||||
wallet.initializer.rustBackend.pathParamsDir.endsWith(
|
||||
"cache/params"
|
||||
wallet.initializer.rustBackend.saplingParamDir.endsWith(
|
||||
"no_backup/co.electricoin.zcash"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -80,11 +81,11 @@ class SanityTest(
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore(
|
||||
"This test needs to be refactored to a separate test module. It causes SSLHandshakeException: Chain " +
|
||||
"validation failed on CI"
|
||||
)
|
||||
@Test
|
||||
fun testLatestHeight() = runBlocking {
|
||||
if (wallet.networkName == "mainnet") {
|
||||
val expectedHeight = BlockExplorer.fetchLatestHeight()
|
||||
|
@ -104,11 +105,11 @@ class SanityTest(
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore(
|
||||
"This test needs to be refactored to a separate test module. It causes SSLHandshakeException: Chain " +
|
||||
"validation failed on CI"
|
||||
)
|
||||
@Test
|
||||
fun testSingleBlockDownload() = runBlocking {
|
||||
// Fetch height directly because the synchronizer hasn't started, yet. Then we test the
|
||||
// result, only if there is no server communication problem.
|
||||
|
|
|
@ -36,7 +36,9 @@ class SmokeTest {
|
|||
)
|
||||
assertTrue(
|
||||
"Invalid CacheDB params dir",
|
||||
wallet.initializer.rustBackend.pathParamsDir.endsWith("cache/params")
|
||||
wallet.initializer.rustBackend.saplingParamDir.endsWith(
|
||||
"no_backup/co.electricoin.zcash"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
package cash.z.ecc.android.sdk.internal
|
||||
|
||||
import androidx.test.filters.MediumTest
|
||||
import androidx.test.filters.SmallTest
|
||||
import cash.z.ecc.android.sdk.exception.TransactionEncoderException
|
||||
import cash.z.ecc.android.sdk.internal.ext.getSha1Hash
|
||||
import cash.z.ecc.android.sdk.internal.ext.listFilesSuspend
|
||||
import cash.z.ecc.android.sdk.test.getAppContext
|
||||
import cash.z.ecc.fixture.SaplingParamToolFixture
|
||||
import cash.z.ecc.fixture.SaplingParamsFixture
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class SaplingParamToolBasicTest {
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
// clear the param files
|
||||
runBlocking {
|
||||
SaplingParamsFixture.clearAllFilesFromDirectory(SaplingParamsFixture.DESTINATION_DIRECTORY)
|
||||
SaplingParamsFixture.clearAllFilesFromDirectory(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
fun init_sapling_param_tool_test() = runTest {
|
||||
val spendSaplingParams = SaplingParamsFixture.new()
|
||||
val outputSaplingParams = SaplingParamsFixture.new(
|
||||
SaplingParamsFixture.DESTINATION_DIRECTORY,
|
||||
SaplingParamsFixture.OUTPUT_FILE_NAME,
|
||||
SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE,
|
||||
SaplingParamsFixture.OUTPUT_FILE_HASH
|
||||
)
|
||||
|
||||
val saplingParamTool = SaplingParamTool(
|
||||
SaplingParamToolProperties(
|
||||
emptyList(),
|
||||
SaplingParamsFixture
|
||||
.DESTINATION_DIRECTORY,
|
||||
SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY
|
||||
)
|
||||
)
|
||||
|
||||
// we inject params files to let the ensureParams() finish successfully without executing its extended operation
|
||||
// like fetchParams, etc.
|
||||
SaplingParamsFixture.createFile(File(spendSaplingParams.destinationDirectory, spendSaplingParams.fileName))
|
||||
SaplingParamsFixture.createFile(File(outputSaplingParams.destinationDirectory, outputSaplingParams.fileName))
|
||||
|
||||
saplingParamTool.ensureParams(spendSaplingParams.destinationDirectory)
|
||||
}
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
fun init_and_get_params_destination_dir_test() = runTest {
|
||||
val destDir = SaplingParamTool.new(getAppContext()).properties.paramsDirectory
|
||||
|
||||
assertNotNull(destDir)
|
||||
assertEquals(
|
||||
SaplingParamsFixture.DESTINATION_DIRECTORY.absolutePath,
|
||||
destDir.absolutePath,
|
||||
"Failed to validate init operation's destination directory."
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun move_files_from_legacy_destination_test() = runTest {
|
||||
SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY.mkdirs()
|
||||
val spendFile = File(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, SaplingParamsFixture.SPEND_FILE_NAME)
|
||||
val outputFile = File(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, SaplingParamsFixture.OUTPUT_FILE_NAME)
|
||||
|
||||
// now we inject params files to the legacy location to be "moved" to the preferred location
|
||||
SaplingParamsFixture.createFile(spendFile)
|
||||
SaplingParamsFixture.createFile(outputFile)
|
||||
|
||||
assertTrue(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, spendFile))
|
||||
assertTrue(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, outputFile))
|
||||
assertFalse(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY, spendFile))
|
||||
assertFalse(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY, outputFile))
|
||||
|
||||
// we need to use modified array of sapling parameters to pass through the SHA1 hashes validation
|
||||
val destDir = SaplingParamTool.initAndGetParamsDestinationDir(
|
||||
SaplingParamToolFixture.new(
|
||||
saplingParamsFiles = SaplingParamToolFixture.SAPLING_PARAMS_FILES
|
||||
.also {
|
||||
it[0].fileHash = spendFile.getSha1Hash()
|
||||
it[1].fileHash = outputFile.getSha1Hash()
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
SaplingParamsFixture.DESTINATION_DIRECTORY.absolutePath,
|
||||
destDir.absolutePath
|
||||
)
|
||||
|
||||
assertFalse(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, spendFile))
|
||||
assertFalse(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY, outputFile))
|
||||
assertTrue(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY, spendFile))
|
||||
assertTrue(isFileInPlace(SaplingParamsFixture.DESTINATION_DIRECTORY, outputFile))
|
||||
}
|
||||
|
||||
private suspend fun isFileInPlace(directory: File, file: File): Boolean {
|
||||
return directory.listFilesSuspend()?.any { it.name == file.name } ?: false
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun ensure_params_exception_thrown_test() = runTest {
|
||||
val saplingParamTool = SaplingParamTool(
|
||||
SaplingParamToolFixture.new(
|
||||
saplingParamsFiles = SaplingParamToolFixture.SAPLING_PARAMS_FILES
|
||||
.also {
|
||||
it[0].fileName = "test_file_0"
|
||||
it[1].fileName = "test_file_1"
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
// now we inject params files to the preferred location to pass through the check missing files phase
|
||||
SaplingParamsFixture.createFile(
|
||||
File(
|
||||
SaplingParamToolFixture.SAPLING_PARAMS_FILES[0].destinationDirectory,
|
||||
SaplingParamToolFixture.SAPLING_PARAMS_FILES[0].fileName
|
||||
)
|
||||
)
|
||||
SaplingParamsFixture.createFile(
|
||||
File(
|
||||
SaplingParamToolFixture.SAPLING_PARAMS_FILES[1].destinationDirectory,
|
||||
SaplingParamToolFixture.SAPLING_PARAMS_FILES[1].fileName
|
||||
)
|
||||
)
|
||||
|
||||
// the ensure params block should fail in validation phase, because we use a different params file names
|
||||
assertFailsWith<TransactionEncoderException.MissingParamsException> {
|
||||
saplingParamTool.ensureParams(SaplingParamToolFixture.PARAMS_DIRECTORY)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
package cash.z.ecc.android.sdk.internal
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.LargeTest
|
||||
import cash.z.ecc.android.sdk.exception.TransactionEncoderException
|
||||
import cash.z.ecc.android.sdk.internal.ext.listFilesSuspend
|
||||
import cash.z.ecc.android.sdk.test.getAppContext
|
||||
import cash.z.ecc.fixture.SaplingParamsFixture
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.File
|
||||
import kotlin.test.assertContains
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
// TODO [#650]: https://github.com/zcash/zcash-android-wallet-sdk/issues/650
|
||||
// TODO [#650]: Move integration tests to separate module
|
||||
@Ignore(
|
||||
"These tests need to be refactored to a separate test module. They cause SSLHandshakeException: Chain " +
|
||||
"validation failed on CI."
|
||||
)
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SaplingParamToolIntegrationTest {
|
||||
|
||||
private val spendSaplingParams = SaplingParamsFixture.new()
|
||||
|
||||
private val outputSaplingParams = SaplingParamsFixture.new(
|
||||
SaplingParamsFixture.DESTINATION_DIRECTORY,
|
||||
SaplingParamsFixture.OUTPUT_FILE_NAME,
|
||||
SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE,
|
||||
SaplingParamsFixture.OUTPUT_FILE_HASH
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
// clear and prepare the param files
|
||||
runBlocking {
|
||||
SaplingParamsFixture.clearAllFilesFromDirectory(SaplingParamsFixture.DESTINATION_DIRECTORY)
|
||||
SaplingParamsFixture.clearAllFilesFromDirectory(SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@LargeTest
|
||||
fun test_files_exists() = runBlocking {
|
||||
val saplingParamTool = SaplingParamTool.new(getAppContext())
|
||||
|
||||
saplingParamTool.fetchParams(spendSaplingParams)
|
||||
saplingParamTool.fetchParams(outputSaplingParams)
|
||||
|
||||
val result = saplingParamTool.validate(
|
||||
SaplingParamsFixture.DESTINATION_DIRECTORY
|
||||
)
|
||||
|
||||
assertTrue(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
@LargeTest
|
||||
fun output_file_exists() = runBlocking {
|
||||
val saplingParamTool = SaplingParamTool.new(getAppContext())
|
||||
|
||||
saplingParamTool.fetchParams(spendSaplingParams)
|
||||
File(spendSaplingParams.destinationDirectory, spendSaplingParams.fileName).delete()
|
||||
|
||||
val result = saplingParamTool.validate(spendSaplingParams.destinationDirectory)
|
||||
|
||||
assertFalse(result, "Validation should fail as the spend param file is missing.")
|
||||
}
|
||||
|
||||
@Test
|
||||
@LargeTest
|
||||
fun spend_file_exists() = runBlocking {
|
||||
val saplingParamTool = SaplingParamTool.new(getAppContext())
|
||||
|
||||
saplingParamTool.fetchParams(outputSaplingParams)
|
||||
File(outputSaplingParams.destinationDirectory, outputSaplingParams.fileName).delete()
|
||||
|
||||
val result = saplingParamTool.validate(outputSaplingParams.destinationDirectory)
|
||||
|
||||
assertFalse(result, "Validation should fail as the output param file is missing.")
|
||||
}
|
||||
|
||||
@Test
|
||||
@LargeTest
|
||||
fun check_all_files_fetched() = runBlocking {
|
||||
val expectedSpendFile = File(
|
||||
SaplingParamsFixture.DESTINATION_DIRECTORY,
|
||||
SaplingParamsFixture.SPEND_FILE_NAME
|
||||
)
|
||||
val expectedOutputFile = File(
|
||||
SaplingParamsFixture.DESTINATION_DIRECTORY,
|
||||
SaplingParamsFixture.OUTPUT_FILE_NAME
|
||||
)
|
||||
|
||||
val saplingParamTool = SaplingParamTool.new(getAppContext())
|
||||
|
||||
saplingParamTool.ensureParams(SaplingParamsFixture.DESTINATION_DIRECTORY)
|
||||
|
||||
val actualFiles = SaplingParamsFixture.DESTINATION_DIRECTORY.listFilesSuspend()
|
||||
assertNotNull(actualFiles)
|
||||
|
||||
assertContains(actualFiles, expectedSpendFile)
|
||||
|
||||
assertContains(actualFiles, expectedOutputFile)
|
||||
}
|
||||
|
||||
@Test
|
||||
@LargeTest
|
||||
fun check_correct_spend_param_file_size() = runBlocking {
|
||||
val saplingParamTool = SaplingParamTool.new(getAppContext())
|
||||
|
||||
saplingParamTool.fetchParams(spendSaplingParams)
|
||||
|
||||
val expectedSpendFile = File(
|
||||
SaplingParamsFixture.DESTINATION_DIRECTORY,
|
||||
SaplingParamsFixture.SPEND_FILE_NAME
|
||||
)
|
||||
|
||||
assertTrue(expectedSpendFile.length() < SaplingParamsFixture.SPEND_FILE_MAX_SIZE)
|
||||
assertFalse(expectedSpendFile.length() < SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE)
|
||||
}
|
||||
|
||||
@Test
|
||||
@LargeTest
|
||||
fun check_correct_output_param_file_size() = runBlocking {
|
||||
val saplingParamTool = SaplingParamTool.new(getAppContext())
|
||||
|
||||
saplingParamTool.fetchParams(outputSaplingParams)
|
||||
|
||||
val expectedOutputFile = File(
|
||||
SaplingParamsFixture.DESTINATION_DIRECTORY,
|
||||
SaplingParamsFixture.OUTPUT_FILE_NAME
|
||||
)
|
||||
|
||||
assertTrue(expectedOutputFile.length() < SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE)
|
||||
assertFalse(expectedOutputFile.length() > SaplingParamsFixture.SPEND_FILE_MAX_SIZE)
|
||||
}
|
||||
|
||||
@Test
|
||||
@LargeTest
|
||||
fun fetch_params_uninitialized_test() = runTest {
|
||||
val saplingParamTool = SaplingParamTool.new(getAppContext())
|
||||
|
||||
SaplingParamsFixture.DESTINATION_DIRECTORY.delete()
|
||||
|
||||
assertFailsWith<TransactionEncoderException.FetchParamsException> {
|
||||
saplingParamTool.fetchParams(spendSaplingParams)
|
||||
}
|
||||
|
||||
assertFalse(saplingParamTool.validate(SaplingParamsFixture.DESTINATION_DIRECTORY))
|
||||
}
|
||||
|
||||
@Test
|
||||
@LargeTest
|
||||
fun fetch_params_incorrect_hash_test() = runTest {
|
||||
val saplingParamTool = SaplingParamTool.new(getAppContext())
|
||||
|
||||
assertFailsWith<TransactionEncoderException.FetchParamsException> {
|
||||
saplingParamTool.fetchParams(
|
||||
SaplingParamsFixture.new(
|
||||
fileName = SaplingParamsFixture.OUTPUT_FILE_NAME,
|
||||
fileMaxSize = SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE,
|
||||
fileHash = "test_hash_which_causes_failure_of_validation"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
assertFalse(saplingParamTool.validate(SaplingParamsFixture.DESTINATION_DIRECTORY))
|
||||
}
|
||||
|
||||
@Test
|
||||
@LargeTest
|
||||
fun fetch_params_incorrect_max_file_size_test() = runTest {
|
||||
val saplingParamTool = SaplingParamTool.new(getAppContext())
|
||||
|
||||
assertFailsWith<TransactionEncoderException.FetchParamsException> {
|
||||
saplingParamTool.fetchParams(
|
||||
SaplingParamsFixture.new(
|
||||
fileName = SaplingParamsFixture.OUTPUT_FILE_NAME,
|
||||
fileHash = SaplingParamsFixture.OUTPUT_FILE_HASH,
|
||||
fileMaxSize = 0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
assertFalse(saplingParamTool.validate(SaplingParamsFixture.DESTINATION_DIRECTORY))
|
||||
}
|
||||
}
|
|
@ -1,137 +0,0 @@
|
|||
package cash.z.ecc.android.sdk.internal
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import cash.z.ecc.fixture.SaplingParamsFixture
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.File
|
||||
import kotlin.test.assertContains
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@Ignore(
|
||||
"These tests need to be refactored to a separate test module. They cause SSLHandshakeException: Chain " +
|
||||
"validation failed on CI"
|
||||
)
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SaplingParamToolTest {
|
||||
|
||||
private val spendSaplingParams = SaplingParamsFixture.newFile()
|
||||
|
||||
private val outputSaplingParams = SaplingParamsFixture.newFile(
|
||||
SaplingParamsFixture.DESTINATION_DIRECTORY,
|
||||
SaplingParamsFixture.OUTPUT_FILE_NAME,
|
||||
SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
// clear the param files
|
||||
runBlocking { SaplingParamTool.clear(spendSaplingParams.destinationDirectoryPath) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_files_exists() = runBlocking {
|
||||
// Given
|
||||
SaplingParamTool.fetchParams(spendSaplingParams)
|
||||
SaplingParamTool.fetchParams(outputSaplingParams)
|
||||
|
||||
// When
|
||||
val result = SaplingParamTool.validate(SaplingParamsFixture.DESTINATION_DIRECTORY)
|
||||
|
||||
// Then
|
||||
assertTrue(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun output_file_exists() = runBlocking {
|
||||
// Given
|
||||
SaplingParamTool.fetchParams(spendSaplingParams)
|
||||
File(spendSaplingParams.destinationDirectoryPath, spendSaplingParams.fileName).delete()
|
||||
|
||||
// When
|
||||
val result = SaplingParamTool.validate(spendSaplingParams.destinationDirectoryPath)
|
||||
|
||||
// Then
|
||||
assertFalse(result, "Validation should fail when the spend params are missing")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun spend_file_exists() = runBlocking {
|
||||
// Given
|
||||
SaplingParamTool.fetchParams(outputSaplingParams)
|
||||
File(outputSaplingParams.destinationDirectoryPath, outputSaplingParams.fileName).delete()
|
||||
|
||||
// When
|
||||
val result = SaplingParamTool.validate(outputSaplingParams.destinationDirectoryPath)
|
||||
|
||||
// Then
|
||||
assertFalse(result, "Validation should fail when the output params are missing")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInsufficientDeviceStorage() = runBlocking {
|
||||
// Given
|
||||
SaplingParamTool.fetchParams(spendSaplingParams)
|
||||
|
||||
assertFalse(false, "insufficient storage")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSufficientDeviceStorageForOnlyOneFile() = runBlocking {
|
||||
SaplingParamTool.fetchParams(spendSaplingParams)
|
||||
|
||||
assertFalse(false, "insufficient storage")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun check_all_files_fetched() = runBlocking {
|
||||
val expectedSpendFile = File(
|
||||
SaplingParamsFixture.DESTINATION_DIRECTORY,
|
||||
SaplingParamsFixture.SPEND_FILE_NAME
|
||||
)
|
||||
val expectedOutputFile = File(
|
||||
SaplingParamsFixture.DESTINATION_DIRECTORY,
|
||||
SaplingParamsFixture.OUTPUT_FILE_NAME
|
||||
)
|
||||
|
||||
SaplingParamTool.ensureParams(SaplingParamsFixture.DESTINATION_DIRECTORY)
|
||||
|
||||
val actualFiles = File(SaplingParamsFixture.DESTINATION_DIRECTORY).listFiles()
|
||||
assertNotNull(actualFiles)
|
||||
|
||||
assertContains(actualFiles, expectedSpendFile)
|
||||
|
||||
assertContains(actualFiles, expectedOutputFile)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun check_correct_spend_param_file_size() = runBlocking {
|
||||
SaplingParamTool.fetchParams(spendSaplingParams)
|
||||
|
||||
val expectedSpendFile = File(
|
||||
SaplingParamsFixture.DESTINATION_DIRECTORY,
|
||||
SaplingParamsFixture.SPEND_FILE_NAME
|
||||
)
|
||||
|
||||
assertTrue(expectedSpendFile.length() < SaplingParamsFixture.SPEND_FILE_MAX_SIZE)
|
||||
assertFalse(expectedSpendFile.length() < SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun check_correct_output_param_file_size() = runBlocking {
|
||||
SaplingParamTool.fetchParams(outputSaplingParams)
|
||||
|
||||
val expectedOutputFile = File(
|
||||
SaplingParamsFixture.DESTINATION_DIRECTORY,
|
||||
SaplingParamsFixture.OUTPUT_FILE_NAME
|
||||
)
|
||||
|
||||
assertTrue(expectedOutputFile.length() < SaplingParamsFixture.OUTPUT_FILE_MAX_SIZE)
|
||||
assertFalse(expectedOutputFile.length() > SaplingParamsFixture.SPEND_FILE_MAX_SIZE)
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import org.junit.Assert.assertEquals
|
|||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* This test is intended to run to make sure that branch ID logic works across all target devices.
|
||||
|
@ -45,8 +46,24 @@ class BranchIdTest internal constructor(
|
|||
// is an abnormal use of the SDK because this really should run at the rust level
|
||||
// However, due to quirks on certain devices, we created this test at the Android level,
|
||||
// as a sanity check
|
||||
val testnetBackend = runBlocking { RustBackend.init("", "", "", ZcashNetwork.Testnet, ZcashNetwork.Testnet.saplingActivationHeight) }
|
||||
val mainnetBackend = runBlocking { RustBackend.init("", "", "", ZcashNetwork.Mainnet, ZcashNetwork.Mainnet.saplingActivationHeight) }
|
||||
val testnetBackend = runBlocking {
|
||||
RustBackend.init(
|
||||
File(""),
|
||||
File(""),
|
||||
File(""),
|
||||
ZcashNetwork.Testnet,
|
||||
ZcashNetwork.Testnet.saplingActivationHeight
|
||||
)
|
||||
}
|
||||
val mainnetBackend = runBlocking {
|
||||
RustBackend.init(
|
||||
File(""),
|
||||
File(""),
|
||||
File(""),
|
||||
ZcashNetwork.Mainnet,
|
||||
ZcashNetwork.Mainnet.saplingActivationHeight
|
||||
)
|
||||
}
|
||||
return listOf(
|
||||
// Mainnet Cases
|
||||
arrayOf("Sapling", BlockHeight.new(ZcashNetwork.Mainnet, 419_200), 1991772603L, "76b809bb", mainnetBackend),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package cash.z.ecc.fixture
|
||||
|
||||
import cash.z.ecc.android.sdk.internal.Files
|
||||
import cash.z.ecc.android.sdk.internal.ext.existsSuspend
|
||||
import cash.z.ecc.android.sdk.internal.ext.getDatabasePathSuspend
|
||||
import cash.z.ecc.android.sdk.internal.ext.getNoBackupFilesDirCompat
|
||||
import cash.z.ecc.android.sdk.test.getAppContext
|
||||
|
@ -16,7 +17,7 @@ object DatabasePathFixture {
|
|||
assert(parentFile != null) { "Failed to create database folder." }
|
||||
parentFile!!.mkdirs()
|
||||
|
||||
assert(parentFile.exists()) { "Failed to check database folder." }
|
||||
assert(parentFile.existsSuspend()) { "Failed to check database folder." }
|
||||
parentFile.absolutePath
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package cash.z.ecc.fixture
|
||||
|
||||
import cash.z.ecc.android.sdk.internal.SaplingParamTool
|
||||
import cash.z.ecc.android.sdk.internal.SaplingParamToolProperties
|
||||
import cash.z.ecc.android.sdk.internal.SaplingParameters
|
||||
import java.io.File
|
||||
|
||||
object SaplingParamToolFixture {
|
||||
|
||||
internal val PARAMS_DIRECTORY = SaplingParamsFixture.DESTINATION_DIRECTORY
|
||||
internal val PARAMS_LEGACY_DIRECTORY = SaplingParamsFixture.DESTINATION_DIRECTORY_LEGACY
|
||||
internal val SAPLING_PARAMS_FILES = listOf(
|
||||
SaplingParameters(
|
||||
PARAMS_DIRECTORY,
|
||||
SaplingParamTool.SPEND_PARAM_FILE_NAME,
|
||||
SaplingParamTool.SPEND_PARAM_FILE_MAX_BYTES_SIZE,
|
||||
SaplingParamTool.SPEND_PARAM_FILE_SHA1_HASH
|
||||
),
|
||||
SaplingParameters(
|
||||
PARAMS_DIRECTORY,
|
||||
SaplingParamTool.OUTPUT_PARAM_FILE_NAME,
|
||||
SaplingParamTool.OUTPUT_PARAM_FILE_MAX_BYTES_SIZE,
|
||||
SaplingParamTool.OUTPUT_PARAM_FILE_SHA1_HASH
|
||||
)
|
||||
)
|
||||
|
||||
internal fun new(
|
||||
saplingParamsFiles: List<SaplingParameters> = SAPLING_PARAMS_FILES,
|
||||
paramsDirectory: File = PARAMS_DIRECTORY,
|
||||
paramsLegacyDirectory: File = PARAMS_LEGACY_DIRECTORY
|
||||
) = SaplingParamToolProperties(
|
||||
saplingParams = saplingParamsFiles,
|
||||
paramsDirectory = paramsDirectory,
|
||||
paramsLegacyDirectory = paramsLegacyDirectory
|
||||
)
|
||||
}
|
|
@ -1,28 +1,58 @@
|
|||
package cash.z.ecc.fixture
|
||||
|
||||
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
||||
import cash.z.ecc.android.sdk.internal.SaplingFileParameters
|
||||
import cash.z.ecc.android.sdk.internal.Files
|
||||
import cash.z.ecc.android.sdk.internal.SaplingParamTool
|
||||
import cash.z.ecc.android.sdk.internal.SaplingParameters
|
||||
import cash.z.ecc.android.sdk.internal.ext.createNewFileSuspend
|
||||
import cash.z.ecc.android.sdk.internal.ext.deleteSuspend
|
||||
import cash.z.ecc.android.sdk.internal.ext.existsSuspend
|
||||
import cash.z.ecc.android.sdk.internal.ext.listFilesSuspend
|
||||
import cash.z.ecc.android.sdk.test.getAppContext
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.File
|
||||
|
||||
object SaplingParamsFixture {
|
||||
|
||||
val DESTINATION_DIRECTORY: String = File(getAppContext().cacheDir, "params").absolutePath
|
||||
|
||||
const val SPEND_FILE_NAME = ZcashSdk.SPEND_PARAM_FILE_NAME
|
||||
const val SPEND_FILE_MAX_SIZE = SaplingParamTool.SPEND_PARAM_FILE_MAX_BYTES_SIZE
|
||||
|
||||
const val OUTPUT_FILE_NAME = ZcashSdk.OUTPUT_PARAM_FILE_NAME
|
||||
const val OUTPUT_FILE_MAX_SIZE = SaplingParamTool.OUTPUT_PARAM_FILE_MAX_BYTES_SIZE
|
||||
|
||||
internal fun newFile(
|
||||
destinationDirectoryPath: String = DESTINATION_DIRECTORY,
|
||||
fileName: String = SPEND_FILE_NAME,
|
||||
fileMaxSize: Long = SPEND_FILE_MAX_SIZE
|
||||
) = SaplingFileParameters(
|
||||
destinationDirectoryPath = destinationDirectoryPath,
|
||||
fileName = fileName,
|
||||
fileMaxSizeBytes = fileMaxSize
|
||||
internal val DESTINATION_DIRECTORY_LEGACY: File = File(
|
||||
getAppContext().cacheDir,
|
||||
SaplingParamTool.SAPLING_PARAMS_LEGACY_SUBDIRECTORY
|
||||
)
|
||||
|
||||
internal val DESTINATION_DIRECTORY: File
|
||||
get() = runBlocking {
|
||||
Files.getZcashNoBackupSubdirectory(getAppContext())
|
||||
}
|
||||
|
||||
internal const val SPEND_FILE_NAME = SaplingParamTool.SPEND_PARAM_FILE_NAME
|
||||
internal const val SPEND_FILE_MAX_SIZE = SaplingParamTool.SPEND_PARAM_FILE_MAX_BYTES_SIZE
|
||||
internal const val SPEND_FILE_HASH = SaplingParamTool.SPEND_PARAM_FILE_SHA1_HASH
|
||||
|
||||
internal const val OUTPUT_FILE_NAME = SaplingParamTool.OUTPUT_PARAM_FILE_NAME
|
||||
internal const val OUTPUT_FILE_MAX_SIZE = SaplingParamTool.OUTPUT_PARAM_FILE_MAX_BYTES_SIZE
|
||||
internal const val OUTPUT_FILE_HASH = SaplingParamTool.OUTPUT_PARAM_FILE_SHA1_HASH
|
||||
|
||||
internal fun new(
|
||||
destinationDirectoryPath: File = DESTINATION_DIRECTORY,
|
||||
fileName: String = SPEND_FILE_NAME,
|
||||
fileMaxSize: Long = SPEND_FILE_MAX_SIZE,
|
||||
fileHash: String = SPEND_FILE_HASH
|
||||
) = SaplingParameters(
|
||||
destinationDirectory = destinationDirectoryPath,
|
||||
fileName = fileName,
|
||||
fileMaxSizeBytes = fileMaxSize,
|
||||
fileHash = fileHash
|
||||
)
|
||||
|
||||
internal suspend fun createFile(paramsFile: File) {
|
||||
paramsFile.createNewFileSuspend()
|
||||
}
|
||||
|
||||
internal suspend fun clearAllFilesFromDirectory(destinationDir: File) {
|
||||
if (!destinationDir.existsSuspend()) {
|
||||
return
|
||||
}
|
||||
for (file in destinationDir.listFilesSuspend()!!) {
|
||||
file.deleteSuspend()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import android.content.Context
|
|||
import cash.z.ecc.android.sdk.db.DatabaseCoordinator
|
||||
import cash.z.ecc.android.sdk.exception.InitializerException
|
||||
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
||||
import cash.z.ecc.android.sdk.internal.ext.getCacheDirSuspend
|
||||
import cash.z.ecc.android.sdk.internal.SaplingParamTool
|
||||
import cash.z.ecc.android.sdk.internal.model.Checkpoint
|
||||
import cash.z.ecc.android.sdk.internal.twig
|
||||
import cash.z.ecc.android.sdk.jni.RustBackend
|
||||
|
@ -15,7 +15,6 @@ import cash.z.ecc.android.sdk.tool.CheckpointTool
|
|||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||
import cash.z.ecc.android.sdk.type.UnifiedViewingKey
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Simplified Initializer focused on starting from a ViewingKey.
|
||||
|
@ -29,7 +28,8 @@ class Initializer private constructor(
|
|||
val lightWalletEndpoint: LightWalletEndpoint,
|
||||
val viewingKeys: List<UnifiedViewingKey>,
|
||||
val overwriteVks: Boolean,
|
||||
internal val checkpoint: Checkpoint
|
||||
internal val checkpoint: Checkpoint,
|
||||
internal val saplingParamTool: SaplingParamTool
|
||||
) {
|
||||
|
||||
suspend fun erase() = erase(context, network, alias)
|
||||
|
@ -334,7 +334,10 @@ class Initializer private constructor(
|
|||
)
|
||||
}
|
||||
|
||||
val rustBackend = initRustBackend(context, config.network, config.alias, loadedCheckpoint.height)
|
||||
val saplingParamTool = SaplingParamTool.new(context.applicationContext)
|
||||
|
||||
val rustBackend =
|
||||
initRustBackend(context, config.network, config.alias, loadedCheckpoint.height, saplingParamTool)
|
||||
|
||||
return Initializer(
|
||||
context.applicationContext,
|
||||
|
@ -344,7 +347,8 @@ class Initializer private constructor(
|
|||
config.lightWalletEndpoint,
|
||||
config.viewingKeys,
|
||||
config.overwriteVks,
|
||||
loadedCheckpoint
|
||||
loadedCheckpoint,
|
||||
saplingParamTool
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -375,14 +379,15 @@ class Initializer private constructor(
|
|||
context: Context,
|
||||
network: ZcashNetwork,
|
||||
alias: String,
|
||||
blockHeight: BlockHeight
|
||||
blockHeight: BlockHeight,
|
||||
saplingParamTool: SaplingParamTool
|
||||
): RustBackend {
|
||||
val coordinator = DatabaseCoordinator.getInstance(context)
|
||||
|
||||
return RustBackend.init(
|
||||
coordinator.cacheDbFile(network, alias).absolutePath,
|
||||
coordinator.dataDbFile(network, alias).absolutePath,
|
||||
File(context.getCacheDirSuspend(), "params").absolutePath,
|
||||
coordinator.cacheDbFile(network, alias),
|
||||
coordinator.dataDbFile(network, alias),
|
||||
saplingParamTool.properties.paramsDirectory,
|
||||
network,
|
||||
blockHeight
|
||||
)
|
||||
|
|
|
@ -32,6 +32,7 @@ import cash.z.ecc.android.sdk.db.entity.isSubmitted
|
|||
import cash.z.ecc.android.sdk.exception.SynchronizerException
|
||||
import cash.z.ecc.android.sdk.ext.ConsensusBranchId
|
||||
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
||||
import cash.z.ecc.android.sdk.internal.SaplingParamTool
|
||||
import cash.z.ecc.android.sdk.internal.block.CompactBlockDbStore
|
||||
import cash.z.ecc.android.sdk.internal.block.CompactBlockDownloader
|
||||
import cash.z.ecc.android.sdk.internal.block.CompactBlockStore
|
||||
|
@ -808,10 +809,11 @@ object DefaultSynchronizerFactory {
|
|||
fun defaultService(initializer: Initializer): LightWalletService =
|
||||
LightWalletGrpcService.new(initializer.context, initializer.lightWalletEndpoint)
|
||||
|
||||
fun defaultEncoder(
|
||||
internal fun defaultEncoder(
|
||||
initializer: Initializer,
|
||||
saplingParamTool: SaplingParamTool,
|
||||
repository: TransactionRepository
|
||||
): TransactionEncoder = WalletTransactionEncoder(initializer.rustBackend, repository)
|
||||
): TransactionEncoder = WalletTransactionEncoder(initializer.rustBackend, saplingParamTool, repository)
|
||||
|
||||
fun defaultDownloader(
|
||||
service: LightWalletService,
|
||||
|
|
|
@ -4,6 +4,7 @@ import cash.z.ecc.android.sdk.block.CompactBlockProcessor
|
|||
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
||||
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
|
||||
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
||||
import cash.z.ecc.android.sdk.internal.SaplingParamTool
|
||||
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||
import cash.z.ecc.android.sdk.model.WalletBalance
|
||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
|
@ -433,10 +434,11 @@ interface Synchronizer {
|
|||
suspend fun new(
|
||||
initializer: Initializer
|
||||
): Synchronizer {
|
||||
val saplingParamTool = SaplingParamTool.new(initializer.context)
|
||||
val repository = DefaultSynchronizerFactory.defaultTransactionRepository(initializer)
|
||||
val blockStore = DefaultSynchronizerFactory.defaultBlockStore(initializer)
|
||||
val service = DefaultSynchronizerFactory.defaultService(initializer)
|
||||
val encoder = DefaultSynchronizerFactory.defaultEncoder(initializer, repository)
|
||||
val encoder = DefaultSynchronizerFactory.defaultEncoder(initializer, saplingParamTool, repository)
|
||||
val downloader = DefaultSynchronizerFactory.defaultDownloader(service, blockStore)
|
||||
val txManager =
|
||||
DefaultSynchronizerFactory.defaultTxManager(initializer, encoder, service)
|
||||
|
|
|
@ -260,11 +260,11 @@ internal class DatabaseCoordinator private constructor(context: Context) {
|
|||
}
|
||||
|
||||
/**
|
||||
* The purpose of this function is to move database files between the old location (given by
|
||||
* the legacyLocationDbFile parameter) and the new location (given by preferredLocationDbFile).
|
||||
* The actual move operation is performed with the renameTo function, which simply renames
|
||||
* a file path and persists the metadata information. The mechanism deals with the additional
|
||||
* database files -journal and -wal too, if they exist.
|
||||
* The purpose of this function is to move database files between the old location (given by the {@code
|
||||
* legacyLocationDbFile} parameter) and the new location (given by {@code preferredLocationDbFile}). The actual
|
||||
* move operation is performed with the renameTo function, which simply renames a file path and persists the
|
||||
* metadata information. The mechanism deals with the additional database files -journal and -wal too, if they
|
||||
* exist.
|
||||
*
|
||||
* @param legacyLocationDbFile the previously used file location (rename from)
|
||||
* @param preferredLocationDbFile the newly used file location (rename to)
|
||||
|
|
|
@ -74,23 +74,6 @@ object ZcashSdk {
|
|||
*/
|
||||
const val REWIND_DISTANCE = 10
|
||||
|
||||
/**
|
||||
* File name for the sapling spend params
|
||||
*/
|
||||
const val SPEND_PARAM_FILE_NAME = "sapling-spend.params"
|
||||
|
||||
/**
|
||||
* File name for the sapling output params
|
||||
*/
|
||||
const val OUTPUT_PARAM_FILE_NAME = "sapling-output.params"
|
||||
|
||||
/**
|
||||
* The Url that is used by default in zcashd.
|
||||
* We'll want to make this externally configurable, rather than baking it into the SDK but
|
||||
* this will do for now, since we're using a cloudfront URL that already redirects.
|
||||
*/
|
||||
const val CLOUD_PARAM_DIR_URL = "https://z.cash/downloads/"
|
||||
|
||||
/**
|
||||
* The default memo to use when shielding transparent funds.
|
||||
*/
|
||||
|
|
|
@ -1,60 +1,189 @@
|
|||
package cash.z.ecc.android.sdk.internal
|
||||
|
||||
import android.content.Context
|
||||
import cash.z.ecc.android.sdk.exception.TransactionEncoderException
|
||||
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
||||
import cash.z.ecc.android.sdk.internal.ext.deleteRecursivelySuspend
|
||||
import cash.z.ecc.android.sdk.internal.ext.deleteSuspend
|
||||
import cash.z.ecc.android.sdk.internal.ext.existsSuspend
|
||||
import cash.z.ecc.android.sdk.internal.ext.getCacheDirSuspend
|
||||
import cash.z.ecc.android.sdk.internal.ext.getSha1Hash
|
||||
import cash.z.ecc.android.sdk.internal.ext.mkdirsSuspend
|
||||
import cash.z.ecc.android.sdk.internal.ext.renameToSuspend
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
import java.nio.channels.Channels
|
||||
|
||||
// TODO [#666]: https://github.com/zcash/zcash-android-wallet-sdk/issues/666
|
||||
// TODO [#666]: Download sapling-spend.params and sapling-output.params atomically
|
||||
internal class SaplingParamTool(val properties: SaplingParamToolProperties) {
|
||||
companion object {
|
||||
/**
|
||||
* Maximum file size for the sapling spend params - 50MB
|
||||
*/
|
||||
internal const val SPEND_PARAM_FILE_MAX_BYTES_SIZE = 50L * 1024L * 1024L
|
||||
|
||||
// TODO [#665]: https://github.com/zcash/zcash-android-wallet-sdk/issues/665
|
||||
// TODO [#665]: Recover from corrupted sapling-spend.params and sapling-output.params
|
||||
/**
|
||||
* Maximum file size for the sapling spend params - 5MB
|
||||
*/
|
||||
internal const val OUTPUT_PARAM_FILE_MAX_BYTES_SIZE = 5L * 1024L * 1024L
|
||||
|
||||
// TODO [#611]: https://github.com/zcash/zcash-android-wallet-sdk/issues/611
|
||||
// TODO [#611]: Move Params Directory to No Backup Directory
|
||||
/**
|
||||
* Subdirectory name, in which are the sapling params files stored.
|
||||
*/
|
||||
internal const val SAPLING_PARAMS_LEGACY_SUBDIRECTORY = "params"
|
||||
|
||||
object SaplingParamTool {
|
||||
/**
|
||||
* Maximum file size for the sapling spend params - 50MB
|
||||
*/
|
||||
internal const val SPEND_PARAM_FILE_MAX_BYTES_SIZE = 50L * 1024L * 1024L
|
||||
/**
|
||||
* File name for the sapling spend params
|
||||
*/
|
||||
internal const val SPEND_PARAM_FILE_NAME = "sapling-spend.params"
|
||||
|
||||
/**
|
||||
* Maximum file size for the sapling spend params - 5MB
|
||||
*/
|
||||
internal const val OUTPUT_PARAM_FILE_MAX_BYTES_SIZE = 5L * 1024L * 1024L
|
||||
/**
|
||||
* File name for the sapling output params
|
||||
*/
|
||||
internal const val OUTPUT_PARAM_FILE_NAME = "sapling-output.params"
|
||||
|
||||
/**
|
||||
* Temporary file prefix to fulfill atomicity requirement of file handling
|
||||
*/
|
||||
private const val TEMPORARY_FILE_NAME_PREFIX = "_"
|
||||
|
||||
/**
|
||||
* File SHA1 hash for the sapling spend params
|
||||
*/
|
||||
internal const val SPEND_PARAM_FILE_SHA1_HASH = "a15ab54c2888880e53c823a3063820c728444126"
|
||||
|
||||
/**
|
||||
* File SHA1 hash for the sapling output params
|
||||
*/
|
||||
internal const val OUTPUT_PARAM_FILE_SHA1_HASH = "0ebc5a1ef3653948e1c46cf7a16071eac4b7e352"
|
||||
|
||||
/**
|
||||
* The Url that is used by default in zcashd
|
||||
*/
|
||||
private const val CLOUD_PARAM_DIR_URL = "https://z.cash/downloads/"
|
||||
|
||||
private val checkFilesMutex = Mutex()
|
||||
|
||||
/**
|
||||
* Initialization of needed properties. This is necessary entry point for other operations from {@code
|
||||
* SaplingParamTool}. This type of implementation also simplifies its testing.
|
||||
*
|
||||
* @param context
|
||||
*/
|
||||
internal suspend fun new(context: Context): SaplingParamTool {
|
||||
val paramsDirectory = Files.getZcashNoBackupSubdirectory(context)
|
||||
val toolProperties = SaplingParamToolProperties(
|
||||
paramsDirectory = paramsDirectory,
|
||||
paramsLegacyDirectory = File(context.getCacheDirSuspend(), SAPLING_PARAMS_LEGACY_SUBDIRECTORY),
|
||||
saplingParams = listOf(
|
||||
SaplingParameters(
|
||||
paramsDirectory,
|
||||
SPEND_PARAM_FILE_NAME,
|
||||
SPEND_PARAM_FILE_MAX_BYTES_SIZE,
|
||||
SPEND_PARAM_FILE_SHA1_HASH
|
||||
),
|
||||
SaplingParameters(
|
||||
paramsDirectory,
|
||||
OUTPUT_PARAM_FILE_NAME,
|
||||
OUTPUT_PARAM_FILE_MAX_BYTES_SIZE,
|
||||
OUTPUT_PARAM_FILE_SHA1_HASH
|
||||
)
|
||||
)
|
||||
)
|
||||
return SaplingParamTool(toolProperties)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns file object pointing to the parameters files parent directory. We need to check if the parameters
|
||||
* files don't sit in the legacy folder first. If they do, then we move the files to the currently used
|
||||
* directory and validate files hashes.
|
||||
*
|
||||
* @return params destination directory file
|
||||
*/
|
||||
internal suspend fun initAndGetParamsDestinationDir(toolProperties: SaplingParamToolProperties): File {
|
||||
checkFilesMutex.withLock {
|
||||
toolProperties.saplingParams.forEach {
|
||||
val legacyFile = File(toolProperties.paramsLegacyDirectory, it.fileName)
|
||||
val currentFile = File(toolProperties.paramsDirectory, it.fileName)
|
||||
|
||||
if (legacyFile.existsSuspend() && isFileHashValid(legacyFile, it.fileHash)) {
|
||||
twig("Moving params file: ${it.fileName} from legacy folder to the currently used folder.")
|
||||
currentFile.parentFile?.mkdirsSuspend()
|
||||
if (!renameParametersFile(legacyFile, currentFile)) {
|
||||
twig("Failed while moving the params file: ${it.fileName} to the preferred location.")
|
||||
}
|
||||
} else {
|
||||
twig(
|
||||
"Legacy file either does not exist or is not valid. Will be fetched to the preferred " +
|
||||
"location."
|
||||
)
|
||||
}
|
||||
}
|
||||
// remove the params folder and its files - a new sapling files will be fetched to the preferred
|
||||
// location
|
||||
toolProperties.paramsLegacyDirectory.deleteRecursivelySuspend()
|
||||
}
|
||||
|
||||
return toolProperties.paramsDirectory
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the input file parameter SHA1 hash with the given input hash.
|
||||
*
|
||||
* @param parametersFile file of which SHA1 hash will be checked
|
||||
* @param fileHash hash to compare with
|
||||
*
|
||||
* @return true in case of hashes are the same, false otherwise
|
||||
*/
|
||||
private suspend fun isFileHashValid(parametersFile: File, fileHash: String): Boolean {
|
||||
return try {
|
||||
fileHash == parametersFile.getSha1Hash()
|
||||
} catch (e: IOException) {
|
||||
twig("Failed in comparing file's hashes with: ${e.message}, caused by: ${e.cause}.")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The purpose of this function is to rename parameters file from the old name (given by the {@code
|
||||
* fromParamFile} parameter) to the new name (given by {@code toParamFile}). This operation covers also the file
|
||||
* move, if it's in a different location.
|
||||
*
|
||||
* @param fromParamFile the previously used file name/location
|
||||
* @param toParamFile the newly used file name/location
|
||||
*/
|
||||
private suspend fun renameParametersFile(
|
||||
fromParamFile: File,
|
||||
toParamFile: File
|
||||
): Boolean {
|
||||
return runCatching {
|
||||
return@runCatching fromParamFile.renameToSuspend(toParamFile)
|
||||
}.onFailure {
|
||||
twig("Failed while renaming parameters file with: $it")
|
||||
}.getOrDefault(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the given directory for the output and spending params and calls [fetchParams] for those, which are
|
||||
* missing.
|
||||
*
|
||||
* Note: Don't forget to call the entry point function {@code initSaplingParamTool} first. Make sure you also
|
||||
* called {@code initAndGetParamsDestinationDir} previously, as it's always better to check the
|
||||
* legacy destination folder first.
|
||||
*
|
||||
* @param destinationDir the directory where the params should be stored.
|
||||
*
|
||||
* @throws TransactionEncoderException.MissingParamsException in case of failure while checking sapling params
|
||||
* files
|
||||
*/
|
||||
@Throws(TransactionEncoderException.MissingParamsException::class)
|
||||
suspend fun ensureParams(destinationDir: String) {
|
||||
arrayOf(
|
||||
SaplingFileParameters(
|
||||
destinationDir,
|
||||
ZcashSdk.SPEND_PARAM_FILE_NAME,
|
||||
SPEND_PARAM_FILE_MAX_BYTES_SIZE
|
||||
),
|
||||
SaplingFileParameters(
|
||||
destinationDir,
|
||||
ZcashSdk.OUTPUT_PARAM_FILE_NAME,
|
||||
OUTPUT_PARAM_FILE_MAX_BYTES_SIZE
|
||||
)
|
||||
).filter {
|
||||
!File(it.destinationDirectoryPath, it.fileName).existsSuspend()
|
||||
internal suspend fun ensureParams(destinationDir: File) {
|
||||
properties.saplingParams.filter {
|
||||
!File(it.destinationDirectory, it.fileName).existsSuspend()
|
||||
}.forEach {
|
||||
try {
|
||||
twig("Attempting to download missing params: ${it.fileName}.")
|
||||
|
@ -80,21 +209,17 @@ object SaplingParamTool {
|
|||
* @throws TransactionEncoderException.FetchParamsException if any error while downloading the params file occurs
|
||||
*/
|
||||
@Throws(TransactionEncoderException.FetchParamsException::class)
|
||||
internal suspend fun fetchParams(paramsToFetch: SaplingFileParameters) {
|
||||
val url = URL("${ZcashSdk.CLOUD_PARAM_DIR_URL}/${paramsToFetch.fileName}")
|
||||
|
||||
val file = File(paramsToFetch.destinationDirectoryPath, paramsToFetch.fileName)
|
||||
if (file.parentFile?.existsSuspend() == true) {
|
||||
twig("Directory ${file.parentFile?.name} exists!")
|
||||
} else {
|
||||
twig("Directory did not exist attempting to make it.")
|
||||
file.parentFile?.mkdirsSuspend()
|
||||
}
|
||||
internal suspend fun fetchParams(paramsToFetch: SaplingParameters) {
|
||||
val url = URL("$CLOUD_PARAM_DIR_URL/${paramsToFetch.fileName}")
|
||||
val temporaryFile = File(
|
||||
paramsToFetch.destinationDirectory,
|
||||
"$TEMPORARY_FILE_NAME_PREFIX${paramsToFetch.fileName}"
|
||||
)
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
Channels.newChannel(url.openStream()).use { readableByteChannel ->
|
||||
file.outputStream().use { fileOutputStream ->
|
||||
temporaryFile.outputStream().use { fileOutputStream ->
|
||||
fileOutputStream.channel.use { fileChannel ->
|
||||
// Transfers bytes from stream to file from position 0 to end position or to max
|
||||
// file size limit. This eliminates the risk of downloading potentially large files
|
||||
|
@ -119,44 +244,69 @@ object SaplingParamTool {
|
|||
throw TransactionEncoderException.FetchParamsException(it)
|
||||
}
|
||||
}.onSuccess {
|
||||
twig("Fetch and write of ${paramsToFetch.fileName} succeeded.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun clear(destinationDir: String) {
|
||||
if (validate(destinationDir)) {
|
||||
arrayOf(
|
||||
ZcashSdk.SPEND_PARAM_FILE_NAME,
|
||||
ZcashSdk.OUTPUT_PARAM_FILE_NAME
|
||||
).forEach { paramFileName ->
|
||||
val file = File(destinationDir, paramFileName)
|
||||
if (file.deleteRecursivelySuspend()) {
|
||||
twig("Files deleted successfully")
|
||||
} else {
|
||||
twig("Error: Files not able to be deleted!")
|
||||
twig(
|
||||
"Fetch and write of the temporary ${temporaryFile.name} succeeded. Validating and moving it to " +
|
||||
"the final destination"
|
||||
)
|
||||
if (!isFileHashValid(temporaryFile, paramsToFetch.fileHash)) {
|
||||
finalizeAndReportError(
|
||||
temporaryFile,
|
||||
message = "Failed while validating fetched params file: ${paramsToFetch.fileName}"
|
||||
)
|
||||
}
|
||||
val resultFile = File(paramsToFetch.destinationDirectory, paramsToFetch.fileName)
|
||||
if (!renameParametersFile(temporaryFile, resultFile)) {
|
||||
finalizeAndReportError(
|
||||
temporaryFile,
|
||||
resultFile,
|
||||
message = "Failed while renaming result params file: ${paramsToFetch.fileName}"
|
||||
)
|
||||
}
|
||||
|
||||
// TODO [#665]: https://github.com/zcash/zcash-android-wallet-sdk/issues/665
|
||||
// TODO [#665]: Recover from corrupted sapling-spend.params and sapling-output.params
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun validate(destinationDir: String): Boolean {
|
||||
@Throws(TransactionEncoderException.FetchParamsException::class)
|
||||
private suspend fun finalizeAndReportError(vararg files: File, message: String) {
|
||||
files.forEach {
|
||||
it.deleteSuspend()
|
||||
}
|
||||
message.also {
|
||||
twig(it)
|
||||
throw TransactionEncoderException.FetchParamsException(it)
|
||||
}
|
||||
}
|
||||
|
||||
internal suspend fun validate(destinationDir: File): Boolean {
|
||||
return arrayOf(
|
||||
ZcashSdk.SPEND_PARAM_FILE_NAME,
|
||||
ZcashSdk.OUTPUT_PARAM_FILE_NAME
|
||||
SPEND_PARAM_FILE_NAME,
|
||||
OUTPUT_PARAM_FILE_NAME
|
||||
).all { paramFileName ->
|
||||
File(destinationDir, paramFileName).existsSuspend()
|
||||
}.also {
|
||||
println("Param files ${if (!it) "did not" else ""} both exist!")
|
||||
twig("Param files ${if (!it) "did not" else ""} both exist!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sapling file parameters class to hold each sapling file attributes.
|
||||
* Sapling file parameter class to hold each sapling file attributes.
|
||||
*/
|
||||
internal data class SaplingFileParameters(
|
||||
val destinationDirectoryPath: String,
|
||||
val fileName: String,
|
||||
val fileMaxSizeBytes: Long
|
||||
internal data class SaplingParameters(
|
||||
val destinationDirectory: File,
|
||||
var fileName: String,
|
||||
val fileMaxSizeBytes: Long,
|
||||
var fileHash: String
|
||||
)
|
||||
|
||||
/**
|
||||
* Sapling param tool helper properties. The goal of this implementation is to ease its testing.
|
||||
*/
|
||||
internal data class SaplingParamToolProperties(
|
||||
val saplingParams: List<SaplingParameters>,
|
||||
val paramsDirectory: File,
|
||||
val paramsLegacyDirectory: File
|
||||
)
|
||||
|
|
|
@ -5,6 +5,9 @@ package cash.z.ecc.android.sdk.internal.ext
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.security.DigestInputStream
|
||||
import java.security.MessageDigest
|
||||
|
||||
internal suspend fun File.deleteSuspend() = withContext(Dispatchers.IO) { delete() }
|
||||
|
||||
|
@ -17,3 +20,37 @@ internal suspend fun File.canWriteSuspend() = withContext(Dispatchers.IO) { canW
|
|||
internal suspend fun File.renameToSuspend(dest: File) = withContext(Dispatchers.IO) { renameTo(dest) }
|
||||
|
||||
suspend fun File.deleteRecursivelySuspend() = withContext(Dispatchers.IO) { deleteRecursively() }
|
||||
|
||||
suspend fun File.listFilesSuspend(): Array<File>? = withContext(Dispatchers.IO) { listFiles() }
|
||||
|
||||
suspend fun File.inputStreamSuspend(): FileInputStream = withContext(Dispatchers.IO) { inputStream() }
|
||||
|
||||
suspend fun File.createNewFileSuspend() = withContext(Dispatchers.IO) { createNewFile() }
|
||||
|
||||
/**
|
||||
* Preferred buffer size. We use the same buffer size as BufferedInputStream does.
|
||||
*/
|
||||
private const val BUFFER_SIZE_BYTES_SIZE = 8192
|
||||
|
||||
/**
|
||||
* Encrypts File to SHA1 format.
|
||||
*
|
||||
* @return String SHA1 encryption of the input file
|
||||
*/
|
||||
suspend fun File.getSha1Hash(): String {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val messageDigest = MessageDigest.getInstance("SHA-1")
|
||||
inputStreamSuspend().use { fis ->
|
||||
DigestInputStream(fis, messageDigest).use { dis ->
|
||||
val buffer = ByteArray(BUFFER_SIZE_BYTES_SIZE)
|
||||
while (dis.read(buffer) >= 0) {
|
||||
// reading the whole buffered stream, which results in update on the message digest
|
||||
}
|
||||
return@withContext messageDigest.digest().joinToString(
|
||||
separator = "",
|
||||
transform = { "%02x".format(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@ import cash.z.ecc.android.sdk.ext.masked
|
|||
import cash.z.ecc.android.sdk.internal.SaplingParamTool
|
||||
import cash.z.ecc.android.sdk.internal.twig
|
||||
import cash.z.ecc.android.sdk.internal.twigTask
|
||||
import cash.z.ecc.android.sdk.jni.RustBackend
|
||||
import cash.z.ecc.android.sdk.jni.RustBackendWelding
|
||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Class responsible for encoding a transaction in a consistent way. This bridges the gap by
|
||||
|
@ -21,6 +21,7 @@ import cash.z.ecc.android.sdk.model.Zatoshi
|
|||
*/
|
||||
internal class WalletTransactionEncoder(
|
||||
private val rustBackend: RustBackendWelding,
|
||||
private val saplingParamTool: SaplingParamTool,
|
||||
private val repository: TransactionRepository
|
||||
) : TransactionEncoder {
|
||||
|
||||
|
@ -109,15 +110,12 @@ internal class WalletTransactionEncoder(
|
|||
memo: ByteArray? = byteArrayOf(),
|
||||
fromAccountIndex: Int = 0
|
||||
): Long {
|
||||
return twigTask(
|
||||
"creating transaction to spend $amount zatoshi to" +
|
||||
" ${toAddress.masked()} with memo $memo"
|
||||
) {
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
return twigTask("Creating transaction to spend $amount zatoshi to ${toAddress.masked()} with memo $memo") {
|
||||
saplingParamTool.ensureParams(rustBackend.saplingParamDir)
|
||||
twig("Params exist! Attempting to send.")
|
||||
try {
|
||||
val branchId = getConsensusBranchId()
|
||||
SaplingParamTool.ensureParams((rustBackend as RustBackend).pathParamsDir)
|
||||
twig("params exist! attempting to send with consensus branchId $branchId...")
|
||||
twig("Attempting to send with consensus branchId $branchId...")
|
||||
rustBackend.createToAddress(
|
||||
branchId,
|
||||
fromAccountIndex,
|
||||
|
@ -126,9 +124,15 @@ internal class WalletTransactionEncoder(
|
|||
amount.value,
|
||||
memo
|
||||
)
|
||||
} catch (t: Throwable) {
|
||||
twig("Caught exception while creating transaction ${t.message}, caused by: ${t.cause}.")
|
||||
throw t
|
||||
} catch (e: IOException) {
|
||||
twig("Caught IO exception while creating transaction ${e.message}, caused by: ${e.cause}.")
|
||||
throw e
|
||||
} catch (e: TransactionEncoderException.IncompleteScanException) {
|
||||
twig(
|
||||
"Caught TransactionEncoderException.IncompleteScanException while creating transaction" +
|
||||
" ${e.message}, caused by: ${e.cause}."
|
||||
)
|
||||
throw e
|
||||
}
|
||||
}.also { result ->
|
||||
twig("result of sendToAddress: $result")
|
||||
|
@ -140,22 +144,21 @@ internal class WalletTransactionEncoder(
|
|||
transparentSecretKey: String,
|
||||
memo: ByteArray? = byteArrayOf()
|
||||
): Long {
|
||||
return twigTask("creating transaction to shield all UTXOs") {
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
return twigTask("Creating transaction to shield all UTXOs.") {
|
||||
saplingParamTool.ensureParams(rustBackend.saplingParamDir)
|
||||
twig("Params exist! attempting to shield...")
|
||||
try {
|
||||
SaplingParamTool.ensureParams((rustBackend as RustBackend).pathParamsDir)
|
||||
twig("params exist! attempting to shield...")
|
||||
rustBackend.shieldToAddress(
|
||||
spendingKey,
|
||||
transparentSecretKey,
|
||||
memo
|
||||
)
|
||||
} catch (t: Throwable) {
|
||||
} catch (e: IOException) {
|
||||
// TODO [#680]: if this error matches: Insufficient balance (have 0, need 1000 including fee)
|
||||
// then consider custom error that says no UTXOs existed to shield
|
||||
// TODO [#680]: https://github.com/zcash/zcash-android-wallet-sdk/issues/680
|
||||
twig("Shield failed due to: ${t.message}, caused by: ${t.cause}.")
|
||||
throw t
|
||||
twig("Caught IO exception - shield failed due to: ${e.message}, caused by: ${e.cause}.")
|
||||
throw e
|
||||
}
|
||||
}.also { result ->
|
||||
twig("result of shieldToAddress: $result")
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package cash.z.ecc.android.sdk.jni
|
||||
|
||||
import cash.z.ecc.android.sdk.ext.ZcashSdk.OUTPUT_PARAM_FILE_NAME
|
||||
import cash.z.ecc.android.sdk.ext.ZcashSdk.SPEND_PARAM_FILE_NAME
|
||||
import cash.z.ecc.android.sdk.internal.SaplingParamTool
|
||||
import cash.z.ecc.android.sdk.internal.SdkDispatchers
|
||||
import cash.z.ecc.android.sdk.internal.ext.deleteSuspend
|
||||
import cash.z.ecc.android.sdk.internal.model.Checkpoint
|
||||
|
@ -26,7 +25,7 @@ internal class RustBackend private constructor(
|
|||
val birthdayHeight: BlockHeight,
|
||||
val dataDbFile: File,
|
||||
val cacheDbFile: File,
|
||||
val pathParamsDir: String
|
||||
override val saplingParamDir: File
|
||||
) : RustBackendWelding {
|
||||
|
||||
suspend fun clear(clearCacheDb: Boolean = true, clearDataDb: Boolean = true) {
|
||||
|
@ -236,8 +235,8 @@ internal class RustBackend private constructor(
|
|||
to,
|
||||
value,
|
||||
memo ?: ByteArray(0),
|
||||
"$pathParamsDir/$SPEND_PARAM_FILE_NAME",
|
||||
"$pathParamsDir/$OUTPUT_PARAM_FILE_NAME",
|
||||
File(saplingParamDir, SaplingParamTool.SPEND_PARAM_FILE_NAME).absolutePath,
|
||||
File(saplingParamDir, SaplingParamTool.OUTPUT_PARAM_FILE_NAME).absolutePath,
|
||||
networkId = network.id
|
||||
)
|
||||
}
|
||||
|
@ -255,8 +254,8 @@ internal class RustBackend private constructor(
|
|||
extsk,
|
||||
tsk,
|
||||
memo ?: ByteArray(0),
|
||||
"$pathParamsDir/$SPEND_PARAM_FILE_NAME",
|
||||
"$pathParamsDir/$OUTPUT_PARAM_FILE_NAME",
|
||||
File(saplingParamDir, SaplingParamTool.SPEND_PARAM_FILE_NAME).absolutePath,
|
||||
File(saplingParamDir, SaplingParamTool.OUTPUT_PARAM_FILE_NAME).absolutePath,
|
||||
networkId = network.id
|
||||
)
|
||||
}
|
||||
|
@ -356,9 +355,9 @@ internal class RustBackend private constructor(
|
|||
* function once, it is idempotent.
|
||||
*/
|
||||
suspend fun init(
|
||||
cacheDbPath: String,
|
||||
dataDbPath: String,
|
||||
paramsPath: String,
|
||||
cacheDbFile: File,
|
||||
dataDbFile: File,
|
||||
saplingParamsDir: File,
|
||||
zcashNetwork: ZcashNetwork,
|
||||
birthdayHeight: BlockHeight
|
||||
): RustBackend {
|
||||
|
@ -367,9 +366,9 @@ internal class RustBackend private constructor(
|
|||
return RustBackend(
|
||||
zcashNetwork,
|
||||
birthdayHeight,
|
||||
dataDbFile = File(dataDbPath),
|
||||
cacheDbFile = File(cacheDbPath),
|
||||
pathParamsDir = paramsPath
|
||||
dataDbFile = dataDbFile,
|
||||
cacheDbFile = cacheDbFile,
|
||||
saplingParamDir = saplingParamsDir
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import cash.z.ecc.android.sdk.model.WalletBalance
|
|||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||
import cash.z.ecc.android.sdk.type.UnifiedViewingKey
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Contract defining the exposed capabilities of the Rust backend.
|
||||
|
@ -18,6 +19,8 @@ internal interface RustBackendWelding {
|
|||
|
||||
val network: ZcashNetwork
|
||||
|
||||
val saplingParamDir: File
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
suspend fun createToAddress(
|
||||
consensusBranchId: Long,
|
||||
|
|
Loading…
Reference in New Issue