2019-07-10 11:12:32 -07:00
|
|
|
package cash.z.wallet.sdk.data
|
|
|
|
|
|
|
|
import android.content.Context
|
|
|
|
import androidx.room.Room
|
|
|
|
import androidx.room.RoomDatabase
|
|
|
|
import cash.z.wallet.sdk.db.PendingTransactionDao
|
|
|
|
import cash.z.wallet.sdk.db.PendingTransactionDb
|
2019-07-14 15:13:12 -07:00
|
|
|
import cash.z.wallet.sdk.entity.PendingTransaction
|
|
|
|
import cash.z.wallet.sdk.entity.Transaction
|
2019-09-26 09:58:37 -07:00
|
|
|
import cash.z.wallet.sdk.ext.ZcashSdk.EXPIRY_OFFSET
|
2019-07-10 11:12:32 -07:00
|
|
|
import cash.z.wallet.sdk.service.LightWalletService
|
|
|
|
import kotlinx.coroutines.Dispatchers.IO
|
|
|
|
import kotlinx.coroutines.withContext
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Facilitates persistent attempts to ensure a transaction occurs.
|
|
|
|
*/
|
|
|
|
// TODO: consider having the manager register the fail listeners rather than having that responsibility spread elsewhere (synchronizer and the broom)
|
|
|
|
class PersistentTransactionManager(private val db: PendingTransactionDb) : TransactionManager {
|
2019-07-14 15:13:12 -07:00
|
|
|
|
|
|
|
private val dao: PendingTransactionDao = db.pendingTransactionDao()
|
2019-07-10 11:12:32 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor that creates the database and then executes a callback on it.
|
|
|
|
*/
|
|
|
|
constructor(
|
|
|
|
appContext: Context,
|
2019-07-14 15:13:12 -07:00
|
|
|
dataDbName: String = "PendingTransactions.db"
|
2019-07-10 11:12:32 -07:00
|
|
|
) : this(
|
2019-07-14 15:13:12 -07:00
|
|
|
Room.databaseBuilder(
|
|
|
|
appContext,
|
|
|
|
PendingTransactionDb::class.java,
|
|
|
|
dataDbName
|
|
|
|
).setJournalMode(RoomDatabase.JournalMode.TRUNCATE).build()
|
|
|
|
)
|
2019-07-10 11:12:32 -07:00
|
|
|
|
|
|
|
override fun start() {
|
|
|
|
twig("TransactionManager starting")
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun stop() {
|
|
|
|
twig("TransactionManager stopping")
|
|
|
|
db.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
suspend fun initPlaceholder(
|
|
|
|
zatoshiValue: Long,
|
|
|
|
toAddress: String,
|
|
|
|
memo: String
|
2019-07-12 01:47:17 -07:00
|
|
|
): PendingTransaction? = withContext(IO) {
|
2019-07-10 11:12:32 -07:00
|
|
|
twig("constructing a placeholder transaction")
|
|
|
|
val tx = initTransaction(zatoshiValue, toAddress, memo)
|
|
|
|
twig("done constructing a placeholder transaction")
|
|
|
|
try {
|
|
|
|
twig("inserting tx into DB: $tx")
|
|
|
|
val insertId = dao.insert(tx)
|
|
|
|
twig("insert returned id of $insertId")
|
|
|
|
tx.copy(id = insertId)
|
|
|
|
} catch (t: Throwable) {
|
|
|
|
val message = "failed initialize a placeholder transaction due to : ${t.message} caused by: ${t.cause}"
|
|
|
|
twig(message)
|
|
|
|
null
|
|
|
|
} finally {
|
|
|
|
twig("done constructing a placeholder transaction")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override suspend fun manageCreation(
|
2019-07-14 15:13:12 -07:00
|
|
|
encoder: TransactionEncoder,
|
2019-07-10 11:12:32 -07:00
|
|
|
zatoshiValue: Long,
|
|
|
|
toAddress: String,
|
|
|
|
memo: String,
|
|
|
|
currentHeight: Int
|
2019-07-12 01:47:17 -07:00
|
|
|
): PendingTransaction = manageCreation(encoder, initTransaction(zatoshiValue, toAddress, memo), currentHeight)
|
2019-07-10 11:12:32 -07:00
|
|
|
|
|
|
|
|
|
|
|
suspend fun manageCreation(
|
2019-07-14 15:13:12 -07:00
|
|
|
encoder: TransactionEncoder,
|
2019-07-12 01:47:17 -07:00
|
|
|
transaction: PendingTransaction,
|
2019-07-10 11:12:32 -07:00
|
|
|
currentHeight: Int
|
2019-07-12 01:47:17 -07:00
|
|
|
): PendingTransaction = withContext(IO){
|
2019-07-10 11:12:32 -07:00
|
|
|
twig("managing the creation of a transaction")
|
|
|
|
var tx = transaction.copy(expiryHeight = if (currentHeight == -1) -1 else currentHeight + EXPIRY_OFFSET)
|
|
|
|
try {
|
|
|
|
twig("beginning to encode transaction with : $encoder")
|
2019-07-14 15:13:12 -07:00
|
|
|
val encodedTx = encoder.create(tx.value, tx.toAddress, tx.memo ?: "")
|
2019-07-10 11:12:32 -07:00
|
|
|
twig("successfully encoded transaction for ${tx.memo}!!")
|
2019-07-14 15:13:12 -07:00
|
|
|
tx = tx.copy(raw = encodedTx.raw, rawTransactionId = encodedTx.txId)
|
2019-07-10 11:12:32 -07:00
|
|
|
tx
|
|
|
|
} catch (t: Throwable) {
|
|
|
|
val message = "failed to encode transaction due to : ${t.message} caused by: ${t.cause}"
|
|
|
|
twig(message)
|
|
|
|
message
|
|
|
|
tx = tx.copy(errorMessage = message)
|
|
|
|
tx
|
|
|
|
} finally {
|
|
|
|
tx = tx.copy(encodeAttempts = Math.max(1, tx.encodeAttempts + 1))
|
|
|
|
twig("inserting tx into DB: $tx")
|
|
|
|
dao.insert(tx)
|
|
|
|
twig("successfully inserted TX into DB")
|
|
|
|
tx
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-14 15:13:12 -07:00
|
|
|
override suspend fun manageSubmission(service: LightWalletService, pendingTransaction: SignedTransaction) {
|
2019-07-12 01:47:17 -07:00
|
|
|
var tx = pendingTransaction as PendingTransaction
|
2019-07-10 11:12:32 -07:00
|
|
|
try {
|
2019-10-21 03:26:02 -07:00
|
|
|
twig("submitting transaction to lightwalletd - memo: ${tx.memo} amount: ${tx.value}")
|
2019-07-10 11:12:32 -07:00
|
|
|
val response = service.submitTransaction(pendingTransaction.raw!!)
|
2019-10-21 03:26:02 -07:00
|
|
|
val error = response.errorCode < 0
|
|
|
|
twig("${if (error) "FAILURE! " else "SUCCESS!"} submit transaction completed with response: ${response.errorCode}: ${response.errorMessage}")
|
|
|
|
tx = tx.copy(errorMessage = if (error) response.errorMessage else null, errorCode = response.errorCode)
|
2019-07-10 11:12:32 -07:00
|
|
|
} catch (t: Throwable) {
|
|
|
|
twig("error while managing submitting transaction: ${t.message} caused by: ${t.cause}")
|
|
|
|
} finally {
|
|
|
|
tx = tx.copy(submitAttempts = Math.max(1, tx.submitAttempts + 1))
|
|
|
|
dao.insert(tx)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-12 01:47:17 -07:00
|
|
|
override suspend fun getAll(): List<PendingTransaction> = withContext(IO) {
|
2019-07-10 11:12:32 -07:00
|
|
|
dao.getAll()
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun initTransaction(
|
|
|
|
value: Long,
|
|
|
|
toAddress: String,
|
|
|
|
memo: String,
|
|
|
|
currentHeight: Int = -1
|
2019-07-12 01:47:17 -07:00
|
|
|
): PendingTransaction {
|
|
|
|
return PendingTransaction(
|
2019-07-10 11:12:32 -07:00
|
|
|
value = value,
|
2019-07-14 15:13:12 -07:00
|
|
|
toAddress = toAddress,
|
2019-07-10 11:12:32 -07:00
|
|
|
memo = memo,
|
|
|
|
expiryHeight = if (currentHeight == -1) -1 else currentHeight + EXPIRY_OFFSET
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-07-14 15:13:12 -07:00
|
|
|
suspend fun manageMined(pendingTx: PendingTransaction, matchingMinedTx: Transaction) = withContext(IO) {
|
2019-07-10 11:12:32 -07:00
|
|
|
twig("a pending transaction has been mined!")
|
2019-10-21 03:26:02 -07:00
|
|
|
val tx = pendingTx.copy(minedHeight = matchingMinedTx.minedHeight!!)
|
2019-07-10 11:12:32 -07:00
|
|
|
dao.insert(tx)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove a transaction and pretend it never existed.
|
|
|
|
*/
|
2019-07-12 01:47:17 -07:00
|
|
|
suspend fun abortTransaction(existingTransaction: PendingTransaction) = withContext(IO) {
|
2019-07-10 11:12:32 -07:00
|
|
|
dao.delete(existingTransaction)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|