From 91275f3cc0f5760d38f00bcf90d392f83e126af7 Mon Sep 17 00:00:00 2001 From: "Nelson R. Perez" Date: Fri, 2 Aug 2019 17:31:48 -0500 Subject: [PATCH] Modified the sample app to make use of the HTLC redeem operation - A very simple HTLC creation & redemption mechanism has been put in place for testing/demonstration purposes. - The HtlcActivity now can both create and redeem an HTLC --- sample/src/main/AndroidManifest.xml | 3 +- .../cy/agorise/labs/sample/CallsActivity.java | 11 +- .../cy/agorise/labs/sample/HtlcActivity.java | 141 ++++++++++++++++-- .../sample/fragments/CreateHtlcFragment.java | 15 +- .../sample/fragments/RedeemHtlcFragment.java | 75 ++++++++++ .../main/res/drawable/ic_switch_hltc_mode.xml | 5 + sample/src/main/res/layout/activity_htlc.xml | 18 ++- .../main/res/layout/fragment_create_htlc.xml | 3 +- .../main/res/layout/fragment_redeem_htlc.xml | 42 ++++++ sample/src/main/res/menu/htlc_menu.xml | 8 + sample/src/main/res/values/colors.xml | 2 + sample/src/main/res/values/strings.xml | 4 + 12 files changed, 293 insertions(+), 34 deletions(-) create mode 100644 sample/src/main/java/cy/agorise/labs/sample/fragments/RedeemHtlcFragment.java create mode 100644 sample/src/main/res/drawable/ic_switch_hltc_mode.xml create mode 100644 sample/src/main/res/layout/fragment_redeem_htlc.xml create mode 100644 sample/src/main/res/menu/htlc_menu.xml diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 1545559..e4528e8 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -14,7 +14,8 @@ android:supportsRtl="true" android:theme="@style/AppTheme" tools:ignore="GoogleAppIndexingWarning"> - + mResponseMap = new HashMap<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_htlc); + + mHtlcMode = getIntent().getStringExtra(Constants.KEY_SELECTED_CALL); + + FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); + if(mHtlcMode.equals(CallsActivity.CREATE_HTLC)){ + mCreateHtlcFragment = new CreateHtlcFragment(); + mActiveBottomFragment = mCreateHtlcFragment; + }else{ + mRedeemHtlcFragment = new RedeemHtlcFragment(); + mActiveBottomFragment = mRedeemHtlcFragment; + } + fragmentTransaction.add(R.id.fragment_root, mActiveBottomFragment, "active-fragment").commit(); + + Toolbar toolbar = findViewById(R.id.toolbar); + if(toolbar != null && mHtlcMode != null){ + toolbar.setTitle(mHtlcMode.replace("_", " ").toUpperCase()); + setSupportActionBar(toolbar); + } + + // While for the real world is best to use a random pre-image, for testing purposes it is more + // convenient to make use of a fixed one. +// SecureRandom secureRandom = new SecureRandom(); +// secureRandom.nextBytes(mPreimage); + mPreimage = Util.hexToBytes("efb79df9b0fd0d27b405e4decf9a2534efc1531f9e133915981fe27cd031ba32"); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.htlc_menu, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + this.switchHtlcMode(); + return true; + } + + @Override + public void onAttachFragment(Fragment fragment) { + super.onAttachFragment(fragment); + if(fragment instanceof CreateHtlcFragment){ + mCreateHtlcFragment = (CreateHtlcFragment) fragment; + } + } + + private void switchHtlcMode(){ + if(mHtlcMode.equals(CallsActivity.CREATE_HTLC)){ + mHtlcMode = CallsActivity.REDEEM_HTLC; + if(mRedeemHtlcFragment == null) + mRedeemHtlcFragment = new RedeemHtlcFragment(); + mActiveBottomFragment = mRedeemHtlcFragment; + }else{ + mHtlcMode = CallsActivity.CREATE_HTLC; + if(mCreateHtlcFragment == null) + mCreateHtlcFragment = new CreateHtlcFragment(); + mActiveBottomFragment = mCreateHtlcFragment; + } + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.fragment_root, mActiveBottomFragment, "active-fragment") + .commit(); + + Toolbar toolbar = findViewById(R.id.toolbar); + if(toolbar != null) + toolbar.setTitle(mHtlcMode.replace("_", " ").toUpperCase()); } @Override @@ -78,42 +163,55 @@ public class HtlcActivity extends ConnectedActivity implements CreateHtlcFragmen @Override public void onHtlcProposal(String from, String to, Double amount, Long timelock) { - Log.d(TAG,"onHtlcProposal"); UserAccount sourceAccount = new UserAccount(from); UserAccount destinationAccount = new UserAccount(to); AssetAmount fee = new AssetAmount(UnsignedLong.valueOf("86726"), new Asset("1.3.0")); AssetAmount operationAmount = new AssetAmount(UnsignedLong.valueOf(Double.valueOf(amount * 100000).longValue()), new Asset("1.3.0")); - SecureRandom secureRandom = new SecureRandom(); - byte[] preimage = new byte[PREIMAGE_LENGTH]; - secureRandom.nextBytes(preimage); try { - byte[] hash = Util.htlcHash(preimage, HtlcHashType.RIPEMD160); + byte[] hash = Util.htlcHash(mPreimage, HtlcHashType.RIPEMD160); HtlcHash htlcHash = new HtlcHash(HtlcHashType.RIPEMD160, hash); // Creating a HTLC operation, used later. createHtlcOperation = new CreateHtlcOperation(fee, sourceAccount, destinationAccount, operationAmount, htlcHash, PREIMAGE_LENGTH, timelock.intValue()); // Requesting dynamic network parameters long id = mNetworkService.sendMessage(new GetDynamicGlobalProperties(), GetDynamicGlobalProperties.REQUIRED_API); + mResponseMap.put(id, CallsActivity.CREATE_HTLC); Log.d(TAG,"sendMessage returned: " + id); } catch (NoSuchAlgorithmException e) { Log.e(TAG,"NoSuchAlgorithmException while trying to create HTLC operation. Msg: " + e.getMessage()); } } + @Override + public void onRedeemProposal(String userId, String htlcId) { + AssetAmount fee = new AssetAmount(UnsignedLong.valueOf("255128"), new Asset("1.3.0")); + UserAccount redeemer = new UserAccount(userId); + Htlc htlc = new Htlc(htlcId); + redeemHtlcOperation = new RedeemHtlcOperation(fee, redeemer, htlc, mPreimage); + long id = mNetworkService.sendMessage(new GetDynamicGlobalProperties(), GetDynamicGlobalProperties.REQUIRED_API); + mResponseMap.put(id, CallsActivity.REDEEM_HTLC); + Log.d(TAG,"sendMessage returned: " + id); + } + private void handleJsonRpcResponse(JsonRpcResponse jsonRpcResponse){ Log.d(TAG,"handleJsonRpcResponse"); if(jsonRpcResponse.error == null && jsonRpcResponse.result instanceof DynamicGlobalProperties){ DynamicGlobalProperties dynamicGlobalProperties = (DynamicGlobalProperties) jsonRpcResponse.result; - Transaction tx = buildHltcTransaction(dynamicGlobalProperties); + Transaction tx = buildHltcTransaction(dynamicGlobalProperties, jsonRpcResponse.id); long id = mNetworkService.sendMessage(new BroadcastTransaction(tx), BroadcastTransaction.REQUIRED_API); Log.d(TAG,"sendMessage returned: " + id); } } - private Transaction buildHltcTransaction(DynamicGlobalProperties dynamicProperties){ - // Deriving private key - BrainKey brainKey = new BrainKey(">> Enter your own test Brainkey here <<", 0); - ECKey privKey = brainKey.getPrivateKey(); - Address address = new Address(ECKey.fromPublicOnly(privKey.getPubKey())); + /** + * Private method used to build a transaction containing a specific HTLC operation. + * + * @param dynamicProperties The current dynamic properties. + * @param responseId The response id, used to decide whether to build a CREATE_HTLC or REDEEM_HTLC operation. + * @return A transaction that contains an HTLC operation. + */ + private Transaction buildHltcTransaction(DynamicGlobalProperties dynamicProperties, long responseId){ + // Private key, to be obtained differently below depending on which operation we'll be performing. + ECKey privKey = null; // Use the valid BlockData just obtained from the blockchain long expirationTime = (dynamicProperties.time.getTime() / 1000) + Transaction.DEFAULT_EXPIRATION_TIME; @@ -123,7 +221,20 @@ public class HtlcActivity extends ConnectedActivity implements CreateHtlcFragmen // Using the HTLC create operaton just obtained before ArrayList operations = new ArrayList<>(); - operations.add(this.createHtlcOperation); + + if(mResponseMap.get(responseId).equals(CallsActivity.CREATE_HTLC)){ + // Deriving private key + BrainKey brainKey = new BrainKey(">> Place brainkey of HTLC creator here <<", 0); + privKey = brainKey.getPrivateKey(); + + operations.add(this.createHtlcOperation); + }else if(mResponseMap.get(responseId).equals(CallsActivity.REDEEM_HTLC)){ + // Deriving private key + BrainKey brainKey = new BrainKey(">> Place brainkey of redeemer here <<", 0); + privKey = brainKey.getPrivateKey(); + + operations.add(this.redeemHtlcOperation); + } // Return a newly built transaction return new Transaction(privKey, blockData, operations); diff --git a/sample/src/main/java/cy/agorise/labs/sample/fragments/CreateHtlcFragment.java b/sample/src/main/java/cy/agorise/labs/sample/fragments/CreateHtlcFragment.java index f332365..b78573e 100644 --- a/sample/src/main/java/cy/agorise/labs/sample/fragments/CreateHtlcFragment.java +++ b/sample/src/main/java/cy/agorise/labs/sample/fragments/CreateHtlcFragment.java @@ -5,7 +5,6 @@ import android.content.Context; import android.os.Bundle; import android.support.design.widget.TextInputEditText; import android.support.v4.app.Fragment; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -34,8 +33,8 @@ public class CreateHtlcFragment extends Fragment { @BindView(R.id.timelock) TextInputEditText timelockField; - // Parent activity, which must implement the HtlcListener interface. - private HtlcListener mListener; + // Parent activity, which must implement the CreateHtlcListener interface. + private CreateHtlcListener mListener; public CreateHtlcFragment() { // Required empty public constructor @@ -52,7 +51,6 @@ public class CreateHtlcFragment extends Fragment { @OnClick(R.id.button_create) public void onSendClicked(View v){ - Log.d(TAG,"onSendClicked"); String from = fromField.getText().toString(); String to = toField.getText().toString(); Double amount = null; @@ -67,7 +65,6 @@ public class CreateHtlcFragment extends Fragment { }catch(NumberFormatException e){ timelockField.setError("Invalid value"); } - Log.d(TAG,"amount: " + amount + ", timelock: " + timeLock); if(amount != null && timeLock != null){ Toast.makeText(getContext(), "Should be sending message up", Toast.LENGTH_SHORT).show(); mListener.onHtlcProposal(from, to, amount, timeLock); @@ -77,17 +74,17 @@ public class CreateHtlcFragment extends Fragment { @Override public void onAttach(Context context) { super.onAttach(context); - if(context instanceof HtlcListener){ - mListener = (HtlcListener) context; + if(context instanceof CreateHtlcListener){ + mListener = (CreateHtlcListener) context; }else{ - throw new ClassCastException(context.toString() + " must implement the HtlcListener interface!"); + throw new ClassCastException(context.toString() + " must implement the CreateHtlcListener interface!"); } } /** * Interface to be implemented by the parent activity. */ - public interface HtlcListener { + public interface CreateHtlcListener { /** * Method used to notify the parent activity of the request to create an HTLC with the following parameters. * diff --git a/sample/src/main/java/cy/agorise/labs/sample/fragments/RedeemHtlcFragment.java b/sample/src/main/java/cy/agorise/labs/sample/fragments/RedeemHtlcFragment.java new file mode 100644 index 0000000..621da5e --- /dev/null +++ b/sample/src/main/java/cy/agorise/labs/sample/fragments/RedeemHtlcFragment.java @@ -0,0 +1,75 @@ +package cy.agorise.labs.sample.fragments; + + +import android.content.Context; +import android.os.Bundle; +import android.support.design.widget.TextInputEditText; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import cy.agorise.labs.sample.R; + +/** + * A simple {@link Fragment} subclass. + */ +public class RedeemHtlcFragment extends Fragment { + + @BindView(R.id.redeemer) + TextInputEditText mRedeemer; + + @BindView(R.id.htlc_id) + TextInputEditText mHtlcId; + + // Parent activity, which must implement the RedeemHtlcListener interface. + private RedeemHtlcListener mListener; + + + public RedeemHtlcFragment() { + // Required empty public constructor + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_redeem_htlc, container, false); + ButterKnife.bind(this, view); + return view; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if(context instanceof RedeemHtlcListener){ + mListener = (RedeemHtlcListener) context; + }else{ + throw new ClassCastException(context.toString() + " must implement the RedeemHtlcListener interface!"); + } + } + + @OnClick(R.id.button_create) + public void onSendClicked(View v){ + String redeemerId = mRedeemer.getText().toString(); + String htlcId = mHtlcId.getText().toString(); + Toast.makeText(getContext(), "Should be sending message up", Toast.LENGTH_SHORT).show(); + mListener.onRedeemProposal(redeemerId, htlcId); + } + + /** + * Interface to be implemented by the parent activity. + */ + public interface RedeemHtlcListener { + /** + * Method used to notify the parent activity about the creation of an HTLC redeem operation. + * + * @param userId The id of the user that wishes to redeem an HTLC. + * @param htlcId The HTLC id. + */ + void onRedeemProposal(String userId, String htlcId); + } +} diff --git a/sample/src/main/res/drawable/ic_switch_hltc_mode.xml b/sample/src/main/res/drawable/ic_switch_hltc_mode.xml new file mode 100644 index 0000000..9908c1e --- /dev/null +++ b/sample/src/main/res/drawable/ic_switch_hltc_mode.xml @@ -0,0 +1,5 @@ + + + diff --git a/sample/src/main/res/layout/activity_htlc.xml b/sample/src/main/res/layout/activity_htlc.xml index 9269ed0..34e4bdd 100644 --- a/sample/src/main/res/layout/activity_htlc.xml +++ b/sample/src/main/res/layout/activity_htlc.xml @@ -3,16 +3,26 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" tools:context=".HtlcActivity"> + - + android:layout_weight="0.6"> + \ No newline at end of file diff --git a/sample/src/main/res/layout/fragment_create_htlc.xml b/sample/src/main/res/layout/fragment_create_htlc.xml index 493dab7..266ddc6 100644 --- a/sample/src/main/res/layout/fragment_create_htlc.xml +++ b/sample/src/main/res/layout/fragment_create_htlc.xml @@ -5,6 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="16dp" + android:clickable="false" tools:context=".fragments.CreateHtlcFragment"> + android:text="1.2.143563" /> + + + + + + + +