diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 59832005..be6a9923 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -#Tue Sep 03 16:14:18 IDT 2019 +#Tue Aug 13 08:37:24 IDT 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/kin-sdk/kin-sdk-lib/build.gradle b/kin-sdk/kin-sdk-lib/build.gradle index 17d609a2..95118798 100644 --- a/kin-sdk/kin-sdk-lib/build.gradle +++ b/kin-sdk/kin-sdk-lib/build.gradle @@ -50,27 +50,26 @@ dependencies { api 'com.github.kinecosystem:kin-utils-android:1.1' testImplementation 'junit:junit:4.12' - testImplementation 'org.mockito:mockito-core:2.27.0' + testImplementation 'org.mockito:mockito-core:2.25.0' testImplementation 'org.hamcrest:hamcrest-library:1.3' testImplementation 'com.squareup.okhttp3:mockwebserver:3.9.1' testImplementation 'org.robolectric:robolectric:4.3' testImplementation 'com.google.code.gson:gson:2.8.5' testImplementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0' + testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0' testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" androidTestImplementation 'junit:junit:4.12' - androidTestImplementation 'org.mockito:mockito-core:2.27.0' + androidTestImplementation 'org.mockito:mockito-core:2.25.0' + androidTestImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0' androidTestImplementation 'org.hamcrest:hamcrest-library:1.3' androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'org.mockito:mockito-android:2.10.0' - androidTestImplementation 'org.mockito:mockito-android:2.10.0' + androidTestImplementation 'org.mockito:mockito-android:2.25.0' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test:rules:1.0.2' androidTestImplementation 'org.hamcrest:hamcrest-library:1.3' androidTestImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - androidTestImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0' } //bundle javadocs with published aar diff --git a/kin-sdk/kin-sdk-lib/src/androidTest/java/kin/sdk/IntegConsts.java b/kin-sdk/kin-sdk-lib/src/androidTest/java/kin/sdk/IntegConsts.java index 501aa818..a42915bd 100644 --- a/kin-sdk/kin-sdk-lib/src/androidTest/java/kin/sdk/IntegConsts.java +++ b/kin-sdk/kin-sdk-lib/src/androidTest/java/kin/sdk/IntegConsts.java @@ -1,17 +1,18 @@ package kin.sdk; -final class IntegConsts { +public final class IntegConsts { - static final String TEST_NETWORK_URL = BuildConfig.INTEG_TESTS_NETWORK_URL.isEmpty() ? + public static final String TEST_NETWORK_URL = BuildConfig.INTEG_TESTS_NETWORK_URL.isEmpty() ? Environment.TEST.getNetworkUrl() : BuildConfig.INTEG_TESTS_NETWORK_URL; - static final String TEST_NETWORK_ID = BuildConfig.INTEG_TESTS_NETWORK_PASSPHRASE.isEmpty() ? + public static final String TEST_NETWORK_ID = + BuildConfig.INTEG_TESTS_NETWORK_PASSPHRASE.isEmpty() ? Environment.TEST.getNetworkPassphrase() : BuildConfig.INTEG_TESTS_NETWORK_PASSPHRASE; - static final String FRIENDBOT_URL = BuildConfig.INTEG_TESTS_NETWORK_FRIENDBOT.isEmpty() ? + public static final String FRIENDBOT_URL = BuildConfig.INTEG_TESTS_NETWORK_FRIENDBOT.isEmpty() ? "https://friendbot-testnet.kininfrastructure.com" : BuildConfig.INTEG_TESTS_NETWORK_FRIENDBOT; - static final String URL_CREATE_ACCOUNT = FRIENDBOT_URL + "?addr=%s&amount=%d"; - static final String URL_FUND = FRIENDBOT_URL + "/fund?addr=%s&amount="; // faucet - static final String URL_WHITELISTING_SERVICE = (BuildConfig.INTEG_TESTS_NETWORK_FRIENDBOT.isEmpty() ? + public static final String URL_CREATE_ACCOUNT = FRIENDBOT_URL + "?addr=%s&amount=%d"; + public static final String URL_FUND = FRIENDBOT_URL + "/fund?addr=%s&amount="; // faucet + public static final String URL_WHITELISTING_SERVICE = (BuildConfig.INTEG_TESTS_NETWORK_FRIENDBOT.isEmpty() ? "http://34.239.111.38:3000" : BuildConfig.INTEG_TESTS_NETWORK_FRIENDBOT) + "/whitelist"; } diff --git a/kin-sdk/kin-sdk-lib/src/androidTest/kotlin/kin/sdk/FakeKinOnBoard.kt b/kin-sdk/kin-sdk-lib/src/androidTest/kotlin/kin/sdk/FakeKinOnBoard.kt index b3635188..faa99c54 100644 --- a/kin-sdk/kin-sdk-lib/src/androidTest/kotlin/kin/sdk/FakeKinOnBoard.kt +++ b/kin-sdk/kin-sdk-lib/src/androidTest/kotlin/kin/sdk/FakeKinOnBoard.kt @@ -11,7 +11,7 @@ import java.util.concurrent.TimeUnit /** * Fake on board for integration test, support creating and funding accounts on stellar test net */ -internal class FakeKinOnBoard @Throws(IOException::class) +class FakeKinOnBoard @Throws(IOException::class) constructor() { private val client: OkHttpClient = OkHttpClient.Builder() diff --git a/kin-sdk/kin-sdk-lib/src/androidTest/kotlin/kin/sdk/IntegrationTestKinClientInjector.kt b/kin-sdk/kin-sdk-lib/src/androidTest/kotlin/kin/sdk/IntegrationTestKinClientInjector.kt new file mode 100644 index 00000000..ff6b24e5 --- /dev/null +++ b/kin-sdk/kin-sdk-lib/src/androidTest/kotlin/kin/sdk/IntegrationTestKinClientInjector.kt @@ -0,0 +1,28 @@ +package kin.sdk + +import android.content.Context +import kin.sdk.internal.blockchain.TransactionSender +import kin.sdk.internal.queue.PaymentQueueImpl + +class IntegrationTestKinClientInjector(context: Context?, environment: Environment?, appId: String?, storeKey: String?, + private val txSender: TransactionSender? = null, + private val configuration: PaymentQueueImpl.PaymentQueueConfiguration?) : + KinClientInjector(context, environment, appId, storeKey) { + + + override fun getPaymentQueueConfiguration(): PaymentQueueImpl.PaymentQueueConfiguration { + return configuration + ?: PaymentQueueImpl.PaymentQueueConfiguration(Constants.delayBetweenPaymentsMillis, Constants.queueTimeoutMillis, Constants.maxNumOfPayments) + } + + override fun getTransactionSender(): TransactionSender { + return txSender ?: super.getTransactionSender() + } + + object Constants { + const val delayBetweenPaymentsMillis: Long = 250 + const val queueTimeoutMillis: Long = 2000 + const val maxNumOfPayments: Int = 5 + } + +} \ No newline at end of file diff --git a/kin-sdk/kin-sdk-lib/src/androidTest/kotlin/kin/sdk/KinAccountIntegrationTest.kt b/kin-sdk/kin-sdk-lib/src/androidTest/kotlin/kin/sdk/KinAccountIntegrationTest.kt index 435224bf..06e34a09 100644 --- a/kin-sdk/kin-sdk-lib/src/androidTest/kotlin/kin/sdk/KinAccountIntegrationTest.kt +++ b/kin-sdk/kin-sdk-lib/src/androidTest/kotlin/kin/sdk/KinAccountIntegrationTest.kt @@ -30,7 +30,6 @@ class KinAccountIntegrationTest { private val appId = "1a2c" private val fee: Int = 100 private val feeInKin: BigDecimal = BigDecimal.valueOf(0.001) - private val appIdVersionPrefix = "1" private val timeoutDurationSeconds: Long = 15 private val timeoutDurationSecondsLong: Long = 20 @@ -106,10 +105,10 @@ class KinAccountIntegrationTest { @LargeTest @Throws(Exception::class) fun sendTransaction_WithMemo() { - val (kinAccountSender, kinAccountReceiver) = onboardAccounts(senderFundAmount = 100) + val (kinAccountSender, kinAccountReceiver) = onboardAccounts(kinClient, fakeKinOnBoard, senderFundAmount = 100) val memo = "fake memo" - val expectedMemo = addAppIdToMemo(memo) + val expectedMemo = addAppIdToMemo(memo, appId) val transactionId = sendTransactionAndAssert(kinAccountSender, kinAccountReceiver, memo) @@ -123,8 +122,8 @@ class KinAccountIntegrationTest { @LargeTest @Throws(Exception::class) fun sendTransaction_WithoutMemoJustPrefix() { - val (kinAccountSender, kinAccountReceiver) = onboardAccounts(senderFundAmount = 100) - val expectedMemo = addAppIdToMemo("") + val (kinAccountSender, kinAccountReceiver) = onboardAccounts(kinClient, fakeKinOnBoard, senderFundAmount = 100) + val expectedMemo = addAppIdToMemo("", appId) val transactionId = sendTransactionAndAssert(kinAccountSender, kinAccountReceiver, null) val server = Server(TEST_NETWORK_URL) val transactionResponse = server.transactions().transaction(transactionId.id()) @@ -136,10 +135,8 @@ class KinAccountIntegrationTest { @LargeTest @Throws(Exception::class) fun sendTransaction_WithoutMemo() { - val (kinAccountSender, kinAccountReceiver) = onboardAccounts( - senderFundAmount = 100, - kinClient = KinClient(InstrumentationRegistry.getTargetContext(), environment, null)) - + val (kinAccountSender, kinAccountReceiver) = onboardAccounts(KinClient(InstrumentationRegistry.getTargetContext(), environment, null), + fakeKinOnBoard, senderFundAmount = 100) val transactionId = sendTransactionAndAssert(kinAccountSender, kinAccountReceiver, null) val server = Server(TEST_NETWORK_URL) val transactionResponse = server.transactions().transaction(transactionId.id()) @@ -151,10 +148,8 @@ class KinAccountIntegrationTest { @LargeTest @Throws(Exception::class) fun sendTransaction_WithoutMemoPrefix() { - val (kinAccountSender, kinAccountReceiver) = onboardAccounts( - senderFundAmount = 100, - kinClient = KinClient(InstrumentationRegistry.getTargetContext(), environment, null)) - + val (kinAccountSender, kinAccountReceiver) = onboardAccounts(KinClient(InstrumentationRegistry.getTargetContext(), environment, null), + fakeKinOnBoard, senderFundAmount = 100) val memo = "fake memo" val transactionId = sendTransactionAndAssert(kinAccountSender, kinAccountReceiver, memo) val server = Server(TEST_NETWORK_URL) @@ -234,7 +229,7 @@ class KinAccountIntegrationTest { @LargeTest @Throws(Exception::class) fun sendTransaction_NotEnoughFee_InsufficientFeeException() { - val (kinAccountSender, kinAccountReceiver) = onboardAccounts() + val (kinAccountSender, kinAccountReceiver) = onboardAccounts(kinClient, fakeKinOnBoard) expectedEx.expect(InsufficientFeeException::class.java) val minFee: Int = Math.toIntExact(kinClient.minimumFeeSync) @@ -246,7 +241,7 @@ class KinAccountIntegrationTest { @LargeTest @Throws(Exception::class) fun sendWhitelistTransaction_FeeNotReduce() { - val (kinAccountSender, kinAccountReceiver) = onboardAccounts(senderFundAmount = 100) + val (kinAccountSender, kinAccountReceiver) = onboardAccounts(kinClient, fakeKinOnBoard, senderFundAmount = 100) val minFee: Int = Math.toIntExact(kinClient.minimumFeeSync) val transaction = kinAccountSender.buildTransactionSync(kinAccountReceiver.publicAddress.orEmpty(), @@ -260,7 +255,7 @@ class KinAccountIntegrationTest { @LargeTest @Throws(Exception::class) fun sendWhitelistTransaction_Success() { - val (kinAccountSender, kinAccountReceiver) = onboardAccounts(senderFundAmount = 100) + val (kinAccountSender, kinAccountReceiver) = onboardAccounts(kinClient, fakeKinOnBoard, senderFundAmount = 100) val minFee: Int = Math.toIntExact(kinClient.minimumFeeSync) val transaction = kinAccountSender.buildTransactionSync(kinAccountReceiver.publicAddress.orEmpty(), @@ -275,7 +270,7 @@ class KinAccountIntegrationTest { @LargeTest @Throws(Exception::class) fun sendTransaction_Success() { - val (kinAccountSender, kinAccountReceiver) = onboardAccounts(senderFundAmount = 100) + val (kinAccountSender, kinAccountReceiver) = onboardAccounts(kinClient, fakeKinOnBoard, senderFundAmount = 100) val transactionId = sendTransactionAndAssert(kinAccountSender, kinAccountReceiver, "fake memo") assertNotNull(transactionId) assertThat(transactionId.id(), not(isEmptyString())) @@ -302,7 +297,7 @@ class KinAccountIntegrationTest { val fundingAmount = BigDecimal("100") val transactionAmount = BigDecimal("21.123") - val (kinAccountSender, kinAccountReceiver) = onboardAccounts(0, 0) + val (kinAccountSender, kinAccountReceiver) = onboardAccounts(kinClient, fakeKinOnBoard, 0, 0) //register listeners for testing val actualPaymentsResults = ArrayList() @@ -324,7 +319,7 @@ class KinAccountIntegrationTest { //send the transaction we want to observe fakeKinOnBoard.fundWithKin(kinAccountSender.publicAddress.orEmpty(), "100") val memo = "memo" - val expectedMemo = addAppIdToMemo(memo) + val expectedMemo = addAppIdToMemo(memo, appId) val transaction = kinAccountSender.buildTransactionSync(kinAccountReceiver.publicAddress.orEmpty(), transactionAmount, fee, memo) val expectedTransactionId = kinAccountSender.sendTransactionSync(transaction) @@ -351,7 +346,7 @@ class KinAccountIntegrationTest { @LargeTest @Throws(Exception::class) fun createPaymentListener_RemoveListener_NoEvents() { - val (kinAccountSender, kinAccountReceiver) = onboardAccounts(senderFundAmount = 100) + val (kinAccountSender, kinAccountReceiver) = onboardAccounts(kinClient, fakeKinOnBoard, senderFundAmount = 100) val latch = CountDownLatch(1) val listenerRegistration = kinAccountReceiver.addPaymentListener { @@ -368,27 +363,13 @@ class KinAccountIntegrationTest { @LargeTest @Throws(Exception::class) fun sendTransaction_NotEnoughKin_InsufficientKinException() { - val (kinAccountSender, kinAccountReceiver) = onboardAccounts() + val (kinAccountSender, kinAccountReceiver) = onboardAccounts(kinClient, fakeKinOnBoard) expectedEx.expect(InsufficientKinException::class.java) val transaction = kinAccountSender.buildTransactionSync(kinAccountReceiver.publicAddress.orEmpty(), BigDecimal("21.123"), fee) kinAccountSender.sendTransactionSync(transaction) } - private fun onboardAccounts(senderFundAmount: Int = 0, - receiverFundAmount: Int = 0, - kinClient: KinClient = this.kinClient): Pair { - val kinAccountSender = kinClient.addAccount() - val kinAccountReceiver = kinClient.addAccount() - fakeKinOnBoard.createAccount(kinAccountSender.publicAddress.orEmpty(), senderFundAmount) - fakeKinOnBoard.createAccount(kinAccountReceiver.publicAddress.orEmpty(), receiverFundAmount) - return Pair(kinAccountSender, kinAccountReceiver) - } - - private fun addAppIdToMemo(memo: String): String { - return appIdVersionPrefix.plus("-").plus(appId).plus("-").plus(memo) - } - private fun sendTransactionAndAssert(kinAccountSender: KinAccount, kinAccountReceiver: KinAccount, memo: String?): TransactionId { val transaction = kinAccountSender.buildTransactionSync(kinAccountReceiver.publicAddress.orEmpty(), BigDecimal("21.123"), fee, memo) diff --git a/kin-sdk/kin-sdk-lib/src/androidTest/kotlin/kin/sdk/Utils.kt b/kin-sdk/kin-sdk-lib/src/androidTest/kotlin/kin/sdk/Utils.kt new file mode 100644 index 00000000..aeaf50d5 --- /dev/null +++ b/kin-sdk/kin-sdk-lib/src/androidTest/kotlin/kin/sdk/Utils.kt @@ -0,0 +1,29 @@ +package kin.sdk + +import android.support.test.InstrumentationRegistry +import kin.sdk.internal.blockchain.TransactionSender +import kin.sdk.internal.queue.PaymentQueueImpl + + +private const val appIdVersionPrefix = "1" + +fun onboardAccounts(kinClient: KinClient, fakeKinOnBoard: FakeKinOnBoard, senderFundAmount: Int = 0, + receiverFundAmount: Int = 0): Pair { + val kinAccountSender = kinClient.addAccount() + val kinAccountReceiver = kinClient.addAccount() + fakeKinOnBoard.createAccount(kinAccountSender.publicAddress.orEmpty(), senderFundAmount) + fakeKinOnBoard.createAccount(kinAccountReceiver.publicAddress.orEmpty(), receiverFundAmount) + return Pair(kinAccountSender, kinAccountReceiver) +} + +fun addAppIdToMemo(memo: String, appId: String): String { + return appIdVersionPrefix.plus("-").plus(appId).plus("-").plus(memo) +} + +fun getPaymentQueueTestKinClient(environment: Environment, appId: String, txSender: TransactionSender? = null, + configuration: PaymentQueueImpl.PaymentQueueConfiguration? = null): KinClient { + val injector = IntegrationTestKinClientInjector(InstrumentationRegistry.getTargetContext(), + environment, appId, "", txSender, configuration) + return KinClient(injector) +} + diff --git a/kin-sdk/kin-sdk-lib/src/androidTest/kotlin/kin/sdk/internal/queue/PaymentQueueEndToEndIntegrationTest.kt b/kin-sdk/kin-sdk-lib/src/androidTest/kotlin/kin/sdk/internal/queue/PaymentQueueEndToEndIntegrationTest.kt new file mode 100644 index 00000000..345ce4ab --- /dev/null +++ b/kin-sdk/kin-sdk-lib/src/androidTest/kotlin/kin/sdk/internal/queue/PaymentQueueEndToEndIntegrationTest.kt @@ -0,0 +1,174 @@ +package kin.sdk.internal.queue + +import kin.base.MemoText +import kin.base.Server +import kin.sdk.* +import kin.sdk.exception.KinException +import kin.sdk.queue.PaymentQueue +import kin.sdk.queue.PaymentQueueTransactionProcess +import kin.sdk.queue.PendingPayment +import kin.sdk.transactiondata.BatchPaymentTransaction +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.After +import org.junit.Before +import org.junit.BeforeClass +import org.junit.Test +import java.io.IOException +import java.math.BigDecimal +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import kotlin.test.assertTrue +import kotlin.test.fail + + +class PaymentQueueEndToEndIntegrationTest { + + private val appId = "1a2c" + private val fee: Int = 100 + private val feeInKin: BigDecimal = BigDecimal.valueOf(0.001) + private val timeoutDurationSecondsLong: Long = 20 + private val environment: Environment = Environment(IntegConsts.TEST_NETWORK_URL, IntegConsts.TEST_NETWORK_ID) + private lateinit var kinClient: KinClient + + + @Before + fun setup() { + kinClient = getPaymentQueueTestKinClient(environment, appId) + kinClient.clearAllAccounts() + } + + @After + fun teardown() { + if (::kinClient.isInitialized) { + kinClient.clearAllAccounts() + } + } + + @Test + fun enqueuePayments_DelayBetweenPayments_Success() { + val amount = BigDecimal(50) + val kinAccountSender = kinClient.addAccount() + fakeKinOnBoard.createAccount(kinAccountSender.publicAddress.orEmpty(), 250) + val kinAccountReceiver1 = addAndCreateAccount() + val kinAccountReceiver2 = addAndCreateAccount() + val kinAccountReceiver3 = addAndCreateAccount() + + val latch = CountDownLatch(1) + val paymentQueue = kinAccountSender.paymentQueue() + setEventListener(paymentQueue, latch) + + paymentQueue.setFee(fee) + paymentQueue.enqueuePayment(kinAccountReceiver2.publicAddress.orEmpty(), amount, "2") + paymentQueue.enqueuePayment(kinAccountReceiver1.publicAddress.orEmpty(), amount, "1") + paymentQueue.enqueuePayment(kinAccountReceiver3.publicAddress.orEmpty(), amount, "3") + paymentQueue.enqueuePayment(kinAccountReceiver1.publicAddress.orEmpty(), amount, "4") + Thread.sleep(50) + assertThat(paymentQueue.pendingPaymentsCount(), equalTo(4)) + + assertTrue(latch.await(timeoutDurationSecondsLong, TimeUnit.SECONDS)) + + assertThat(kinAccountReceiver1.balanceSync.value(), equalTo(BigDecimal("100.00000"))) + assertThat(kinAccountReceiver2.balanceSync.value(), equalTo(BigDecimal("50.00000"))) + assertThat(kinAccountReceiver3.balanceSync.value(), equalTo(BigDecimal("50.00000"))) + assertThat(kinAccountSender.balanceSync.value(), equalTo(BigDecimal("50.00000").subtract(feeInKin.multiply(BigDecimal(4))))) + } + + @Test + fun enqueuePayments_MaxQueueElements_Intercept_Success() { + val amount = BigDecimal(50) + val pendingPayments = mutableListOf() + val kinAccountSender = kinClient.addAccount() + fakeKinOnBoard.createAccount(kinAccountSender.publicAddress.orEmpty(), 350) + + val kinAccountReceiver1 = addAndCreateAccount() + val kinAccountReceiver2 = addAndCreateAccount() + val kinAccountReceiver3 = addAndCreateAccount() + + val latch = CountDownLatch(1) + val paymentQueue = kinAccountSender.paymentQueue() + setEventListener(paymentQueue, latch) + + paymentQueue.setFee(fee) + + val memo = "fake memo" + val expectedMemo = addAppIdToMemo(memo, appId) + + + var transactionId: TransactionId? = null + paymentQueue.setTransactionInterceptor(object : TransactionInterceptor { + override fun interceptTransactionSending(process: PaymentQueueTransactionProcess?): TransactionId? { + val transaction = process?.transaction(memo) + // simulate some work + Thread.sleep(5000) + transactionId = process?.send(transaction) + return transactionId + } + }) + + paymentQueue.enqueuePayment(kinAccountReceiver1.publicAddress.orEmpty(), amount, "1") + paymentQueue.enqueuePayment(kinAccountReceiver2.publicAddress.orEmpty(), amount, "2") + paymentQueue.enqueuePayment(kinAccountReceiver3.publicAddress.orEmpty(), amount, "3") + paymentQueue.enqueuePayment(kinAccountReceiver1.publicAddress.orEmpty(), amount, "4") + paymentQueue.enqueuePayment(kinAccountReceiver3.publicAddress.orEmpty(), amount, "5") + Thread.sleep(100) + assertThat(paymentQueue.pendingPaymentsCount(), equalTo(0)) + + assertTrue(latch.await(timeoutDurationSecondsLong, TimeUnit.SECONDS)) + + assertThat(kinAccountReceiver1.balanceSync.value(), equalTo(BigDecimal("100.00000"))) + assertThat(kinAccountReceiver2.balanceSync.value(), equalTo(BigDecimal("50.00000"))) + assertThat(kinAccountReceiver3.balanceSync.value(), equalTo(BigDecimal("100.00000"))) + assertThat(kinAccountSender.balanceSync.value(), equalTo(BigDecimal("100.00000").subtract(feeInKin.multiply(BigDecimal(5))))) + + val server = Server(IntegConsts.TEST_NETWORK_URL) + val transactionResponse = server.transactions().transaction(transactionId?.id()) + val actualMemo = transactionResponse.memo + assertThat((actualMemo as MemoText).text, equalTo(expectedMemo)) + } + + + private fun setEventListener(paymentQueue: PaymentQueue, latch: CountDownLatch) { + paymentQueue.setEventListener(object : PaymentQueue.EventListener { + override fun onPaymentEnqueued(payment: PendingPayment?) { + } + + override fun onTransactionSend(transaction: BatchPaymentTransaction?, payments: MutableList?) { + assertThat(paymentQueue.transactionInProgress(), equalTo(true)) + } + + override fun onTransactionSendSuccess(transactionId: TransactionId?, payments: MutableList?) { + latch.countDown() + } + + override fun onTransactionSendFailed(payments: MutableList?, exception: KinException?) { + latch.countDown() + } + + }) + } + + //TODO include events manager and balance updater to the happy path and transaction params happy path + + private fun addAndCreateAccount(): KinAccount { + val kinAccount = kinClient.addAccount() + kinAccount.publicAddress?.let { + fakeKinOnBoard.createAccount(it) + } ?: kotlin.run { + fail("public address is null") + } + return kinAccount + } + + companion object { + private lateinit var fakeKinOnBoard: FakeKinOnBoard + + @BeforeClass + @JvmStatic + @Throws(IOException::class) + fun setupKinOnBoard() { + fakeKinOnBoard = FakeKinOnBoard() + } + } + +} \ No newline at end of file diff --git a/kin-sdk/kin-sdk-lib/src/androidTest/kotlin/kin/sdk/internal/queue/PaymentQueueIntegrationTests.kt b/kin-sdk/kin-sdk-lib/src/androidTest/kotlin/kin/sdk/internal/queue/PaymentQueueIntegrationTests.kt new file mode 100644 index 00000000..70385bfc --- /dev/null +++ b/kin-sdk/kin-sdk-lib/src/androidTest/kotlin/kin/sdk/internal/queue/PaymentQueueIntegrationTests.kt @@ -0,0 +1,193 @@ +package kin.sdk.internal.queue + +import com.nhaarman.mockitokotlin2.* +import kin.sdk.* +import kin.sdk.IntegrationTestKinClientInjector.Constants.delayBetweenPaymentsMillis +import kin.sdk.IntegrationTestKinClientInjector.Constants.maxNumOfPayments +import kin.sdk.IntegrationTestKinClientInjector.Constants.queueTimeoutMillis +import kin.sdk.exception.KinException +import kin.sdk.internal.blockchain.TransactionSender +import kin.sdk.internal.data.TransactionIdImpl +import kin.sdk.internal.queue.PaymentQueueIntegrationTests.CONSTANTS.APP_ID +import kin.sdk.internal.queue.PaymentQueueIntegrationTests.CONSTANTS.FEE +import kin.sdk.internal.queue.PaymentQueueIntegrationTests.CONSTANTS.TIMEOUT_DURATION_SECONDS_LONG +import kin.sdk.queue.PaymentQueue +import kin.sdk.queue.PaymentQueueTransactionProcess +import kin.sdk.queue.PendingPayment +import kin.sdk.transactiondata.BatchPaymentTransaction +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.* +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.`when` +import java.math.BigDecimal +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +class PaymentQueueIntegrationTests { + + object CONSTANTS { + const val APP_ID = "1a2c" + const val TIMEOUT_DURATION_SECONDS_LONG: Long = 20 + const val FEE: Int = 100 + } + + private val environment: Environment = Environment(IntegConsts.TEST_NETWORK_URL, IntegConsts.TEST_NETWORK_ID) + private lateinit var kinClient: KinClient + + private val transactionSender: TransactionSender = mock() + private val transactionInterceptor: TransactionInterceptor = mock() + + + @Before + fun setup() { + val paymentQueueConfiguration = PaymentQueueImpl.PaymentQueueConfiguration(50, queueTimeoutMillis, maxNumOfPayments) + kinClient = getPaymentQueueTestKinClient(environment, APP_ID, transactionSender, paymentQueueConfiguration) + kinClient.clearAllAccounts() + } + + @After + fun teardown() { + if (::kinClient.isInitialized) { + kinClient.clearAllAccounts() + } + } + + @Test + fun enqueuePayments_NegativeAmount_IllegalArgumentException() { + val amount = BigDecimal(-50) + val kinAccountSender = kinClient.addAccount() + val kinAccountReceiver = kinClient.addAccount() + val paymentQueue = kinAccountSender.paymentQueue() + + assertFailsWith { paymentQueue.enqueuePayment(kinAccountReceiver.publicAddress.orEmpty(), amount) } + } + + @Test + fun enqueuePayments_NegativeFee_IllegalArgumentException() { + val kinAccountSender = kinClient.addAccount() + val paymentQueue = kinAccountSender.paymentQueue() + assertFailsWith { paymentQueue.setFee(-FEE) } + } + + @Test + fun enqueuePayments_ZeroFee_GotMinimumFee() { + //TODO captor the fee parameter from build and see that it is the minimum + val argumentCaptor = argumentCaptor() + val kinAccountSender = kinClient.addAccount() + val kinAccountReceiver = kinClient.addAccount() + val paymentQueue = kinAccountSender.paymentQueue() + +// `when`(transactionSender.buildBatchPaymentTransaction(any(), any(), any(), any())).thenReturn(null) + + paymentQueue.enqueuePayment(kinAccountReceiver.publicAddress.orEmpty(), BigDecimal(50)) + Thread.sleep(delayBetweenPaymentsMillis + 1000) + + verify(transactionSender).buildBatchPaymentTransaction(any(), any(), argumentCaptor.capture(), eq(null)) + assertThat(kinClient.minimumFeeSync.toInt(), equalTo((argumentCaptor.firstValue))) + } + + @Test + fun enqueuePayments_UseInterceptor_VerifyInterceptorCalled() { + val kinAccountSender = kinClient.addAccount() + val kinAccountReceiver = kinClient.addAccount() + val paymentQueue = kinAccountSender.paymentQueue() + paymentQueue.setTransactionInterceptor(transactionInterceptor) + paymentQueue.setFee(FEE) + + val latch = CountDownLatch(5) + paymentQueue.setEventListener(object : PaymentQueue.EventListener { + override fun onPaymentEnqueued(payment: PendingPayment?) { + } + + override fun onTransactionSend(transaction: BatchPaymentTransaction?, payments: MutableList?) { + + } + + override fun onTransactionSendSuccess(transactionId: TransactionId?, payments: MutableList?) { + } + + override fun onTransactionSendFailed(payments: MutableList?, exception: KinException?) { + // this will also verify that it fails 5 times + latch.countDown() + } + + }) + + `when`(transactionInterceptor.interceptTransactionSending(any())).thenAnswer { + Thread.sleep(200) + null + } + for (x in 0 until 5) { + for (y in 0 until 4) { + paymentQueue.enqueuePayment(kinAccountReceiver.publicAddress.orEmpty(), BigDecimal(50)) + } + Thread.sleep(delayBetweenPaymentsMillis + 10) + } + assertTrue(latch.await(TIMEOUT_DURATION_SECONDS_LONG, TimeUnit.SECONDS)) + + verify(transactionInterceptor, times(5)).interceptTransactionSending(any()) + } + + @Test + fun enqueuePayments_AddMultipleTasks_TasksInvokedInCorrectOrder() { + // in this tests we are inserting to the task queue 5 lists of pending payments, each with 4 elements. the first one is taken but the others are not because the first one is in progress. + // after they merged in the task queue we have 4 lists, the top 3 with 5 elements and the last one with 1. + // then we check that indeed we send 5 transactions with the correct order and number of elements with their ids. + + val amount = BigDecimal(50) + val kinAccountSender = kinClient.addAccount() + val kinAccountReceiver = kinClient.addAccount() + val paymentQueue = kinAccountSender.paymentQueue() + paymentQueue.setFee(FEE) + + `when`(transactionSender.sendTransaction(null)).thenAnswer { + Thread.sleep(1000) // wait for the tasks to accumulate and merged in the task queue + TransactionIdImpl("fake transaction id") + } + + val listOfListOfPayments: MutableList?> = mutableListOf() + val latch = CountDownLatch(5) + paymentQueue.setEventListener(object : PaymentQueue.EventListener { + override fun onPaymentEnqueued(payment: PendingPayment?) { + } + + override fun onTransactionSend(transaction: BatchPaymentTransaction?, payments: MutableList?) { + listOfListOfPayments.add(payments) + latch.countDown() + } + + override fun onTransactionSendSuccess(transactionId: TransactionId?, payments: MutableList?) { + } + + override fun onTransactionSendFailed(payments: MutableList?, exception: KinException?) { + } + + }) + + var idCounter = 1 + // create 'maxNumOfPayments' lists, each one with 4 pending payments + for (x in 0 until maxNumOfPayments) { + for (y in 0 until 4) { + paymentQueue.enqueuePayment(kinAccountReceiver.publicAddress.orEmpty(), amount, idCounter) + idCounter++ + } + Thread.sleep(delayBetweenPaymentsMillis + 10) + } + + assertTrue(latch.await(TIMEOUT_DURATION_SECONDS_LONG, TimeUnit.SECONDS)) + + val listOfListOfIds = listOfListOfPayments.map { it?.map { pendingPayment -> pendingPayment.metadata() as Int } } + val expectedListOfListOfIds = listOf(listOf(1, 2, 3, 4), listOf(5, 6, 7, 8, 9), listOf(10, 11, 12, 13, 14), listOf(15, 16, 17, 18, 19), listOf(20)) + + assertThat(listOfListOfIds, hasSize(expectedListOfListOfIds.size)) + for (i in listOfListOfIds.indices) { + assertThat(listOfListOfIds[i], `is`(expectedListOfListOfIds[i])) + } + } + + //TODO add tests which includes testings for the events manager, balance updater and transaction params, and tests for negative/exception path (fee to low, all exceptions, etc) +} \ No newline at end of file diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/KinAccount.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/KinAccount.java index c9698e81..65f76f77 100644 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/KinAccount.java +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/KinAccount.java @@ -2,13 +2,20 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import kin.sdk.exception.*; + +import java.math.BigDecimal; + +import kin.sdk.exception.AccountNotFoundException; +import kin.sdk.exception.CryptoException; +import kin.sdk.exception.InsufficientKinException; +import kin.sdk.exception.OperationFailedException; +import kin.sdk.exception.TransactionFailedException; import kin.sdk.queue.PaymentQueue; +import kin.sdk.queue.TransactionProcess; import kin.sdk.transactiondata.PaymentTransaction; +import kin.sdk.transactiondata.TransactionParams; import kin.utils.Request; -import java.math.BigDecimal; - /** * Represents an account which holds Kin. */ @@ -20,7 +27,12 @@ public interface KinAccount { @Nullable String getPublicAddress(); + // TODO: 2019-08-21 finalize the deprecated texts + /** + * @deprecated This method was deprecated in version 2.0.0, please use the PaymentQueue, + * TransactionInterceptor and/or sendTransaction(sync or not) classes + * * Build a Transaction object of the given amount in kin, to the specified public address. *

