Initial commit
This commit is contained in:
10
darkside-test-lib/src/androidTest/AndroidManifest.xml
Normal file
10
darkside-test-lib/src/androidTest/AndroidManifest.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="cash.z.ecc.android.sdk.darkside">
|
||||
|
||||
<!-- For code coverage -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application android:name="androidx.multidex.MultiDexApplication" />
|
||||
</manifest>
|
||||
@@ -0,0 +1,88 @@
|
||||
package cash.z.ecc.android.sdk.darkside // package cash.z.ecc.android.sdk.integration
|
||||
//
|
||||
// import cash.z.ecc.android.sdk.test.ScopedTest
|
||||
// import cash.z.ecc.android.sdk.internal.twigTask
|
||||
// import cash.z.ecc.android.sdk.darkside.test.DarksideTestCoordinator
|
||||
// import kotlinx.coroutines.runBlocking
|
||||
// import org.junit.BeforeClass
|
||||
// import org.junit.Test
|
||||
//
|
||||
// class MultiAccountIntegrationTest : ScopedTest() {
|
||||
//
|
||||
// /**
|
||||
// * Test multiple viewing keys by doing the following:
|
||||
// *
|
||||
// * - sync "account A" with 100 test blocks containing:
|
||||
// * (in zatoshi) four 100_000 notes and one 10_000 note
|
||||
// * - import a viewing key for "account B"
|
||||
// * - send a 10_000 zatoshi transaction from A to B
|
||||
// * - include that tx in the next block and mine that block (on the darkside), then scan it
|
||||
// * - verify that A's balance reflects a single 100_000 note being spent but pending confirmations
|
||||
// * - advance the chain by 9 more blocks to reach 10 confirmations
|
||||
// * - verify that the change from the spent note is reflected in A's balance
|
||||
// * - check B's balance and verify that it received the full 10_000 (i.e. that A paid the mining fee)
|
||||
// *
|
||||
// * Although we sent funds to an address, the synchronizer has both spending keys so it is able
|
||||
// * to track transactions for both addresses!
|
||||
// */
|
||||
// @Test
|
||||
// fun testViewingKeyImport() = runBlocking {
|
||||
// validatePreConditions()
|
||||
//
|
||||
// with(sithLord) {
|
||||
// twigTask("importing viewing key") {
|
||||
// synchronizer.importViewingKey(secondKey)
|
||||
// }
|
||||
//
|
||||
// twigTask("Sending funds") {
|
||||
// sithLord.createAndSubmitTx(10_000, secondAddress, "multi-account works!")
|
||||
// chainMaker.applyPendingTransactions(663251)
|
||||
// await(targetHeight = 663251)
|
||||
// }
|
||||
// // verify that the transaction block height was scanned
|
||||
// validator.validateMinHeightScanned(663251)
|
||||
//
|
||||
// // balance before confirmations (the large 100_000 note gets selected)
|
||||
// validator.validateBalance(310_000)
|
||||
//
|
||||
// // add remaining confirmations so that funds become spendable and await until they're scanned
|
||||
// chainMaker.advanceBy(9)
|
||||
// await(targetHeight = 663260)
|
||||
//
|
||||
// // balance after confirmations
|
||||
// validator.validateBalance(390_000)
|
||||
//
|
||||
// // check the extra viewing key balance!!!
|
||||
// // accountIndex 1 corresponds to the imported viewingKey for the address where we sent the funds!
|
||||
// validator.validateBalance(available = 10_000, accountIndex = 1)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Verify that before the integration test begins, the wallet is synced up to the expected block
|
||||
// * and contains the expected balance.
|
||||
// */
|
||||
// private fun validatePreConditions() {
|
||||
// with(sithLord) {
|
||||
// twigTask("validating preconditions") {
|
||||
// validator.validateMinHeightScanned(663250)
|
||||
// validator.validateMinBalance(410_000)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// companion object {
|
||||
// private val sithLord = DarksideTestCoordinator()
|
||||
// private val secondAddress = "zs15tzaulx5weua5c7l47l4pku2pw9fzwvvnsp4y80jdpul0y3nwn5zp7tmkcclqaca3mdjqjkl7hx"
|
||||
// private val secondKey = "zxviews1q0w208wwqqqqpqyxp978kt2qgq5gcyx4er907zhczxpepnnhqn0a47ztefjnk65w2573v7g5fd3hhskrg7srpxazfvrj4n2gm4tphvr74a9xnenpaxy645dmuqkevkjtkf5jld2f7saqs3xyunwquhksjpqwl4zx8zj73m8gk2d5d30pck67v5hua8u3chwtxyetmzjya8jdjtyn2aum7au0agftfh5q9m4g596tev9k365s84jq8n3laa5f4palt330dq0yede053sdyfv6l"
|
||||
//
|
||||
// @BeforeClass
|
||||
// @JvmStatic
|
||||
// fun startAllTests() {
|
||||
// sithLord.enterTheDarkside()
|
||||
// sithLord.chainMaker.makeSimpleChain()
|
||||
// sithLord.startSync(classScope).await()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,75 @@
|
||||
package cash.z.ecc.android.sdk.darkside
|
||||
|
||||
// import cash.z.ecc.android.sdk.SdkSynchronizer
|
||||
// import cash.z.ecc.android.sdk.db.entity.isSubmitSuccess
|
||||
// import cash.z.ecc.android.sdk.test.ScopedTest
|
||||
// import cash.z.ecc.android.sdk.internal.twig
|
||||
// import cash.z.ecc.android.sdk.darkside.test.DarksideTestCoordinator
|
||||
// import kotlinx.coroutines.Job
|
||||
// import kotlinx.coroutines.delay
|
||||
// import kotlinx.coroutines.flow.launchIn
|
||||
// import kotlinx.coroutines.flow.onEach
|
||||
// import kotlinx.coroutines.runBlocking
|
||||
// import org.junit.Assert.assertEquals
|
||||
// import org.junit.BeforeClass
|
||||
// import org.junit.Test
|
||||
|
||||
// class MultiAccountTest : ScopedTest() {
|
||||
//
|
||||
// @Test
|
||||
// fun testTargetBlock_sanityCheck() {
|
||||
// with(sithLord) {
|
||||
// validator.validateMinHeightScanned(663250)
|
||||
// validator.validateMinBalance(200000)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testTargetBlock_send() = runBlocking {
|
||||
// with(sithLord) {
|
||||
//
|
||||
// twig("<importing viewing key><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>")
|
||||
// synchronizer.importViewingKey(secondKey)
|
||||
// twig("<DONE importing viewing key><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>")
|
||||
//
|
||||
// twig("IM GONNA SEND!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
||||
// sithLord.sendAndWait(testScope, spendingKey, 10000, secondAddress, "multi-account works!")
|
||||
// chainMaker.applySentTransactions()
|
||||
// await(targetHeight = 663251)
|
||||
//
|
||||
// twig("done waiting for 663251!")
|
||||
// validator.validateMinHeightScanned(663251)
|
||||
//
|
||||
// // balance before confirmations
|
||||
// validator.validateBalance(310000)
|
||||
//
|
||||
// // add remaining confirmations
|
||||
// chainMaker.advanceBy(9)
|
||||
// await(targetHeight = 663260)
|
||||
//
|
||||
// // balance after confirmations
|
||||
// validator.validateBalance(390000)
|
||||
//
|
||||
// // check the extra viewing key balance!!!
|
||||
// val account1Balance = (synchronizer as SdkSynchronizer).processor.getBalanceInfo(1)
|
||||
// assertEquals(10000, account1Balance.totalZatoshi)
|
||||
// twig("done waiting for 663261!")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// companion object {
|
||||
// private const val blocksUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"
|
||||
// private val sithLord = DarksideTestCoordinator()
|
||||
// private val secondAddress = "zs15tzaulx5weua5c7l47l4pku2pw9fzwvvnsp4y80jdpul0y3nwn5zp7tmkcclqaca3mdjqjkl7hx"
|
||||
// private val secondKey = "zxviews1q0w208wwqqqqpqyxp978kt2qgq5gcyx4er907zhczxpepnnhqn0a47ztefjnk65w2573v7g5fd3hhskrg7srpxazfvrj4n2gm4tphvr74a9xnenpaxy645dmuqkevkjtkf5jld2f7saqs3xyunwquhksjpqwl4zx8zj73m8gk2d5d30pck67v5hua8u3chwtxyetmzjya8jdjtyn2aum7au0agftfh5q9m4g596tev9k365s84jq8n3laa5f4palt330dq0yede053sdyfv6l"
|
||||
//
|
||||
// @BeforeClass
|
||||
// @JvmStatic
|
||||
// fun startAllTests() {
|
||||
// sithLord.enterTheDarkside()
|
||||
// sithLord.chainMaker.simpleChain()
|
||||
// sithLord.startSync(classScope).await()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,196 @@
|
||||
package cash.z.ecc.android.sdk.darkside // package cash.z.ecc.android.sdk.integration
|
||||
//
|
||||
// import cash.z.ecc.android.sdk.test.ScopedTest
|
||||
// import cash.z.ecc.android.sdk.internal.twig
|
||||
// import cash.z.ecc.android.sdk.internal.twigTask
|
||||
// import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
||||
// import cash.z.ecc.android.sdk.darkside.test.DarksideTestCoordinator
|
||||
// import cash.z.ecc.android.sdk.util.SimpleMnemonics
|
||||
// import cash.z.wallet.sdk.rpc.CompactFormats
|
||||
// import cash.z.wallet.sdk.rpc.Service
|
||||
// import io.grpc.*
|
||||
// import kotlinx.coroutines.delay
|
||||
// import kotlinx.coroutines.runBlocking
|
||||
// import org.junit.Assert.assertEquals
|
||||
// import org.junit.BeforeClass
|
||||
// import org.junit.Ignore
|
||||
// import org.junit.Test
|
||||
// import java.util.concurrent.TimeUnit
|
||||
|
||||
// class MultiRecipientIntegrationTest : ScopedTest() {
|
||||
//
|
||||
// @Test
|
||||
// @Ignore
|
||||
// fun testMultiRecipients() = runBlocking {
|
||||
// with(sithLord) {
|
||||
// val m = SimpleMnemonics()
|
||||
// randomPhrases.map {
|
||||
// m.toSeed(it.toCharArray())
|
||||
// }.forEach { seed ->
|
||||
// twig("ZyZ4: I've got a seed $seed")
|
||||
// initializer.apply {
|
||||
// // delay(250)
|
||||
// twig("VKZyZ: ${deriveViewingKeys(seed)[0]}")
|
||||
// // delay(500)
|
||||
// twig("SKZyZ: ${deriveSpendingKeys(seed)[0]}")
|
||||
// // delay(500)
|
||||
// twig("ADDRZyZ: ${deriveAddress(seed)}")
|
||||
// // delay(250)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// delay(500)
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun loadVks() = runBlocking {
|
||||
// with(sithLord) {
|
||||
// viewingKeys.forEach {
|
||||
// twigTask("importing viewing key") {
|
||||
// synchronizer.importViewingKey(it)
|
||||
// }
|
||||
// }
|
||||
// twigTask("Sending funds") {
|
||||
// createAndSubmitTx(10_000, addresses[0], "multi-account works!")
|
||||
// chainMaker.applyPendingTransactions(663251)
|
||||
// await(targetHeight = 663251)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // private fun sendToMyHomies() {
|
||||
// // twig("uno")
|
||||
// // val rustPoc = LightWalletGrpcService(localChannel)
|
||||
// // twig("dos")
|
||||
// // val pong: Int = rustPoc.getLatestBlockHeight()
|
||||
// // twig("tres")
|
||||
// // assertEquals(800000, pong)
|
||||
// // }
|
||||
//
|
||||
//
|
||||
// private fun sendToMyHomies0() {
|
||||
// val rustPoc = LocalWalletGrpcService(localChannel)
|
||||
// val pong: Service.PingResponse = rustPoc.sendMoney(Service.PingResponse.newBuilder().setEntry(10).setEntry(11).build())
|
||||
// assertEquals(pong.entry, 12)
|
||||
// }
|
||||
//
|
||||
// object localChannel : ManagedChannel() {
|
||||
// private var _isShutdown = false
|
||||
// get() {
|
||||
// twig("zyz: returning _isShutdown")
|
||||
// return field
|
||||
// }
|
||||
// private var _isTerminated = false
|
||||
// get() {
|
||||
// twig("zyz: returning _isTerminated")
|
||||
// return field
|
||||
// }
|
||||
//
|
||||
// override fun <RequestT : Any?, ResponseT : Any?> newCall(
|
||||
// methodDescriptor: MethodDescriptor<RequestT, ResponseT>?,
|
||||
// callOptions: CallOptions?
|
||||
// ): ClientCall<RequestT, ResponseT> {
|
||||
// twig("zyz: newCall")
|
||||
// return LocalCall()
|
||||
// }
|
||||
//
|
||||
// override fun isTerminated() = _isTerminated
|
||||
//
|
||||
// override fun authority(): String {
|
||||
// twig("zyz: authority")
|
||||
// return "none"
|
||||
// }
|
||||
//
|
||||
// override fun shutdown(): ManagedChannel {
|
||||
// twig("zyz: shutdown")
|
||||
// _isShutdown = true
|
||||
// return this
|
||||
// }
|
||||
//
|
||||
// override fun isShutdown() = _isShutdown
|
||||
//
|
||||
// override fun shutdownNow() = shutdown()
|
||||
//
|
||||
// override fun awaitTermination(timeout: Long, unit: TimeUnit?): Boolean {
|
||||
// twig("zyz: awaitTermination")
|
||||
// _isTerminated = true
|
||||
// return _isTerminated
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// class LocalCall<RequestT, ResponseT> : ClientCall<RequestT, ResponseT>() {
|
||||
// override fun sendMessage(message: RequestT) {
|
||||
// twig("zyz: sendMessage: $message")
|
||||
// }
|
||||
//
|
||||
// override fun halfClose() {
|
||||
// twig("zyz: halfClose")
|
||||
// }
|
||||
//
|
||||
// override fun start(responseListener: Listener<ResponseT>?, headers: Metadata?) {
|
||||
// twig("zyz: start")
|
||||
// responseListener?.onMessage(Service.BlockID.newBuilder().setHeight(800000).build() as? ResponseT)
|
||||
// responseListener?.onClose(Status.OK, headers)
|
||||
// }
|
||||
//
|
||||
// override fun cancel(message: String?, cause: Throwable?) {
|
||||
// twig("zyz: cancel: $message caused by $cause")
|
||||
// }
|
||||
//
|
||||
// override fun request(numMessages: Int) {
|
||||
// twig("zyz: request $numMessages")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private fun sendToMyHomies1() = runBlocking {
|
||||
// with(sithLord) {
|
||||
// twigTask("Sending funds") {
|
||||
// // createAndSubmitTx(200_000, addresses[0], "multi-account works!")
|
||||
// chainMaker.applyPendingTransactions(663251)
|
||||
// await(targetHeight = 663251)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// companion object {
|
||||
// private val sithLord = DarksideTestCoordinator(, "MultiRecipientInRust")
|
||||
//
|
||||
// private val randomPhrases = listOf(
|
||||
// "profit save black expose rude feature early rocket alter borrow finish october few duty flush kick spell bean burden enforce bitter theme silent uphold",
|
||||
// "unit ice dial annual duty feature smoke expose hard joy globe just accuse inner fog cash neutral forum strategy crash subject hurdle lecture sand",
|
||||
// "average talent frozen work brand output major soldier witness keen brown bind indicate burden furnace long crime joke inhale chronic ordinary renew boat flame",
|
||||
// "echo viable panic unaware stay magnet cake museum yellow abandon mountain height lunch advance tongue market bamboo cushion okay morning minute icon obtain december",
|
||||
// "renew enlist travel stand trust execute decade surge follow push student school focus woman ripple movie that bitter plug same index wife spread differ"
|
||||
// )
|
||||
//
|
||||
// private val viewingKeys = listOf(
|
||||
// "zxviews1qws7ryw7qqqqpqq77dmhl9tufzdsgy8hcjq8kxjtgkfwwgqn4a26ahmhmjqueptd2pmq3f73pm8uaa25aze5032qw4dppkx4l625xcjcm94d5e65fcq4j2uptnjuqpyu2rvud88dtjwseglgzfe5l4te2xw62yq4tv62d2f6kl4706c6dmfxg2cmsdlzlt9ykpvacaterq4alljr3efke7k46xcrg4pxc02ezj0txwqjjve23nqqp7t5n5qat4d8569krxgkcd852uqg2t2vn",
|
||||
// "zxviews1qdtp7dwfqqqqpqq3zxegnzc6qtacjp4m6qhyz7typdw9h9smra3rn322dkhyfg8kktk66k7zaj9tt5j6e58enx89pwry4rxwmcuzqyxlsap965r5gxpt604chmjyuhder6xwu3tx0h608as5sgxapqdqa6v6hy6qzh9fft0ns3cj9f8zrhu0ukzf9gn2arr02kzdct0jh5ee3zjch3xscjv34pzkgpueuq0pyl706alssuchqu4jmjm22fcq3htlwxt3f3hdytne7mgscrz5m",
|
||||
// "zxviews1qvfmgpzjqqqqpqqnpl2s9n774mrv72zsuw73km9x6ax2s26d0d0ua20nuxvkexa4lq5fsc6psl8csspyqrlwfeuele5crlwpyjufgkzyy6ffw8hc52hn04jzru6mntms8c2cm255gu200zx4pmz06k3s90jatwehazl465tf6uyj6whwarpcca9exzr7wzltelq5tusn3x3jchjyk6cj09xyctjzykp902w4x23zdsf46d3fn9rtkgm0rmek296c5nhuzf99a2x6umqr804k9",
|
||||
// "zxviews1qv85jn3hqqqqpq9jam3g232ylvvhy8e5vdhp0x9zjppr49sw6awwrm3a3d8l9j9es2ed9h29r6ta5tzt53j2y0ex84lzns0thp7n9wzutjapq29chfewqz34q5g6545f8jf0e69jcg9eyv66s8pt3y5dwxg9nrezz8q9j9fwxryeleayay6m09zpt0dem8hkazlw5jk6gedrakp9z7wzq2ptf6aqkft6z02mtrnq4a5pguwp4m8xkh52wz0r3naeycnqllnvsn8ag5q73pqgd",
|
||||
// "zxviews1qwhel8pxqqqqpqxjl3cqu2z8hu0tqdd5qchkrdtsjuce9egdqlpu7eff2rn3gknm0msw7ug6qp4ynppscvv6hfm2nkf42lhz8la5et3zsej84xafcn0xdd9ms452hfjp4tljshtffscsl68wgdv3j5nnelxsdcle5rnwkuz6lvvpqs7s2x0cnhemhnwzhx5ccakfgxfym0w8dxglq4h6pwukf2az6lcm38346qc5s9rgx6s988fr0kxnqg0c6g6zlxa2wpc7jh0gz7q4ysx0l"
|
||||
// )
|
||||
// private val spendingKeys = listOf(
|
||||
// "secret-extended-key-main1qws7ryw7qqqqpqq77dmhl9tufzdsgy8hcjq8kxjtgkfwwgqn4a26ahmhmjqueptd2pt49qhm63lt8v93tlqzw7psmkvqqfm6xdnc2qwkflfcenqs7s4sj2yn0c75n982wjrf5k5h37vt3wxwr3pqnjk426lltctrms2uqmqgkl4706c6dmfxg2cmsdlzlt9ykpvacaterq4alljr3efke7k46xcrg4pxc02ezj0txwqjjve23nqqp7t5n5qat4d8569krxgkcd852uqxj5ljt",
|
||||
// "secret-extended-key-main1qdtp7dwfqqqqpqq3zxegnzc6qtacjp4m6qhyz7typdw9h9smra3rn322dkhyfg8kk26p0fcjuklryw0ed6falf6c7dwqehleca0xf6m6tlnv5zdjx7lqs4xmseqjz0fvk273aczatxxjaqmy3kv8wtzcc6pf6qtrjy5g2mqgs3cj9f8zrhu0ukzf9gn2arr02kzdct0jh5ee3zjch3xscjv34pzkgpueuq0pyl706alssuchqu4jmjm22fcq3htlwxt3f3hdytne7mgacmaq6",
|
||||
// "secret-extended-key-main1qvfmgpzjqqqqpqqnpl2s9n774mrv72zsuw73km9x6ax2s26d0d0ua20nuxvkexa4lzc4n8a3zfvyn2qns37fx00avdtjewghmxz5nc2ey738nrpu4pqqnwysmcls5yek94lf03d5jtsa25nmuln4xjvu6e4g0yrr6xesp9cr6uyj6whwarpcca9exzr7wzltelq5tusn3x3jchjyk6cj09xyctjzykp902w4x23zdsf46d3fn9rtkgm0rmek296c5nhuzf99a2x6umqvf4man",
|
||||
// "secret-extended-key-main1qv85jn3hqqqqpq9jam3g232ylvvhy8e5vdhp0x9zjppr49sw6awwrm3a3d8l9j9estq9a548lguf0n9fsjs7c96uaymhysuzeek5eg8un0fk8umxszxstm0xfq77x68yjk4t4j7h2xqqjf8nmkx0va3cphnhxpvd0l5dhzgyxryeleayay6m09zpt0dem8hkazlw5jk6gedrakp9z7wzq2ptf6aqkft6z02mtrnq4a5pguwp4m8xkh52wz0r3naeycnqllnvsn8ag5qru36vk",
|
||||
// "secret-extended-key-main1qwhel8pxqqqqpqxjl3cqu2z8hu0tqdd5qchkrdtsjuce9egdqlpu7eff2rn3gknm0mdwr9358t3dlcf47vakdwewxy64k7ds7y3k455rfch7s2x8mfesjsxptyfvc9heme3zj08wwdk4l9mwce92lvrl797wmmddt65ygwcqlvvpqs7s2x0cnhemhnwzhx5ccakfgxfym0w8dxglq4h6pwukf2az6lcm38346qc5s9rgx6s988fr0kxnqg0c6g6zlxa2wpc7jh0gz7qx7zl33"
|
||||
// )
|
||||
// private val addresses = listOf(
|
||||
// "zs1d8lenyz7uznnna6ttmj6rk9l266989f78c3d79f0r6r28hn0gc9fzdktrdnngpcj8wr2cd4zcq2",
|
||||
// "zs13x79khp5z0ydgnfue8p88fjnrjxtnz0gwxyef525gd77p72nqh7zr447n6klgr5yexzp64nc7hf",
|
||||
// "zs1jgvqpsyzs90hlqz85qry3zv52keejgx0f4pnljes8h4zs96zcxldu9llc03dvhkp6ds67l4s0d5",
|
||||
// "zs1lr428hhedq3yk8n2wr378e6ua3u3r4ma5a8dqmf3r64y96vww5vh6327jfudtyt7v3eqw22c2t6",
|
||||
// "zs1hy7mdwl6y0hwxts6a5lca2xzlr0p8v5tkvvz7jfa4d04lx5uedg6ya8fmthywujacx0acvfn837"
|
||||
// )
|
||||
//
|
||||
// @BeforeClass
|
||||
// @JvmStatic
|
||||
// fun startAllTests() {
|
||||
// sithLord.enterTheDarkside()
|
||||
// sithLord.chainMaker.makeSimpleChain()
|
||||
// sithLord.startSync(classScope).await()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,96 @@
|
||||
package cash.z.ecc.android.sdk.darkside // package cash.z.ecc.android.sdk.integration
|
||||
//
|
||||
// import cash.z.ecc.android.sdk.test.ScopedTest
|
||||
// import cash.z.ecc.android.sdk.darkside.test.DarksideTestCoordinator
|
||||
// import org.junit.Before
|
||||
// import org.junit.BeforeClass
|
||||
// import org.junit.Test
|
||||
//
|
||||
// class OutboundTransactionsTest : ScopedTest() {
|
||||
//
|
||||
// @Before
|
||||
// fun beforeEachTest() {
|
||||
// testCoordinator.clearUnminedTransactions()
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testSendIncrementsTransaction() {
|
||||
// validator.validateTransactionCount(initialTxCount)
|
||||
// testCoordinator.sendTransaction(txAmount).awaitSync()
|
||||
// validator.validatTransactionCount(initialTxCount + 1)
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testSendReducesBalance() {
|
||||
// validator.validateBalance(initialBalance)
|
||||
// testCoordinator.sendTransaction(txAmount).awaitSync()
|
||||
// validator.validateBalanceLessThan(initialBalance)
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testTransactionPending() {
|
||||
// testCoordinator.sendTransaction(txAmount).awaitSync()
|
||||
// validator.validateTransactionPending(testCoordinator.lastTransactionId)
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testTransactionConfirmations_1() {
|
||||
// testCoordinator.sendTransaction(txAmount).generateNextBlock().awaitSync()
|
||||
// validator.validateConfirmations(testCoordinator.lastTransactionId, 1)
|
||||
// validator.validateBalanceLessThan(initialBalance - txAmount)
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testTransactionConfirmations_9() {
|
||||
// testCoordinator.sendTransaction(txAmount).generateNextBlock().advanceBlocksBy(8).awaitSync()
|
||||
// validator.validateConfirmations(testCoordinator.lastTransactionId, 9)
|
||||
// validator.validateBalanceLessThan(initialBalance - txAmount)
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testTransactionConfirmations_10() {
|
||||
// testCoordinator.sendTransaction(txAmount).generateNextBlock().advanceBlocksBy(9).awaitSync()
|
||||
// validator.validateConfirmations(testCoordinator.lastTransactionId, 10)
|
||||
// validator.validateBalance(initialBalance - txAmount)
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testTransactionExpiration() {
|
||||
// validator.validateBalance(initialBalance)
|
||||
//
|
||||
// // pending initially
|
||||
// testCoordinator.sendTransaction(txAmount).awaitSync()
|
||||
// val id = testCoordinator.lastTransactionId
|
||||
// validator.validateTransactionPending(id)
|
||||
//
|
||||
// // still pending after 9 blocks
|
||||
// testCoordinator.advanceBlocksBy(9).awaitSync()
|
||||
// validator.validateTransactionPending(id)
|
||||
// validator.validateBalanceLessThan(initialBalance)
|
||||
//
|
||||
// // expired after 10 blocks
|
||||
// testCoordinator.advanceBlocksBy(1).awaitSync()
|
||||
// validator.validateTransactionExpired(id)
|
||||
//
|
||||
// validator.validateBalance(initialBalance)
|
||||
// }
|
||||
//
|
||||
//
|
||||
//
|
||||
// companion object {
|
||||
// private const val blocksUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"
|
||||
// private const val initialBalance = 1.234
|
||||
// private const val txAmount = 1.1
|
||||
// private const val initialTxCount = 3
|
||||
// private val testCoordinator = DarksideTestCoordinator()
|
||||
// private val validator = testCoordinator.validator
|
||||
//
|
||||
// @BeforeClass
|
||||
// @JvmStatic
|
||||
// fun startAllTests() {
|
||||
// testCoordinator
|
||||
// .enterTheDarkside()
|
||||
// .resetBlocks(blocksUrl)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,25 @@
|
||||
package cash.z.ecc.android.sdk.darkside
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import cash.z.ecc.android.sdk.darkside.test.DarksideTest
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
/**
|
||||
* Integration test to run in order to catch any regressions in transparent behavior.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class TransparentIntegrationTest : DarksideTest() {
|
||||
@Before
|
||||
fun setup() = runOnce {
|
||||
sithLord.await()
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("This test is broken")
|
||||
fun sanityTest() {
|
||||
validator.validateTxCount(5)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package cash.z.ecc.android.sdk.darkside.reorgs
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import cash.z.ecc.android.sdk.darkside.test.DarksideTestCoordinator
|
||||
import cash.z.ecc.android.sdk.darkside.test.ScopedTest
|
||||
import cash.z.ecc.android.sdk.internal.twig
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class InboundTxTests : ScopedTest() {
|
||||
|
||||
@Test
|
||||
fun testTargetBlock_downloaded() {
|
||||
validator.validateMinHeightDownloaded(firstBlock)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTargetBlock_scanned() {
|
||||
validator.validateMinHeightScanned(targetTxBlock - 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLatestHeight() {
|
||||
validator.validateLatestHeight(targetTxBlock - 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTxCountInitial() {
|
||||
validator.validateTxCount(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTxCountAfter() {
|
||||
twig("ADDING TRANSACTIONS!!!")
|
||||
// add 2 transactions to block 663188 and 'mine' that block
|
||||
addTransactions(targetTxBlock, tx663174, tx663188)
|
||||
sithLord.await(timeout = 30_000L, targetHeight = targetTxBlock)
|
||||
validator.validateTxCount(2)
|
||||
}
|
||||
|
||||
private fun addTransactions(targetHeight: Int, vararg txs: String) {
|
||||
val overwriteBlockCount = 5
|
||||
chainMaker
|
||||
// .stageEmptyBlocks(targetHeight, overwriteBlockCount)
|
||||
.stageTransactions(targetHeight, *txs)
|
||||
.applyTipHeight(targetHeight)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val blocksUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"
|
||||
private const val tx663174 = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/0821a89be7f2fc1311792c3fa1dd2171a8cdfb2effd98590cbd5ebcdcfcf491f.txt"
|
||||
private const val tx663188 = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/15a677b6770c5505fb47439361d3d3a7c21238ee1a6874fdedad18ae96850590.txt"
|
||||
private const val txIndexReorg = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/tx-index-reorg/t1.txt"
|
||||
private val txSend = arrayOf(
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/t-shielded-spend.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/c9e35e6ff444b071d63bf9bab6480409d6361760445c8a28d24179adb35c2495.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/72a29d7db511025da969418880b749f7fc0fc910cdb06f52193b5fa5c0401d9d.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/ff6ea36765dc29793775c7aa71de19fca039c5b5b873a0497866e9c4bc48af01.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/34e507cab780546f980176f3ff2695cd404917508c7e5ee18cc1d2ff3858cb08.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/6edf869063eccff3345676b0fed9f1aa6988fb2524e3d9ca7420a13cfadcd76c.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/de97394ae220c28a33ba78b944e82dabec8cb404a4407650b134b3d5950358c0.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/4eaa902279f8380914baf5bcc470d8b7c11d84fda809f67f517a7cb48912b87b.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/sent/73c5edf8ffba774d99155121ccf07e67fbcf14284458f7e732751fea60d3bcbc.txt"
|
||||
)
|
||||
|
||||
private val txRecv = arrayOf(
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/8f064d23c66dc36e32445e5f3b50e0f32ac3ddb78cff21fb521eb6c19c07c99a.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/15a677b6770c5505fb47439361d3d3a7c21238ee1a6874fdedad18ae96850590.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/d2e7be14bbb308f9d4d68de424d622cbf774226d01cd63cc6f155fafd5cd212c.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/e6566be3a4f9a80035dab8e1d97e40832a639e3ea938fb7972ea2f8482ff51ce.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/0821a89be7f2fc1311792c3fa1dd2171a8cdfb2effd98590cbd5ebcdcfcf491f.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/e9527891b5d43d1ac72f2c0a3ac18a33dc5a0529aec04fa600616ed35f8123f8.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/4dcc95dd0a2f1f51bd64bb9f729b423c6de1690664a1b6614c75925e781662f7.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/75f2cdd2ff6a94535326abb5d9e663d53cbfa5f31ebb24b4d7e420e9440d41a2.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/7690c8ec740c1be3c50e2aedae8bf907ac81141ae8b6a134c1811706c73f49a6.txt",
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/71935e29127a7de0b96081f4c8a42a9c11584d83adedfaab414362a6f3d965cf.txt"
|
||||
)
|
||||
|
||||
private const val firstBlock = 663150
|
||||
private const val targetTxBlock = 663188
|
||||
private const val lastBlockHash = "2fc7b4682f5ba6ba6f86e170b40f0aa9302e1d3becb2a6ee0db611ff87835e4a"
|
||||
private val sithLord = DarksideTestCoordinator()
|
||||
private val validator = sithLord.validator
|
||||
private val chainMaker = sithLord.chainMaker
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun startAllTests() {
|
||||
sithLord.enterTheDarkside()
|
||||
|
||||
chainMaker
|
||||
.resetBlocks(blocksUrl, startHeight = firstBlock, tipHeight = targetTxBlock)
|
||||
.stageEmptyBlocks(firstBlock + 1, 100)
|
||||
.applyTipHeight(targetTxBlock - 1)
|
||||
|
||||
sithLord.synchronizer.start(classScope)
|
||||
sithLord.await()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package cash.z.ecc.android.sdk.darkside.reorgs // package cash.z.ecc.android.sdk.integration
|
||||
//
|
||||
// import cash.z.ecc.android.sdk.test.ScopedTest
|
||||
// import cash.z.ecc.android.sdk.darkside.test.DarksideTestCoordinator
|
||||
// import org.junit.Assert.assertFalse
|
||||
// import org.junit.Assert.assertTrue
|
||||
// import org.junit.BeforeClass
|
||||
// import org.junit.Test
|
||||
//
|
||||
// class ReorgBasicTest : ScopedTest() {
|
||||
//
|
||||
// private var callbackTriggered = false
|
||||
//
|
||||
// @Test
|
||||
// fun testReorgChangesBlockHash() {
|
||||
// testCoordinator.resetBlocks(blocksUrl)
|
||||
// validator.validateBlockHash(targetHeight, targetHash)
|
||||
// testCoordinator.updateBlocks(reorgUrl)
|
||||
// validator.validateBlockHash(targetHeight, reorgHash)
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testReorgTriggersCallback() {
|
||||
// callbackTriggered = false
|
||||
// testCoordinator.resetBlocks(blocksUrl)
|
||||
// testCoordinator.synchronizer.registerReorgListener(reorgCallback)
|
||||
// assertFalse(callbackTriggered)
|
||||
//
|
||||
// testCoordinator.updateBlocks(reorgUrl).awaitSync()
|
||||
// assertTrue(callbackTriggered)
|
||||
// testCoordinator.synchronizer.unregisterReorgListener()
|
||||
// }
|
||||
//
|
||||
// fun reorgCallback() {
|
||||
// callbackTriggered = true
|
||||
// }
|
||||
//
|
||||
// companion object {
|
||||
// private const val blocksUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"
|
||||
// private const val reorgUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/after-small-reorg.txt"
|
||||
// private const val targetHeight = 663250
|
||||
// private const val targetHash = "tbd"
|
||||
// private const val reorgHash = "tbd"
|
||||
// private val testCoordinator = DarksideTestCoordinator()
|
||||
// private val validator = testCoordinator.validator
|
||||
//
|
||||
// @BeforeClass
|
||||
// @JvmStatic
|
||||
// fun startAllTests() {
|
||||
// testCoordinator.enterTheDarkside()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,239 @@
|
||||
package cash.z.ecc.android.sdk.darkside.reorgs // package cash.z.ecc.android.sdk.integration
|
||||
//
|
||||
// import androidx.test.platform.app.InstrumentationRegistry
|
||||
// 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.test.ScopedTest
|
||||
// import cash.z.ecc.android.sdk.ext.import
|
||||
// import cash.z.ecc.android.sdk.internal.twig
|
||||
// import cash.z.ecc.android.sdk.darkside.test.DarksideApi
|
||||
// import io.grpc.StatusRuntimeException
|
||||
// import kotlinx.coroutines.delay
|
||||
// import kotlinx.coroutines.flow.filter
|
||||
// import kotlinx.coroutines.flow.first
|
||||
// import kotlinx.coroutines.flow.onEach
|
||||
// import kotlinx.coroutines.runBlocking
|
||||
// import org.junit.Assert.*
|
||||
// import org.junit.Before
|
||||
// import org.junit.BeforeClass
|
||||
// import org.junit.Test
|
||||
//
|
||||
// class ReorgHandlingTest : ScopedTest() {
|
||||
//
|
||||
// @Before
|
||||
// fun setup() {
|
||||
// timeout(30_000L) {
|
||||
// synchronizer.awaitSync()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testBeforeReorg_minHeight() = timeout(30_000L) {
|
||||
// // validate that we are synced, at least to the birthday height
|
||||
// synchronizer.validateMinSyncHeight(birthdayHeight)
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testBeforeReorg_maxHeight() = timeout(30_000L) {
|
||||
// // validate that we are not synced beyond the target height
|
||||
// synchronizer.validateMaxSyncHeight(targetHeight)
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testBeforeReorg_latestBlockHash() = timeout(30_000L) {
|
||||
// val latestBlock = getBlock(targetHeight)
|
||||
// assertEquals("foo", latestBlock.header.toStringUtf8())
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testAfterSmallReorg_callbackTriggered() = timeout(30_000L) {
|
||||
// hadReorg = false
|
||||
// triggerSmallReorg()
|
||||
// assertTrue(hadReorg)
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testAfterSmallReorg_callbackTriggered() = timeout(30_000L) {
|
||||
// hadReorg = false
|
||||
// triggerSmallReorg()
|
||||
// assertTrue(hadReorg)
|
||||
// }
|
||||
// // @Test
|
||||
// // fun testSync_100Blocks()= timeout(10_000L) {
|
||||
// // // validate that we are synced below the target height, at first
|
||||
// // synchronizer.validateMaxSyncHeight(targetHeight - 1)
|
||||
// // // then trigger and await more blocks
|
||||
// // synchronizer.awaitHeight(targetHeight)
|
||||
// // // validate that we are above the target height afterward
|
||||
// // synchronizer.validateMinSyncHeight(targetHeight)
|
||||
// // }
|
||||
//
|
||||
// private fun Synchronizer.awaitSync() = runBlocking<Unit> {
|
||||
// twig("*** Waiting for sync ***")
|
||||
// status.onEach {
|
||||
// twig("got processor status $it")
|
||||
// assertTrue("Error: Cannot complete test because the server is disconnected.", it != Synchronizer.Status.DISCONNECTED)
|
||||
// delay(1000)
|
||||
// }.filter { it == Synchronizer.Status.SYNCED }.first()
|
||||
// twig("*** Done waiting for sync! ***")
|
||||
// }
|
||||
//
|
||||
// private fun Synchronizer.awaitHeight(height: Int) = runBlocking<Unit> {
|
||||
// twig("*** Waiting for block $height ***")
|
||||
// // processorInfo.first { it.lastScannedHeight >= height }
|
||||
// processorInfo.onEach {
|
||||
// twig("got processor info $it")
|
||||
// delay(1000)
|
||||
// }.first { it.lastScannedHeight >= height }
|
||||
// twig("*** Done waiting for block $height! ***")
|
||||
// }
|
||||
//
|
||||
// private fun Synchronizer.validateMinSyncHeight(minHeight: Int) = runBlocking<Unit> {
|
||||
// val info = processorInfo.first()
|
||||
// val lastDownloadedHeight = info.lastDownloadedHeight
|
||||
// assertTrue("Expected to be synced beyond $minHeight but the last downloaded block was" +
|
||||
// " $lastDownloadedHeight details: $info", lastDownloadedHeight >= minHeight)
|
||||
// }
|
||||
//
|
||||
// private fun Synchronizer.validateMaxSyncHeight(maxHeight: Int) = runBlocking<Unit> {
|
||||
// val lastDownloadedHeight = processorInfo.first().lastScannedHeight
|
||||
// assertTrue("Did not expect to be synced beyond $maxHeight but we are synced to" +
|
||||
// " $lastDownloadedHeight", lastDownloadedHeight <= maxHeight)
|
||||
// }
|
||||
//
|
||||
// private fun getBlock(height: Int) =
|
||||
// lightwalletd.getBlockRange(height..height).first()
|
||||
//
|
||||
// private val lightwalletd
|
||||
// get() = (synchronizer as SdkSynchronizer).processor.downloader.lightwalletService
|
||||
//
|
||||
// companion object {
|
||||
// private const val port = 9067
|
||||
// private const val birthdayHeight = 663150
|
||||
// private const val targetHeight = 663200
|
||||
// private const val seedPhrase = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"
|
||||
// private val context = InstrumentationRegistry.getInstrumentation().context
|
||||
// private val initializer = Initializer(context, host, port, "ReorgHandlingTests")
|
||||
// private lateinit var synchronizer: Synchronizer
|
||||
// private lateinit var sithLord: DarksideApi
|
||||
//
|
||||
// @BeforeClass
|
||||
// @JvmStatic
|
||||
// fun startOnce() {
|
||||
//
|
||||
// sithLord = DarksideApi(context, host, port)
|
||||
// enterTheDarkside()
|
||||
//
|
||||
// // don't start until after we enter the darkside (otherwise the we find no blocks to begin with and sleep for an interval)
|
||||
// synchronizer.start(classScope)
|
||||
// }
|
||||
//
|
||||
// private fun enterTheDarkside() = runBlocking<Unit> {
|
||||
// // verify that we are on the darkside
|
||||
// try {
|
||||
// twig("entering the darkside")
|
||||
// var info = synchronizer.getServerInfo()
|
||||
// assertTrue(
|
||||
// "Error: not on the darkside",
|
||||
// info.chainName.contains("darkside")
|
||||
// or info.vendor.toLowerCase().contains("darkside", true)
|
||||
// )
|
||||
// twig("initiating the darkside")
|
||||
// sithLord.initiate(birthdayHeight + 10)
|
||||
// info = synchronizer.getServerInfo()
|
||||
// assertTrue(
|
||||
// "Error: server not configured for the darkside. Expected initial height of" +
|
||||
// " $birthdayHeight but found ${info.blockHeight}", birthdayHeight <= info.blockHeight)
|
||||
// twig("darkside initiation complete!")
|
||||
// } catch (error: StatusRuntimeException) {
|
||||
// fail("Error while fetching server status. Testing cannot begin due to:" +
|
||||
// " ${error.message}. Verify that the server is running")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// /*
|
||||
//
|
||||
// beginning to process new blocks (with lower bound: 663050)...
|
||||
// downloading blocks in range 663202..663202
|
||||
// found 1 missing blocks, downloading in 1 batches of 100...
|
||||
// downloaded 663202..663202 (batch 1 of 1) [663202..663202] | 10ms
|
||||
// validating blocks in range 663202..663202 in db: /data/user/0/cash.z.ecc.android.sdk.test/databases/ReorgTest22_Cache.db
|
||||
// offset = min(100, 10 * (1)) = 10
|
||||
// lowerBound = max(663201 - 10, 663050) = 663191
|
||||
// handling chain error at 663201 by rewinding to block 663191
|
||||
// chain error detected at height: 663201. Rewinding to: 663191
|
||||
// beginning to process new blocks (with lower bound: 663050)...
|
||||
// downloading blocks in range 663192..663202
|
||||
// found 11 missing blocks, downloading in 1 batches of 100...
|
||||
// downloaded 663192..663202 (batch 1 of 1) [663192..663202] | 8ms
|
||||
// validating blocks in range 663192..663202 in db: /data/user/0/cash.z.ecc.android.sdk.test/databases/ReorgTest22_Cache.db
|
||||
// offset = min(100, 10 * (2)) = 20
|
||||
// lowerBound = max(663191 - 20, 663050) = 663171
|
||||
// handling chain error at 663191 by rewinding to block 663171
|
||||
// chain error detected at height: 663191. Rewinding to: 663171
|
||||
// beginning to process new blocks (with lower bound: 663050)...
|
||||
// downloading blocks in range 663172..663202
|
||||
// found 31 missing blocks, downloading in 1 batches of 100...
|
||||
// downloaded 663172..663202 (batch 1 of 1) [663172..663202] | 15ms
|
||||
// validating blocks in range 663172..663202 in db: /data/user/0/cash.z.ecc.android.sdk.test/databases/ReorgTest22_Cache.db
|
||||
// scanning blocks for range 663172..663202 in batches
|
||||
// batch scanned: 663202/663202
|
||||
// batch scan complete!
|
||||
// Successfully processed new blocks. Sleeping for 20000ms
|
||||
//
|
||||
// */
|
||||
// //
|
||||
// // @Test
|
||||
// // fun testHeightChange() {
|
||||
// // setTargetHeight(targetHeight)
|
||||
// // synchronizer.validateSyncedTo(targetHeight)
|
||||
// // }
|
||||
// //
|
||||
// // @Test
|
||||
// // fun testSmallReorgSync() {
|
||||
// // verifyReorgSync(smallReorgSize)
|
||||
// // }
|
||||
// //
|
||||
// // @Test
|
||||
// // fun testSmallReorgCallback() {
|
||||
// // verifyReorgCallback(smallReorgSize)
|
||||
// // }
|
||||
// //
|
||||
// // @Test
|
||||
// // fun testLargeReorgSync() {
|
||||
// // verifyReorgSync(largeReorgSize)
|
||||
// // }
|
||||
// //
|
||||
// // @Test
|
||||
// // fun testLargeReorgCallback() {
|
||||
// // verifyReorgCallback(largeReorgSize)
|
||||
// // }
|
||||
// //
|
||||
// //
|
||||
// // //
|
||||
// // // Helper Functions
|
||||
// // //
|
||||
// //
|
||||
// // fun verifyReorgSync(reorgSize: Int) {
|
||||
// // setTargetHeight(targetHeight)
|
||||
// // synchronizer.validateSyncedTo(targetHeight)
|
||||
// // getHash(targetHeight).let { initialHash ->
|
||||
// // setReorgHeight(targetHeight - reorgSize)
|
||||
// // synchronizer.validateSyncedTo(targetHeight)
|
||||
// // assertNotEquals("Hash should change after a reorg", initialHash, getHash(targetHeight))
|
||||
// // }
|
||||
// // }
|
||||
// //
|
||||
// // fun verifyReorgCallback(reorgSize: Int) {
|
||||
// // setTargetHeight(targetHeight)
|
||||
// // synchronizer.validateSyncedTo(targetHeight)
|
||||
// // getHash(targetHeight).let { initialHash ->
|
||||
// // setReorgHeight(targetHeight - 10)
|
||||
// // synchronizer.validateReorgCallback()
|
||||
// // }
|
||||
// // }
|
||||
//
|
||||
//
|
||||
// }
|
||||
//
|
||||
@@ -0,0 +1,46 @@
|
||||
package cash.z.ecc.android.sdk.darkside.reorgs
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import cash.z.ecc.android.sdk.darkside.test.DarksideTestCoordinator
|
||||
import cash.z.ecc.android.sdk.darkside.test.ScopedTest
|
||||
import org.junit.Before
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ReorgSetupTest : ScopedTest() {
|
||||
|
||||
private val birthdayHeight = 663150
|
||||
private val targetHeight = 663250
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
sithLord.await()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBeforeReorg_minHeight() = timeout(30_000L) {
|
||||
// validate that we are synced, at least to the birthday height
|
||||
validator.validateMinHeightDownloaded(birthdayHeight)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBeforeReorg_maxHeight() = timeout(30_000L) {
|
||||
// validate that we are not synced beyond the target height
|
||||
validator.validateMaxHeightScanned(targetHeight)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val sithLord = DarksideTestCoordinator()
|
||||
private val validator = sithLord.validator
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun startOnce() {
|
||||
sithLord.enterTheDarkside()
|
||||
sithLord.synchronizer.start(classScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package cash.z.ecc.android.sdk.darkside.reorgs
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import cash.z.ecc.android.sdk.darkside.test.DarksideTestCoordinator
|
||||
import cash.z.ecc.android.sdk.darkside.test.ScopedTest
|
||||
import cash.z.ecc.android.sdk.internal.twig
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ReorgSmallTest : ScopedTest() {
|
||||
|
||||
private val targetHeight = 663250
|
||||
private val hashBeforeReorg = "09ec0d5de30d290bc5a2318fbf6a2427a81c7db4790ce0e341a96aeac77108b9"
|
||||
private val hashAfterReorg = "tbd"
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
sithLord.await()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBeforeReorg_latestBlockHash() = timeout(30_000L) {
|
||||
validator.validateBlockHash(targetHeight, hashBeforeReorg)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAfterReorg_callbackTriggered() = timeout(30_000L) {
|
||||
hadReorg = false
|
||||
// sithLord.triggerSmallReorg()
|
||||
sithLord.await()
|
||||
|
||||
twig("checking whether a reorg happened (spoiler: ${if (hadReorg) "yep" else "nope"})")
|
||||
assertTrue(hadReorg)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAfterReorg_latestBlockHash() = timeout(30_000L) {
|
||||
validator.validateBlockHash(targetHeight, hashAfterReorg)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val sithLord = DarksideTestCoordinator()
|
||||
private val validator = sithLord.validator
|
||||
private var hadReorg = false
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun startOnce() {
|
||||
sithLord.enterTheDarkside()
|
||||
validator.onReorg { _, _ ->
|
||||
hadReorg = true
|
||||
}
|
||||
sithLord.synchronizer.start(classScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package cash.z.ecc.android.sdk.darkside.reorgs
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import cash.z.ecc.android.sdk.darkside.test.DarksideTestCoordinator
|
||||
import cash.z.ecc.android.sdk.darkside.test.ScopedTest
|
||||
import cash.z.ecc.android.sdk.darkside.test.SimpleMnemonics
|
||||
import cash.z.ecc.android.sdk.ext.toHex
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SetupTest : ScopedTest() {
|
||||
|
||||
// @Test
|
||||
// fun testFirstBlockExists() {
|
||||
// validator.validateHasBlock(
|
||||
// firstBlock
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testLastBlockExists() {
|
||||
// validator.validateHasBlock(
|
||||
// lastBlock
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testLastBlockHash() {
|
||||
// validator.validateBlockHash(
|
||||
// lastBlock,
|
||||
// lastBlockHash
|
||||
// )
|
||||
// }
|
||||
|
||||
@Test
|
||||
@Ignore("This test is broken")
|
||||
fun tempTest() {
|
||||
val phrase = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"
|
||||
val result = SimpleMnemonics().toSeed(phrase.toCharArray()).toHex()
|
||||
assertEquals("abc", result)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("This test is broken")
|
||||
fun tempTest2() {
|
||||
val s = SimpleMnemonics()
|
||||
val ent = s.nextEntropy()
|
||||
val phrase = s.nextMnemonic(ent)
|
||||
|
||||
assertEquals("a", "${ent.toHex()}|${String(phrase)}")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val blocksUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"
|
||||
private const val firstBlock = 663150
|
||||
private const val lastBlock = 663200
|
||||
private const val lastBlockHash = "2fc7b4682f5ba6ba6f86e170b40f0aa9302e1d3becb2a6ee0db611ff87835e4a"
|
||||
private val sithLord = DarksideTestCoordinator()
|
||||
private val validator = sithLord.validator
|
||||
|
||||
// @BeforeClass
|
||||
// @JvmStatic
|
||||
// fun startAllTests() {
|
||||
// sithLord
|
||||
// .enterTheDarkside()
|
||||
// // TODO: fix this
|
||||
// // .resetBlocks(blocksUrl, startHeight = firstBlock, tipHeight = lastBlock)
|
||||
// .startSync(classScope)
|
||||
// .await()
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cash.z.ecc.android.sdk.darkside.reproduce
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import cash.z.ecc.android.sdk.darkside.test.DarksideTest
|
||||
import org.junit.Before
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ReproduceZ2TFailureTest : DarksideTest() {
|
||||
@Before
|
||||
fun setup() {
|
||||
println("dBUG RUNNING")
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("This test is broken")
|
||||
fun once() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("This test is broken")
|
||||
fun twice() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@BeforeClass
|
||||
fun beforeAll() {
|
||||
println("dBUG BEFOERE IOT ALL")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package cash.z.ecc.android.sdk.darkside.test
|
||||
|
||||
import android.content.Context
|
||||
import cash.z.ecc.android.sdk.R
|
||||
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
||||
import cash.z.ecc.android.sdk.internal.twig
|
||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
||||
import cash.z.wallet.sdk.rpc.Darkside
|
||||
import cash.z.wallet.sdk.rpc.Darkside.DarksideTransactionsURL
|
||||
import cash.z.wallet.sdk.rpc.DarksideStreamerGrpc
|
||||
import cash.z.wallet.sdk.rpc.Service
|
||||
import io.grpc.ManagedChannel
|
||||
import io.grpc.stub.StreamObserver
|
||||
import java.lang.RuntimeException
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.random.Random
|
||||
|
||||
class DarksideApi(
|
||||
private val channel: ManagedChannel,
|
||||
private val singleRequestTimeoutSec: Long = 10L
|
||||
) {
|
||||
|
||||
constructor(
|
||||
appContext: Context,
|
||||
host: String,
|
||||
port: Int = ZcashNetwork.Mainnet.defaultPort,
|
||||
usePlainText: Boolean = appContext.resources.getBoolean(
|
||||
R.bool.lightwalletd_allow_very_insecure_connections
|
||||
)
|
||||
) : this(
|
||||
LightWalletGrpcService.createDefaultChannel(
|
||||
appContext,
|
||||
host,
|
||||
port,
|
||||
usePlainText
|
||||
)
|
||||
)
|
||||
|
||||
//
|
||||
// Service APIs
|
||||
//
|
||||
|
||||
fun reset(
|
||||
saplingActivationHeight: Int = 419200,
|
||||
branchId: String = "e9ff75a6", // Canopy,
|
||||
chainName: String = "darkside${ZcashNetwork.Mainnet.networkName}"
|
||||
) = apply {
|
||||
twig("resetting darksidewalletd with saplingActivation=$saplingActivationHeight branchId=$branchId chainName=$chainName")
|
||||
Darkside.DarksideMetaState.newBuilder()
|
||||
.setBranchID(branchId)
|
||||
.setChainName(chainName)
|
||||
.setSaplingActivation(saplingActivationHeight)
|
||||
.build().let { request ->
|
||||
createStub().reset(request)
|
||||
}
|
||||
}
|
||||
|
||||
fun stageBlocks(url: String) = apply {
|
||||
twig("staging blocks url=$url")
|
||||
createStub().stageBlocks(url.toUrl())
|
||||
}
|
||||
|
||||
fun stageTransactions(url: String, targetHeight: Int) = apply {
|
||||
twig("staging transaction at height=$targetHeight from url=$url")
|
||||
createStub().stageTransactions(
|
||||
DarksideTransactionsURL.newBuilder().setHeight(targetHeight).setUrl(url).build()
|
||||
)
|
||||
}
|
||||
|
||||
fun stageEmptyBlocks(startHeight: Int, count: Int = 10, nonce: Int = Random.nextInt()) = apply {
|
||||
twig("staging $count empty blocks starting at $startHeight with nonce $nonce")
|
||||
createStub().stageBlocksCreate(
|
||||
Darkside.DarksideEmptyBlocks.newBuilder().setHeight(startHeight).setCount(count).setNonce(nonce).build()
|
||||
)
|
||||
}
|
||||
|
||||
fun stageTransactions(txs: Iterator<Service.RawTransaction>?, tipHeight: Int) {
|
||||
if (txs == null) {
|
||||
twig("no transactions to stage")
|
||||
return
|
||||
}
|
||||
twig("staging transaction at height=$tipHeight")
|
||||
val response = EmptyResponse()
|
||||
createStreamingStub().stageTransactionsStream(response).apply {
|
||||
txs.forEach {
|
||||
twig("stageTransactions: onNext calling!!!")
|
||||
onNext(it.newBuilderForType().setData(it.data).setHeight(tipHeight.toLong()).build()) // apply the tipHeight because the passed in txs might not know their destination height (if they were created via SendTransaction)
|
||||
twig("stageTransactions: onNext called")
|
||||
}
|
||||
twig("stageTransactions: onCompleted calling!!!")
|
||||
onCompleted()
|
||||
twig("stageTransactions: onCompleted called")
|
||||
}
|
||||
response.await()
|
||||
}
|
||||
|
||||
fun applyBlocks(tipHeight: Int) {
|
||||
twig("applying blocks up to tipHeight=$tipHeight")
|
||||
createStub().applyStaged(tipHeight.toHeight())
|
||||
}
|
||||
|
||||
fun getSentTransactions(): MutableIterator<Service.RawTransaction>? {
|
||||
twig("grabbing sent transactions...")
|
||||
return createStub().getIncomingTransactions(Service.Empty.newBuilder().build())
|
||||
}
|
||||
// fun setMetaState(
|
||||
// branchId: String = "2bb40e60", // Blossom,
|
||||
// chainName: String = "darkside",
|
||||
// saplingActivationHeight: Int = 419200
|
||||
// ): DarksideApi = apply {
|
||||
// createStub().setMetaState(
|
||||
// Darkside.DarksideMetaState.newBuilder()
|
||||
// .setBranchID(branchId)
|
||||
// .setChainName(chainName)
|
||||
// .setSaplingActivation(saplingActivationHeight)
|
||||
// .build()
|
||||
// )
|
||||
// }
|
||||
|
||||
// fun setLatestHeight(latestHeight: Int) = setState(latestHeight, reorgHeight)
|
||||
//
|
||||
// fun setReorgHeight(reorgHeight: Int)
|
||||
// = setState(latestHeight.coerceAtLeast(reorgHeight), reorgHeight)
|
||||
//
|
||||
// fun setState(latestHeight: Int = -1, reorgHeight: Int = latestHeight): DarksideApi {
|
||||
// this.latestHeight = latestHeight
|
||||
// this.reorgHeight = reorgHeight
|
||||
// // TODO: change this service to accept ints as heights, like everywhere else
|
||||
// createStub().darksideSetState(
|
||||
// Darkside.DarksideState.newBuilder()
|
||||
// .setLatestHeight(latestHeight.toLong())
|
||||
// .setReorgHeight(reorgHeight.toLong())
|
||||
// .build()
|
||||
// )
|
||||
// return this
|
||||
// }
|
||||
|
||||
private fun createStub(): DarksideStreamerGrpc.DarksideStreamerBlockingStub =
|
||||
DarksideStreamerGrpc
|
||||
.newBlockingStub(channel)
|
||||
.withDeadlineAfter(singleRequestTimeoutSec, TimeUnit.SECONDS)
|
||||
|
||||
private fun createStreamingStub(): DarksideStreamerGrpc.DarksideStreamerStub =
|
||||
DarksideStreamerGrpc
|
||||
.newStub(channel)
|
||||
.withDeadlineAfter(singleRequestTimeoutSec, TimeUnit.SECONDS)
|
||||
|
||||
private fun String.toUrl() = Darkside.DarksideBlocksURL.newBuilder().setUrl(this).build()
|
||||
private fun Int.toHeight() = Darkside.DarksideHeight.newBuilder().setHeight(this).build()
|
||||
|
||||
class EmptyResponse : StreamObserver<Service.Empty> {
|
||||
var completed = false
|
||||
var error: Throwable? = null
|
||||
override fun onNext(value: Service.Empty?) {
|
||||
twig("<><><><><><><><> EMPTY RESPONSE: ONNEXT CALLED!!!!")
|
||||
}
|
||||
|
||||
override fun onError(t: Throwable?) {
|
||||
twig("<><><><><><><><> EMPTY RESPONSE: ONERROR CALLED!!!!")
|
||||
error = t
|
||||
completed = true
|
||||
}
|
||||
|
||||
override fun onCompleted() {
|
||||
twig("<><><><><><><><> EMPTY RESPONSE: ONCOMPLETED CALLED!!!")
|
||||
completed = true
|
||||
}
|
||||
|
||||
fun await() {
|
||||
while (!completed) {
|
||||
twig("awaiting server response...")
|
||||
Thread.sleep(20L)
|
||||
}
|
||||
if (error != null) throw RuntimeException("Server responded with an error: $error caused by ${error?.cause}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package cash.z.ecc.android.sdk.darkside.test
|
||||
|
||||
open class DarksideTest(name: String = javaClass.simpleName) : ScopedTest() {
|
||||
val sithLord = DarksideTestCoordinator()
|
||||
val validator = sithLord.validator
|
||||
|
||||
fun runOnce(block: () -> Unit) {
|
||||
if (!ranOnce) {
|
||||
sithLord.enterTheDarkside()
|
||||
sithLord.synchronizer.start(classScope)
|
||||
block()
|
||||
ranOnce = true
|
||||
}
|
||||
}
|
||||
companion object {
|
||||
private var ranOnce = false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
package cash.z.ecc.android.sdk.darkside.test
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import cash.z.ecc.android.sdk.SdkSynchronizer
|
||||
import cash.z.ecc.android.sdk.Synchronizer
|
||||
import cash.z.ecc.android.sdk.internal.twig
|
||||
import cash.z.ecc.android.sdk.type.ZcashNetwork
|
||||
import io.grpc.StatusRuntimeException
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
|
||||
class DarksideTestCoordinator(val wallet: TestWallet) {
|
||||
constructor(
|
||||
alias: String = "DarksideTestCoordinator",
|
||||
seedPhrase: String = DEFAULT_SEED_PHRASE,
|
||||
startHeight: Int = DEFAULT_START_HEIGHT,
|
||||
host: String = COMPUTER_LOCALHOST,
|
||||
network: ZcashNetwork = ZcashNetwork.Mainnet,
|
||||
port: Int = network.defaultPort
|
||||
) : this(TestWallet(seedPhrase, alias, network, host, startHeight = startHeight, port = port))
|
||||
|
||||
private val targetHeight = 663250
|
||||
private val context = InstrumentationRegistry.getInstrumentation().context
|
||||
|
||||
// dependencies: private
|
||||
private lateinit var darkside: DarksideApi
|
||||
|
||||
// dependencies: public
|
||||
val validator = DarksideTestValidator()
|
||||
val chainMaker = DarksideChainMaker()
|
||||
|
||||
// wallet delegates
|
||||
val synchronizer get() = wallet.synchronizer
|
||||
val send get() = wallet::send
|
||||
//
|
||||
// High-level APIs
|
||||
//
|
||||
|
||||
/**
|
||||
* Setup dependencies, including the synchronizer and the darkside API connection
|
||||
*/
|
||||
fun enterTheDarkside(): DarksideTestCoordinator = runBlocking {
|
||||
// verify that we are on the darkside
|
||||
try {
|
||||
twig("entering the darkside")
|
||||
initiate()
|
||||
synchronizer.getServerInfo().apply {
|
||||
assertTrue(
|
||||
"Error: not on the darkside",
|
||||
vendor.contains("dark", true)
|
||||
or chainName.contains("dark", true)
|
||||
)
|
||||
}
|
||||
twig("darkside initiation complete!")
|
||||
} catch (error: StatusRuntimeException) {
|
||||
Assert.fail(
|
||||
"Error while fetching server status. Testing cannot begin due to:" +
|
||||
" ${error.message} Caused by: ${error.cause} Verify that the server is running!"
|
||||
)
|
||||
}
|
||||
this@DarksideTestCoordinator
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the synchronizer and darksidewalletd with their initial state
|
||||
*/
|
||||
fun initiate() {
|
||||
twig("*************** INITIALIZING TEST COORDINATOR (ONLY ONCE) ***********************")
|
||||
val channel = synchronizer.channel
|
||||
darkside = DarksideApi(channel)
|
||||
darkside.reset()
|
||||
}
|
||||
|
||||
// fun triggerSmallReorg() {
|
||||
// darkside.setBlocksUrl(smallReorg)
|
||||
// }
|
||||
//
|
||||
// fun triggerLargeReorg() {
|
||||
// darkside.setBlocksUrl(largeReorg)
|
||||
// }
|
||||
|
||||
// redo this as a call to wallet but add delay time to wallet join() function
|
||||
/**
|
||||
* Waits for, at most, the given amount of time for the synchronizer to download and scan blocks
|
||||
* and reach a 'SYNCED' status.
|
||||
*/
|
||||
fun await(timeout: Long = 60_000L, targetHeight: Int = -1) = runBlocking {
|
||||
ScopedTest.timeoutWith(this, timeout) {
|
||||
twig("*** Waiting up to ${timeout / 1_000}s for sync ***")
|
||||
synchronizer.status.onEach {
|
||||
twig("got processor status $it")
|
||||
if (it == Synchronizer.Status.DISCONNECTED) {
|
||||
twig("waiting a bit before giving up on connection...")
|
||||
} else if (targetHeight != -1 && (synchronizer as SdkSynchronizer).processor.getLastScannedHeight() < targetHeight) {
|
||||
twig("awaiting new blocks from server...")
|
||||
}
|
||||
}.map {
|
||||
// whenever we're waiting for a target height, for simplicity, if we're sleeping,
|
||||
// and in between polls, then consider it that we're not synced
|
||||
if (targetHeight != -1 && (synchronizer as SdkSynchronizer).processor.getLastScannedHeight() < targetHeight) {
|
||||
twig("switching status to DOWNLOADING because we're still waiting for height $targetHeight")
|
||||
Synchronizer.Status.DOWNLOADING
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}.filter { it == Synchronizer.Status.SYNCED }.first()
|
||||
twig("*** Done waiting for sync! ***")
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Send a transaction and wait until it has been fully created and successfully submitted, which
|
||||
// * takes about 10 seconds.
|
||||
// */
|
||||
// suspend fun createAndSubmitTx(
|
||||
// zatoshi: Long,
|
||||
// toAddress: String,
|
||||
// memo: String = "",
|
||||
// fromAccountIndex: Int = 0
|
||||
// ) = coroutineScope {
|
||||
//
|
||||
// wallet.send(toAddress, memo, zatoshi, fromAccountIndex)
|
||||
// }
|
||||
|
||||
fun stall(delay: Long = 5000L) = runBlocking {
|
||||
twig("*** Stalling for ${delay}ms ***")
|
||||
delay(delay)
|
||||
}
|
||||
|
||||
//
|
||||
// Validation
|
||||
//
|
||||
|
||||
inner class DarksideTestValidator {
|
||||
|
||||
fun validateHasBlock(height: Int) {
|
||||
runBlocking {
|
||||
assertTrue((synchronizer as SdkSynchronizer).findBlockHashAsHex(height) != null)
|
||||
assertTrue((synchronizer as SdkSynchronizer).findBlockHash(height)?.size ?: 0 > 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun validateLatestHeight(height: Int) = runBlocking<Unit> {
|
||||
val info = synchronizer.processorInfo.first()
|
||||
val networkBlockHeight = info.networkBlockHeight
|
||||
assertTrue(
|
||||
"Expected latestHeight of $height but the server last reported a height of" +
|
||||
" $networkBlockHeight! Full details: $info",
|
||||
networkBlockHeight == height
|
||||
)
|
||||
}
|
||||
|
||||
fun validateMinHeightDownloaded(minHeight: Int) = runBlocking<Unit> {
|
||||
val info = synchronizer.processorInfo.first()
|
||||
val lastDownloadedHeight = info.lastDownloadedHeight
|
||||
assertTrue(
|
||||
"Expected to have at least downloaded $minHeight but the last downloaded block was" +
|
||||
" $lastDownloadedHeight! Full details: $info",
|
||||
lastDownloadedHeight >= minHeight
|
||||
)
|
||||
}
|
||||
|
||||
fun validateMinHeightScanned(minHeight: Int) = runBlocking<Unit> {
|
||||
val info = synchronizer.processorInfo.first()
|
||||
val lastScannedHeight = info.lastScannedHeight
|
||||
assertTrue(
|
||||
"Expected to have at least scanned $minHeight but the last scanned block was" +
|
||||
" $lastScannedHeight! Full details: $info",
|
||||
lastScannedHeight >= minHeight
|
||||
)
|
||||
}
|
||||
|
||||
fun validateMaxHeightScanned(maxHeight: Int) = runBlocking<Unit> {
|
||||
val lastDownloadedHeight = synchronizer.processorInfo.first().lastScannedHeight
|
||||
assertTrue(
|
||||
"Did not expect to be synced beyond $maxHeight but we are synced to" +
|
||||
" $lastDownloadedHeight",
|
||||
lastDownloadedHeight <= maxHeight
|
||||
)
|
||||
}
|
||||
|
||||
fun validateBlockHash(height: Int, expectedHash: String) {
|
||||
val hash = runBlocking { (synchronizer as SdkSynchronizer).findBlockHashAsHex(height) }
|
||||
assertEquals(expectedHash, hash)
|
||||
}
|
||||
|
||||
fun onReorg(callback: (errorHeight: Int, rewindHeight: Int) -> Unit) {
|
||||
synchronizer.onChainErrorHandler = callback
|
||||
}
|
||||
|
||||
fun validateTxCount(count: Int) {
|
||||
val txCount = runBlocking { (synchronizer as SdkSynchronizer).getTransactionCount() }
|
||||
assertEquals("Expected $count transactions but found $txCount instead!", count, txCount)
|
||||
}
|
||||
|
||||
fun validateMinBalance(available: Long = -1, total: Long = -1) {
|
||||
val balance = synchronizer.saplingBalances.value
|
||||
if (available > 0) {
|
||||
assertTrue("invalid available balance. Expected a minimum of $available but found ${balance?.available}", available <= balance?.available?.value!!)
|
||||
}
|
||||
if (total > 0) {
|
||||
assertTrue("invalid total balance. Expected a minimum of $total but found ${balance?.total}", total <= balance?.total?.value!!)
|
||||
}
|
||||
}
|
||||
suspend fun validateBalance(available: Long = -1, total: Long = -1, accountIndex: Int = 0) {
|
||||
val balance = (synchronizer as SdkSynchronizer).processor.getBalanceInfo(accountIndex)
|
||||
if (available > 0) {
|
||||
assertEquals("invalid available balance", available, balance.available)
|
||||
}
|
||||
if (total > 0) {
|
||||
assertEquals("invalid total balance", total, balance.total)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Chain Creations
|
||||
//
|
||||
|
||||
inner class DarksideChainMaker {
|
||||
var lastTipHeight = -1
|
||||
|
||||
/**
|
||||
* Resets the darksidelightwalletd server, stages the blocks represented by the given URL, then
|
||||
* applies those changes and waits for them to take effect.
|
||||
*/
|
||||
fun resetBlocks(
|
||||
blocksUrl: String,
|
||||
startHeight: Int = DEFAULT_START_HEIGHT,
|
||||
tipHeight: Int = startHeight + 100
|
||||
): DarksideChainMaker = apply {
|
||||
darkside
|
||||
.reset(startHeight)
|
||||
.stageBlocks(blocksUrl)
|
||||
applyTipHeight(tipHeight)
|
||||
}
|
||||
|
||||
fun stageTransaction(url: String, targetHeight: Int): DarksideChainMaker = apply {
|
||||
darkside.stageTransactions(url, targetHeight)
|
||||
}
|
||||
|
||||
fun stageTransactions(targetHeight: Int, vararg urls: String): DarksideChainMaker = apply {
|
||||
urls.forEach {
|
||||
darkside.stageTransactions(it, targetHeight)
|
||||
}
|
||||
}
|
||||
|
||||
fun stageEmptyBlocks(startHeight: Int, count: Int = 10): DarksideChainMaker = apply {
|
||||
darkside.stageEmptyBlocks(startHeight, count)
|
||||
}
|
||||
|
||||
fun stageEmptyBlock() = stageEmptyBlocks(lastTipHeight + 1, 1)
|
||||
|
||||
fun applyTipHeight(tipHeight: Int): DarksideChainMaker = apply {
|
||||
twig("applying tip height of $tipHeight")
|
||||
darkside.applyBlocks(tipHeight)
|
||||
lastTipHeight = tipHeight
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a chain with 100 blocks and a transaction in the middle.
|
||||
*
|
||||
* The chain starts at block 663150 and ends at block 663250
|
||||
*/
|
||||
fun makeSimpleChain() {
|
||||
darkside
|
||||
.reset(DEFAULT_START_HEIGHT)
|
||||
.stageBlocks("https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/tx-incoming/blocks.txt")
|
||||
applyTipHeight(DEFAULT_START_HEIGHT + 100)
|
||||
}
|
||||
|
||||
fun advanceBy(numEmptyBlocks: Int) {
|
||||
val nextBlock = lastTipHeight + 1
|
||||
twig("adding $numEmptyBlocks empty blocks to the chain starting at $nextBlock")
|
||||
darkside.stageEmptyBlocks(nextBlock, numEmptyBlocks)
|
||||
applyTipHeight(nextBlock + numEmptyBlocks)
|
||||
}
|
||||
|
||||
fun applyPendingTransactions(targetHeight: Int = lastTipHeight + 1) {
|
||||
stageEmptyBlocks(lastTipHeight + 1, targetHeight - lastTipHeight)
|
||||
darkside.stageTransactions(darkside.getSentTransactions()?.iterator(), targetHeight)
|
||||
applyTipHeight(targetHeight)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* This is a special localhost value on the Android emulator, which allows it to contact
|
||||
* the localhost of the computer running the emulator.
|
||||
*/
|
||||
const val COMPUTER_LOCALHOST = "10.0.2.2"
|
||||
|
||||
// Block URLS
|
||||
private const val beforeReorg =
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"
|
||||
private const val smallReorg =
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/after-small-reorg.txt"
|
||||
private const val largeReorg =
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/after-large-reorg.txt"
|
||||
private const val DEFAULT_START_HEIGHT = 663150
|
||||
private const val DEFAULT_SEED_PHRASE =
|
||||
"still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package cash.z.ecc.android.sdk.darkside.test
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import org.junit.Before
|
||||
|
||||
/**
|
||||
* Subclass this to validate the environment for running Darkside tests.
|
||||
*/
|
||||
open class DarksideTestPrerequisites {
|
||||
@Before
|
||||
fun verifyEmulator() {
|
||||
require(isProbablyEmulator(ApplicationProvider.getApplicationContext())) {
|
||||
"Darkside tests are configured to only run on the Android Emulator. Please see https://github.com/zcash/zcash-android-wallet-sdk/blob/master/docs/tests/Darkside.md"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun isProbablyEmulator(context: Context): Boolean {
|
||||
if (isDebuggable(context)) {
|
||||
// This is imperfect and could break in the future
|
||||
if (null == Build.DEVICE ||
|
||||
"generic" == Build.DEVICE || // $NON-NLS
|
||||
("generic_x86" == Build.DEVICE) // $NON-NLS
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the application running is debuggable. This is determined from the
|
||||
* ApplicationInfo object (`BuildInfo` is useless for libraries.)
|
||||
*/
|
||||
private fun isDebuggable(context: Context): Boolean {
|
||||
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
context.packageManager.getPackageInfo(
|
||||
context.packageName,
|
||||
PackageManager.PackageInfoFlags.of(0L)
|
||||
)
|
||||
} else {
|
||||
@Suppress("Deprecation")
|
||||
context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
}
|
||||
|
||||
// Normally shouldn't be null, but could be with a MockContext
|
||||
return packageInfo.applicationInfo?.let {
|
||||
0 != (it.flags and ApplicationInfo.FLAG_DEBUGGABLE)
|
||||
} ?: false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package cash.z.ecc.android.sdk.darkside.test
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import cash.z.ecc.android.sdk.internal.TroubleshootingTwig
|
||||
import cash.z.ecc.android.sdk.internal.Twig
|
||||
import cash.z.ecc.android.sdk.internal.twig
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.newFixedThreadPoolContext
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.After
|
||||
import org.junit.AfterClass
|
||||
import org.junit.Before
|
||||
import org.junit.BeforeClass
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
open class ScopedTest(val defaultTimeout: Long = 2000L) : DarksideTestPrerequisites() {
|
||||
protected lateinit var testScope: CoroutineScope
|
||||
|
||||
// if an androidTest doesn't need a context, then maybe it should be a unit test instead?!
|
||||
val context: Context = InstrumentationRegistry.getInstrumentation().context
|
||||
|
||||
@Before
|
||||
fun start() {
|
||||
twig("===================== TEST STARTED ==================================")
|
||||
testScope = CoroutineScope(
|
||||
Job(classScope.coroutineContext[Job]!!) + newFixedThreadPoolContext(
|
||||
5,
|
||||
this.javaClass.simpleName
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@After
|
||||
fun end() = runBlocking<Unit> {
|
||||
twig("======================= TEST CANCELLING =============================")
|
||||
testScope.cancel()
|
||||
testScope.coroutineContext[Job]?.join()
|
||||
twig("======================= TEST ENDED ==================================")
|
||||
}
|
||||
|
||||
fun timeout(duration: Long, block: suspend () -> Unit) = timeoutWith(testScope, duration, block)
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
lateinit var classScope: CoroutineScope
|
||||
|
||||
init {
|
||||
Twig.plant(TroubleshootingTwig())
|
||||
twig("================================================================ INIT")
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun createScope() {
|
||||
twig("======================= CLASS STARTED ===============================")
|
||||
classScope = CoroutineScope(
|
||||
SupervisorJob() + newFixedThreadPoolContext(2, this.javaClass.simpleName)
|
||||
)
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
@JvmStatic
|
||||
fun destroyScope() = runBlocking<Unit> {
|
||||
twig("======================= CLASS CANCELLING ============================")
|
||||
classScope.cancel()
|
||||
classScope.coroutineContext[Job]?.join()
|
||||
twig("======================= CLASS ENDED =================================")
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun timeoutWith(scope: CoroutineScope, duration: Long, block: suspend () -> Unit) {
|
||||
scope.launch {
|
||||
delay(duration)
|
||||
val message = "ERROR: Test timed out after ${duration}ms"
|
||||
twig(message)
|
||||
throw TimeoutException(message)
|
||||
}.let { selfDestruction ->
|
||||
scope.launch {
|
||||
block()
|
||||
selfDestruction.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cash.z.ecc.android.sdk.darkside.test
|
||||
|
||||
import cash.z.android.plugin.MnemonicPlugin
|
||||
import cash.z.ecc.android.bip39.Mnemonics
|
||||
import cash.z.ecc.android.bip39.Mnemonics.MnemonicCode
|
||||
import cash.z.ecc.android.bip39.Mnemonics.WordCount
|
||||
import cash.z.ecc.android.bip39.toEntropy
|
||||
import cash.z.ecc.android.bip39.toSeed
|
||||
import java.util.Locale
|
||||
|
||||
class SimpleMnemonics : MnemonicPlugin {
|
||||
override fun fullWordList(languageCode: String) = Mnemonics.getCachedWords(Locale.ENGLISH.language)
|
||||
override fun nextEntropy(): ByteArray = WordCount.COUNT_24.toEntropy()
|
||||
override fun nextMnemonic(): CharArray = MnemonicCode(WordCount.COUNT_24).chars
|
||||
override fun nextMnemonic(entropy: ByteArray): CharArray = MnemonicCode(entropy).chars
|
||||
override fun nextMnemonicList(): List<CharArray> = MnemonicCode(WordCount.COUNT_24).words
|
||||
override fun nextMnemonicList(entropy: ByteArray): List<CharArray> = MnemonicCode(entropy).words
|
||||
override fun toSeed(mnemonic: CharArray): ByteArray = MnemonicCode(mnemonic).toSeed()
|
||||
override fun toWordList(mnemonic: CharArray): List<CharArray> = MnemonicCode(mnemonic).words
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package cash.z.ecc.android.sdk.darkside.test
|
||||
|
||||
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
|
||||
import cash.z.ecc.android.sdk.internal.Twig
|
||||
import cash.z.ecc.android.sdk.internal.service.LightWalletGrpcService
|
||||
import cash.z.ecc.android.sdk.internal.twig
|
||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||
import cash.z.ecc.android.sdk.type.WalletBalance
|
||||
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
|
||||
import kotlinx.coroutines.runBlocking
|
||||
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(
|
||||
val seedPhrase: String,
|
||||
val alias: String = "TestWallet",
|
||||
val network: ZcashNetwork = ZcashNetwork.Testnet,
|
||||
val host: String = network.defaultHost,
|
||||
startHeight: Int? = null,
|
||||
val port: Int = network.defaultPort
|
||||
) {
|
||||
constructor(
|
||||
backup: Backups,
|
||||
network: ZcashNetwork = ZcashNetwork.Testnet,
|
||||
alias: String = "TestWallet"
|
||||
) : this(
|
||||
backup.seedPhrase,
|
||||
network = network,
|
||||
startHeight = if (network == ZcashNetwork.Mainnet) backup.mainnetBirthday else backup.testnetBirthday,
|
||||
alias = alias
|
||||
)
|
||||
|
||||
val walletScope = CoroutineScope(
|
||||
SupervisorJob() + newFixedThreadPoolContext(3, this.javaClass.simpleName)
|
||||
)
|
||||
|
||||
// Although runBlocking isn't great, this usage is OK because this is only used within the
|
||||
// automated tests
|
||||
|
||||
private val context = InstrumentationRegistry.getInstrumentation().context
|
||||
private val seed: ByteArray = Mnemonics.MnemonicCode(seedPhrase).toSeed()
|
||||
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) }
|
||||
}
|
||||
}
|
||||
val synchronizer: SdkSynchronizer = runBlocking { Synchronizer.new(initializer) } as SdkSynchronizer
|
||||
val service = (synchronizer.processor.downloader.lightWalletService as LightWalletGrpcService)
|
||||
|
||||
val available get() = synchronizer.saplingBalances.value?.available
|
||||
val shieldedAddress =
|
||||
runBlocking { DerivationTool.deriveShieldedAddress(seed, network = network) }
|
||||
val transparentAddress =
|
||||
runBlocking { DerivationTool.deriveTransparentAddress(seed, network = network) }
|
||||
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
|
||||
}
|
||||
|
||||
suspend fun send(address: String = transparentAddress, memo: String = "", amount: Zatoshi = Zatoshi(500L), fromAccountIndex: Int = 0): TestWallet {
|
||||
Twig.sprout("$alias sending")
|
||||
synchronizer.sendToAddress(shieldedSpendingKey, amount, address, memo, fromAccountIndex)
|
||||
.takeWhile { it.isPending() }
|
||||
.collect {
|
||||
twig("Updated transaction: $it")
|
||||
}
|
||||
Twig.clip("$alias sending")
|
||||
return this
|
||||
}
|
||||
|
||||
suspend fun rewindToHeight(height: Int): TestWallet {
|
||||
synchronizer.rewindToNearestHeight(height, false)
|
||||
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 ->
|
||||
twig("FOUND utxo balance of total: ${walletBalance.total} available: ${walletBalance.available}")
|
||||
|
||||
if (walletBalance.available.value > 0L) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
;
|
||||
}
|
||||
}
|
||||
7
darkside-test-lib/src/main/AndroidManifest.xml
Normal file
7
darkside-test-lib/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="cash.z.ecc.android.sdk.darkside">
|
||||
|
||||
<application android:name="androidx.multidex.MultiDexApplication" />
|
||||
</manifest>
|
||||
4
darkside-test-lib/src/main/res/values/bools.xml
Normal file
4
darkside-test-lib/src/main/res/values/bools.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="lightwalletd_allow_very_insecure_connections">true</bool>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user