2021-04-09 19:29:29 -07:00
package cash.z.ecc.android.sdk.util
import androidx.test.platform.app.InstrumentationRegistry
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.toSeed
import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.SdkSynchronizer
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.db.entity.isPending
2021-10-13 07:20:13 -07:00
import cash.z.ecc.android.sdk.internal.Twig
2021-10-04 04:18:37 -07:00
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
2021-11-18 04:10:30 -08:00
import cash.z.ecc.android.sdk.internal.twig
2022-07-07 05:52:07 -07:00
import cash.z.ecc.android.sdk.model.WalletBalance
2022-06-21 16:34:42 -07:00
import cash.z.ecc.android.sdk.model.Zatoshi
2021-04-09 19:29:29 -07:00
import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.type.ZcashNetwork
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch
import kotlinx.coroutines.newFixedThreadPoolContext
2021-10-21 13:05:02 -07:00
import kotlinx.coroutines.runBlocking
2021-04-09 19:29:29 -07:00
import java.util.concurrent.TimeoutException
/ * *
* A simple wallet that connects to testnet for integration testing . The intention is that it is
* easy to drive and nice to use .
* /
class TestWallet (
2021-04-22 15:52:32 -07:00
val seedPhrase : String ,
val alias : String = " TestWallet " ,
val network : ZcashNetwork = ZcashNetwork . Testnet ,
val host : String = network . defaultHost ,
startHeight : Int ? = null ,
2022-06-23 05:31:02 -07:00
val port : Int = network . defaultPort
2021-04-09 19:29:29 -07:00
) {
constructor (
backup : Backups ,
network : ZcashNetwork = ZcashNetwork . Testnet ,
alias : String = " TestWallet "
) : this (
backup . seedPhrase ,
network = network ,
2021-04-29 12:57:13 -07:00
startHeight = if ( network == ZcashNetwork . Mainnet ) backup . mainnetBirthday else backup . testnetBirthday ,
2021-04-09 19:29:29 -07:00
alias = alias
)
val walletScope = CoroutineScope (
SupervisorJob ( ) + newFixedThreadPoolContext ( 3 , this . javaClass . simpleName )
)
2021-10-21 13:05:02 -07:00
// Although runBlocking isn't great, this usage is OK because this is only used within the
// automated tests
2021-04-09 19:29:29 -07:00
private val context = InstrumentationRegistry . getInstrumentation ( ) . context
private val seed : ByteArray = Mnemonics . MnemonicCode ( seedPhrase ) . toSeed ( )
2021-10-21 13:05:02 -07:00
private val shieldedSpendingKey =
runBlocking { DerivationTool . deriveSpendingKeys ( seed , network = network ) [ 0 ] }
private val transparentSecretKey =
runBlocking { DerivationTool . deriveTransparentSecretKey ( seed , network = network ) }
val initializer = runBlocking {
Initializer . new ( context ) { config ->
runBlocking { config . importWallet ( seed , startHeight , network , host , alias = alias ) }
}
2021-04-09 19:29:29 -07:00
}
2022-01-19 10:39:07 -08:00
val synchronizer : SdkSynchronizer = Synchronizer . newBlocking ( initializer ) as SdkSynchronizer
2021-04-09 19:29:29 -07:00
val service = ( synchronizer . processor . downloader . lightWalletService as LightWalletGrpcService )
2022-06-21 16:34:42 -07:00
val available get ( ) = synchronizer . saplingBalances . value ?. available
2021-10-21 13:05:02 -07:00
val shieldedAddress =
runBlocking { DerivationTool . deriveShieldedAddress ( seed , network = network ) }
val transparentAddress =
runBlocking { DerivationTool . deriveTransparentAddress ( seed , network = network ) }
2021-04-09 19:29:29 -07:00
val birthdayHeight get ( ) = synchronizer . latestBirthdayHeight
val networkName get ( ) = synchronizer . network . networkName
val connectionInfo get ( ) = service . connectionInfo . toString ( )
suspend fun transparentBalance ( ) : WalletBalance {
synchronizer . refreshUtxos ( transparentAddress , synchronizer . latestBirthdayHeight )
return synchronizer . getTransparentBalance ( transparentAddress )
}
suspend fun sync ( timeout : Long = - 1 ) : TestWallet {
val killSwitch = walletScope . launch {
if ( timeout > 0 ) {
delay ( timeout )
throw TimeoutException ( " Failed to sync wallet within ${timeout} ms " )
}
}
if ( ! synchronizer . isStarted ) {
twig ( " Starting sync " )
synchronizer . start ( walletScope )
} else {
twig ( " Awaiting next SYNCED status " )
}
// block until synced
synchronizer . status . first { it == Synchronizer . Status . SYNCED }
killSwitch . cancel ( )
twig ( " Synced! " )
return this
}
2022-06-21 16:34:42 -07:00
suspend fun send ( address : String = transparentAddress , memo : String = " " , amount : Zatoshi = Zatoshi ( 500L ) , fromAccountIndex : Int = 0 ) : TestWallet {
2021-04-22 15:52:32 -07:00
Twig . sprout ( " $alias sending " )
synchronizer . sendToAddress ( shieldedSpendingKey , amount , address , memo , fromAccountIndex )
2021-04-09 19:29:29 -07:00
. takeWhile { it . isPending ( ) }
. collect {
twig ( " Updated transaction: $it " )
}
2021-04-22 15:52:32 -07:00
Twig . clip ( " $alias sending " )
2021-04-09 19:29:29 -07:00
return this
}
suspend fun rewindToHeight ( height : Int ) : TestWallet {
2021-04-26 14:37:30 -07:00
synchronizer . rewindToNearestHeight ( height , false )
2021-04-09 19:29:29 -07:00
return this
}
suspend fun shieldFunds ( ) : TestWallet {
twig ( " checking $transparentAddress for transactions! " )
synchronizer . refreshUtxos ( transparentAddress , 935000 ) . let { count ->
twig ( " FOUND $count new UTXOs " )
}
synchronizer . getTransparentBalance ( transparentAddress ) . let { walletBalance ->
2022-06-21 16:34:42 -07:00
twig ( " FOUND utxo balance of total: ${walletBalance.total} available: ${walletBalance.available} " )
2021-04-09 19:29:29 -07:00
2022-06-21 16:34:42 -07:00
if ( walletBalance . available . value > 0L ) {
2021-04-09 19:29:29 -07:00
synchronizer . shieldFunds ( shieldedSpendingKey , transparentSecretKey )
. onCompletion { twig ( " done shielding funds " ) }
. catch { twig ( " Failed with $it " ) }
. collect ( )
}
}
return this
}
suspend fun join ( timeout : Long ? = null ) : TestWallet {
// block until stopped
twig ( " Staying alive until synchronizer is stopped! " )
if ( timeout != null ) {
twig ( " Scheduling a stop in ${timeout} ms " )
walletScope . launch {
delay ( timeout )
synchronizer . stop ( )
}
}
synchronizer . status . first { it == Synchronizer . Status . STOPPED }
twig ( " Stopped! " )
return this
}
companion object {
init {
Twig . enabled ( true )
}
}
2021-04-29 12:57:13 -07:00
enum class Backups ( val seedPhrase : String , val testnetBirthday : Int , val mainnetBirthday : Int ) {
// TODO: get the proper birthday values for these wallets
DEFAULT ( " column rhythm acoustic gym cost fit keen maze fence seed mail medal shrimp tell relief clip cannon foster soldier shallow refuse lunar parrot banana " , 1 _355 _928 , 1 _000 _000 ) ,
SAMPLE _WALLET ( " input frown warm senior anxiety abuse yard prefer churn reject people glimpse govern glory crumble swallow verb laptop switch trophy inform friend permit purpose " , 1 _330 _190 , 1 _000 _000 ) ,
DEV _WALLET ( " still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread " , 1 _000 _000 , 991645 ) ,
ALICE ( " quantum whisper lion route fury lunar pelican image job client hundred sauce chimney barely life cliff spirit admit weekend message recipe trumpet impact kitten " , 1 _330 _190 , 1 _000 _000 ) ,
BOB ( " canvas wine sugar acquire garment spy tongue odor hole cage year habit bullet make label human unit option top calm neutral try vocal arena " , 1 _330 _190 , 1 _000 _000 ) ,
2021-04-09 19:29:29 -07:00
;
}
}