See {@link KinAccount#buildTransactionSync(String, BigDecimal, int)} for possibles errors

* @@ -33,6 +45,9 @@ public interface KinAccount { Request buildTransaction(@NonNull String publicAddress, @NonNull BigDecimal amount, int fee); /** + * @deprecated This method was deprecated in version 2.0.0, please use the PaymentQueue, + * TransactionInterceptor and/or sendTransaction(sync or not) classes + * * Build a Transaction object of the given amount in kin, to the specified public address and with a memo(that * can be empty or null). *

See {@link KinAccount#buildTransactionSync(String, BigDecimal, int, String)} for possibles errors

@@ -49,6 +64,9 @@ Request buildTransaction(@NonNull String publicAddress, @Non @Nullable String memo); /** + * @deprecated This method was deprecated in version 2.0.0, please use the PaymentQueue, + * TransactionInterceptor and/or sendTransaction(sync or not) classes + * * Create {@link Request} for signing and sending a transaction *

See {@link KinAccount#sendTransactionSync(PaymentTransaction)} for possibles errors

* @@ -60,6 +78,9 @@ Request buildTransaction(@NonNull String publicAddress, @Non Request sendTransaction(PaymentTransaction transaction); /** + * @deprecated This method was deprecated in version 2.0.0, please use the PaymentQueue, + * TransactionInterceptor and/or sendTransaction(sync or not) classes + * * Create {@link Request} for signing and sending a transaction from a whitelist. * whitelist a transaction means that the user will not pay any fee(if your App is in the Kin whitelist) *

See {@link KinAccount#sendWhitelistTransactionSync(String)} for possibles errors

@@ -72,6 +93,9 @@ Request buildTransaction(@NonNull String publicAddress, @Non Request sendWhitelistTransaction(String whitelist); /** + * @deprecated This method was deprecated in version 2.0.0, please use the PaymentQueue, + * TransactionInterceptor and/or sendTransaction(sync or not) classes + * * Build a Transaction object of the given amount in kin, to the specified public address. *

Note: This method accesses the network, and should not be called on the android main thread.

* @@ -86,6 +110,9 @@ Request buildTransaction(@NonNull String publicAddress, @Non PaymentTransaction buildTransactionSync(@NonNull String publicAddress, @NonNull BigDecimal amount, int fee) throws OperationFailedException; /** + * @deprecated This method was deprecated in version 2.0.0, please use the PaymentQueue, + * TransactionInterceptor and/or sendTransaction(sync or not) classes + * * Build a Transaction object of the given amount in kin, to the specified public address and with a memo(that * can be empty or null). *

Note: This method accesses the network, and should not be called on the android main thread.

@@ -104,6 +131,9 @@ PaymentTransaction buildTransactionSync(@NonNull String publicAddress, @NonNull @Nullable String memo) throws OperationFailedException; /** + * @deprecated This method was deprecated in version 2.0.0, please use the PaymentQueue, + * TransactionInterceptor and/or sendTransaction(sync or not) classes + * * send a transaction. *

Note: This method accesses the network, and should not be called on the android main thread.

* @@ -119,6 +149,9 @@ PaymentTransaction buildTransactionSync(@NonNull String publicAddress, @NonNull TransactionId sendTransactionSync(PaymentTransaction transaction) throws OperationFailedException; /** + * @deprecated This method was deprecated in version 2.0.0, please use the PaymentQueue, + * TransactionInterceptor and/or sendTransaction(sync or not) classes + * * send a whitelist transaction. * whitelist a transaction means that the user will not pay any fee(if your App is in the Kin whitelist) *

Note: This method accesses the network, and should not be called on the android main thread.

@@ -135,28 +168,25 @@ PaymentTransaction buildTransactionSync(@NonNull String publicAddress, @NonNull TransactionId sendWhitelistTransactionSync(String whitelist) throws OperationFailedException; /** - * send a transaction. - *

Note: This method accesses the network, and should not be called on the android main thread.

+ * Create {@link Request} for signing and sending a transaction + *

See {@link KinAccount#sendTransaction(TransactionParams, TransactionInterceptor)} + * for possibles errors

* - * @param transaction is the transaction object to send. - * @param interceptor is the interceptor for the transaction before it is being sent. - * @return TransactionId the transaction identifier. - * @throws AccountNotFoundException if the sender or destination account was not created. - * @throws InsufficientKinException if account balance has not enough kin. - * @throws TransactionFailedException if transaction failed, contains blockchain failure details. - * @throws OperationFailedException other error occurred. + * @param transactionParams is the transaction parameters which define the transaction object to send. + * @return {@code Request}, TransactionId - the transaction identifier. */ - TransactionId sendTransactionSync(SendTransactionParams transaction, TransactionInterceptor interceptor) throws OperationFailedException; + Request sendTransaction(final TransactionParams transactionParams); /** * Create {@link Request} for signing and sending a transaction - *

See {@link KinAccount#sendTransactionSync(PaymentTransaction)} for possibles errors

+ * for possibles errors

* - * @param transaction is the transaction object to send. + * @param transactionParams is the transaction parameters which define the transaction object to send. * @param interceptor is the interceptor for the transaction before it is being sent. * @return {@code Request}, TransactionId - the transaction identifier. */ - Request sendTransaction(SendTransactionParams transaction, TransactionInterceptor interceptor); + Request sendTransaction(TransactionParams transactionParams, + @Nullable TransactionInterceptor interceptor); /** * @return the payment queue. diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/KinClient.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/KinClient.java index 748ca264..4db89676 100644 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/KinClient.java +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/KinClient.java @@ -4,30 +4,31 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; -import android.util.Log; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + import kin.base.KeyPair; -import kin.base.Network; -import kin.base.Server; -import kin.sdk.exception.*; +import kin.sdk.exception.CorruptedDataException; +import kin.sdk.exception.CreateAccountException; +import kin.sdk.exception.CryptoException; +import kin.sdk.exception.DeleteAccountException; +import kin.sdk.exception.LoadAccountException; +import kin.sdk.exception.OperationFailedException; import kin.sdk.internal.account.KinAccountImpl; import kin.sdk.internal.backuprestore.BackupRestore; -import kin.sdk.internal.backuprestore.BackupRestoreImpl; import kin.sdk.internal.blockchain.AccountInfoRetriever; import kin.sdk.internal.blockchain.GeneralBlockchainInfoRetrieverImpl; import kin.sdk.internal.blockchain.TransactionSender; import kin.sdk.internal.blockchain.events.BlockchainEventsCreator; +import kin.sdk.internal.queue.PaymentQueueImpl; import kin.sdk.internal.storage.KeyStore; -import kin.sdk.internal.storage.KeyStoreImpl; -import kin.sdk.internal.storage.SharedPrefStore; +import kin.sdk.queue.PaymentQueue; import kin.utils.Request; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; - import static kin.sdk.internal.Utils.checkNotNull; /** @@ -35,8 +36,6 @@ */ public class KinClient { - private static final String STORE_NAME_PREFIX = "KinKeyStore_"; - private static final int TRANSACTIONS_TIMEOUT = 30; private final Environment environment; private final KeyStore keyStore; private final TransactionSender transactionSender; @@ -46,48 +45,72 @@ public class KinClient { private final BackupRestore backupRestore; private final String appId; private final String storeKey; + private final PaymentQueueImpl.PaymentQueueConfiguration paymentQueueConfiguration; @NonNull private List kinAccounts = new ArrayList<>(1); /** - * For more details please look at {@link #KinClient(Context context,Environment environment, String appId, String storeKey)} + * For more details please look at + * {@link #KinClient(Context context, Environment environment, String appId, String storeKey)} */ public KinClient(@NonNull Context context, @NonNull Environment environment, String appId) { - this(context, environment, appId,""); + this(context, environment, appId, ""); } /** * Build KinClient object. - * @param context android context + * + * @param context android context * @param environment the blockchain network details. - * @param appId a 3 or 4 character string which represent the application id which will be added to each - * transaction. - *
Note: appId must contain only upper and/or lower case letters and/or digits and that the total string length is between 3 to 4. - * For example 1234 or 2ab3 or bca, etc.
- * @param storeKey an optional param which is the key for storing this KinClient data, different keys will store a different accounts. + * @param appId a 3 or 4 character string which represent the application id which will + * be added to each + * transaction. + *
Note: appId must contain only upper and/or lower case + * letters and/or digits and that the total string length is between 3 to 4. + * For example 1234 or 2ab3 or bca, etc.
+ * @param storeKey an optional param which is the key for storing this KinClient data, + * different keys will store a different accounts. */ - public KinClient(@NonNull Context context, @NonNull Environment environment, @NonNull String appId, @NonNull String storeKey) { + public KinClient(@NonNull Context context, @NonNull Environment environment, + @NonNull String appId, @NonNull String storeKey) { checkNotNull(storeKey, "storeKey"); checkNotNull(context, "context"); checkNotNull(environment, "environment"); - validateAppId(appId); + KinClientInjector injector = new KinClientInjector(context, environment, appId, storeKey); this.environment = environment; - this.backupRestore = new BackupRestoreImpl(); - Server server = initServer(); + this.backupRestore = injector.getBackupRestore(); this.appId = appId; this.storeKey = storeKey; - keyStore = initKeyStore(context.getApplicationContext(), storeKey); - transactionSender = new TransactionSender(server, appId); - accountInfoRetriever = new AccountInfoRetriever(server); - generalBlockchainInfoRetriever = new GeneralBlockchainInfoRetrieverImpl(server); - blockchainEventsCreator = new BlockchainEventsCreator(server); + keyStore = injector.getKeyStore(); + transactionSender = injector.getTransactionSender(); + accountInfoRetriever = injector.getAccountInfoRetriever(); + generalBlockchainInfoRetriever = injector.getGeneralBlockchainInfoRetriever(); + blockchainEventsCreator = injector.getBlockchainEventsCreator(); + this.paymentQueueConfiguration = injector.getPaymentQueueConfiguration(); + loadAccounts(); + } + + @VisibleForTesting + KinClient(KinClientInjector injector) { + this.environment = injector.getEnvironment(); + this.keyStore = injector.getKeyStore(); + this.transactionSender = injector.getTransactionSender(); + this.accountInfoRetriever = injector.getAccountInfoRetriever(); + this.generalBlockchainInfoRetriever = injector.getGeneralBlockchainInfoRetriever(); + this.blockchainEventsCreator = injector.getBlockchainEventsCreator(); + this.backupRestore = injector.getBackupRestore(); + this.appId = injector.getAppId(); + this.storeKey = injector.getStoreKey(); + this.paymentQueueConfiguration = injector.getPaymentQueueConfiguration(); loadAccounts(); } @VisibleForTesting KinClient(Environment environment, KeyStore keyStore, TransactionSender transactionSender, - AccountInfoRetriever accountInfoRetriever, GeneralBlockchainInfoRetrieverImpl generalBlockchainInfoRetriever, - BlockchainEventsCreator blockchainEventsCreator, BackupRestore backupRestore, String appId, String storeKey) { + AccountInfoRetriever accountInfoRetriever, + GeneralBlockchainInfoRetrieverImpl generalBlockchainInfoRetriever, + BlockchainEventsCreator blockchainEventsCreator, BackupRestore backupRestore, + String appId, String storeKey) { this.environment = environment; this.keyStore = keyStore; this.transactionSender = transactionSender; @@ -97,20 +120,10 @@ public KinClient(@NonNull Context context, @NonNull Environment environment, @No this.backupRestore = backupRestore; this.appId = appId; this.storeKey = storeKey; + this.paymentQueueConfiguration = new PaymentQueueImpl.PaymentQueueConfiguration(0, 0, 0); loadAccounts(); } - private Server initServer() { - Network.use(environment.getNetwork()); - return new Server(environment.getNetworkUrl(), TRANSACTIONS_TIMEOUT, TimeUnit.SECONDS); - } - - private KeyStore initKeyStore(Context context, String id) { - SharedPrefStore store = new SharedPrefStore( - context.getSharedPreferences(STORE_NAME_PREFIX + id, Context.MODE_PRIVATE)); - return new KeyStoreImpl(store, backupRestore); - } - private void loadAccounts() { List accounts = null; try { @@ -141,16 +154,6 @@ private void updateKinAccounts(List storageAccounts) { kinAccounts = newKinAccountsList; } - private void validateAppId(String appId) { - if (appId == null || appId.equals("")) { - Log.w("KinClient","WARNING: KinClient instance was created without a proper application ID. Is this what you intended to do?"); - } - else if (!appId.matches("[a-zA-Z0-9]{3,4}")) { - throw new IllegalArgumentException("appId must contain only upper and/or lower case letters and/or digits and that the total string length is between 3 to 4.\n" + - "for example 1234 or 2ab3 or cd2 or fqa, etc."); - } - } - /** * Creates and adds an account. *

Once created, the account information will be stored securely on the device and can @@ -168,19 +171,20 @@ KinAccount addAccount() throws CreateAccountException { * Import an account from a JSON-formatted string. * * @param exportedJson The exported JSON-formatted string. - * @param passphrase The passphrase to decrypt the secret key. + * @param passphrase The passphrase to decrypt the secret key. * @return The imported account */ @NonNull public KinAccount importAccount(@NonNull String exportedJson, @NonNull String passphrase) - throws CryptoException, CreateAccountException, CorruptedDataException { + throws CryptoException, CreateAccountException, CorruptedDataException { KeyPair account = keyStore.importAccount(exportedJson, passphrase); KinAccount kinAccount = getAccountByPublicAddress(account.getAccountId()); return kinAccount != null ? kinAccount : addKeyPair(account); } @Nullable - private KinAccount getAccountByPublicAddress(String accountId) { //TODO we should make this method public + private KinAccount getAccountByPublicAddress(String accountId) { //TODO we should make this + // method public loadAccounts(); KinAccount kinAccount = null; for (int i = 0; i < kinAccounts.size(); i++) { @@ -229,8 +233,10 @@ public int getAccountCount() { /** * Deletes the account at input index (if it exists) + * * @return true if the delete was successful or false otherwise - * @throws DeleteAccountException in case of a delete account exception while trying to delete the account + * @throws DeleteAccountException in case of a delete account exception while trying to + * delete the account */ public boolean deleteAccount(int index) throws DeleteAccountException { boolean deleteSuccess = false; @@ -240,6 +246,7 @@ public boolean deleteAccount(int index) throws DeleteAccountException { KinAccountImpl removedAccount = kinAccounts.remove(index); removedAccount.markAsDeleted(); deleteSuccess = true; + removedAccount.paymentQueue().releaseQueue(); // TODO: 2019-09-11 add tests for that } return deleteSuccess; } @@ -251,6 +258,7 @@ public void clearAllAccounts() { keyStore.clearAllAccounts(); for (KinAccountImpl kinAccount : kinAccounts) { kinAccount.markAsDeleted(); + kinAccount.paymentQueue().releaseQueue(); // TODO: 2019-09-11 add tests for that } kinAccounts.clear(); } @@ -261,8 +269,10 @@ public Environment getEnvironment() { @NonNull private KinAccountImpl createNewKinAccount(KeyPair account) { - return new KinAccountImpl(account, backupRestore, transactionSender, - accountInfoRetriever, blockchainEventsCreator); + PaymentQueue paymentQueue = new PaymentQueueImpl(account, transactionSender, + generalBlockchainInfoRetriever, paymentQueueConfiguration); + return new KinAccountImpl(account, backupRestore, transactionSender, accountInfoRetriever, + blockchainEventsCreator, paymentQueue); } /** @@ -283,7 +293,8 @@ public Long call() throws Exception { /** * Get the current minimum fee that the network charges per operation. * This value is expressed in Quarks (1 Quark = 0.00001 KIN). - *

Note: This method accesses the network, and should not be called on the android main thread.

+ *

Note: This method accesses the network, and should not be called on the android + * main thread.

* * @return the minimum fee. */ diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/KinClientInjector.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/KinClientInjector.java new file mode 100644 index 00000000..24bfd0f2 --- /dev/null +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/KinClientInjector.java @@ -0,0 +1,129 @@ +package kin.sdk; + +import android.content.Context; + +import java.util.concurrent.TimeUnit; + +import kin.base.Network; +import kin.base.Server; +import kin.sdk.internal.backuprestore.BackupRestoreImpl; +import kin.sdk.internal.blockchain.AccountInfoRetriever; +import kin.sdk.internal.blockchain.GeneralBlockchainInfoRetrieverImpl; +import kin.sdk.internal.blockchain.TransactionSender; +import kin.sdk.internal.blockchain.events.BlockchainEventsCreator; +import kin.sdk.internal.queue.PaymentQueueImpl; +import kin.sdk.internal.storage.KeyStore; +import kin.sdk.internal.storage.KeyStoreImpl; +import kin.sdk.internal.storage.SharedPrefStore; + +import static kin.sdk.internal.Utils.checkNotNull; +import static kin.sdk.internal.Utils.validateAppId; + +public class KinClientInjector { + + private static final long delayBetweenPaymentsMillis = 3000; // TODO: 2019-08-15 get those + // number from somewhere + private static final long queueTimeoutMillis = 10000; // TODO: 2019-08-15 get those + // number from somewhere + private static final int maxNumOfPayments = 100; // TODO: 2019-08-15 get those + // number from somewhere + private static final String STORE_NAME_PREFIX = "KinKeyStore_"; + private static final int TRANSACTIONS_TIMEOUT = 30; + + private Context context; + private Environment environment; + private String appId; + private String storeKey; + private Server server; + private BackupRestoreImpl backupRestore; + + public KinClientInjector(Context context, Environment environment, String appId, + String storeKey) { + checkNotNull(storeKey, "storeKey"); + checkNotNull(context, "context"); + checkNotNull(environment, "environment"); + validateAppId(appId); + this.context = context; + this.environment = environment; + this.appId = appId; + this.storeKey = storeKey; + } + + KeyStore getKeyStore() { + return initKeyStore(context.getApplicationContext(), storeKey); + } + + TransactionSender getTransactionSender() { + return new TransactionSender(getServer(), appId); + } + + BackupRestoreImpl getBackupRestore() { + if (backupRestore == null) { + backupRestore = new BackupRestoreImpl(); + } + return backupRestore; + } + + AccountInfoRetriever getAccountInfoRetriever() { + return new AccountInfoRetriever(getServer()); + } + + GeneralBlockchainInfoRetrieverImpl getGeneralBlockchainInfoRetriever() { + return new GeneralBlockchainInfoRetrieverImpl(getServer()); + } + + BlockchainEventsCreator getBlockchainEventsCreator() { + return new BlockchainEventsCreator(getServer()); + } + + PaymentQueueImpl.PaymentQueueConfiguration getPaymentQueueConfiguration() { + return new PaymentQueueImpl.PaymentQueueConfiguration(delayBetweenPaymentsMillis, + queueTimeoutMillis, maxNumOfPayments); + } + + Server getServer() { + if (server != null) { + return server; + } else { + return initServer(); + } + } + + void setServer(Server server) { + this.server = server; + } + + String getStoreNamePrefix() { + return STORE_NAME_PREFIX; + } + + int getTransactionTimeout() { + return TRANSACTIONS_TIMEOUT; + } + + public Environment getEnvironment() { + return environment; + } + + public String getAppId() { + return appId; + } + + public String getStoreKey() { + return storeKey; + } + + private KeyStore initKeyStore(Context context, String id) { + SharedPrefStore store = new SharedPrefStore( + context.getSharedPreferences(getStoreNamePrefix() + id, Context.MODE_PRIVATE)); + return new KeyStoreImpl(store, getBackupRestore()); + } + + private Server initServer() { + Network.use(environment.getNetwork()); + setServer(new Server(environment.getNetworkUrl(), getTransactionTimeout(), + TimeUnit.SECONDS)); + return new Server(environment.getNetworkUrl(), getTransactionTimeout(), TimeUnit.SECONDS); + } + +} diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/SendTransactionParams.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/SendTransactionParams.java deleted file mode 100644 index 12f64b50..00000000 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/SendTransactionParams.java +++ /dev/null @@ -1,44 +0,0 @@ -package kin.sdk; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import kin.base.Memo; -import kin.base.Operation; - -import java.math.BigDecimal; -import java.util.List; - -public class SendTransactionParams { - - // TODO: 2019-08-06 implement - - // Private Constructor, cannot init from outside, only by factory methods - private SendTransactionParams() { - - } - - public static SendTransactionParams createSendPaymentParams(@NonNull String publicAddress, - @NonNull BigDecimal amount, int fee) { - return null; - } - - public static SendTransactionParams createSendPaymentParams(@NonNull String publicAddress, - @NonNull BigDecimal amount, int fee, - @Nullable String memo) { - return null; - } - - public List operations() { - return null; - } - - public int fee() { - return 0; - } - - @Nullable - public Memo memo() { - return null; - } - -} diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/TransactionInterceptor.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/TransactionInterceptor.java index 757e47e7..b7299b7e 100644 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/TransactionInterceptor.java +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/TransactionInterceptor.java @@ -5,7 +5,7 @@ /** * provide the generated transaction before sending to the blockchain. */ -public interface TransactionInterceptor { +public interface TransactionInterceptor { /** * Intercept the generated transaction before sending to the blockchain @@ -21,6 +21,6 @@ public interface TransactionInterceptor { * @return the transaction identifier. * @throws Exception */ - TransactionId interceptTransactionSending(TransactionProcess transactionProcess) throws Exception; + TransactionId interceptTransactionSending(T transactionProcess) throws Exception; } diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/Utils.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/Utils.java index 9e84988f..aedb15b7 100644 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/Utils.java +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/Utils.java @@ -2,12 +2,15 @@ import android.support.annotation.NonNull; +import android.util.Log; + +import java.math.BigDecimal; +import java.util.ArrayList; + import kin.base.responses.SubmitTransactionResponse; import kin.base.responses.SubmitTransactionResponse.Extras.ResultCodes; import kin.sdk.exception.TransactionFailedException; -import java.util.ArrayList; - public final class Utils { private Utils() { @@ -32,6 +35,17 @@ public static String byteArrayToHex(byte[] a) { return sb.toString(); } + public static void validateAppId(String appId) { + if (appId == null || appId.equals("")) { + Log.w("KinClient", "WARNING: KinClient instance was created without a proper " + + "application ID. Is this what you intended to do?"); + } else if (!appId.matches("[a-zA-Z0-9]{3,4}")) { + throw new IllegalArgumentException("appId must contain only upper and/or lower case " + + "letters and/or digits and that the total string length is between 3 to 4.\n" + + "for example 1234 or 2ab3 or cd2 or fqa, etc."); + } + } + public static void checkNotNull(Object obj, String paramName) { if (obj == null) { throw new IllegalArgumentException(paramName + " == null"); @@ -43,4 +57,24 @@ public static void checkNotEmpty(String string, String paramName) { throw new IllegalArgumentException(paramName + " cannot be null or empty."); } } + + public static void checkForNegativeFee(int fee) { + if (fee < 0) { + throw new IllegalArgumentException("Fee can't be negative"); + } + } + + public static void checkForNegativeAmount(@NonNull BigDecimal amount) { + if (amount.signum() == -1) { + throw new IllegalArgumentException("Amount can't be negative"); + } + } + + @SuppressWarnings("ConstantConditions") + public static void checkAddressNotEmpty(@NonNull String publicAddress) { + if (publicAddress == null || publicAddress.isEmpty()) { + throw new IllegalArgumentException("Addressee not valid - public address can't be " + + "null or empty"); + } + } } diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/account/AbstractKinAccount.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/account/AbstractKinAccount.java index ccf7b0c8..fafb46fe 100644 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/account/AbstractKinAccount.java +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/account/AbstractKinAccount.java @@ -2,36 +2,51 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; + +import java.math.BigDecimal; +import java.util.concurrent.Callable; + import kin.sdk.Balance; import kin.sdk.KinAccount; import kin.sdk.TransactionId; +import kin.sdk.TransactionInterceptor; +import kin.sdk.queue.PaymentQueue; +import kin.sdk.queue.TransactionProcess; import kin.sdk.transactiondata.PaymentTransaction; +import kin.sdk.transactiondata.TransactionParams; import kin.utils.Request; -import java.math.BigDecimal; -import java.util.concurrent.Callable; - abstract class AbstractKinAccount implements KinAccount { + private final PaymentQueue paymentQueue; + + AbstractKinAccount(PaymentQueue paymentQueue) { + this.paymentQueue = paymentQueue; + } + @NonNull @Override public Request buildTransaction(@NonNull final String publicAddress, - @NonNull final BigDecimal amount, final int fee) { + @NonNull final BigDecimal amount, + final int fee) { return new Request<>(new Callable() { @Override public PaymentTransaction call() throws Exception { return buildTransactionSync(publicAddress, amount, fee); } }); - }@NonNull + } + + @NonNull @Override public Request buildTransaction(@NonNull final String publicAddress, @NonNull final BigDecimal amount, - final int fee, @Nullable final String memo) { + final int fee, + @Nullable final String memo) { return new Request<>(new Callable() { @Override public PaymentTransaction call() throws Exception { - return buildTransactionSync(publicAddress, amount, fee, memo); + return buildTransactionSync(publicAddress, amount, fee, memo); } }); } @@ -58,6 +73,37 @@ public TransactionId call() throws Exception { }); } + @Override + public Request sendTransaction(final TransactionParams transactionParams) { + return sendTransaction(transactionParams, null); + } + + @Override + public Request sendTransaction(final TransactionParams transactionParams, + final TransactionInterceptor interceptor) { + return new Request<>(new Callable() { + @Override + public TransactionId call() { +// return ((PaymentQueueImpl) paymentQueue).enqueueTransactionParams +// (transactionParams, interceptor); + // TODO: 2019-09-09 implement + return null; + } + + }); + } + + @Override + public Request getPendingBalance() { + return new Request<>(new Callable() { + @Override + public Balance call() { + return getPendingBalanceSync(); + } + }); + } + + @NonNull @Override public Request getBalance() { diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/account/KinAccountImpl.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/account/KinAccountImpl.java index 84ef3d41..0659d885 100644 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/account/KinAccountImpl.java +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/account/KinAccountImpl.java @@ -2,8 +2,15 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; + +import java.math.BigDecimal; + import kin.base.KeyPair; -import kin.sdk.*; +import kin.sdk.Balance; +import kin.sdk.EventListener; +import kin.sdk.ListenerRegistration; +import kin.sdk.PaymentInfo; +import kin.sdk.TransactionId; import kin.sdk.exception.AccountDeletedException; import kin.sdk.exception.CryptoException; import kin.sdk.exception.OperationFailedException; @@ -12,12 +19,8 @@ import kin.sdk.internal.blockchain.TransactionSender; import kin.sdk.internal.blockchain.events.BlockchainEvents; import kin.sdk.internal.blockchain.events.BlockchainEventsCreator; -import kin.sdk.internal.queue.PaymentQueueImpl; import kin.sdk.queue.PaymentQueue; import kin.sdk.transactiondata.PaymentTransaction; -import kin.utils.Request; - -import java.math.BigDecimal; public final class KinAccountImpl extends AbstractKinAccount { @@ -29,14 +32,18 @@ public final class KinAccountImpl extends AbstractKinAccount { private final PaymentQueue paymentQueue; private boolean isDeleted = false; - public KinAccountImpl(KeyPair account, BackupRestore backupRestore, TransactionSender transactionSender, - AccountInfoRetriever accountInfoRetriever, BlockchainEventsCreator blockchainEventsCreator) { + public KinAccountImpl(KeyPair account, BackupRestore backupRestore, + TransactionSender transactionSender, + AccountInfoRetriever accountInfoRetriever, + BlockchainEventsCreator blockchainEventsCreator, + PaymentQueue paymentQueue) { + super(paymentQueue); this.account = account; this.backupRestore = backupRestore; this.transactionSender = transactionSender; this.accountInfoRetriever = accountInfoRetriever; this.blockchainEvents = blockchainEventsCreator.create(account.getAccountId()); - this.paymentQueue = new PaymentQueueImpl(account.getAccountId()); + this.paymentQueue = paymentQueue; } @Override @@ -48,17 +55,19 @@ public String getPublicAddress() { } @Override - public PaymentTransaction buildTransactionSync(@NonNull String publicAddress, @NonNull BigDecimal amount, + public PaymentTransaction buildTransactionSync(@NonNull String publicAddress, + @NonNull BigDecimal amount, int fee) throws OperationFailedException { checkValidAccount(); - return transactionSender.buildTransaction(account, publicAddress, amount, fee); + return transactionSender.buildPaymentTransaction(account, publicAddress, amount, fee); } @Override - public PaymentTransaction buildTransactionSync(@NonNull String publicAddress, @NonNull BigDecimal amount, + public PaymentTransaction buildTransactionSync(@NonNull String publicAddress, + @NonNull BigDecimal amount, int fee, @Nullable String memo) throws OperationFailedException { checkValidAccount(); - return transactionSender.buildTransaction(account, publicAddress, amount, fee, memo); + return transactionSender.buildPaymentTransaction(account, publicAddress, amount, fee, memo); } @NonNull @@ -75,27 +84,11 @@ public TransactionId sendWhitelistTransactionSync(String whitelist) throws Opera return transactionSender.sendWhitelistTransaction(whitelist); } - @Override - public TransactionId sendTransactionSync(SendTransactionParams transaction, TransactionInterceptor interceptor) throws OperationFailedException { - return null; // TODO: 2019-08-15 implement - } - - @Override - public Request sendTransaction(SendTransactionParams transaction, - TransactionInterceptor interceptor) { - return null; // TODO: 2019-08-15 implement - } - @Override public PaymentQueue paymentQueue() { return paymentQueue; } - @Override - public Request getPendingBalance() { - return null; // TODO: 2019-08-15 implement - } - @NonNull @Override public Balance getPendingBalanceSync() { diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/blockchain/GeneralBlockchainInfoRetriever.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/blockchain/GeneralBlockchainInfoRetriever.java index 71925e3d..fd3ccb44 100644 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/blockchain/GeneralBlockchainInfoRetriever.java +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/blockchain/GeneralBlockchainInfoRetriever.java @@ -2,7 +2,7 @@ import kin.sdk.exception.OperationFailedException; -interface GeneralBlockchainInfoRetriever { +public interface GeneralBlockchainInfoRetriever { /** * Get the current minimum fee that the network charges per operation. diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/blockchain/GeneralBlockchainInfoRetrieverImpl.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/blockchain/GeneralBlockchainInfoRetrieverImpl.java index bb137778..8bb18bcf 100644 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/blockchain/GeneralBlockchainInfoRetrieverImpl.java +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/blockchain/GeneralBlockchainInfoRetrieverImpl.java @@ -1,5 +1,8 @@ package kin.sdk.internal.blockchain; +import java.io.IOException; +import java.util.ArrayList; + import kin.base.Server; import kin.base.requests.LedgersRequestBuilder; import kin.base.requests.RequestBuilder; @@ -7,11 +10,10 @@ import kin.base.responses.Page; import kin.sdk.exception.OperationFailedException; -import java.io.IOException; -import java.util.ArrayList; - public class GeneralBlockchainInfoRetrieverImpl implements GeneralBlockchainInfoRetriever { + private static final String ERROR_MESSAGE = "Couldn't retrieve minimum fee data"; + private final Server server; public GeneralBlockchainInfoRetrieverImpl(Server server) { @@ -21,6 +23,7 @@ public GeneralBlockchainInfoRetrieverImpl(Server server) { @Override public long getMinimumFeeSync() throws OperationFailedException { LedgersRequestBuilder builder = server.ledgers().order(RequestBuilder.Order.DESC).limit(1); + try { Page response = builder.execute(); ArrayList records = response.getRecords(); @@ -32,7 +35,7 @@ public long getMinimumFeeSync() throws OperationFailedException { } throw new OperationFailedException("Couldn't retrieve minimum fee data"); } catch (IOException e) { - throw new OperationFailedException(e); + throw new OperationFailedException(ERROR_MESSAGE, e); } } } diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/blockchain/TransactionSender.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/blockchain/TransactionSender.java index 9c007474..35b251e1 100644 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/blockchain/TransactionSender.java +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/blockchain/TransactionSender.java @@ -3,32 +3,48 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; -import kin.base.*; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.util.List; + +import kin.base.AssetTypeNative; +import kin.base.KeyPair; +import kin.base.Memo; +import kin.base.PaymentOperation; +import kin.base.Server; import kin.base.Transaction.Builder; import kin.base.responses.AccountResponse; import kin.base.responses.HttpResponseException; import kin.base.responses.SubmitTransactionResponse; import kin.sdk.TransactionId; -import kin.sdk.exception.*; +import kin.sdk.exception.AccountNotFoundException; +import kin.sdk.exception.IllegalAmountException; +import kin.sdk.exception.InsufficientFeeException; +import kin.sdk.exception.InsufficientKinException; +import kin.sdk.exception.OperationFailedException; +import kin.sdk.exception.TransactionFailedException; import kin.sdk.internal.Utils; import kin.sdk.internal.data.TransactionIdImpl; +import kin.sdk.queue.PendingPayment; +import kin.sdk.transactiondata.BatchPaymentTransaction; import kin.sdk.transactiondata.PaymentTransaction; import kin.sdk.transactiondata.Transaction; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.math.BigDecimal; -import java.util.List; - public class TransactionSender { - private static final int MEMO_BYTES_LENGTH_LIMIT = 21; //Memo length limitation(in bytes) is 28 but we add 7 more bytes which includes the appId and some characters. - private static final int MAX_NUM_OF_DECIMAL_PLACES = 4 ; - private static String MEMO_APP_ID_VERSION_PREFIX = "1"; - private static String MEMO_DELIMITER = "-"; + // Memo length limitation(in bytes) is 28 but we add 7(or 6) more bytes which includes the + // appId and some characters. + private static final int MEMO_BYTES_LENGTH_LIMIT = 21; // TODO: 2019-09-08 we should fix this + // because it could be 22 in case of a 3 char appId + private static final int MAX_NUM_OF_DECIMAL_PLACES = 4; + private static final String MEMO_APP_ID_VERSION_PREFIX = "1"; + private static final String MEMO_DELIMITER = "-"; private static final String INSUFFICIENT_KIN_RESULT_CODE = "op_underfunded"; private static final String INSUFFICIENT_FEE_RESULT_CODE = "tx_insufficient_fee"; private static final String INSUFFICIENT_BALANCE_RESULT_CODE = "tx_insufficient_balance"; + private final Server server; //horizon server private final String appId; @@ -37,14 +53,16 @@ public TransactionSender(Server server, String appId) { this.appId = appId; } - public PaymentTransaction buildTransaction(@NonNull KeyPair from, @NonNull String publicAddress, - @NonNull BigDecimal amount, - int fee) throws OperationFailedException { - return buildTransaction(from, publicAddress, amount, fee, null); + public PaymentTransaction buildPaymentTransaction(@NonNull KeyPair from, + @NonNull String publicAddress, + @NonNull BigDecimal amount, int fee) throws OperationFailedException { + return buildPaymentTransaction(from, publicAddress, amount, fee, null); } - public PaymentTransaction buildTransaction(@NonNull KeyPair from, @NonNull String publicAddress, @NonNull BigDecimal amount, - int fee, @Nullable String memo) throws OperationFailedException { + public PaymentTransaction buildPaymentTransaction(@NonNull KeyPair from, + @NonNull String publicAddress, + @NonNull BigDecimal amount, + int fee, @Nullable String memo) throws OperationFailedException { checkParams(from, publicAddress, amount, fee, memo); if (appId != null && !appId.equals("")) { memo = addAppIdToMemo(memo); @@ -53,11 +71,35 @@ public PaymentTransaction buildTransaction(@NonNull KeyPair from, @NonNull Strin KeyPair addressee = generateAddresseeKeyPair(publicAddress); AccountResponse sourceAccount = loadSourceAccount(from); verifyAddresseeAccount(generateAddresseeKeyPair(addressee.getAccountId())); - kin.base.Transaction stellarTransaction = buildStellarTransaction(from, amount, addressee, sourceAccount, fee - , memo); + kin.base.Transaction stellarTransaction = buildStellarTransaction(from, amount, addressee + , sourceAccount, fee, memo); return new PaymentTransaction(stellarTransaction, addressee.getAccountId(), amount, memo); } + public BatchPaymentTransaction buildBatchPaymentTransaction(@NonNull KeyPair from, + @NonNull List pendingPayments, + int fee, @Nullable String memo) throws OperationFailedException { + checkBatchPaymentkParams(from, fee, memo); + if (appId != null && !appId.equals("")) { + memo = addAppIdToMemo(memo); + } + + AccountResponse sourceAccount = loadSourceAccount(from); + + Builder transactionBuilder = new Builder(sourceAccount); + for (PendingPayment pendingPayment : pendingPayments) { + KeyPair destination = + generateAddresseeKeyPair(pendingPayment.destinationPublicAddress()); + addPaymentOperationToTransaction(transactionBuilder, destination, + pendingPayment.amount()); + } + + kin.base.Transaction transaction = addTransactionParametersAndSign(from, fee, memo, + transactionBuilder); + return new BatchPaymentTransaction(transaction); + } + + public TransactionId sendTransaction(Transaction transaction) throws OperationFailedException { return sendTransaction(((TransactionInternal) transaction).baseTransaction()); } @@ -87,49 +129,39 @@ private String addAppIdToMemo(@Nullable String memo) { return sb.toString(); } - private void checkParams(@NonNull KeyPair from, @NonNull String publicAddress, @NonNull BigDecimal amount, + private void checkParams(@NonNull KeyPair from, @NonNull String publicAddress, + @NonNull BigDecimal amount, int fee, @Nullable String memo) throws OperationFailedException { Utils.checkNotNull(from, "account"); Utils.checkNotNull(amount, "amount"); validateAmountDecimalPoint(amount); - checkForNegativeFee(fee); - checkAddressNotEmpty(publicAddress); - checkForNegativeAmount(amount); + Utils.checkForNegativeFee(fee); + Utils.checkAddressNotEmpty(publicAddress); + Utils.checkForNegativeAmount(amount); checkMemo(memo); } + private void checkBatchPaymentkParams(@NonNull KeyPair from, int fee, @Nullable String memo) { + Utils.checkNotNull(from, "account"); + Utils.checkForNegativeFee(fee); + checkMemo(memo); + } private void validateAmountDecimalPoint(BigDecimal amount) throws OperationFailedException { BigDecimal amountWithoutTrailingZeros = amount.stripTrailingZeros(); int numOfDecimalPlaces = amountWithoutTrailingZeros.scale(); if (numOfDecimalPlaces > MAX_NUM_OF_DECIMAL_PLACES) { - throw new IllegalAmountException("amount can't have more then 5 digits after the decimal point"); - } - } - - @SuppressWarnings("ConstantConditions") - private void checkAddressNotEmpty(@NonNull String publicAddress) { - if (publicAddress == null || publicAddress.isEmpty()) { - throw new IllegalArgumentException("Addressee not valid - public address can't be null or empty"); - } - } - - private void checkForNegativeAmount(@NonNull BigDecimal amount) { - if (amount.signum() == -1) { - throw new IllegalArgumentException("Amount can't be negative"); - } - } - - private void checkForNegativeFee(int fee) { - if (fee < 0) { - throw new IllegalArgumentException("Fee can't be negative"); + throw new IllegalAmountException("amount can't have more then 5 digits after the " + + "decimal point"); } } private void checkMemo(String memo) { try { if (memo != null && memo.getBytes("UTF-8").length > MEMO_BYTES_LENGTH_LIMIT) { - throw new IllegalArgumentException("Memo cannot be longer that " + MEMO_BYTES_LENGTH_LIMIT + " bytes(UTF-8 characters)"); + throw new IllegalArgumentException( + "Memo cannot be longer that " + MEMO_BYTES_LENGTH_LIMIT + " bytes(UTF-8 " + + "characters)"); } } catch (UnsupportedEncodingException e) { throw new IllegalArgumentException("Memo text have unsupported characters encoding"); @@ -145,12 +177,31 @@ private KeyPair generateAddresseeKeyPair(@NonNull String publicAddress) throws O } } + void foo() { + + } + @NonNull - private kin.base.Transaction buildStellarTransaction(@NonNull KeyPair from, @NonNull BigDecimal amount, KeyPair addressee, - AccountResponse sourceAccount, int fee, @Nullable String memo) { - Builder transactionBuilder = new Builder(sourceAccount) - .addOperation( - new PaymentOperation.Builder(addressee, new AssetTypeNative(), amount.toString()).build()); + private kin.base.Transaction buildStellarTransaction(@NonNull KeyPair from, + @NonNull BigDecimal amount, + KeyPair addressee, + AccountResponse sourceAccount, int fee, + @Nullable String memo) { + Builder transactionBuilder = new Builder(sourceAccount); + addPaymentOperationToTransaction(transactionBuilder, addressee, amount); + + return addTransactionParametersAndSign(from, fee, memo, transactionBuilder); + } + + private void addPaymentOperationToTransaction(Builder transactionBuilder, KeyPair destination, + BigDecimal amount) { + transactionBuilder.addOperation(new PaymentOperation.Builder(destination, + new AssetTypeNative(), amount.toString()).build()); + } + + private kin.base.Transaction addTransactionParametersAndSign(@NonNull KeyPair from, int fee, + String memo, + Builder transactionBuilder) { transactionBuilder.addFee(fee); if (memo != null) { transactionBuilder.addMemo(Memo.text(memo)); @@ -208,7 +259,8 @@ private TransactionId sendTransaction(kin.base.Transaction transaction) throws O private TransactionId createFailureException(SubmitTransactionResponse response) throws TransactionFailedException, InsufficientKinException, InsufficientFeeException { - TransactionFailedException transactionException = Utils.createTransactionException(response); + TransactionFailedException transactionException = + Utils.createTransactionException(response); if (isInsufficientKinException(transactionException)) { throw new InsufficientKinException(); } else if (isInsufficientFeeException(transactionException)) { @@ -221,8 +273,9 @@ private TransactionId createFailureException(SubmitTransactionResponse response) private boolean isInsufficientKinException(TransactionFailedException transactionException) { List resultCodes = transactionException.getOperationsResultCodes(); String transactionResultCode = transactionException.getTransactionResultCode(); - return ((resultCodes != null && resultCodes.size() > 0 && INSUFFICIENT_KIN_RESULT_CODE.equals(resultCodes.get(0))) || - !TextUtils.isEmpty(transactionResultCode) && INSUFFICIENT_BALANCE_RESULT_CODE.equals(transactionResultCode)); + return ( + (resultCodes != null && resultCodes.size() > 0 && INSUFFICIENT_KIN_RESULT_CODE.equals(resultCodes.get(0))) + || !TextUtils.isEmpty(transactionResultCode) && INSUFFICIENT_BALANCE_RESULT_CODE.equals(transactionResultCode)); } private boolean isInsufficientFeeException(TransactionFailedException transactionException) { diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/events/EventsManager.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/events/EventsManager.java index 5f9bfc25..35d290bf 100644 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/events/EventsManager.java +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/events/EventsManager.java @@ -1,5 +1,62 @@ package kin.sdk.internal.events; +import java.util.List; + +import kin.sdk.TransactionId; +import kin.sdk.exception.KinException; +import kin.sdk.queue.PaymentQueue; +import kin.sdk.queue.PendingPayment; +import kin.sdk.transactiondata.BatchPaymentTransaction; + public interface EventsManager { + /** + * set the event listener + * + * @param eventListener the event listener to set + */ + void setEventListener(PaymentQueue.EventListener eventListener); + + /** + * Invoked when a new payment is enqueued + * + * @param payment the pending payment which was created when the payment was enqueued + *

See {@link PendingPayment}

+ */ + void onPaymentEnqueued(PendingPayment payment); + + /** + * Invoked when a Transaction just before the transaction will be send. + * + * @param transaction the transaction to send + * @param payments the list of payments that the transaction consist of. + *

See {@link BatchPaymentTransaction} and {@link PendingPayment} for + * more + * information

+ */ + void onTransactionSend(BatchPaymentTransaction transaction, List payments); + + /** + * Invoked when a Transaction just before the transaction will be send. + * + * @param transaction the transaction to send + * @param payments the list of payments that the transaction consist of. + *

See {@link BatchPaymentTransaction} and {@link PendingPayment} for + * more + * information

+ */ + void onTransactionSendSuccess(TransactionId transactionId, List payments); + + /** + * Invoked when a Transaction just before the transaction will be send. + * + * @param transaction the transaction to send + * @param payments the list of payments that the transaction consist of. + * @param exception the exception the occurred. + *

See {@link BatchPaymentTransaction} and {@link PendingPayment} for + * more + * information

+ */ + void onTransactionSendFailed(List payments, KinException exception); + } diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/events/EventsManagerImpl.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/events/EventsManagerImpl.java index 4ed0c809..4f5774c2 100644 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/events/EventsManagerImpl.java +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/events/EventsManagerImpl.java @@ -1,7 +1,64 @@ package kin.sdk.internal.events; +import java.util.List; + +import kin.sdk.TransactionId; +import kin.sdk.exception.KinException; +import kin.sdk.queue.PaymentQueue; +import kin.sdk.queue.PendingPayment; +import kin.sdk.transactiondata.BatchPaymentTransaction; + public class EventsManagerImpl implements EventsManager { - // TODO: 2019-08-14 implement + private PaymentQueue.EventListener eventListener; + + // TODO: 2019-09-11 if we should use the main thread in the callback then we will have a + // problem because we also use this event manager for our needs and not necessarily want to + // run on main thread + + public EventsManagerImpl() { + } + + public EventsManagerImpl(PaymentQueue.EventListener eventListener) { + this.eventListener = eventListener; + } + + @Override + public void setEventListener(PaymentQueue.EventListener eventListener) { + // TODO: 2019-09-05 probably implement as a list of them and not one + this.eventListener = eventListener; + } + + @Override + public void onPaymentEnqueued(PendingPayment payment) { + if (eventListener != null) { + eventListener.onPaymentEnqueued(payment); + } + } + + @Override + public void onTransactionSend(BatchPaymentTransaction transaction, + List payments) { + if (eventListener != null) { + eventListener.onTransactionSend(transaction, payments); + } + } + + @Override + public void onTransactionSendSuccess(TransactionId transactionId, + List payments) { + if (eventListener != null) { + eventListener.onTransactionSendSuccess(transactionId, payments); + } + } + + @Override + public void onTransactionSendFailed(List payments, KinException exception) { + if (eventListener != null) { + eventListener.onTransactionSendFailed(payments, exception); + } + } + + } diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/PaymentQueueImpl.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/PaymentQueueImpl.java index 1c5e6e25..b23522a4 100644 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/PaymentQueueImpl.java +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/PaymentQueueImpl.java @@ -4,60 +4,92 @@ import java.math.BigDecimal; +import kin.base.KeyPair; import kin.sdk.TransactionInterceptor; import kin.sdk.exception.InsufficientKinException; +import kin.sdk.internal.Utils; +import kin.sdk.internal.blockchain.GeneralBlockchainInfoRetriever; +import kin.sdk.internal.blockchain.TransactionSender; import kin.sdk.internal.data.PendingBalanceUpdaterImpl; +import kin.sdk.internal.events.EventsManager; import kin.sdk.internal.events.EventsManagerImpl; import kin.sdk.queue.PaymentQueue; +import kin.sdk.queue.PaymentQueueTransactionProcess; import kin.sdk.queue.PendingPayment; +import kin.sdk.queue.TransactionProcess; +import kin.sdk.transactiondata.TransactionParams; public class PaymentQueueImpl implements PaymentQueue { - private static final long DELAY_BETWEEN_PAYMENTS_MILLIS = 3000; // TODO: 2019-08-15 get those number from somewhere - private static final long QUEUE_TIMEOUT_MILLIS = 10000; // TODO: 2019-08-15 get those number from somewhere - private static final int MAX_NUM_OF_PAYMENTS = 100; - - private final String sourcePublicAddress; + private final KeyPair accountFrom; private final PaymentQueueManager paymentQueueManager; - private final TransactionTaskQueueManager txTaskQueueManager; - - public PaymentQueueImpl(@NonNull String sourcePublicAddress) { - this.sourcePublicAddress = sourcePublicAddress; - this.txTaskQueueManager = new TransactionTaskQueueManagerImpl(); - this.paymentQueueManager = new PaymentQueueManagerImpl(txTaskQueueManager, new QueueSchedulerImpl(), - new PendingBalanceUpdaterImpl(), new EventsManagerImpl(), - DELAY_BETWEEN_PAYMENTS_MILLIS, QUEUE_TIMEOUT_MILLIS, MAX_NUM_OF_PAYMENTS); + private final TransactionTasksQueueManager txTasksQueueManager; + private final EventsManager eventsManager; + private final TasksQueueImpl tasksQueue; + + public PaymentQueueImpl(@NonNull KeyPair accountFrom, TransactionSender transactionSender, + GeneralBlockchainInfoRetriever generalBlockchainInfoRetriever, + PaymentQueueConfiguration configuration) { + this.accountFrom = accountFrom; + this.eventsManager = new EventsManagerImpl(); + tasksQueue = new TasksQueueImpl(transactionSender, eventsManager, + generalBlockchainInfoRetriever, accountFrom); + this.txTasksQueueManager = new TransactionTasksQueueManagerImpl(tasksQueue, eventsManager + , configuration.maxNumOfPayments); + this.paymentQueueManager = new PaymentQueueManagerImpl(txTasksQueueManager, + new QueueSchedulerImpl(), new PendingBalanceUpdaterImpl(), eventsManager, + configuration); } @Override - public PendingPayment enqueuePayment(@NonNull String publicAddress, @NonNull BigDecimal amount) throws InsufficientKinException { - return enqueuePayment(publicAddress, amount, null); + public PendingPayment enqueuePayment(@NonNull String destinationPublicAddress, + @NonNull BigDecimal amount) throws InsufficientKinException { + return enqueuePayment(destinationPublicAddress, amount, null); } @Override - public PendingPayment enqueuePayment(@NonNull String publicAddress, @NonNull BigDecimal amount, Object metadata) throws InsufficientKinException { - PendingPayment pendingPayment = new PendingPaymentImpl(publicAddress, sourcePublicAddress, amount, metadata); + public PendingPayment enqueuePayment(@NonNull String destinationPublicAddress, + @NonNull BigDecimal amount, Object metadata) throws InsufficientKinException { + Utils.checkForNegativeAmount(amount); + Utils.checkAddressNotEmpty(destinationPublicAddress); + PendingPayment pendingPayment = new PendingPaymentImpl(destinationPublicAddress, + accountFrom.getAccountId(), amount, metadata); paymentQueueManager.enqueue(pendingPayment); return pendingPayment; } + /** + * enqueue a non blocking transaction. This transaction will be pushed to the top of the queue. + * + * @param transactionParams the transaction parameters + * @param transactionInterceptor an optional interceptor that will be used to intercept the + * transaction. + */ + public void enqueueTransactionParams(TransactionParams transactionParams, + TransactionInterceptor transactionInterceptor) { + + tasksQueue.scheduleTransactionParamsTask(transactionParams, transactionInterceptor); + } + @Override - public void setTransactionInterceptor(TransactionInterceptor interceptor) { - // TODO: 2019-08-15 implement + public void setTransactionInterceptor(TransactionInterceptor transactionInterceptor) { + tasksQueue.setPaymentQueueTransactionInterceptor(transactionInterceptor); } @Override - public void addEventsListener(EventsListener listener) { - // TODO: 2019-08-15 implement + public void setEventListener(EventListener eventListener) { + eventsManager.setEventListener(eventListener); } @Override public void setFee(int fee) { + Utils.checkForNegativeFee(fee); + tasksQueue.setBatchPaymentsFee(fee); } @Override public boolean transactionInProgress() { - return false; // TODO: 2019-08-15 implement + return txTasksQueueManager.transactionInProgress(); } @Override @@ -65,4 +97,35 @@ public int pendingPaymentsCount() { return paymentQueueManager.getPendingPaymentCount(); } + @Override + public void releaseQueue() { + tasksQueue.stopTaskQueue(); + } + + public static class PaymentQueueConfiguration { + + private long delayBetweenPaymentsMillis; // TODO: 2019-08-15 get this number from somewhere + private long queueTimeoutMillis; // TODO: 2019-08-15 get this number from somewhere + private int maxNumOfPayments; // TODO: 2019-08-15 get this number from somewhere + + public PaymentQueueConfiguration(long delayBetweenPaymentsMillis, long queueTimeoutMillis, + int maxNumOfPayments) { + this.delayBetweenPaymentsMillis = delayBetweenPaymentsMillis; + this.queueTimeoutMillis = queueTimeoutMillis; + this.maxNumOfPayments = maxNumOfPayments; + } + + long getDelayBetweenPayments() { + return delayBetweenPaymentsMillis; + } + + long getQueueTimeout() { + return queueTimeoutMillis; + } + + int getMaxNumOfPayments() { + return maxNumOfPayments; + } + } + } diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/PaymentQueueManagerImpl.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/PaymentQueueManagerImpl.java index a2846389..05633ede 100644 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/PaymentQueueManagerImpl.java +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/PaymentQueueManagerImpl.java @@ -11,28 +11,24 @@ class PaymentQueueManagerImpl implements PaymentQueueManager { - private final TransactionTaskQueueManager txTaskQueueManager; + private final TransactionTasksQueueManager txTasksQueueManager; private final QueueScheduler queueScheduler; private final PendingBalanceUpdater pendingBalanceUpdater; private final EventsManager eventsManager; - private final long delayBetweenPayments; - private final long queueTimeout; - private final int maxNumOfPayments; + private final PaymentQueueImpl.PaymentQueueConfiguration configuration; private List queue; private Runnable dequeueTask; private Runnable timeoutTask; - PaymentQueueManagerImpl(TransactionTaskQueueManager txTaskQueueManager, QueueScheduler queueScheduler, + PaymentQueueManagerImpl(TransactionTasksQueueManager txTasksQueueManager, QueueScheduler queueScheduler, PendingBalanceUpdater pendingBalanceUpdater, EventsManager eventsManager, - long delayBetweenPayments, long queueTimeout, int maxNumOfPayments) { - this.txTaskQueueManager = txTaskQueueManager; + PaymentQueueImpl.PaymentQueueConfiguration configuration) { + this.txTasksQueueManager = txTasksQueueManager; this.queueScheduler = queueScheduler; this.pendingBalanceUpdater = pendingBalanceUpdater; this.eventsManager = eventsManager; - this.delayBetweenPayments = delayBetweenPayments; - this.queueTimeout = queueTimeout; - this.maxNumOfPayments = maxNumOfPayments; - queue = new ArrayList<>(maxNumOfPayments); + this.configuration = configuration; + queue = new ArrayList<>(configuration.getMaxNumOfPayments()); dequeueTask = new DequeueTask(); } @@ -44,7 +40,7 @@ public void enqueue(final PendingPayment pendingPayment) { public void run() { validatePendingBalance(); // TODO: 2019-08-15 handle it - if (getPendingPaymentCount() == maxNumOfPayments - 1) { + if (getPendingPaymentCount() == configuration.getMaxNumOfPayments() - 1) { addToQueue(pendingPayment); sendPendingPaymentsToTaskQueue(); } else { @@ -57,19 +53,21 @@ private void handlePendingPayment() { if (queue.size() == 1) { queueScheduler.removePendingTask(timeoutTask); timeoutTask = new DequeueTask(); - queueScheduler.scheduleDelayed(timeoutTask, queueTimeout); + queueScheduler.scheduleDelayed(timeoutTask, configuration.getQueueTimeout()); } resetScheduler(); } private void addToQueue(PendingPayment pendingPayment) { queue.add(pendingPayment); + eventsManager.onPaymentEnqueued(pendingPayment); } private void resetScheduler() { queueScheduler.removePendingTask(dequeueTask); dequeueTask = new DequeueTask(); - queueScheduler.scheduleDelayed(dequeueTask, delayBetweenPayments); + queueScheduler.scheduleDelayed(dequeueTask, + configuration.getDelayBetweenPayments()); } }); } @@ -94,8 +92,8 @@ private void validatePendingBalance() { private void sendPendingPaymentsToTaskQueue() { queueScheduler.removeAllPendingTasks(); List pendingPayments = queue; - queue = new ArrayList<>(maxNumOfPayments); - txTaskQueueManager.enqueue(pendingPayments); + queue = new ArrayList<>(configuration.getMaxNumOfPayments()); + txTasksQueueManager.enqueue(pendingPayments); } private class DequeueTask implements Runnable { diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/PendingPaymentImpl.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/PendingPaymentImpl.java index 4f8d6e73..22303951 100644 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/PendingPaymentImpl.java +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/PendingPaymentImpl.java @@ -10,7 +10,6 @@ public class PendingPaymentImpl implements PendingPayment { private final String sourcePublicAddress; private final BigDecimal amount; private final Object metadata; - private Status status; public PendingPaymentImpl(String destinationPublicAddress, String sourcePublicAddress, BigDecimal amount) { this(destinationPublicAddress, sourcePublicAddress, amount, null); @@ -22,7 +21,6 @@ public PendingPaymentImpl(String destinationPublicAddress, String sourcePublicAd this.sourcePublicAddress = sourcePublicAddress; this.amount = amount; this.metadata = metadata; - this.status = Status.PENDING; } @Override @@ -44,13 +42,4 @@ public BigDecimal amount() { public Object metadata() { return metadata; } - - @Override - public Status status() { - return status; - } - - void setStatus(Status status) { - this.status = status; - } } diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/SendPendingPaymentsTask.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/SendPendingPaymentsTask.java new file mode 100644 index 00000000..471acd22 --- /dev/null +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/SendPendingPaymentsTask.java @@ -0,0 +1,103 @@ +package kin.sdk.internal.queue; + +import java.util.List; + +import kin.base.KeyPair; +import kin.sdk.TransactionId; +import kin.sdk.TransactionInterceptor; +import kin.sdk.exception.KinException; +import kin.sdk.exception.OperationFailedException; +import kin.sdk.internal.blockchain.GeneralBlockchainInfoRetriever; +import kin.sdk.internal.blockchain.TransactionSender; +import kin.sdk.internal.events.EventsManager; +import kin.sdk.queue.PaymentQueueTransactionProcess; +import kin.sdk.queue.PendingPayment; +import kin.sdk.transactiondata.BatchPaymentTransaction; + +class SendPendingPaymentsTask extends SendTransactionTask { + + private final List pendingPayments; + private final TransactionSender transactionSender; + private final TransactionInterceptor transactionInterceptor; + private final EventsManager eventsManager; + private final GeneralBlockchainInfoRetriever generalBlockchainInfoRetriever; + private int fee; + private final KeyPair accountFrom; + + SendPendingPaymentsTask(List pendingPayments, + TransactionSender transactionSender, + TransactionInterceptor transactionInterceptor, + TaskFinishListener taskFinishListener, + EventsManager eventsManager, + GeneralBlockchainInfoRetriever generalBlockchainInfoRetriever, + int fee, KeyPair accountFrom) { + super(transactionSender, transactionInterceptor, taskFinishListener); + this.pendingPayments = pendingPayments; + this.transactionSender = transactionSender; + this.transactionInterceptor = transactionInterceptor; + this.eventsManager = eventsManager; + this.generalBlockchainInfoRetriever = generalBlockchainInfoRetriever; + this.fee = fee; + this.accountFrom = accountFrom; + } + + @Override + public void run() { + setFeeIfNeeded(); + super.run(); + } + + @Override + void invokeInterceptor() { + PaymentQueueTransactionProcess transactionProcess = + new PaymentQueueTransactionProcess(transactionSender, pendingPayments, + accountFrom, fee); + try { + TransactionId transactionId = + transactionInterceptor.interceptTransactionSending(transactionProcess); + handleTransactionFinished(transactionId); + } catch (Exception e) { + if (!(e instanceof KinException)) { + e = new OperationFailedException(e); + } + + //TODO send it with events manager + } + } + + @Override + void handleTransactionFinished(TransactionId transactionId) { + super.handleTransactionFinished(transactionId); + if (transactionId != null) { + eventsManager.onTransactionSendSuccess(transactionId, pendingPayments); + // TODO: 2019-09-01 invoke events for transaction success + } else { + //TODO need to create a meaningful message for this scenario + eventsManager.onTransactionSendFailed(pendingPayments, new OperationFailedException( + "Transaction Id is null")); + // TODO: 2019-09-01 invoke events for transaction fail + } + } + + @Override + void buildAndSendTransaction() throws OperationFailedException { + BatchPaymentTransaction transaction = + transactionSender.buildBatchPaymentTransaction(accountFrom, pendingPayments, fee, + null); + eventsManager.onTransactionSend(transaction, pendingPayments); + sendTransaction(transaction); + } + + private void setFeeIfNeeded() { + // If fee is not set then retrieve the minimum fee. + if (fee < 0) { + try { + fee = (int) generalBlockchainInfoRetriever.getMinimumFeeSync(); + } catch (OperationFailedException e) { + e.printStackTrace(); + // TODO: 2019-08-27 need to maybe add an exception to the events manager or + // something... + } + } + } +} diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/SendTransactionParamsTask.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/SendTransactionParamsTask.java new file mode 100644 index 00000000..61386f3a --- /dev/null +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/SendTransactionParamsTask.java @@ -0,0 +1,69 @@ +package kin.sdk.internal.queue; + +import kin.base.KeyPair; +import kin.sdk.TransactionId; +import kin.sdk.TransactionInterceptor; +import kin.sdk.exception.KinException; +import kin.sdk.exception.OperationFailedException; +import kin.sdk.internal.blockchain.TransactionSender; +import kin.sdk.queue.TransactionParamsProcessImpl; +import kin.sdk.queue.TransactionProcess; +import kin.sdk.transactiondata.PaymentTransactionParams; +import kin.sdk.transactiondata.Transaction; +import kin.sdk.transactiondata.TransactionParams; + +class SendTransactionParamsTask extends SendTransactionTask { + + private final TransactionParams transactionParams; + private final TransactionSender transactionSender; + private final TransactionInterceptor transactionInterceptor; + private final KeyPair accountFrom; + + SendTransactionParamsTask(TransactionParams transactionParams, + TransactionSender transactionSender, + TransactionInterceptor transactionInterceptor, + TaskFinishListener taskFinishListener, KeyPair accountFrom) { + super(transactionSender, transactionInterceptor, taskFinishListener); + this.transactionParams = transactionParams; + this.transactionSender = transactionSender; + this.transactionInterceptor = transactionInterceptor; + this.accountFrom = accountFrom; + } + + @Override + void invokeInterceptor() { + TransactionProcess transactionProcess = + new TransactionParamsProcessImpl(transactionSender, transactionParams, + accountFrom); + TransactionId transactionId = null; + try { + transactionId = transactionInterceptor.interceptTransactionSending(transactionProcess); + } catch (Exception e) { + if (!(e instanceof KinException)) { + e = new OperationFailedException(e); + } + // TODO: 2019-09-11 because transaction params doesn't have any event listener then + // what should we do here? + } + handleTransactionFinished(transactionId); + } + + @Override + void buildAndSendTransaction() throws OperationFailedException { + // TODO: 2019-09-02 don't like this instanceof of shit. maybe we should think about an + // Object Oriented Solution, + // for example, create a PaymentTransactionTask Class and in the future + // SomeOtherTransactionTask + Transaction transaction = null; + if (transactionParams instanceof PaymentTransactionParams) { + PaymentTransactionParams paymentTransactionParams = + (PaymentTransactionParams) transactionParams; + transaction = + transactionSender.buildPaymentTransaction(accountFrom, + paymentTransactionParams.destinationPublicAddress(), + paymentTransactionParams.amount(), paymentTransactionParams.fee(), + paymentTransactionParams.memo()); + } + sendTransaction(transaction); + } +} diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/SendTransactionTask.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/SendTransactionTask.java new file mode 100644 index 00000000..522398bf --- /dev/null +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/SendTransactionTask.java @@ -0,0 +1,58 @@ +package kin.sdk.internal.queue; + +import kin.sdk.TransactionId; +import kin.sdk.TransactionInterceptor; +import kin.sdk.exception.OperationFailedException; +import kin.sdk.internal.blockchain.TransactionSender; +import kin.sdk.transactiondata.Transaction; + +// A class that sends a transaction to the blockchain. +abstract class SendTransactionTask implements Runnable { + + private final TransactionSender transactionSender; + private final TransactionInterceptor transactionInterceptor; + private final TaskFinishListener taskFinishListener; + + SendTransactionTask(TransactionSender transactionSender, + TransactionInterceptor transactionInterceptor, + TaskFinishListener taskFinishListener) { + this.transactionSender = transactionSender; + this.transactionInterceptor = transactionInterceptor; + this.taskFinishListener = taskFinishListener; + } + + void sendTransaction(Transaction transaction) throws OperationFailedException { + TransactionId transactionId = transactionSender.sendTransaction(transaction); + handleTransactionFinished(transactionId); + } + + void handleTransactionFinished(TransactionId transactionId) { + // if transactionId is null then it means that the transaction has failed + if (transactionId != null) { + // TODO: 2019-09-01 invoke events for transaction success + } else { + // TODO: 2019-09-01 invoke events for transaction fail + } + taskFinishListener.onTaskFinish(); + } + + @Override + public void run() { + try { + if (transactionInterceptor != null) { + invokeInterceptor(); + } else { + // TODO: 2019-09-01 if they wanted to send a memo then they should intercept it. + // but maybe we should let them add memo easily without the need to intercept? + buildAndSendTransaction(); + } + } catch (Exception e) { + // TODO: 2019-09-11 because transaction params doesn't have any event listener then + // what should we do here? + } + } + + abstract void invokeInterceptor(); + + abstract void buildAndSendTransaction() throws OperationFailedException; +} diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TaskFinishListener.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TaskFinishListener.java new file mode 100644 index 00000000..1e1a0b31 --- /dev/null +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TaskFinishListener.java @@ -0,0 +1,10 @@ +package kin.sdk.internal.queue; + +public interface TaskFinishListener { + + /** + * Callback for when a task has finished successfully or with failure. + */ + void onTaskFinish(); + +} diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TasksQueue.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TasksQueue.java new file mode 100644 index 00000000..64dfccd0 --- /dev/null +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TasksQueue.java @@ -0,0 +1,75 @@ +package kin.sdk.internal.queue; + +import android.support.annotation.Nullable; + +import java.util.List; + +import kin.sdk.TransactionInterceptor; +import kin.sdk.queue.PaymentQueueTransactionProcess; +import kin.sdk.queue.PendingPayment; +import kin.sdk.queue.TransactionProcess; +import kin.sdk.transactiondata.TransactionParams; + +public interface TasksQueue { + + /** + * Scheduling a batch payment transaction task to run as soon as possible. + * Task are running sequentially so if there are tasks in the queue then they will run before + * this task. + * If this task has high priority then it will enter to the top of the queue, meaning that it + * will be executed next. + * + * @param batchPendingPayments the list of batched pending payments from which we will create + * a batch transaction. + */ + void schedulePendingPaymentsTask(List batchPendingPayments); + + /** + * Scheduling a payment transaction params task to run as soon as possible. + * Task are running sequentially so if there are tasks in the queue then they will run before + * this task. + * If this task has high priority then it will enter to the top of the queue, meaning that it + * will be executed next. + * + * @param paymentTransactionParams is the object from which we will create the payment + * transaction + * @param transactionParamsInterceptor an optional interceptor that will be used to intercept + * the transaction. + */ + void scheduleTransactionParamsTask(TransactionParams paymentTransactionParams, + @Nullable TransactionInterceptor transactionParamsInterceptor); + + /** + * Stop all tasks in the queue. + *

Note that after calling this method then this task queue is not usable anymore and + * any call to scheduleTask wont work. + * You will need to create a new TasksQueue.

+ */ + void stopTaskQueue(); // TODO: 2019-08-27 should we even try to stop the queue or it will + // always live? i think it should live as long as needed + + /** + * Setter for the task finish listener. + * + * @param taskFinishListener is listener + */ + void setTaskFinishListener(TaskFinishListener taskFinishListener); + + /** + * Setter for the fee + * If this fee is below the minimum then the transaction will fail. + * If no fee will be set then the minimum blockchain fee wil be used. + * + * @param fee the amount of fee(in Quarks) for each payment (1 Quark = 0.00001 KIN). + */ + void setBatchPaymentsFee(int fee); + + /** + * Set a transaction interceptor. + * + * @param pendingPaymentsTransactionInterceptor is the TransactionInterceptor for batch + * payments transactions to set. + *

See {@link TransactionInterceptor}

+ */ + void setPaymentQueueTransactionInterceptor(TransactionInterceptor pendingPaymentsTransactionInterceptor); +} diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TasksQueueImpl.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TasksQueueImpl.java new file mode 100644 index 00000000..81f6ba58 --- /dev/null +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TasksQueueImpl.java @@ -0,0 +1,102 @@ +package kin.sdk.internal.queue; + +import android.os.Handler; +import android.os.HandlerThread; +import android.support.annotation.NonNull; + +import java.util.List; + +import kin.base.KeyPair; +import kin.sdk.TransactionInterceptor; +import kin.sdk.internal.blockchain.GeneralBlockchainInfoRetriever; +import kin.sdk.internal.blockchain.TransactionSender; +import kin.sdk.internal.events.EventsManager; +import kin.sdk.queue.PaymentQueueTransactionProcess; +import kin.sdk.queue.PendingPayment; +import kin.sdk.queue.TransactionProcess; +import kin.sdk.transactiondata.TransactionParams; + +/** + * Class which handles the task queue. + * When finishing then please call to 'stopTaskQueue' in order to free the resources. + */ +class TasksQueueImpl extends HandlerThread implements TasksQueue { + + private static final String nameTag = "TaskQueueHandlerThread"; + + private final TransactionSender transactionSender; + private TransactionInterceptor paymentQueueTransactionInterceptor; + private final EventsManager eventsManager; + private final GeneralBlockchainInfoRetriever generalBlockchainInfoRetriever; + private final KeyPair accountFrom; + private TaskFinishListener taskFinishListener; + private Handler backgroundHandler; + private int batchPaymentsFee = -1; + + TasksQueueImpl(@NonNull TransactionSender transactionSender, + @NonNull EventsManager eventsManager, + @NonNull GeneralBlockchainInfoRetriever generalBlockchainInfoRetriever, + @NonNull KeyPair accountFrom) { + super(nameTag); + this.transactionSender = transactionSender; + this.eventsManager = eventsManager; + this.generalBlockchainInfoRetriever = generalBlockchainInfoRetriever; + this.accountFrom = accountFrom; + + start(); + } + + @Override + protected void onLooperPrepared() { + super.onLooperPrepared(); + backgroundHandler = new Handler(getLooper()); + } + + @Override + public void schedulePendingPaymentsTask(List batchPendingPayments) { + if (backgroundHandler != null) { + SendPendingPaymentsTask sendPendingPaymentsTask = + new SendPendingPaymentsTask(batchPendingPayments, transactionSender, + paymentQueueTransactionInterceptor, taskFinishListener, + eventsManager, generalBlockchainInfoRetriever, batchPaymentsFee, + accountFrom); + backgroundHandler.post(sendPendingPaymentsTask); + } + } + + @Override + public void scheduleTransactionParamsTask(TransactionParams transactionParams, + TransactionInterceptor transactionParamsInterceptor) { + if (backgroundHandler != null) { + SendTransactionParamsTask sendTransactionParamsTask = + new SendTransactionParamsTask(transactionParams, + transactionSender, + transactionParamsInterceptor, taskFinishListener, accountFrom); + backgroundHandler.postAtFrontOfQueue(sendTransactionParamsTask); + } + } + + @Override + public void stopTaskQueue() { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) { + quitSafely(); + } else { + quit(); + } + } + + @Override + public void setTaskFinishListener(TaskFinishListener taskFinishListener) { + this.taskFinishListener = taskFinishListener; + } + + @Override + public void setBatchPaymentsFee(int batchPaymentsFee) { + this.batchPaymentsFee = batchPaymentsFee; + } + + @Override + public void setPaymentQueueTransactionInterceptor(TransactionInterceptor pendingPaymentsTransactionInterceptor) { + this.paymentQueueTransactionInterceptor = pendingPaymentsTransactionInterceptor; + } +} diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TransactionInterceptorImpl.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TransactionInterceptorImpl.java deleted file mode 100644 index 9d8b0b6f..00000000 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TransactionInterceptorImpl.java +++ /dev/null @@ -1,16 +0,0 @@ -package kin.sdk.internal.queue; - -import kin.sdk.TransactionId; -import kin.sdk.TransactionInterceptor; -import kin.sdk.queue.TransactionProcess; - -public class TransactionInterceptorImpl implements TransactionInterceptor { - - // TODO: 2019-08-04 implement - - @Override - public TransactionId interceptTransactionSending(TransactionProcess transactionProcess) throws Exception { - return null; - } - -} diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TransactionProcessImpl.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TransactionProcessImpl.java deleted file mode 100644 index a5fcab5d..00000000 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TransactionProcessImpl.java +++ /dev/null @@ -1,38 +0,0 @@ -package kin.sdk.internal.queue; - -import kin.sdk.TransactionId; -import kin.sdk.queue.PendingPayment; -import kin.sdk.queue.TransactionProcess; -import kin.sdk.transactiondata.Transaction; - -import java.util.List; - -public class TransactionProcessImpl implements TransactionProcess { - - // TODO: 2019-08-04 implement and add java docs - - @Override - public Transaction transaction() { - return null; - } - - @Override - public Transaction transaction(String memo) { - return null; - } - - @Override - public List payments() { - return null; - } - - @Override - public TransactionId send(Transaction transaction) { - return null; - } - - @Override - public TransactionId send(String whitelistPayload) { - return null; - } -} diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TransactionTaskQueueManager.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TransactionTaskQueueManager.java deleted file mode 100644 index a00bc565..00000000 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TransactionTaskQueueManager.java +++ /dev/null @@ -1,15 +0,0 @@ -package kin.sdk.internal.queue; - -import kin.sdk.SendTransactionParams; -import kin.sdk.queue.PendingPayment; - -import java.util.List; - -public interface TransactionTaskQueueManager { - - // TODO: 2019-08-15 add java docs - - void enqueue(List queueToSend); - - void enqueue(SendTransactionParams sendTransactionParams); -} diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TransactionTaskQueueManagerImpl.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TransactionTaskQueueManagerImpl.java deleted file mode 100644 index c39a324e..00000000 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TransactionTaskQueueManagerImpl.java +++ /dev/null @@ -1,19 +0,0 @@ -package kin.sdk.internal.queue; - -import kin.sdk.SendTransactionParams; -import kin.sdk.queue.PendingPayment; - -import java.util.List; - -public class TransactionTaskQueueManagerImpl implements TransactionTaskQueueManager { - - @Override - public void enqueue(List queueToSend) { - - } - - @Override - public void enqueue(SendTransactionParams sendTransactionParams) { - - } -} diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TransactionTasksQueueManager.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TransactionTasksQueueManager.java new file mode 100644 index 00000000..9284f154 --- /dev/null +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TransactionTasksQueueManager.java @@ -0,0 +1,36 @@ +package kin.sdk.internal.queue; + +import java.util.List; + +import kin.sdk.TransactionInterceptor; +import kin.sdk.queue.PendingPayment; +import kin.sdk.queue.TransactionProcess; +import kin.sdk.transactiondata.PaymentTransactionParams; + +public interface TransactionTasksQueueManager { + + + /** + * enqueue pending payments list. + * + * @param pendingPayments the list of pending payments to enqueue. + */ + void enqueue(List pendingPayments); + + /** + * enqueue the transaction parameters object which will later on become a transaction. + * + * @param transactionParams the transaction parameters + * @param transactionInterceptor an optional interceptor that will be used to intercept + * * the transaction. + */ + void enqueue(PaymentTransactionParams transactionParams, + TransactionInterceptor transactionInterceptor); + + /** + * @return true if currently there is a transaction in progress, meaning it will return true if + * there is a transaction that was entered to the task queue and until it is finished(success + * or fail). + */ + boolean transactionInProgress(); +} diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TransactionTasksQueueManagerImpl.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TransactionTasksQueueManagerImpl.java new file mode 100644 index 00000000..8ab6389a --- /dev/null +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/internal/queue/TransactionTasksQueueManagerImpl.java @@ -0,0 +1,154 @@ +package kin.sdk.internal.queue; + +import android.util.Log; + +import java.util.ArrayDeque; +import java.util.List; +import java.util.Queue; + +import kin.sdk.TransactionInterceptor; +import kin.sdk.internal.events.EventsManager; +import kin.sdk.queue.PendingPayment; +import kin.sdk.queue.TransactionProcess; +import kin.sdk.transactiondata.PaymentTransactionParams; + +/** + * Class which manages the transaction task queue. + */ +class TransactionTasksQueueManagerImpl implements TransactionTasksQueueManager, TaskFinishListener { + + private static final String TAG = TransactionTasksQueueManagerImpl.class.getSimpleName(); + + private final TasksQueue taskQueue; + private final EventsManager eventsManager; // TODO: 2019-09-09 verify if it is necessary here + private final int maxNumOfPayments; + private Queue> batchPendingPaymentsQueue; + private PaymentTransactionParams currentTransactionParams; + private TransactionInterceptor currentTransactionParamsInterceptor; + private boolean transactionInProgress = false; + + TransactionTasksQueueManagerImpl(TasksQueue taskQueue, EventsManager eventsManager, + int maxNumOfPayments) { + this.taskQueue = taskQueue; + this.taskQueue.setTaskFinishListener(this); + this.eventsManager = eventsManager; + this.maxNumOfPayments = maxNumOfPayments; + batchPendingPaymentsQueue = new ArrayDeque<>(); + } + + @Override + public synchronized void enqueue(List pendingPayments) { + mergePayments(pendingPayments); + sendTask(); + } + + @Override + public synchronized void enqueue(PaymentTransactionParams paymentTransactionParams, + TransactionInterceptor transactionInterceptor) { + if (currentTransactionParams != null) { + // TODO: 2019-08-26 throw some exception or use event manager + } else { + currentTransactionParams = paymentTransactionParams; + currentTransactionParamsInterceptor = transactionInterceptor; + sendTask(); + } + } + + @Override + public synchronized boolean transactionInProgress() { + return transactionInProgress; + } + + @Override + public synchronized void onTaskFinish() { + transactionInProgress = false; + sendTask(); + } + + /** + * Taking the new pending payment and instead of just adding it to the queue, we will + * merge/combine this list with the with whatever we can in the queue of batched payments list + * In this way instead sending many transactions we will have less transactions to send. + * For example, if currently our list consist of a list of 2 items, the first with 100 batch + * payments and the second with 40 and our new pending payments consist of 70 items. + * Then our new queue of list of batch payments will now have the first item still 100, + * second item 100 and the third item with 10. + * + * @param pendingPayments the new pending payments list that needed to be enqueued and batched. + */ + private void mergePayments(List pendingPayments) { + // the current number of pending payments that need to be batched + int numOfPendingPaymentsToBatch = pendingPayments.size(); + // the index in the pending payments list in which to start take elements to next batch + // payments list. + int pendingPaymentIndexToTakeFrom = 0; + + for (List batchPayments : batchPendingPaymentsQueue) { + // the current number of pending payments that can be entered to the current batch + // payments list + int numOfEntriesAvailableInBatchList = + Math.abs(maxNumOfPayments - batchPayments.size()); + if (numOfEntriesAvailableInBatchList == 0) { + // no room to add pending payments so go to next batch payments + continue; + } else { + // If there are more pending payments then the number of payments to fill then take + // a sublist of them and fill and take the rest and fill the next batch payments, + // otherwise just add all of them to the current batch payments + if (numOfEntriesAvailableInBatchList >= numOfPendingPaymentsToBatch) { + batchPayments.addAll(pendingPayments); + return; + } else { + // update the rest of pending payments that still need to be batched. + numOfPendingPaymentsToBatch -= numOfEntriesAvailableInBatchList; + List subPendingPayments = + pendingPayments.subList(pendingPaymentIndexToTakeFrom, + pendingPaymentIndexToTakeFrom + numOfEntriesAvailableInBatchList); + // update the index + pendingPaymentIndexToTakeFrom += numOfEntriesAvailableInBatchList; + batchPayments.addAll(subPendingPayments); + } + } + } + // add to the queue + batchPendingPaymentsQueue.add(pendingPayments.subList(pendingPaymentIndexToTakeFrom, + pendingPaymentIndexToTakeFrom + numOfPendingPaymentsToBatch)); + } + + /** + * Sending the next task to the task queue if there is no transaction in progress. + * If there is a transaction param task then send it first, otherwise send the next task in + * the queue. + * If no task in the queue then we don't do anything. + */ + private void sendTask() { + // Check if there is a task running and if not then schedule a task + if (!transactionInProgress()) { + + if (currentTransactionParams != null) { + sendTransactionParamsTask(); + } else if (batchPendingPaymentsQueue.size() > 0) { + sendPendingPaymentsTask(); + } + } else { + Log.d(TAG, "sendTask: can't send task because there is a transaction in progress"); + // TODO: 2019-09-08 should we add logs here or something? probably we should + } + } + + private void sendTransactionParamsTask() { + transactionInProgress = true; + PaymentTransactionParams toSend = currentTransactionParams; + currentTransactionParams = null; + TransactionInterceptor transactionInterceptor = + currentTransactionParamsInterceptor; + currentTransactionParamsInterceptor = null; + taskQueue.scheduleTransactionParamsTask(toSend, transactionInterceptor); + } + + private void sendPendingPaymentsTask() { + transactionInProgress = true; + taskQueue.schedulePendingPaymentsTask(batchPendingPaymentsQueue.poll()); + } + +} diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/queue/PaymentQueue.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/queue/PaymentQueue.java index ae79f585..fa2dbed5 100644 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/queue/PaymentQueue.java +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/queue/PaymentQueue.java @@ -1,20 +1,22 @@ package kin.sdk.queue; import android.support.annotation.NonNull; + +import java.math.BigDecimal; +import java.util.List; + +import kin.sdk.TransactionId; import kin.sdk.TransactionInterceptor; import kin.sdk.exception.InsufficientKinException; import kin.sdk.exception.KinException; import kin.sdk.transactiondata.BatchPaymentTransaction; -import java.math.BigDecimal; -import java.util.List; - public interface PaymentQueue { /** * Register/unregister for queue events. */ - interface EventsListener { + interface EventListener { /** * Invoked when a new payment is enqueued @@ -42,7 +44,7 @@ interface EventsListener { *

See {@link BatchPaymentTransaction} and {@link PendingPayment} for more * information

*/ - void onTransactionSendSuccess(BatchPaymentTransaction transaction, List payments); + void onTransactionSendSuccess(TransactionId transactionId, List payments); /** * Invoked when a Transaction just before the transaction will be send. @@ -53,8 +55,7 @@ interface EventsListener { *

See {@link BatchPaymentTransaction} and {@link PendingPayment} for more * information

*/ - void onTransactionSendFailed(BatchPaymentTransaction transaction, List payments, - KinException exception); + void onTransactionSendFailed(List payments, KinException exception); } /** @@ -88,17 +89,17 @@ void onTransactionSendFailed(BatchPaymentTransaction transaction, List See {@link TransactionInterceptor}

*/ - void setTransactionInterceptor(TransactionInterceptor interceptor); + void setTransactionInterceptor(TransactionInterceptor interceptor); /** * @param listener is a queue event listener. */ - void addEventsListener(EventsListener listener); + void setEventListener(EventListener listener); /** * Setter for the fee. * If this fee is below the minimum then the transaction will fail. - * + * If no fee will be set then the minimum blockchain fee wil be used. * @param fee the amount of fee(in Quarks) for each payment (1 Quark = 0.00001 KIN). */ void setFee(int fee); @@ -113,4 +114,14 @@ void onTransactionSendFailed(BatchPaymentTransaction transaction, List pendingPayments; + private final KeyPair accountFrom; + private final int fee; + + public PaymentQueueTransactionProcess(TransactionSender transactionSender, + List pendingPayments, + KeyPair accountFrom, int fee) { + super(transactionSender); + this.transactionSender = transactionSender; + this.pendingPayments = pendingPayments; + this.accountFrom = accountFrom; + this.fee = fee; + } + + /** + * @return the list of pending payment that the current transaction consist of. + * Can be null or empty. + */ + public List payments() { + return pendingPayments; + } + + @Override + public BatchPaymentTransaction transaction() throws OperationFailedException { + return transaction(null); + } + + /** + * @return a transaction that consist of the list of pending payments. + * Also add a memo to that transaction + */ + + public BatchPaymentTransaction transaction(String memo) throws OperationFailedException { + return buildTransaction(memo); + } + + /** + * Build the transaction + * + * @param memo the memo that should be added to the transaction + * @return a new created transaction. + * @throws OperationFailedException in case it couldn't be build. + */ + private BatchPaymentTransaction buildTransaction(String memo) throws OperationFailedException { + return transactionSender.buildBatchPaymentTransaction(accountFrom, pendingPayments, fee, + memo); + } +} diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/queue/PendingPayment.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/queue/PendingPayment.java index 3bdc093b..c17fbc51 100644 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/queue/PendingPayment.java +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/queue/PendingPayment.java @@ -6,10 +6,6 @@ public interface PendingPayment { - enum Status { - PENDING, COMPLETED, FAILED - } - /** * Destination account public address. */ @@ -31,9 +27,4 @@ enum Status { */ @Nullable Object metadata(); - - /** - * Current Payment status - */ - Status status(); } diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/queue/TransactionParamsProcessImpl.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/queue/TransactionParamsProcessImpl.java new file mode 100644 index 00000000..5af51bd4 --- /dev/null +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/queue/TransactionParamsProcessImpl.java @@ -0,0 +1,49 @@ +package kin.sdk.queue; + +import kin.base.KeyPair; +import kin.sdk.exception.OperationFailedException; +import kin.sdk.internal.blockchain.TransactionSender; +import kin.sdk.transactiondata.PaymentTransactionParams; +import kin.sdk.transactiondata.Transaction; +import kin.sdk.transactiondata.TransactionParams; + +public class TransactionParamsProcessImpl extends TransactionProcess { + + private final TransactionSender transactionSender; + private final TransactionParams transactionParams; + private final KeyPair accountFrom; + + public TransactionParamsProcessImpl(TransactionSender transactionSender, + TransactionParams transactionParams, KeyPair accountFrom) { + super(transactionSender); + this.transactionSender = transactionSender; + this.transactionParams = transactionParams; + this.accountFrom = accountFrom; + } + + @Override + public Transaction transaction() throws OperationFailedException { + return buildTransaction(); + } + + /** + * Build the transaction + * + * @return a new created transaction. + * @throws OperationFailedException in case it couldn't be build. + */ + private Transaction buildTransaction() throws OperationFailedException { + if (transactionParams instanceof PaymentTransactionParams) { + PaymentTransactionParams paymentTransactionParams = + (PaymentTransactionParams) transactionParams; + return transactionSender.buildPaymentTransaction(accountFrom, + paymentTransactionParams.destinationPublicAddress(), + paymentTransactionParams.amount(), + paymentTransactionParams.fee(), paymentTransactionParams.memo()); + } else { + // will be implemented in the future when we will add more types + return null; + } + + } +} diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/queue/TransactionProcess.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/queue/TransactionProcess.java index 78bdba43..32693fc8 100644 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/queue/TransactionProcess.java +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/queue/TransactionProcess.java @@ -1,33 +1,26 @@ package kin.sdk.queue; -import android.support.annotation.Nullable; import kin.sdk.TransactionId; +import kin.sdk.exception.OperationFailedException; +import kin.sdk.internal.blockchain.TransactionSender; import kin.sdk.transactiondata.Transaction; -import java.util.List; - /** - * This class can be used to access both generated transaction and all of its associated PendingPayments. + * This class can be used to access both generated transaction and all of its associated + * PendingPayments. */ -public interface TransactionProcess { +public abstract class TransactionProcess { - /** - * @return a transaction that consist of the list of pending payments. - */ - Transaction transaction(); + private final TransactionSender transactionSender; - /** - * @return a transaction that consist of the list of pending payments. - * Also add a memo to that transaction - */ - Transaction transaction(String memo); + TransactionProcess(TransactionSender transactionSender) { + this.transactionSender = transactionSender; + } /** - * @return the list of pending payment that the current transaction consist of. - * Can be null or empty. + * @return a transaction that consist of the list of pending payments. */ - @Nullable - List payments(); + public abstract Transaction transaction() throws OperationFailedException; /** * Send the transaction with a Transaction object @@ -35,7 +28,9 @@ public interface TransactionProcess { * @param transaction the Transaction object to send * @return the transaction id. */ - TransactionId send(Transaction transaction); + public TransactionId send(Transaction transaction) throws OperationFailedException { + return transactionSender.sendTransaction(transaction); + } /** * Send the transaction with a whitelisted payload @@ -43,5 +38,7 @@ public interface TransactionProcess { * @param whitelistPayload the whitelist payload * @return the transaction id. */ - TransactionId send(String whitelistPayload); + public TransactionId send(String whitelistPayload) throws OperationFailedException { + return transactionSender.sendWhitelistTransaction(whitelistPayload); + } } diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/transactiondata/BatchPaymentTransaction.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/transactiondata/BatchPaymentTransaction.java index 86fc61bd..699e4f42 100644 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/transactiondata/BatchPaymentTransaction.java +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/transactiondata/BatchPaymentTransaction.java @@ -1,14 +1,14 @@ package kin.sdk.transactiondata; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + import kin.base.Operation; import kin.base.Transaction; import kin.sdk.internal.blockchain.TransactionInternal; import kin.sdk.internal.data.PaymentOperationImpl; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.List; - public class BatchPaymentTransaction extends TransactionInternal { private final Transaction baseTransaction; @@ -61,4 +61,5 @@ public List payments() { public String memo() { return memo(); } + } diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/transactiondata/PaymentTransaction.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/transactiondata/PaymentTransaction.java index 4d6dc244..84a5d1aa 100644 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/transactiondata/PaymentTransaction.java +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/transactiondata/PaymentTransaction.java @@ -10,9 +10,10 @@ public class PaymentTransaction extends TransactionInternal { private final BigDecimal amount; private final String memo; + // TODO: 2019-08-26 We should state that this class represent a transaction with a single + // payment operation public PaymentTransaction(kin.base.Transaction baseTransaction, - String destinationPublicAddress, BigDecimal amount, - String memo) { + String destinationPublicAddress, BigDecimal amount, String memo) { super(baseTransaction); this.destinationPublicAddress = destinationPublicAddress; this.amount = amount; @@ -35,7 +36,7 @@ public BigDecimal amount() { /** * @return the memo of this transaction. - * In this case you will get a string representation of a memo as opposed to {@link RawTransaction#memo()} + * In this case you will get a string representation of a memo. */ public String memo() { return memo; diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/transactiondata/PaymentTransactionParams.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/transactiondata/PaymentTransactionParams.java new file mode 100644 index 00000000..97193af4 --- /dev/null +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/transactiondata/PaymentTransactionParams.java @@ -0,0 +1,40 @@ +package kin.sdk.transactiondata; + +import android.support.annotation.NonNull; + +import java.math.BigDecimal; + +public class PaymentTransactionParams extends TransactionParams { + + // TODO: 2019-08-21 implement + + private PaymentTransactionParams() { + } + + public String destinationPublicAddress() { + return null; + } + + public BigDecimal amount() { + return null; + } + + public String memo() { + return null; + } + + public class Builder { + + Builder(@NonNull String publicAddress, @NonNull BigDecimal amount, int fee) { + } + + public Builder memo(String memo) { + return null; + } + + public PaymentTransactionParams build() { + return null; + } + } + +} diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/transactiondata/Transaction.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/transactiondata/Transaction.java index a54a34f3..f9397a21 100644 --- a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/transactiondata/Transaction.java +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/transactiondata/Transaction.java @@ -1,6 +1,14 @@ package kin.sdk.transactiondata; -import kin.base.*; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.List; + +import kin.base.MemoText; +import kin.base.Network; +import kin.base.Operation; +import kin.base.PaymentOperation; +import kin.base.TimeBounds; import kin.base.xdr.DecoratedSignature; import kin.sdk.KinAccount; import kin.sdk.TransactionId; @@ -10,10 +18,6 @@ import kin.sdk.internal.account.KinAccountImpl; import kin.sdk.internal.data.TransactionIdImpl; -import java.io.IOException; -import java.math.BigDecimal; -import java.util.List; - public abstract class Transaction { private final kin.base.Transaction baseTransaction; @@ -48,6 +52,8 @@ public static Transaction decodeTransaction(String transactionEnvelope) throws D if (paymentOperationCount == 1) { return new PaymentTransaction(transaction, paymentOperation.getDestination().getAccountId(), new BigDecimal(paymentOperation.getAmount()), ((MemoText) transaction.getMemo()).getText()); + } else if (paymentOperationCount > 1) { + return new BatchPaymentTransaction(transaction); } else { return new RawTransaction(transaction); } diff --git a/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/transactiondata/TransactionParams.java b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/transactiondata/TransactionParams.java new file mode 100644 index 00000000..8a7c3b5e --- /dev/null +++ b/kin-sdk/kin-sdk-lib/src/main/java/kin/sdk/transactiondata/TransactionParams.java @@ -0,0 +1,21 @@ +package kin.sdk.transactiondata; + +import android.support.annotation.NonNull; + +import java.math.BigDecimal; + +public class TransactionParams { + + // TODO: 2019-08-06 implement + + static PaymentTransactionParams.Builder paymentTransactionBuilder(@NonNull String publicAddress, + @NonNull BigDecimal amount, + int fee) { + return null; + } + + public int fee() { + return 0; + } + +} diff --git a/kin-sdk/kin-sdk-lib/src/test/java/kin/sdk/KinAccountImplTest.java b/kin-sdk/kin-sdk-lib/src/test/java/kin/sdk/KinAccountImplTest.java index db636949..100d28cf 100644 --- a/kin-sdk/kin-sdk-lib/src/test/java/kin/sdk/KinAccountImplTest.java +++ b/kin-sdk/kin-sdk-lib/src/test/java/kin/sdk/KinAccountImplTest.java @@ -17,6 +17,7 @@ import kin.sdk.internal.blockchain.events.BlockchainEventsCreator; import kin.sdk.internal.data.BalanceImpl; import kin.sdk.internal.data.TransactionIdImpl; +import kin.sdk.queue.PaymentQueue; import kin.sdk.transactiondata.PaymentTransaction; import static junit.framework.Assert.assertEquals; @@ -35,18 +36,22 @@ public class KinAccountImplTest { private AccountInfoRetriever mockAccountInfoRetriever; @Mock private BlockchainEventsCreator mockBlockchainEventsCreator; + @Mock + private PaymentQueue mockPaymentQueue; + private KinAccountImpl kinAccount; private KeyPair expectedRandomAccount; @Before - public void setUp() throws Exception { + public void setUp() { MockitoAnnotations.initMocks(this); } private void initWithRandomAccount() { expectedRandomAccount = KeyPair.random(); - kinAccount = new KinAccountImpl(expectedRandomAccount, new FakeBackupRestore(), mockTransactionSender, - mockAccountInfoRetriever, mockBlockchainEventsCreator); + kinAccount = new KinAccountImpl(expectedRandomAccount, new FakeBackupRestore(), + mockTransactionSender, mockAccountInfoRetriever, mockBlockchainEventsCreator, + mockPaymentQueue); } @Test @@ -66,7 +71,8 @@ public void sendTransactionSync() throws Exception { when(mockTransactionSender.sendTransaction((PaymentTransaction) any())).thenReturn(expectedTransactionId); - PaymentTransaction transaction = kinAccount.buildTransactionSync(expectedAccountId, expectedAmount, 100); + PaymentTransaction transaction = kinAccount.buildTransactionSync(expectedAccountId, + expectedAmount, 100); TransactionId transactionId = kinAccount.sendTransactionSync(transaction); verify(mockTransactionSender).sendTransaction(transaction); @@ -84,7 +90,8 @@ public void sendTransactionSync_WithMemo() throws Exception { when(mockTransactionSender.sendTransaction((PaymentTransaction) any())).thenReturn(expectedTransactionId); - PaymentTransaction transaction = kinAccount.buildTransactionSync(expectedAccountId, expectedAmount, 100, memo); + PaymentTransaction transaction = kinAccount.buildTransactionSync(expectedAccountId, + expectedAmount, 100, memo); TransactionId transactionId = kinAccount.sendTransactionSync(transaction); verify(mockTransactionSender).sendTransaction(transaction); @@ -122,7 +129,8 @@ public void sendTransactionSync_DeletedAccount_Exception() throws Exception { kinAccount.markAsDeleted(); PaymentTransaction transaction = kinAccount.buildTransactionSync( - "GDKJAMCTGZGD6KM7RBEII6QUYAHQQUGERXKM3ESHBX2UUNTNAVNB3OGX", new BigDecimal("12.2"), 100); + "GDKJAMCTGZGD6KM7RBEII6QUYAHQQUGERXKM3ESHBX2UUNTNAVNB3OGX", + new BigDecimal("12.2"), 100); kinAccount.sendTransactionSync(transaction); } diff --git a/kin-sdk/kin-sdk-lib/src/test/java/kin/sdk/TransactionSenderTest.java b/kin-sdk/kin-sdk-lib/src/test/java/kin/sdk/TransactionSenderTest.java index f9243675..a08a95db 100644 --- a/kin-sdk/kin-sdk-lib/src/test/java/kin/sdk/TransactionSenderTest.java +++ b/kin-sdk/kin-sdk-lib/src/test/java/kin/sdk/TransactionSenderTest.java @@ -1,17 +1,5 @@ package kin.sdk; -import kin.base.FormatException; -import kin.base.KeyPair; -import kin.base.Network; -import kin.base.Server; -import kin.base.responses.HttpResponseException; -import kin.sdk.exception.*; -import kin.sdk.internal.blockchain.TransactionSender; -import kin.sdk.internal.storage.KeyStore; -import kin.sdk.transactiondata.PaymentTransaction; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.SocketPolicy; import org.hamcrest.beans.HasPropertyWithValue; import org.junit.Assert; import org.junit.Before; @@ -29,9 +17,33 @@ import java.net.SocketTimeoutException; import java.util.concurrent.TimeUnit; +import kin.base.FormatException; +import kin.base.KeyPair; +import kin.base.Network; +import kin.base.Server; +import kin.base.responses.HttpResponseException; +import kin.sdk.exception.AccountNotFoundException; +import kin.sdk.exception.IllegalAmountException; +import kin.sdk.exception.InsufficientFeeException; +import kin.sdk.exception.InsufficientKinException; +import kin.sdk.exception.OperationFailedException; +import kin.sdk.exception.TransactionFailedException; +import kin.sdk.internal.blockchain.TransactionSender; +import kin.sdk.internal.storage.KeyStore; +import kin.sdk.transactiondata.PaymentTransaction; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.SocketPolicy; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.fail; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isA; import static org.junit.Assert.assertThat; @RunWith(RobolectricTestRunner.class) @@ -83,7 +95,8 @@ public void sendTransaction_success() throws Exception { mockWebServer.enqueue(TestUtils.generateSuccessMockResponse(this.getClass(), "tx_account_to.json")); mockWebServer.enqueue(TestUtils.generateSuccessMockResponse(this.getClass(), "tx_success_res.json")); - PaymentTransaction transaction = transactionSender.buildTransaction(account, ACCOUNT_ID_TO, new BigDecimal("1" + + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, + ACCOUNT_ID_TO, new BigDecimal("1" + ".5"), FEE); TransactionId transactionId = transactionSender.sendTransaction(transaction); @@ -104,7 +117,8 @@ public void sendTransaction_WithMemo_success() throws Exception { mockWebServer.enqueue(TestUtils.generateSuccessMockResponse(this.getClass(), "tx_success_res.json")); String fakeMemo = "fake memo"; - PaymentTransaction transaction = transactionSender.buildTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE, fakeMemo); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, + ACCOUNT_ID_TO, new BigDecimal("200"), FEE, fakeMemo); TransactionId transactionId = transactionSender.sendTransaction(transaction); assertEquals("8f1e0cd1d922f4c57cc1898ececcf47375e52ec4abf77a7e32d0d9bb4edecb69", transactionId.id()); @@ -123,7 +137,8 @@ public void sendTransaction_ToAccountNotExist() throws Exception { expectedEx.expect(AccountNotFoundException.class); expectedEx.expect(new HasPropertyWithValue<>("accountId", equalTo(ACCOUNT_ID_TO))); - PaymentTransaction transaction = transactionSender.buildTransaction(account, ACCOUNT_ID_TO, new BigDecimal("1.5"), FEE); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, + ACCOUNT_ID_TO, new BigDecimal("1.5"), FEE); transactionSender.sendTransaction(transaction); } @@ -143,7 +158,7 @@ public void sendTransaction_Http_307_Response_Success() throws Exception { mockWebServerHttp307.enqueue(TestUtils.generateSuccessMockResponse(this.getClass(), "tx_success_res.json")); PaymentTransaction transaction = transactionSender - .buildTransaction(account, ACCOUNT_ID_TO, new BigDecimal("1.5"), FEE); + .buildPaymentTransaction(account, ACCOUNT_ID_TO, new BigDecimal("1.5"), FEE); TransactionId transactionId = transactionSender.sendTransaction(transaction); assertEquals("8f1e0cd1d922f4c57cc1898ececcf47375e52ec4abf77a7e32d0d9bb4edecb69", transactionId.id()); @@ -162,7 +177,7 @@ public void sendTransaction_FromAccountNotExist() throws Exception { expectedEx.expect(AccountNotFoundException.class); expectedEx.expect(new HasPropertyWithValue<>("accountId", equalTo(ACCOUNT_ID_FROM))); - PaymentTransaction transaction = transactionSender.buildTransaction(account, ACCOUNT_ID_TO, new BigDecimal("1.5"), FEE); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, ACCOUNT_ID_TO, new BigDecimal("1.5"), FEE); transactionSender.sendTransaction(transaction); } @@ -180,7 +195,7 @@ public void sendTransaction_GeneralStellarError() throws Exception { expectedEx.expect(new HasPropertyWithValue<>("operationsResultCodes", contains("op_malformed"))); expectedEx.expect(new HasPropertyWithValue<>("operationsResultCodes", hasSize(1))); - PaymentTransaction transaction = transactionSender.buildTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); transactionSender.sendTransaction(transaction); } @@ -196,7 +211,7 @@ public void sendTransaction_Underfunded_InsufficientKinException() throws Except expectedEx.expect(InsufficientKinException.class); - PaymentTransaction transaction = transactionSender.buildTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); transactionSender.sendTransaction(transaction); } @@ -211,7 +226,7 @@ public void sendTransaction_InsufficientFeeException() throws Exception { expectedEx.expect(InsufficientFeeException.class); - PaymentTransaction transaction = transactionSender.buildTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); transactionSender.sendTransaction(transaction); } @@ -226,7 +241,7 @@ public void sendTransaction_InsufficientBalanceException() throws Exception { expectedEx.expect(InsufficientKinException.class); - PaymentTransaction transaction = transactionSender.buildTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); transactionSender.sendTransaction(transaction); } @@ -254,7 +269,7 @@ public void sendTransaction_FirstQuery_ConnectionException() throws Exception { expectedEx.expect(OperationFailedException.class); expectedEx.expectCause(isA(IOException.class)); - PaymentTransaction transaction = transactionSender.buildTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); transactionSender.sendTransaction(transaction); } @@ -265,7 +280,7 @@ public void sendTransaction_SecondQuery_ConnectionException() throws Exception { expectedEx.expect(OperationFailedException.class); - PaymentTransaction transaction = transactionSender.buildTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); transactionSender.sendTransaction(transaction); } @@ -277,7 +292,7 @@ public void sendTransaction_ThirdQuery_ConnectionException() throws Exception { expectedEx.expect(OperationFailedException.class); - PaymentTransaction transaction = transactionSender.buildTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); transactionSender.sendTransaction(transaction); } @@ -293,7 +308,7 @@ public void sendTransaction_changeTimeOut() throws Exception { expectedEx.expect(OperationFailedException.class); expectedEx.expectCause(isA(SocketTimeoutException.class)); - PaymentTransaction transaction = transactionSender.buildTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); transactionSender.sendTransaction(transaction); } @@ -304,7 +319,7 @@ public void sendTransaction_FirstQuery_NullResponse() throws Exception { expectedEx.expect(OperationFailedException.class); expectedEx.expectMessage(ACCOUNT_ID_FROM); - PaymentTransaction transaction = transactionSender.buildTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); transactionSender.sendTransaction(transaction); } @@ -316,7 +331,7 @@ public void sendTransaction_SecondQuery_NullResponse() throws Exception { expectedEx.expect(OperationFailedException.class); expectedEx.expectMessage(ACCOUNT_ID_TO); - PaymentTransaction transaction = transactionSender.buildTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); transactionSender.sendTransaction(transaction); } @@ -329,7 +344,7 @@ public void sendTransaction_ThirdQuery_NullResponse() throws Exception { expectedEx.expect(OperationFailedException.class); expectedEx.expectMessage("transaction"); - PaymentTransaction transaction = transactionSender.buildTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); transactionSender.sendTransaction(transaction); } @@ -339,7 +354,7 @@ public void sendTransaction_TooLongMemo() throws Exception { String tooLongMemo = "memo string can be only 21 characters"; expectedEx.expect(IllegalArgumentException.class); expectedEx.expectMessage("Memo cannot be longer that 21 bytes(UTF-8 characters)"); - PaymentTransaction transaction = transactionSender.buildTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE, tooLongMemo); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE, tooLongMemo); transactionSender.sendTransaction(transaction); assertThat(mockWebServer.getRequestCount(), equalTo(0)); } @@ -349,7 +364,7 @@ public void sendTransaction_TooLongMemo() throws Exception { public void sendTransaction_NullAccount() throws Exception { expectedEx.expect(IllegalArgumentException.class); expectedEx.expectMessage("account"); - PaymentTransaction transaction = transactionSender.buildTransaction(null, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(null, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); transactionSender.sendTransaction(transaction); } @@ -358,7 +373,7 @@ public void sendTransaction_NullAccount() throws Exception { public void sendTransaction_NullPublicAddress() throws Exception { expectedEx.expect(IllegalArgumentException.class); expectedEx.expectMessage("public address"); - PaymentTransaction transaction = transactionSender.buildTransaction(account, null, new BigDecimal("200"), FEE); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, null, new BigDecimal("200"), FEE); transactionSender.sendTransaction(transaction); } @@ -367,7 +382,7 @@ public void sendTransaction_NullPublicAddress() throws Exception { public void sendTransaction_NullAmount() throws Exception { expectedEx.expect(IllegalArgumentException.class); expectedEx.expectMessage("amount"); - PaymentTransaction transaction = transactionSender.buildTransaction(account, ACCOUNT_ID_TO, null, FEE); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, ACCOUNT_ID_TO, null, FEE); transactionSender.sendTransaction(transaction); } @@ -375,7 +390,7 @@ public void sendTransaction_NullAmount() throws Exception { public void sendTransaction_EmptyPublicAddress() throws Exception { expectedEx.expect(IllegalArgumentException.class); expectedEx.expectMessage("public address"); - PaymentTransaction transaction = transactionSender.buildTransaction(account, "", new BigDecimal("200"), FEE); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, "", new BigDecimal("200"), FEE); transactionSender.sendTransaction(transaction); } @@ -383,7 +398,7 @@ public void sendTransaction_EmptyPublicAddress() throws Exception { public void sendTransaction_NegativeAmount() throws Exception { expectedEx.expect(IllegalArgumentException.class); expectedEx.expectMessage("Amount"); - PaymentTransaction transaction = transactionSender.buildTransaction(account, ACCOUNT_ID_TO, new BigDecimal("-200"), FEE); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, ACCOUNT_ID_TO, new BigDecimal("-200"), FEE); transactionSender.sendTransaction(transaction); } @@ -391,7 +406,7 @@ public void sendTransaction_NegativeAmount() throws Exception { public void sendTransaction_NegativeFee() throws Exception { expectedEx.expect(IllegalArgumentException.class); expectedEx.expectMessage("Fee"); - PaymentTransaction transaction = transactionSender.buildTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), -FEE); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), -FEE); transactionSender.sendTransaction(transaction); } @@ -399,7 +414,7 @@ public void sendTransaction_NegativeFee() throws Exception { public void sendTransaction_AmountExceedNumOfDecimalPlaces() throws Exception { expectedEx.expect(IllegalAmountException.class); expectedEx.expectMessage("amount can't have more then 5 digits after the decimal point"); - PaymentTransaction transaction = transactionSender.buildTransaction(account, ACCOUNT_ID_TO, new BigDecimal("20.012345"), FEE); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, ACCOUNT_ID_TO, new BigDecimal("20.012345"), FEE); transactionSender.sendTransaction(transaction); } @@ -409,7 +424,7 @@ public void sendTransaction_InvalidPublicIdLength() throws Exception { expectedEx.expect(OperationFailedException.class); expectedEx.expectCause(isA(FormatException.class)); expectedEx.expectMessage("public address"); - PaymentTransaction transaction = transactionSender.buildTransaction(account, "ABCDEF", new BigDecimal("200"), FEE); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, "ABCDEF", new BigDecimal("200"), FEE); transactionSender.sendTransaction(transaction); } @@ -419,7 +434,7 @@ public void sendTransaction_InvalidChecksumPublicId() throws Exception { expectedEx.expect(OperationFailedException.class); expectedEx.expectCause(isA(FormatException.class)); expectedEx.expectMessage("public address"); - PaymentTransaction transaction = transactionSender.buildTransaction(account, + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, "GDKJAMCTGZGD6KM7RBEII6QUYAHQQUGERXKM3ESHBX2UUNTNAVNB3OG3", new BigDecimal("200"), FEE); transactionSender.sendTransaction(transaction); } @@ -427,7 +442,7 @@ public void sendTransaction_InvalidChecksumPublicId() throws Exception { @SuppressWarnings("SameParameterValue") private void testHttpResponseCode(int resCode) { try { - PaymentTransaction transaction = transactionSender.buildTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); + PaymentTransaction transaction = transactionSender.buildPaymentTransaction(account, ACCOUNT_ID_TO, new BigDecimal("200"), FEE); transactionSender.sendTransaction(transaction); fail("Expected OperationFailedException"); } catch (Exception ex) { diff --git a/kin-sdk/kin-sdk-lib/src/test/kotlin/kin/sdk/internal/queue/FakeQueueScheduler.kt b/kin-sdk/kin-sdk-lib/src/test/kotlin/kin/sdk/internal/queue/FakeQueueScheduler.kt index 9f38d3b3..dcb7c720 100644 --- a/kin-sdk/kin-sdk-lib/src/test/kotlin/kin/sdk/internal/queue/FakeQueueScheduler.kt +++ b/kin-sdk/kin-sdk-lib/src/test/kotlin/kin/sdk/internal/queue/FakeQueueScheduler.kt @@ -7,7 +7,7 @@ import java.util.concurrent.TimeUnit open class FakeQueueScheduler : QueueScheduler { - var scheduler: ScheduledThreadPoolExecutor = ScheduledThreadPoolExecutor(1) + private var scheduler: ScheduledThreadPoolExecutor = ScheduledThreadPoolExecutor(1) private val futureTasks: HashMap?> = HashMap() val numOfTasks: Int get() = futureTasks.size @@ -25,19 +25,12 @@ open class FakeQueueScheduler : QueueScheduler { // killing all future futureTasks not including the current running task if there is one. scheduler.shutdown() scheduler = ScheduledThreadPoolExecutor(1) - println(futureTasks.size) futureTasks.clear() - println(futureTasks.size) - println() } override fun removePendingTask(runnable: Runnable?) { - println(futureTasks.size) val removedRunnable = futureTasks.remove(runnable) - println(removedRunnable) - println(futureTasks.size) removedRunnable?.cancel(false) - println() } override fun schedule(runnable: Runnable?) { diff --git a/kin-sdk/kin-sdk-lib/src/test/kotlin/kin/sdk/internal/queue/PaymentQueueManagerTest.kt b/kin-sdk/kin-sdk-lib/src/test/kotlin/kin/sdk/internal/queue/PaymentQueueManagerTest.kt index 270b9a14..18e0c574 100644 --- a/kin-sdk/kin-sdk-lib/src/test/kotlin/kin/sdk/internal/queue/PaymentQueueManagerTest.kt +++ b/kin-sdk/kin-sdk-lib/src/test/kotlin/kin/sdk/internal/queue/PaymentQueueManagerTest.kt @@ -22,7 +22,7 @@ class PaymentQueueManagerTest { } private var queueScheduler: FakeQueueScheduler = FakeQueueScheduler() - private var txTaskQueueManager: TransactionTaskQueueManager = mock() + private var txTaskQueueManager: TransactionTasksQueueManager = mock() private var pendingBalanceUpdater: PendingBalanceUpdater = mock() private var eventsManager: EventsManager = mock() @@ -40,8 +40,10 @@ class PaymentQueueManagerTest { private fun initPaymentQueueManager() { destinationAccount = KeyPair.random().accountId sourceAccount = KeyPair.random().accountId - paymentQueueManager = PaymentQueueManagerImpl(txTaskQueueManager, queueScheduler, pendingBalanceUpdater, eventsManager, - Constants.DELAY_BETWEEN_PAYMENTS_MILLIS, Constants.QUEUE_TIMEOUT_MILLIS, Constants.MAX_NUM_OF_PAYMENTS) + val configuration = PaymentQueueImpl.PaymentQueueConfiguration(Constants.DELAY_BETWEEN_PAYMENTS_MILLIS, + Constants.QUEUE_TIMEOUT_MILLIS, Constants.MAX_NUM_OF_PAYMENTS) + paymentQueueManager = PaymentQueueManagerImpl(txTaskQueueManager, queueScheduler, + pendingBalanceUpdater, eventsManager, configuration) } @Test diff --git a/kin-sdk/kin-sdk-lib/src/test/kotlin/kin/sdk/internal/queue/TasksQueueTest.kt b/kin-sdk/kin-sdk-lib/src/test/kotlin/kin/sdk/internal/queue/TasksQueueTest.kt new file mode 100644 index 00000000..9a404c82 --- /dev/null +++ b/kin-sdk/kin-sdk-lib/src/test/kotlin/kin/sdk/internal/queue/TasksQueueTest.kt @@ -0,0 +1,20 @@ +package kin.sdk.internal.queue + +class TasksQueueTest { + + +// @Test +// fun `enqueue, list size is increased by one`() { +// //given +// val tasksQueue = TasksQueueImpl() +// var value = 5 +// +// tasksQueue.execute { +// value = 10 +// } +// +// //then +// assertThat(value, equalTo(10)) +// } + +} \ No newline at end of file diff --git a/kin-sdk/kin-sdk-sample/src/main/java/sdk/sample/TransactionActivity.java b/kin-sdk/kin-sdk-sample/src/main/java/sdk/sample/TransactionActivity.java index b15cbbc4..e9ce3c2d 100644 --- a/kin-sdk/kin-sdk-sample/src/main/java/sdk/sample/TransactionActivity.java +++ b/kin-sdk/kin-sdk-sample/src/main/java/sdk/sample/TransactionActivity.java @@ -10,6 +10,11 @@ import android.util.Log; import android.view.View; import android.widget.EditText; + +import org.json.JSONException; + +import java.math.BigDecimal; + import kin.sdk.KinAccount; import kin.sdk.TransactionId; import kin.sdk.exception.AccountDeletedException; @@ -18,9 +23,6 @@ import kin.sdk.transactiondata.PaymentTransaction; import kin.utils.Request; import kin.utils.ResultCallback; -import org.json.JSONException; - -import java.math.BigDecimal; /** * Displays form to enter public address and amount and a button to send a transaction @@ -281,7 +283,8 @@ private class BuildTransactionCallback implements ResultCallback