Added a responseMap to the SettingsFragment to differentiate different requests made to the Node through graphenej's NetworkService. Added a method to obtain the encrypted private keys from the db through the SettingsFragmentViewModel, to create and sign the Upgrade to LTM transaction.

This commit is contained in:
Severiano Jaramillo 2019-02-22 17:01:32 -06:00
parent 99e378d123
commit b1a1abf231
6 changed files with 91 additions and 19 deletions

View file

@ -19,5 +19,8 @@ interface AuthorityDao {
fun getAll(): LiveData<List<Authority>> fun getAll(): LiveData<List<Authority>>
@Query("SELECT encrypted_wif FROM authorities WHERE user_id=:userId AND authority_type=:authorityType") @Query("SELECT encrypted_wif FROM authorities WHERE user_id=:userId AND authority_type=:authorityType")
fun getWIF(userId: String, authorityType: Int): Single<String> fun getWIFOld(userId: String, authorityType: Int): Single<String>
@Query("SELECT encrypted_wif FROM authorities WHERE user_id=:userId AND authority_type=:authorityType")
fun getWIF(userId: String, authorityType: Int): LiveData<String>
} }

View file

@ -181,7 +181,7 @@ class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHand
// Obtain the WifKey from the db, which is used in the Send Transfer procedure // Obtain the WifKey from the db, which is used in the Send Transfer procedure
mDisposables.add( mDisposables.add(
authorityRepository!!.getWIF(userId, AuthorityType.ACTIVE.ordinal) authorityRepository!!.getWIFOld(userId, AuthorityType.ACTIVE.ordinal)
.subscribeOn(Schedulers.computation()) .subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { encryptedWIF -> .subscribe { encryptedWIF ->

View file

@ -16,6 +16,7 @@ import com.afollestad.materialdialogs.customview.customView
import com.afollestad.materialdialogs.list.customListAdapter import com.afollestad.materialdialogs.list.customListAdapter
import com.afollestad.materialdialogs.list.listItemsSingleChoice import com.afollestad.materialdialogs.list.listItemsSingleChoice
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.google.common.primitives.UnsignedLong
import cy.agorise.bitsybitshareswallet.BuildConfig import cy.agorise.bitsybitshareswallet.BuildConfig
import cy.agorise.bitsybitshareswallet.R import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.adapters.FullNodesAdapter import cy.agorise.bitsybitshareswallet.adapters.FullNodesAdapter
@ -23,19 +24,23 @@ import cy.agorise.bitsybitshareswallet.repositories.AuthorityRepository
import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.Constants
import cy.agorise.bitsybitshareswallet.utils.CryptoUtils import cy.agorise.bitsybitshareswallet.utils.CryptoUtils
import cy.agorise.bitsybitshareswallet.viewmodels.SettingsFragmentViewModel import cy.agorise.bitsybitshareswallet.viewmodels.SettingsFragmentViewModel
import cy.agorise.graphenej.BrainKey import cy.agorise.graphenej.*
import cy.agorise.graphenej.UserAccount
import cy.agorise.graphenej.api.ConnectionStatusUpdate import cy.agorise.graphenej.api.ConnectionStatusUpdate
import cy.agorise.graphenej.api.calls.BroadcastTransaction
import cy.agorise.graphenej.api.calls.GetDynamicGlobalProperties import cy.agorise.graphenej.api.calls.GetDynamicGlobalProperties
import cy.agorise.graphenej.models.DynamicGlobalProperties import cy.agorise.graphenej.models.DynamicGlobalProperties
import cy.agorise.graphenej.models.JsonRpcResponse import cy.agorise.graphenej.models.JsonRpcResponse
import cy.agorise.graphenej.network.FullNode import cy.agorise.graphenej.network.FullNode
import cy.agorise.graphenej.operations.AccountUpgradeOperationBuilder
import io.reactivex.Observer import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.fragment_settings.* import kotlinx.android.synthetic.main.fragment_settings.*
import org.bitcoinj.core.DumpedPrivateKey
import org.bitcoinj.core.ECKey
import java.text.NumberFormat import java.text.NumberFormat
import javax.crypto.AEADBadTagException
class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatternEnteredListener { class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatternEnteredListener {
@ -45,18 +50,27 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
// Constants used to perform security locked requests // Constants used to perform security locked requests
private const val ACTION_CHANGE_SECURITY_LOCK = 1 private const val ACTION_CHANGE_SECURITY_LOCK = 1
private const val ACTION_SHOW_BRAINKEY = 2 private const val ACTION_SHOW_BRAINKEY = 2
// Constants used to organize NetworkService requests
private const val RESPONSE_GET_DYNAMIC_GLOBAL_PARAMETERS = 1
private const val RESPONSE_BROADCAST_TRANSACTION = 2
} }
private lateinit var mViewModel: SettingsFragmentViewModel private lateinit var mViewModel: SettingsFragmentViewModel
private var mUserAccount: UserAccount? = null private var mUserAccount: UserAccount? = null
private var privateKey: String? = null
// Dialog displaying the list of nodes and their latencies // Dialog displaying the list of nodes and their latencies
private var mNodesDialog: MaterialDialog? = null private var mNodesDialog: MaterialDialog? = null
/** Adapter that holds the FullNode list used in the Bitshares nodes modal */ /** Adapter that holds the FullNode list used in the Bitshares nodes modal */
private var nodesAdapter: FullNodesAdapter? = null private var nodesAdapter: FullNodesAdapter? = null
// Map used to keep track of request and response id pairs
private val responseMap = HashMap<Long, Int>()
private val mHandler = Handler() private val mHandler = Handler()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
@ -80,6 +94,18 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
androidx.lifecycle.Observer<cy.agorise.bitsybitshareswallet.database.entities.UserAccount>{ userAccount -> androidx.lifecycle.Observer<cy.agorise.bitsybitshareswallet.database.entities.UserAccount>{ userAccount ->
if (userAccount != null) { if (userAccount != null) {
mUserAccount = UserAccount(userAccount.id, userAccount.name) mUserAccount = UserAccount(userAccount.id, userAccount.name)
btnUpgradeToLTM.isEnabled = !userAccount.isLtm // Disable button if already LTM
}
})
mViewModel.getWIF(userId, AuthorityType.ACTIVE.ordinal).observe(this,
androidx.lifecycle.Observer<String> { encryptedWIF ->
context?.let {
try {
privateKey = CryptoUtils.decrypt(it, encryptedWIF)
} catch (e: AEADBadTagException) {
Log.e(TAG, "AEADBadTagException. Class: " + e.javaClass + ", Msg: " + e.message)
}
} }
}) })
@ -153,31 +179,50 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
} }
override fun handleJsonRpcResponse(response: JsonRpcResponse<*>) { override fun handleJsonRpcResponse(response: JsonRpcResponse<*>) {
if (response.result is DynamicGlobalProperties) { if (responseMap.containsKey(response.id)) {
val dynamicGlobalProperties = response.result as DynamicGlobalProperties val responseType = responseMap[response.id]
when (responseType) {
RESPONSE_GET_DYNAMIC_GLOBAL_PARAMETERS -> handleDynamicGlobalProperties(response.result)
RESPONSE_BROADCAST_TRANSACTION -> handleBroadcastTransaction(response)
}
responseMap.remove(response.id)
}
}
override fun handleConnectionStatusUpdate(connectionStatusUpdate: ConnectionStatusUpdate) { }
/** Handles the result of the [GetDynamicGlobalProperties] api call to obtain the current block number and update
* it in the Nodes Dialog */
private fun handleDynamicGlobalProperties(result: Any?) {
if (result is DynamicGlobalProperties) {
if (mNodesDialog != null && mNodesDialog?.isShowing == true) { if (mNodesDialog != null && mNodesDialog?.isShowing == true) {
val blockNumber = NumberFormat.getInstance().format(dynamicGlobalProperties.head_block_number) val blockNumber = NumberFormat.getInstance().format(result.head_block_number)
mNodesDialog?.message(text = getString(R.string.title__bitshares_nodes_dialog, blockNumber)) mNodesDialog?.message(text = getString(R.string.title__bitshares_nodes_dialog, blockNumber))
} }
} }
} }
override fun handleConnectionStatusUpdate(connectionStatusUpdate: ConnectionStatusUpdate) { } /** Handles the result of the [BroadcastTransaction] api call to find out if the Transaction was sent successfully
* or not and acts accordingly */
private fun handleBroadcastTransaction(message: JsonRpcResponse<*>) {
if (message.result == null && message.error == null) {
// context?.toast(getString(R.string.text__transaction_sent))
//
// // Return to the main screen
// findNavController().navigateUp()
} else if (message.error != null) {
// context?.toast(message.error.message, Toast.LENGTH_LONG)
}
}
/** /**
* Task used to obtain frequent updates on the global dynamic properties object * Task used to obtain frequent updates on the global dynamic properties object
*/ */
private val mRequestDynamicGlobalPropertiesTask = object : Runnable { private val mRequestDynamicGlobalPropertiesTask = object : Runnable {
override fun run() { override fun run() {
if (mNetworkService != null) { val id = mNetworkService?.sendMessage(GetDynamicGlobalProperties(), GetDynamicGlobalProperties.REQUIRED_API)
if (mNetworkService?.isConnected == true) { if (id != null) responseMap[id] = RESPONSE_GET_DYNAMIC_GLOBAL_PARAMETERS
mNetworkService?.sendMessage(GetDynamicGlobalProperties(), GetDynamicGlobalProperties.REQUIRED_API)
} else {
Log.d(TAG, "NetworkService exists but is not connected")
}
} else {
Log.d(TAG, "NetworkService reference is null")
}
mHandler.postDelayed(this, Constants.BLOCK_PERIOD) mHandler.postDelayed(this, Constants.BLOCK_PERIOD)
} }
} }
@ -384,7 +429,20 @@ class SettingsFragment : ConnectedFragment(), BaseSecurityLockDialog.OnPINPatter
message(text = content) message(text = content)
negativeButton(android.R.string.cancel) negativeButton(android.R.string.cancel)
positiveButton(android.R.string.ok) { positiveButton(android.R.string.ok) {
val operation = AccountUpgradeOperationBuilder()
.setIsUpgrade(true)
.setFee(AssetAmount(UnsignedLong.ZERO, Asset("1.3.0"))) // 0 BTS
.setAccountToUpgrade(mUserAccount).build()
val operations = ArrayList<BaseOperation>()
operations.add(operation)
val currentPrivateKey = ECKey.fromPrivate(
DumpedPrivateKey.fromBase58(null, privateKey).key.privKeyBytes)
val transaction = Transaction(currentPrivateKey, null, operations)
val id = mNetworkService?.sendMessage(BroadcastTransaction(transaction), BroadcastTransaction.REQUIRED_API)
if (id != null) responseMap[id] = RESPONSE_BROADCAST_TRANSACTION
} }
} }
} }

