New: Added darksidewalletd testing support.
Also modified tests so that they can compile again.
This commit is contained in:
parent
5cc8a38a5f
commit
c5ad25a5ce
|
@ -150,12 +150,5 @@ class SampleCodeTest {
|
|||
}
|
||||
|
||||
fun log(message: String?) = twig(message ?: "null")
|
||||
|
||||
private fun ByteArray.toHex(): String {
|
||||
val sb = StringBuilder(size * 2)
|
||||
for (b in this)
|
||||
sb.append(String.format("%02x", b))
|
||||
return sb.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
package cash.z.wallet.sdk.ext
|
||||
|
||||
import cash.z.wallet.sdk.Initializer
|
||||
import cash.z.wallet.sdk.Initializer.DefaultBirthdayStore.Companion.ImportedWalletBirthdayStore
|
||||
import cash.z.wallet.sdk.util.SimpleMnemonics
|
||||
import kotlinx.coroutines.*
|
||||
import org.junit.After
|
||||
import org.junit.AfterClass
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.BeforeClass
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
fun Initializer.import(
|
||||
seedPhrase: String,
|
||||
birthdayHeight: Int,
|
||||
alias: String = ZcashSdk.DEFAULT_DB_NAME_PREFIX
|
||||
) {
|
||||
SimpleMnemonics().toSeed(seedPhrase.toCharArray()).let { seed ->
|
||||
ImportedWalletBirthdayStore(context, birthdayHeight, alias).getBirthday().let {
|
||||
import(seed, it, true, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open class ScopedTest(val defaultTimeout: Long = 2000L) {
|
||||
protected lateinit var testScope: CoroutineScope
|
||||
|
||||
@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,96 @@
|
|||
//package cash.z.wallet.sdk.integration
|
||||
//
|
||||
//import cash.z.wallet.sdk.ext.ScopedTest
|
||||
//import cash.z.wallet.sdk.util.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,53 @@
|
|||
//package cash.z.wallet.sdk.integration
|
||||
//
|
||||
//import cash.z.wallet.sdk.ext.ScopedTest
|
||||
//import cash.z.wallet.sdk.util.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,240 @@
|
|||
//package cash.z.wallet.sdk.integration
|
||||
//
|
||||
//import androidx.test.platform.app.InstrumentationRegistry
|
||||
//import cash.z.wallet.sdk.Initializer
|
||||
//import cash.z.wallet.sdk.SdkSynchronizer
|
||||
//import cash.z.wallet.sdk.Synchronizer
|
||||
//import cash.z.wallet.sdk.ext.ScopedTest
|
||||
//import cash.z.wallet.sdk.ext.import
|
||||
//import cash.z.wallet.sdk.ext.twig
|
||||
//import cash.z.wallet.sdk.util.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 host = "192.168.1.134"
|
||||
// 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.wallet.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.wallet.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.wallet.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,45 @@
|
|||
package cash.z.wallet.sdk.integration
|
||||
|
||||
import cash.z.wallet.sdk.ext.ScopedTest
|
||||
import cash.z.wallet.sdk.util.DarksideTestCoordinator
|
||||
import org.junit.Before
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
|
||||
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("192.168.1.134")
|
||||
private val validator = sithLord.validator
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun startOnce() {
|
||||
sithLord.enterTheDarkside()
|
||||
sithLord.synchronizer.start(classScope)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package cash.z.wallet.sdk.integration
|
||||
|
||||
import cash.z.wallet.sdk.ext.ScopedTest
|
||||
import cash.z.wallet.sdk.ext.twig
|
||||
import cash.z.wallet.sdk.util.DarksideTestCoordinator
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
|
||||
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("192.168.1.134")
|
||||
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,99 @@
|
|||
package cash.z.wallet.sdk.integration.reorgs
|
||||
|
||||
import cash.z.wallet.sdk.ext.ScopedTest
|
||||
import cash.z.wallet.sdk.ext.twig
|
||||
import cash.z.wallet.sdk.util.DarksideTestCoordinator
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
|
||||
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("192.168.1.134")
|
||||
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.startSync(classScope).await()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package cash.z.wallet.sdk.integration.reorgs
|
||||
|
||||
import cash.z.wallet.sdk.ext.ScopedTest
|
||||
import cash.z.wallet.sdk.ext.toHex
|
||||
import cash.z.wallet.sdk.util.DarksideTestCoordinator
|
||||
import cash.z.wallet.sdk.util.SimpleMnemonics
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
|
||||
class SetupTest : ScopedTest() {
|
||||
|
||||
// @Test
|
||||
// fun testFirstBlockExists() {
|
||||
// validator.validateHasBlock(
|
||||
// firstBlock
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testLastBlockExists() {
|
||||
// validator.validateHasBlock(
|
||||
// lastBlock
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun testLastBlockHash() {
|
||||
// validator.validateBlockHash(
|
||||
// lastBlock,
|
||||
// lastBlockHash
|
||||
// )
|
||||
// }
|
||||
|
||||
@Test
|
||||
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
|
||||
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("192.168.1.134")
|
||||
private val validator = sithLord.validator
|
||||
|
||||
// @BeforeClass
|
||||
// @JvmStatic
|
||||
// fun startAllTests() {
|
||||
// sithLord
|
||||
// .enterTheDarkside()
|
||||
// // TODO: fix this
|
||||
//// .resetBlocks(blocksUrl, startHeight = firstBlock, tipHeight = lastBlock)
|
||||
// .startSync(classScope)
|
||||
// .await()
|
||||
// }
|
||||
}
|
||||
}
|
|
@ -1,25 +1,26 @@
|
|||
package cash.z.wallet.sdk.db
|
||||
package cash.z.wallet.sdk.util
|
||||
|
||||
//import cash.z.wallet.sdk.secure.Wallet
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import cash.z.wallet.sdk.Initializer
|
||||
import cash.z.wallet.sdk.Synchronizer
|
||||
import cash.z.wallet.sdk.block.CompactBlockDbStore
|
||||
import cash.z.wallet.sdk.block.CompactBlockDownloader
|
||||
import cash.z.wallet.sdk.block.CompactBlockProcessor
|
||||
import cash.z.wallet.sdk.ext.TroubleshootingTwig
|
||||
import cash.z.wallet.sdk.ext.Twig
|
||||
import cash.z.wallet.sdk.ext.twig
|
||||
import cash.z.wallet.sdk.ext.SampleSeedProvider
|
||||
import cash.z.wallet.sdk.jni.RustBackend
|
||||
import cash.z.wallet.sdk.secure.Wallet
|
||||
import cash.z.wallet.sdk.service.LightWalletGrpcService
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okio.Okio
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.io.IOException
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
/**
|
||||
* A tool for checking transactions since the given birthday and printing balances. This was useful for the Zcon1 app to
|
||||
|
@ -28,36 +29,47 @@ import kotlin.properties.Delegates
|
|||
@ExperimentalCoroutinesApi
|
||||
class BalancePrinterUtil {
|
||||
|
||||
private val host = "34.65.230.46"
|
||||
private val host = "lightd-main.zecwallet.co"
|
||||
private val port = 443
|
||||
private val downloadBatchSize = 9_000
|
||||
private val birthday = 523240
|
||||
private val birthdayHeight = 523240
|
||||
|
||||
|
||||
private val mnemonics = SimpleMnemonics()
|
||||
private val context = InstrumentationRegistry.getInstrumentation().context
|
||||
private val cacheDbName = "BalanceUtilCache.db"
|
||||
private val dataDbName = "BalanceUtilData.db"
|
||||
private val rustBackend = RustBackend.init(context, cacheDbName, dataDbName)
|
||||
private val alias = "BalanceUtil"
|
||||
private val caceDbPath = Initializer.cacheDbPath(context, alias)
|
||||
|
||||
private val downloader = CompactBlockDownloader(
|
||||
LightWalletGrpcService(context, host),
|
||||
CompactBlockDbStore(context)
|
||||
LightWalletGrpcService(context, host, port),
|
||||
CompactBlockDbStore(context, caceDbPath)
|
||||
)
|
||||
|
||||
// private val processor = CompactBlockProcessor(downloader)
|
||||
|
||||
// private val rustBackend = RustBackend.init(context, cacheDbName, dataDbName)
|
||||
|
||||
private val initializer = Initializer(context, host, port, alias)
|
||||
|
||||
private lateinit var birthday: Initializer.WalletBirthday
|
||||
private var synchronizer: Synchronizer? = null
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
Twig.plant(TroubleshootingTwig())
|
||||
cacheBlocks()
|
||||
birthday = Initializer.DefaultBirthdayStore(context, birthdayHeight, alias).getBirthday()
|
||||
}
|
||||
|
||||
private fun cacheBlocks() = runBlocking {
|
||||
twig("downloading compact blocks...")
|
||||
val latestBlockHeight = downloader.getLatestBlockHeight()
|
||||
val lastDownloaded = downloader.getLastDownloadedHeight()
|
||||
val blockRange = (Math.max(birthday, lastDownloaded))..latestBlockHeight
|
||||
downloadNewBlocks(blockRange)
|
||||
val error = validateNewBlocks(blockRange)
|
||||
twig("validation completed with result $error")
|
||||
assertEquals(-1, error)
|
||||
// twig("downloading compact blocks...")
|
||||
// val latestBlockHeight = downloader.getLatestBlockHeight()
|
||||
// val lastDownloaded = downloader.getLastDownloadedHeight()
|
||||
// val blockRange = (Math.max(birthday, lastDownloaded))..latestBlockHeight
|
||||
// downloadNewBlocks(blockRange)
|
||||
// val error = validateNewBlocks(blockRange)
|
||||
// twig("validation completed with result $error")
|
||||
// assertEquals(-1, error)
|
||||
}
|
||||
|
||||
private fun deleteDb(dbName: String) {
|
||||
|
@ -66,23 +78,65 @@ class BalancePrinterUtil {
|
|||
|
||||
@Test
|
||||
fun printBalances() = runBlocking {
|
||||
readLines().collect { seed ->
|
||||
deleteDb(dataDbName)
|
||||
initWallet(seed)
|
||||
twig("scanning blocks for seed <$seed>")
|
||||
rustBackend.scanBlocks()
|
||||
twig("done scanning blocks for seed $seed")
|
||||
val total = rustBackend.getBalance(0)
|
||||
twig("found total: $total")
|
||||
val available = rustBackend.getVerifiedBalance(0)
|
||||
twig("found available: $available")
|
||||
twig("xrxrx2\t$seed\t$total\t$available")
|
||||
println("xrxrx2\t$seed\t$total\t$available")
|
||||
readLines()
|
||||
.map { seedPhrase ->
|
||||
twig("checking balance for: $seedPhrase")
|
||||
mnemonics.toSeed(seedPhrase.toCharArray())
|
||||
}.collect { seed ->
|
||||
initializer.import(seed, birthday, clearDataDb = true, clearCacheDb = false)
|
||||
/*
|
||||
what I need to do right now
|
||||
- for each seed
|
||||
- I can reuse the cache of blocks... so just like get the cache once
|
||||
- I need to scan into a new database
|
||||
- I don't really need a new rustbackend
|
||||
- I definitely don't need a new grpc connection
|
||||
- can I just use a processor and point it to a different DB?
|
||||
+ so yeah, I think I need to use the processor directly right here and just swap out its pieces
|
||||
- perhaps create a new initializer and use that to configure the processor?
|
||||
- or maybe just set the data destination for the processor
|
||||
- I might need to consider how state is impacting this design
|
||||
- can we be more stateless and thereby improve the flexibility of this code?!!!
|
||||
*/
|
||||
synchronizer?.stop()
|
||||
synchronizer = Synchronizer(initializer)
|
||||
|
||||
// deleteDb(dataDbPath)
|
||||
// initWallet(seed)
|
||||
// twig("scanning blocks for seed <$seed>")
|
||||
//// rustBackend.scanBlocks()
|
||||
// twig("done scanning blocks for seed $seed")
|
||||
//// val total = rustBackend.getBalance(0)
|
||||
// twig("found total: $total")
|
||||
//// val available = rustBackend.getVerifiedBalance(0)
|
||||
// twig("found available: $available")
|
||||
// twig("xrxrx2\t$seed\t$total\t$available")
|
||||
// println("xrxrx2\t$seed\t$total\t$available")
|
||||
}
|
||||
|
||||
Thread.sleep(5000)
|
||||
Thread.sleep(3000)
|
||||
assertEquals("foo", "bar")
|
||||
}
|
||||
|
||||
// @Test
|
||||
// fun printBalances() = runBlocking {
|
||||
// readLines().collect { seed ->
|
||||
// deleteDb(dataDbName)
|
||||
// initWallet(seed)
|
||||
// twig("scanning blocks for seed <$seed>")
|
||||
//// rustBackend.scanBlocks()
|
||||
// twig("done scanning blocks for seed $seed")
|
||||
//// val total = rustBackend.getBalance(0)
|
||||
// twig("found total: $total")
|
||||
//// val available = rustBackend.getVerifiedBalance(0)
|
||||
// twig("found available: $available")
|
||||
// twig("xrxrx2\t$seed\t$total\t$available")
|
||||
// println("xrxrx2\t$seed\t$total\t$available")
|
||||
// }
|
||||
|
||||
// Thread.sleep(5000)
|
||||
// assertEquals("foo", "bar")
|
||||
// }
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun readLines() = flow<String> {
|
||||
|
@ -96,20 +150,20 @@ class BalancePrinterUtil {
|
|||
}
|
||||
}
|
||||
|
||||
private fun initWallet(seed: String): Wallet {
|
||||
val spendingKeyProvider = Delegates.notNull<String>()
|
||||
return Wallet(
|
||||
context,
|
||||
rustBackend,
|
||||
SampleSeedProvider(seed),
|
||||
spendingKeyProvider,
|
||||
Wallet.loadBirthdayFromAssets(context, birthday)
|
||||
).apply {
|
||||
runCatching {
|
||||
initialize()
|
||||
}
|
||||
}
|
||||
}
|
||||
// private fun initWallet(seed: String): Wallet {
|
||||
// val spendingKeyProvider = Delegates.notNull<String>()
|
||||
// return Wallet(
|
||||
// context,
|
||||
// rustBackend,
|
||||
// SampleSeedProvider(seed),
|
||||
// spendingKeyProvider,
|
||||
// Wallet.loadBirthdayFromAssets(context, birthday)
|
||||
// ).apply {
|
||||
// runCatching {
|
||||
// initialize()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
private fun downloadNewBlocks(range: IntRange) = runBlocking {
|
||||
Twig.sprout("downloading")
|
||||
|
@ -123,7 +177,7 @@ class BalancePrinterUtil {
|
|||
val end = Math.min(range.first + (i * downloadBatchSize), range.last + 1)
|
||||
val batchRange = downloadedBlockHeight until end
|
||||
twig("downloaded $batchRange (batch $i of $batches)") {
|
||||
downloader.downloadBlockRange(batchRange)
|
||||
// downloader.downloadBlockRange(batchRange)
|
||||
}
|
||||
downloadedBlockHeight = end
|
||||
|
||||
|
@ -131,13 +185,13 @@ class BalancePrinterUtil {
|
|||
Twig.clip("downloading")
|
||||
}
|
||||
|
||||
private fun validateNewBlocks(range: IntRange?): Int {
|
||||
val dummyWallet = initWallet("dummySeed")
|
||||
Twig.sprout("validating")
|
||||
twig("validating blocks in range $range")
|
||||
val result = rustBackend.validateCombinedChain()
|
||||
Twig.clip("validating")
|
||||
return result
|
||||
}
|
||||
// private fun validateNewBlocks(range: IntRange?): Int {
|
||||
//// val dummyWallet = initWallet("dummySeed")
|
||||
// Twig.sprout("validating")
|
||||
// twig("validating blocks in range $range")
|
||||
//// val result = rustBackend.validateCombinedChain()
|
||||
// Twig.clip("validating")
|
||||
// return result
|
||||
// }
|
||||
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
package cash.z.wallet.sdk.util
|
||||
|
||||
import android.content.Context
|
||||
import cash.z.wallet.sdk.R
|
||||
import cash.z.wallet.sdk.ext.ZcashSdk
|
||||
import cash.z.wallet.sdk.ext.twig
|
||||
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.service.LightWalletGrpcService
|
||||
import io.grpc.ManagedChannel
|
||||
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 = ZcashSdk.DEFAULT_LIGHTWALLETD_PORT,
|
||||
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 = "2bb40e60", // Blossom,
|
||||
chainName: String = "darkside"
|
||||
) = 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 block 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 {
|
||||
createStub().stageBlocksCreate(
|
||||
Darkside.DarksideEmptyBlocks.newBuilder().setHeight(startHeight).setCount(count).setNonce(nonce).build()
|
||||
)
|
||||
}
|
||||
|
||||
fun applyBlocks(tipHeight: Int) {
|
||||
twig("applying blocks up to tipHeight=$tipHeight")
|
||||
createStub().applyStaged(tipHeight.toHeight())
|
||||
}
|
||||
|
||||
// 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 String.toUrl() = Darkside.DarksideBlocksURL.newBuilder().setUrl(this).build()
|
||||
private fun Int.toHeight() = Darkside.DarksideHeight.newBuilder().setHeight(this).build()
|
||||
|
||||
}
|
|
@ -0,0 +1,244 @@
|
|||
package cash.z.wallet.sdk.util
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import cash.z.wallet.sdk.Initializer
|
||||
import cash.z.wallet.sdk.SdkSynchronizer
|
||||
import cash.z.wallet.sdk.Synchronizer
|
||||
import cash.z.wallet.sdk.ext.ScopedTest
|
||||
import cash.z.wallet.sdk.ext.import
|
||||
import cash.z.wallet.sdk.ext.twig
|
||||
import io.grpc.StatusRuntimeException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
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 host: String = "127.0.0.1") {
|
||||
private val port = 9067
|
||||
private val birthdayHeight = 663150
|
||||
private val targetHeight = 663250
|
||||
private 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
|
||||
|
||||
// dependencies: private
|
||||
private val initializer = Initializer(context, host, port, "DarksideTestCoordinator")
|
||||
private lateinit var darkside: DarksideApi
|
||||
|
||||
// dependencies: public
|
||||
val validator = DarksideTestValidator()
|
||||
val chainMaker = DarksideChainMaker()
|
||||
lateinit var synchronizer: Synchronizer
|
||||
|
||||
|
||||
//
|
||||
// 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}. 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) ***********************")
|
||||
initializer.import(
|
||||
seedPhrase,
|
||||
birthdayHeight
|
||||
)
|
||||
synchronizer = Synchronizer(initializer) as SdkSynchronizer
|
||||
val channel = (synchronizer as SdkSynchronizer).channel
|
||||
darkside = DarksideApi(channel)
|
||||
}
|
||||
|
||||
// fun triggerSmallReorg() {
|
||||
// darkside.setBlocksUrl(smallReorg)
|
||||
// }
|
||||
//
|
||||
// fun triggerLargeReorg() {
|
||||
// darkside.setBlocksUrl(largeReorg)
|
||||
// }
|
||||
|
||||
fun startSync(scope: CoroutineScope): DarksideTestCoordinator = apply {
|
||||
synchronizer.start(scope)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = 10_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! ***")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun stall(delay: Long = 5000L) = runBlocking {
|
||||
twig("*** Stalling for ${delay}ms ***")
|
||||
delay(delay)
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Validation
|
||||
//
|
||||
|
||||
inner class DarksideTestValidator {
|
||||
|
||||
fun validateHasBlock(height: Int) {
|
||||
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 = (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 = (synchronizer as SdkSynchronizer).getTransactionCount()
|
||||
assertEquals("Expected $count transactions but found $txCount instead!", count, txCount)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Chain Creations
|
||||
//
|
||||
|
||||
inner class DarksideChainMaker {
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
|
||||
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 applyTipHeight(tipHeight: Int): DarksideChainMaker = apply {
|
||||
darkside.applyBlocks(tipHeight)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
// 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
|
||||
}
|
||||
}
|
|
@ -1,82 +1,20 @@
|
|||
package cash.z.wallet.sdk.util
|
||||
|
||||
import cash.z.android.plugin.MnemonicPlugin
|
||||
import io.github.novacrypto.bip39.MnemonicGenerator
|
||||
import io.github.novacrypto.bip39.SeedCalculator
|
||||
import io.github.novacrypto.bip39.Words
|
||||
import io.github.novacrypto.bip39.wordlists.English
|
||||
import java.security.SecureRandom
|
||||
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.*
|
||||
|
||||
class SimpleMnemonics : MnemonicPlugin {
|
||||
|
||||
override fun nextEntropy(): ByteArray {
|
||||
return ByteArray(Words.TWENTY_FOUR.byteLength()).apply {
|
||||
SecureRandom().nextBytes(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun nextMnemonic(): CharArray {
|
||||
return nextMnemonic(nextEntropy())
|
||||
}
|
||||
|
||||
override fun nextMnemonic(entropy: ByteArray): CharArray {
|
||||
return StringBuilder().let { builder ->
|
||||
MnemonicGenerator(English.INSTANCE).createMnemonic(entropy) { c ->
|
||||
builder.append(c)
|
||||
}
|
||||
builder.toString().toCharArray()
|
||||
}
|
||||
}
|
||||
|
||||
override fun nextMnemonicList(): List<CharArray> {
|
||||
return nextMnemonicList(nextEntropy())
|
||||
}
|
||||
|
||||
override fun nextMnemonicList(entropy: ByteArray): List<CharArray> {
|
||||
return WordListBuilder().let { builder ->
|
||||
MnemonicGenerator(English.INSTANCE).createMnemonic(entropy) { c ->
|
||||
builder.append(c)
|
||||
}
|
||||
builder.wordList
|
||||
}
|
||||
}
|
||||
|
||||
override fun toSeed(mnemonic: CharArray): ByteArray {
|
||||
return SeedCalculator().calculateSeed(String(mnemonic), "")
|
||||
}
|
||||
|
||||
override fun toWordList(mnemonic: CharArray): List<CharArray> {
|
||||
val wordList = mutableListOf<CharArray>()
|
||||
var cursor = 0
|
||||
repeat(mnemonic.size) { i ->
|
||||
val isSpace = mnemonic[i] == ' '
|
||||
if (isSpace || i == (mnemonic.size - 1)) {
|
||||
val wordSize = i - cursor + if (isSpace) 0 else 1
|
||||
wordList.add(CharArray(wordSize).apply {
|
||||
repeat(wordSize) {
|
||||
this[it] = mnemonic[cursor + it]
|
||||
}
|
||||
})
|
||||
cursor = i + 1
|
||||
}
|
||||
}
|
||||
return wordList
|
||||
}
|
||||
|
||||
class WordListBuilder {
|
||||
val wordList = mutableListOf<CharArray>()
|
||||
fun append(c: CharSequence) {
|
||||
if (c[0] != English.INSTANCE.space) addWord(c)
|
||||
}
|
||||
|
||||
private fun addWord(c: CharSequence) {
|
||||
c.length.let { size ->
|
||||
val word = CharArray(size)
|
||||
repeat(size) {
|
||||
word[it] = c[it]
|
||||
}
|
||||
wordList.add(word)
|
||||
}
|
||||
}
|
||||
}
|
||||
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,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="lightwalletd_allow_very_insecure_connections">false</bool>
|
||||
</resources>
|
|
@ -1,3 +1,4 @@
|
|||
kitchen renew wide common vague fold vacuum tilt amazing pear square gossip jewel month tree shock scan alpha just spot fluid toilet view dinner
|
||||
urban kind wise collect social marble riot primary craft lucky head cause syrup odor artist decorate rhythm phone style benefit portion bus truck top
|
||||
wish puppy smile loan doll curve hole maze file ginger hair nose key relax knife witness cannon grab despair throw review deal slush frame
|
||||
labor elite banana cement royal tiger smile robust talk street bread bitter admit spy leg alcohol opinion mimic crane bid damp trigger wagon share
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"network": "mainnet",
|
||||
"height": 663150,
|
||||
"hash": "0000000002fd3be4c24c437bd22620901617125ec2a3a6c902ec9a6c06f734fc",
|
||||
"time": 1576821833,
|
||||
"tree": "01ec6278a1bed9e1b080fd60ef50eb17411645e3746ff129283712bc4757ecc833001001b4e1d4a26ac4a2810b57a14f4ffb69395f55dde5674ecd2462af96f9126e054701a36afb68534f640938bdffd80dfcb3f4d5e232488abbf67d049b33a761e7ed6901a16e35205fb7fe626a9b13fc43e1d2b98a9c241f99f93d5e93a735454073025401f5b9bcbf3d0e3c83f95ee79299e8aeadf30af07717bda15ffb7a3d00243b58570001fa6d4c2390e205f81d86b85ace0b48f3ce0afb78eeef3e14c70bcfd7c5f0191c0000011bc9521263584de20822f9483e7edb5af54150c4823c775b2efc6a1eded9625501a6030f8d4b588681eddb66cad63f09c5c7519db49500fc56ebd481ce5e903c22000163f4eec5a2fe00a5f45e71e1542ff01e937d2210c99f03addcce5314a5278b2d0163ab01f46a3bb6ea46f5a19d5bdd59eb3f81e19cfa6d10ab0fd5566c7a16992601fa6980c053d84f809b6abcf35690f03a11f87b28e3240828e32e3f57af41e54e01319312241b0031e3a255b0d708750b4cb3f3fe79e3503fe488cc8db1dd00753801754bb593ea42d231a7ddf367640f09bbf59dc00f2c1d2003cc340e0c016b5b13"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"network": "mainnet",
|
||||
"height": 663150,
|
||||
"hash": "0000000002fd3be4c24c437bd22620901617125ec2a3a6c902ec9a6c06f734fc",
|
||||
"time": 1576821833,
|
||||
"tree": "01ec6278a1bed9e1b080fd60ef50eb17411645e3746ff129283712bc4757ecc833001001b4e1d4a26ac4a2810b57a14f4ffb69395f55dde5674ecd2462af96f9126e054701a36afb68534f640938bdffd80dfcb3f4d5e232488abbf67d049b33a761e7ed6901a16e35205fb7fe626a9b13fc43e1d2b98a9c241f99f93d5e93a735454073025401f5b9bcbf3d0e3c83f95ee79299e8aeadf30af07717bda15ffb7a3d00243b58570001fa6d4c2390e205f81d86b85ace0b48f3ce0afb78eeef3e14c70bcfd7c5f0191c0000011bc9521263584de20822f9483e7edb5af54150c4823c775b2efc6a1eded9625501a6030f8d4b588681eddb66cad63f09c5c7519db49500fc56ebd481ce5e903c22000163f4eec5a2fe00a5f45e71e1542ff01e937d2210c99f03addcce5314a5278b2d0163ab01f46a3bb6ea46f5a19d5bdd59eb3f81e19cfa6d10ab0fd5566c7a16992601fa6980c053d84f809b6abcf35690f03a11f87b28e3240828e32e3f57af41e54e01319312241b0031e3a255b0d708750b4cb3f3fe79e3503fe488cc8db1dd00753801754bb593ea42d231a7ddf367640f09bbf59dc00f2c1d2003cc340e0c016b5b13"
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
package cash.z.wallet.sdk.integration
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import cash.z.wallet.sdk.Initializer
|
||||
import cash.z.wallet.sdk.Synchronizer
|
||||
import cash.z.wallet.sdk.Synchronizer.Status.SYNCED
|
||||
import cash.z.wallet.sdk.entity.isSubmitSuccess
|
||||
import cash.z.wallet.sdk.ext.*
|
||||
import cash.z.wallet.sdk.jni.RustBackend
|
||||
import cash.z.wallet.sdk.service.LightWalletGrpcService
|
||||
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.AfterClass
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
class TestnetIntegrationTest : ScopedTest() {
|
||||
|
||||
var stopWatch = CountDownLatch(1)
|
||||
|
||||
@Test
|
||||
fun testLatestBlockTest() {
|
||||
val service = LightWalletGrpcService(
|
||||
context,
|
||||
host,
|
||||
port)
|
||||
val height = service.getLatestBlockHeight()
|
||||
assertTrue(height > ZcashSdk.SAPLING_ACTIVATION_HEIGHT)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLoadBirthday() {
|
||||
val (height, hash, time, tree) = Initializer.DefaultBirthdayStore.loadBirthdayFromAssets(context, ZcashSdk.SAPLING_ACTIVATION_HEIGHT + 1)
|
||||
assertEquals(ZcashSdk.SAPLING_ACTIVATION_HEIGHT, height)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getAddress() = runBlocking {
|
||||
assertEquals(address, synchronizer.getAddress())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBalance() = runBlocking {
|
||||
var availableBalance: Long = 0L
|
||||
synchronizer.balances.onFirst {
|
||||
availableBalance = it.availableZatoshi
|
||||
}
|
||||
|
||||
synchronizer.status.filter { it == SYNCED }.onFirst {
|
||||
delay(100)
|
||||
}
|
||||
|
||||
assertTrue("No funds available when we expected a balance greater than zero!",
|
||||
availableBalance > 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
fun testSpend() = runBlocking {
|
||||
var success = false
|
||||
synchronizer.balances.filter { it.availableZatoshi > 0 }.onEach {
|
||||
success = sendFunds()
|
||||
}.first()
|
||||
log("asserting $success")
|
||||
assertTrue(success)
|
||||
}
|
||||
|
||||
private suspend fun sendFunds(): Boolean {
|
||||
val spendingKey = RustBackend().deriveSpendingKeys(seed)[0]
|
||||
log("sending to address")
|
||||
synchronizer.sendToAddress(
|
||||
spendingKey,
|
||||
ZcashSdk.MINERS_FEE_ZATOSHI,
|
||||
toAddress,
|
||||
"first mainnet tx from the SDK"
|
||||
).filter { it?.isSubmitSuccess() == true }.onFirst {
|
||||
log("DONE SENDING!!!")
|
||||
}
|
||||
log("returning true from sendFunds")
|
||||
return true
|
||||
}
|
||||
|
||||
fun log(message: String) {
|
||||
twig("\n---\n[TESTLOG]: $message\n---\n")
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
init { Twig.plant(TroubleshootingTwig()) }
|
||||
|
||||
const val host = "lightwalletd.testnet.z.cash"
|
||||
const val port = 9067
|
||||
private const val birthdayHeight = 963150
|
||||
private const val targetHeight = 663250
|
||||
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"
|
||||
val seed = "cash.z.wallet.sdk.integration.IntegrationTest.seed.value.64bytes".toByteArray()
|
||||
val address = "zs1m30y59wxut4zk9w24d6ujrdnfnl42hpy0ugvhgyhr8s0guszutqhdj05c7j472dndjstulph74m"
|
||||
val toAddress = "zs1vp7kvlqr4n9gpehztr76lcn6skkss9p8keqs3nv8avkdtjrcctrvmk9a7u494kluv756jeee5k0"
|
||||
|
||||
private val context = InstrumentationRegistry.getInstrumentation().context
|
||||
private val initializer = Initializer(context, host, port, "TestnetIntegrationTests")
|
||||
private lateinit var synchronizer: Synchronizer
|
||||
|
||||
@JvmStatic
|
||||
@BeforeClass
|
||||
fun startUp() {
|
||||
initializer.import(seedPhrase, birthdayHeight)
|
||||
synchronizer = Synchronizer(initializer)
|
||||
synchronizer.start(classScope)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
// Copyright (c) 2019-2020 The Zcash developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
syntax = "proto3";
|
||||
package cash.z.wallet.sdk.rpc;
|
||||
option go_package = ".;walletrpc";
|
||||
option swift_prefix = "";
|
||||
import "service.proto";
|
||||
|
||||
message DarksideMetaState {
|
||||
int32 saplingActivation = 1;
|
||||
string branchID = 2;
|
||||
string chainName = 3;
|
||||
}
|
||||
|
||||
// A block is a hex-encoded string.
|
||||
message DarksideBlock {
|
||||
string block = 1;
|
||||
}
|
||||
|
||||
// DarksideBlocksURL is typically something like:
|
||||
// https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt
|
||||
message DarksideBlocksURL {
|
||||
string url = 1;
|
||||
}
|
||||
|
||||
// DarksideTransactionsURL refers to an HTTP source that contains a list
|
||||
// of hex-encoded transactions, one per line, that are to be associated
|
||||
// with the given height (fake-mined into the block at that height)
|
||||
message DarksideTransactionsURL {
|
||||
int32 height = 1;
|
||||
string url = 2;
|
||||
}
|
||||
|
||||
message DarksideHeight {
|
||||
int32 height = 1;
|
||||
}
|
||||
|
||||
message DarksideEmptyBlocks {
|
||||
int32 height = 1;
|
||||
int32 nonce = 2;
|
||||
int32 count = 3;
|
||||
}
|
||||
|
||||
// Darksidewalletd maintains two staging areas, blocks and transactions. The
|
||||
// Stage*() gRPCs add items to the staging area; ApplyStaged() "applies" everything
|
||||
// in the staging area to the working (operational) state that the mock zcashd
|
||||
// serves; transactions are placed into their corresponding blocks (by height).
|
||||
service DarksideStreamer {
|
||||
// Reset reverts all darksidewalletd state (active block range, latest height,
|
||||
// staged blocks and transactions) and lightwalletd state (cache) to empty,
|
||||
// the same as the initial state. This occurs synchronously and instantaneously;
|
||||
// no reorg happens in lightwalletd. This is good to do before each independent
|
||||
// test so that no state leaks from one test to another.
|
||||
// Also sets (some of) the values returned by GetLightdInfo(). The Sapling
|
||||
// activation height specified here must be where the block range starts.
|
||||
rpc Reset(DarksideMetaState) returns (Empty) {}
|
||||
|
||||
// StageBlocksStream accepts a list of blocks and saves them into the blocks
|
||||
// staging area until ApplyStaged() is called; there is no immediate effect on
|
||||
// the mock zcashd. Blocks are hex-encoded. Order is important, see ApplyStaged.
|
||||
rpc StageBlocksStream(stream DarksideBlock) returns (Empty) {}
|
||||
|
||||
// StageBlocks is the same as StageBlocksStream() except the blocks are fetched
|
||||
// from the given URL. Blocks are one per line, hex-encoded (not JSON).
|
||||
rpc StageBlocks(DarksideBlocksURL) returns (Empty) {}
|
||||
|
||||
// StageBlocksCreate is like the previous two, except it creates 'count'
|
||||
// empty blocks at consecutive heights starting at height 'height'. The
|
||||
// 'nonce' is part of the header, so it contributes to the block hash; this
|
||||
// lets you create two fake blocks with the same transactions (or no
|
||||
// transactions) and same height, with two different hashes.
|
||||
rpc StageBlocksCreate(DarksideEmptyBlocks) returns (Empty) {}
|
||||
|
||||
// StageTransactionsStream stores the given transaction-height pairs in the
|
||||
// staging area until ApplyStaged() is called. Note that these transactions
|
||||
// are not returned by the production GetTransaction() gRPC until they
|
||||
// appear in a "mined" block (contained in the active blockchain presented
|
||||
// by the mock zcashd).
|
||||
rpc StageTransactionsStream(stream RawTransaction) returns (Empty) {}
|
||||
|
||||
// StageTransactions is the same except the transactions are fetched
|
||||
// from the given url. They are all staged into the block at the given
|
||||
// height. Staging transactions at multiple different heights requires
|
||||
// multiple calls.
|
||||
rpc StageTransactions(DarksideTransactionsURL) returns (Empty) {}
|
||||
|
||||
// ApplyStaged iterates the list of blocks that were staged by the
|
||||
// StageBlocks*() gRPCs, in the order they were staged, and "merges" each
|
||||
// into the active, working blocks list that the mock zcashd is presenting
|
||||
// to lightwalletd. The resulting working block list can't have gaps; if the
|
||||
// working block range is 1000-1006, and the staged block range is 1003-1004,
|
||||
// the resulting range is 1000-1004, with 1000-1002 unchanged, blocks
|
||||
// 1003-1004 from the new range, and 1005-1006 dropped. After merging all
|
||||
// blocks, ApplyStaged() appends staged transactions (in the order received)
|
||||
// into each one's corresponding block. The staging area is then cleared.
|
||||
//
|
||||
// The argument specifies the latest block height that mock zcashd reports
|
||||
// (i.e. what's returned by GetLatestBlock). Note that ApplyStaged() can
|
||||
// also be used to simply advance the latest block height presented by mock
|
||||
// zcashd. That is, there doesn't need to be anything in the staging area.
|
||||
rpc ApplyStaged(DarksideHeight) returns (Empty) {}
|
||||
|
||||
// Calls to the production gRPC SendTransaction() store the transaction in
|
||||
// a separate area (not the staging area); this method returns all transactions
|
||||
// in this separate area, which is then cleared. The height returned
|
||||
// with each transaction is -1 (invalid) since these transactions haven't
|
||||
// been mined yet. The intention is that the transactions returned here can
|
||||
// then, for example, be given to StageTransactions() to get them "mined"
|
||||
// into a specified block on the next ApplyStaged().
|
||||
rpc GetIncomingTransactions(Empty) returns (stream RawTransaction) {}
|
||||
|
||||
// Clear the incoming transaction pool.
|
||||
rpc ClearIncomingTransactions(Empty) returns (Empty) {}
|
||||
}
|
|
@ -1,3 +1,7 @@
|
|||
// Copyright (c) 2019-2020 The Zcash developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
syntax = "proto3";
|
||||
package cash.z.wallet.sdk.rpc;
|
||||
option go_package = "walletrpc";
|
||||
|
@ -5,15 +9,14 @@ option swift_prefix = "";
|
|||
import "compact_formats.proto";
|
||||
|
||||
// A BlockID message contains identifiers to select a block: a height or a
|
||||
// hash. If the hash is present it takes precedence.
|
||||
// hash. Specification by hash is not implemented, but may be in the future.
|
||||
message BlockID {
|
||||
uint64 height = 1;
|
||||
bytes hash = 2;
|
||||
}
|
||||
|
||||
// BlockRange technically allows ranging from hash to hash etc but this is not
|
||||
// currently intended for support, though there is no reason you couldn't do
|
||||
// it. Further permutations are left as an exercise.
|
||||
// BlockRange specifies a series of blocks from start to end inclusive.
|
||||
// Both BlockIDs must be heights; specification by hash is not yet supported.
|
||||
message BlockRange {
|
||||
BlockID start = 1;
|
||||
BlockID end = 2;
|
||||
|
@ -21,29 +24,86 @@ message BlockRange {
|
|||
|
||||
// A TxFilter contains the information needed to identify a particular
|
||||
// transaction: either a block and an index, or a direct transaction hash.
|
||||
// Currently, only specification by hash is supported.
|
||||
message TxFilter {
|
||||
BlockID block = 1;
|
||||
uint64 index = 2;
|
||||
bytes hash = 3;
|
||||
BlockID block = 1; // block identifier, height or hash
|
||||
uint64 index = 2; // index within the block
|
||||
bytes hash = 3; // transaction ID (hash, txid)
|
||||
}
|
||||
|
||||
// RawTransaction contains the complete transaction data.
|
||||
// RawTransaction contains the complete transaction data. It also optionally includes
|
||||
// the block height in which the transaction was included.
|
||||
message RawTransaction {
|
||||
bytes data = 1;
|
||||
bytes data = 1; // exact data returned by zcash 'getrawtransaction'
|
||||
uint64 height = 2; // height that the transaction was mined (or -1)
|
||||
}
|
||||
|
||||
// A SendResponse encodes an error code and a string. It is currently used
|
||||
// only by SendTransaction(). If error code is zero, the operation was
|
||||
// successful; if non-zero, it and the message specify the failure.
|
||||
message SendResponse {
|
||||
int32 errorCode = 1;
|
||||
string errorMessage = 2;
|
||||
}
|
||||
|
||||
// Empty placeholder. Someday we may want to specify e.g. a particular chain fork.
|
||||
// Chainspec is a placeholder to allow specification of a particular chain fork.
|
||||
message ChainSpec {}
|
||||
|
||||
// Empty is for gRPCs that take no arguments, currently only GetLightdInfo.
|
||||
message Empty {}
|
||||
|
||||
// LightdInfo returns various information about this lightwalletd instance
|
||||
// and the state of the blockchain.
|
||||
message LightdInfo {
|
||||
string version = 1;
|
||||
string vendor = 2;
|
||||
bool taddrSupport = 3; // true
|
||||
string chainName = 4; // either "main" or "test"
|
||||
uint64 saplingActivationHeight = 5; // depends on mainnet or testnet
|
||||
string consensusBranchId = 6; // protocol identifier, see consensus/upgrades.cpp
|
||||
uint64 blockHeight = 7; // latest block on the best chain
|
||||
}
|
||||
|
||||
// TransparentAddressBlockFilter restricts the results to the given address
|
||||
// or block range.
|
||||
message TransparentAddressBlockFilter {
|
||||
string address = 1; // t-address
|
||||
BlockRange range = 2; // start, end heights
|
||||
}
|
||||
|
||||
// Duration is currently used only for testing, so that the Ping rpc
|
||||
// can simulate a delay, to create many simultaneous connections. Units
|
||||
// are microseconds.
|
||||
message Duration {
|
||||
int64 intervalUs = 1;
|
||||
}
|
||||
|
||||
// PingResponse is used to indicate concurrency, how many Ping rpcs
|
||||
// are executing upon entry and upon exit (after the delay).
|
||||
// This rpc is used for testing only.
|
||||
message PingResponse {
|
||||
int64 entry = 1;
|
||||
int64 exit = 2;
|
||||
}
|
||||
|
||||
service CompactTxStreamer {
|
||||
// Return the height of the tip of the best chain
|
||||
rpc GetLatestBlock(ChainSpec) returns (BlockID) {}
|
||||
// Return the compact block corresponding to the given block identifier
|
||||
rpc GetBlock(BlockID) returns (CompactBlock) {}
|
||||
// Return a list of consecutive compact blocks
|
||||
rpc GetBlockRange(BlockRange) returns (stream CompactBlock) {}
|
||||
|
||||
// Return the requested full (not compact) transaction (as from zcashd)
|
||||
rpc GetTransaction(TxFilter) returns (RawTransaction) {}
|
||||
// Submit the given transaction to the zcash network
|
||||
rpc SendTransaction(RawTransaction) returns (SendResponse) {}
|
||||
|
||||
// Return the txids corresponding to the given t-address within the given block range
|
||||
rpc GetAddressTxids(TransparentAddressBlockFilter) returns (stream RawTransaction) {}
|
||||
|
||||
// Return information about this lightwalletd instance and the blockchain
|
||||
rpc GetLightdInfo(Empty) returns (LightdInfo) {}
|
||||
// Testing-only
|
||||
rpc Ping(Duration) returns (PingResponse) {}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"network": "mainnet",
|
||||
"height": 800000,
|
||||
"hash": "00000000013f1f4e5634e896ebdbe63dec115547c1480de0d83c64426f913c27",
|
||||
"time": 1587134100,
|
||||
"tree": "01a8689663d3c4ff2bb9aaeb7b75e66a7c68705d147bea9fe67cf7f1ffa9a071720011000196bff8c908d7015ad5df6bc5f5268a157da076c5d2ba63d222f0353c8810320100000001b0a0c048035d6f3f229bedadb2fb7688d03bc49062eae092666a8a55883afb0d000000013a365306be60039724201a594b84cb24ae09ea92a3077722da662289471f6658000184abf3f567f42573deb9e2ae56557fb6a763f16f90577ba5511b55c090eb6f2e00014da3714363acb83872f51c87fed3d42a1093420c3cb96b74ad65966ce27e8c4e0001e2bf698f5ac10b44da560d11a5e1d5c191a82a968a2be0a6948aa8748b54516001d2ea556f49fb934dc76f087935a5c07788000b4e3aae24883adfec51b5f4d260"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"network": "mainnet",
|
||||
"height": 820000,
|
||||
"hash": "00000000001bde1cd7d26b21c7c0a4eccf3c16805c6e68f499398463fa91ed93",
|
||||
"time": 1588640783,
|
||||
"tree": "0127679ec6e5f7205bd7088782922da4afd0606bf5404faf74feeebe5f548d3639018f80d63358b79bdbdd5a19c6fe3c76e8542f6a1a309ed1dedad95fe3ca5016681101f1a4674cf60a7a56e52fde0a308481a4c9b77f059687b965dffcd67be7c4a83101ed990844ef4a7751262755367e6e16d5e351f190fca5f07124a8f71efee2cc1900000001e83b41c625d651681527f2a95ff2925b96f77c1311144e191da1a59303e39a4200000001f33bcadb822e6d55ba2fa044e2c790d957bc8dd7a6ea773d92114517a97ea76501e0b92e780e46114e308d615f417d051613215bc5974946a4b056013d6351f52401ae652143148288b1328e1b511803db03fbdf319ee20da5c7e49374331c525d3a0000011e76743978cad6dd5263d5a654db307368cb997b0c984ad51018d007c955972101e2bf698f5ac10b44da560d11a5e1d5c191a82a968a2be0a6948aa8748b54516001d2ea556f49fb934dc76f087935a5c07788000b4e3aae24883adfec51b5f4d260"
|
||||
}
|
Loading…
Reference in New Issue