View file

@ -103,7 +103,7 @@ class TransfersLoader(private var mContext: Context?) {
if (userId != "") { if (userId != "") {
mCurrentAccount = UserAccount(userId) mCurrentAccount = UserAccount(userId)
mDisposables.add( mDisposables.add(
authorityRepository!!.getWIF(userId, AuthorityType.MEMO.ordinal) authorityRepository!!.getWIFOld(userId, AuthorityType.MEMO.ordinal)
.subscribeOn(Schedulers.computation()) .subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { encryptedWIF -> .subscribe { encryptedWIF ->

View file

@ -2,6 +2,7 @@ package cy.agorise.bitsybitshareswallet.repositories
import android.content.Context import android.content.Context
import android.os.AsyncTask import android.os.AsyncTask
import androidx.lifecycle.LiveData
import cy.agorise.bitsybitshareswallet.database.daos.AuthorityDao import cy.agorise.bitsybitshareswallet.database.daos.AuthorityDao
import cy.agorise.bitsybitshareswallet.database.BitsyDatabase import cy.agorise.bitsybitshareswallet.database.BitsyDatabase
import cy.agorise.bitsybitshareswallet.database.entities.Authority import cy.agorise.bitsybitshareswallet.database.entities.Authority
@ -24,7 +25,11 @@ class AuthorityRepository internal constructor(context: Context) {
return mAuthorityDao.get(userId) return mAuthorityDao.get(userId)
} }
fun getWIF(userId: String, authorityType: Int): Single<String> { fun getWIFOld(userId: String, authorityType: Int): Single<String> {
return mAuthorityDao.getWIFOld(userId, authorityType)
}
fun getWIF(userId: String, authorityType: Int): LiveData<String> {
return mAuthorityDao.getWIF(userId, authorityType) return mAuthorityDao.getWIF(userId, authorityType)
} }

View file

@ -4,12 +4,18 @@ import android.app.Application
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import cy.agorise.bitsybitshareswallet.database.entities.UserAccount import cy.agorise.bitsybitshareswallet.database.entities.UserAccount
import cy.agorise.bitsybitshareswallet.repositories.AuthorityRepository
import cy.agorise.bitsybitshareswallet.repositories.UserAccountRepository import cy.agorise.bitsybitshareswallet.repositories.UserAccountRepository
class SettingsFragmentViewModel(application: Application) : AndroidViewModel(application) { class SettingsFragmentViewModel(application: Application) : AndroidViewModel(application) {
private var mUserAccountRepository = UserAccountRepository(application) private var mUserAccountRepository = UserAccountRepository(application)
private var mAuthorityRepository = AuthorityRepository(application)
internal fun getUserAccount(id: String): LiveData<UserAccount> { internal fun getUserAccount(id: String): LiveData<UserAccount> {
return mUserAccountRepository.getUserAccount(id) return mUserAccountRepository.getUserAccount(id)
} }
internal fun getWIF(userId: String, authorityType: Int): LiveData<String> {
return mAuthorityRepository.getWIF(userId, authorityType)
}
} }