Page MenuHomePhabricator

[Cashtab][Alias] Track pending aliases
AbandonedPublic

Authored by emack on Aug 7 2023, 15:31.

Details

Reviewers
bytesofman
Fabien
Group Reviewers
Restricted Project
Summary

T3216

Depends on D14334

Upon successful broadcast of an alias registration tx, the new alias will be added to localForage and tracked via the Pending Aliases dropdown.

If the user's cashtab was not opened at the time of a new block, visiting the Alias.js page will trigger validation to remove any recently registered aliases in the pending aliases list.

An interval task is also added to validate the registration status of pending aliases every 30 seconds.

Test Plan
  • Register a new alias and observe it is displayed in the Pending Aliases dropdown
  • Register another alias and observe it is added to the previous pending alias
  • Wait for a new block to be found, verify the alias has now moved from the Pending Aliases to the Registered Aliases dropdown
  • Register another alias, close Cashtab. When the next block has been found via explorer, open Cashtab and ensure this pending alias has moved into the Registered Aliases dropdown.

Diff Detail

Repository
rABC Bitcoin ABC
Branch
aliasFormatting
Lint
Lint Passed
Unit
No Test Coverage
Build Status
Buildable 24725
Build 49042: Build Diffcashtab-tests
Build 49041: arc lint + arc unit

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes
emack requested review of this revision.Aug 7 2023, 15:31
emack planned changes to this revision.Aug 7 2023, 15:32

Further integration testing planned before opening for review

Improved unit tests, ready for review.

Fabien requested changes to this revision.Aug 8 2023, 20:51
Fabien added a subscriber: Fabien.
Fabien added inline comments.
cashtab/src/hooks/useWallet.js
857 ↗(On Diff #41745)

Why would pending alias be updated upon new blocks ? There is a high chance the server didn't complete the registration yet because of the avalanche finalization delay, so this will no work as you expect

This revision now requires changes to proceed.Aug 8 2023, 20:51
cashtab/src/hooks/useWallet.js
857 ↗(On Diff #41745)

@bytesofman - would it be more appropriate for alias-server to be making the isfinaltransaction RPC call to confirm avalanche finalization? In which case is it better for alias-server to provide the pending endpoint?

cashtab/src/hooks/useWallet.js
857 ↗(On Diff #41745)

That's already the case, but that doesn't change the point.
IMO the easiest and most efficient way is to check for alias confirmation upon startup + periodically

This revision now requires changes to proceed.Aug 9 2023, 08:50
cashtab/src/hooks/useWallet.js
857 ↗(On Diff #41745)

the easiest and most efficient way is to check for alias confirmation upon startup + periodically

True.

We could set up a websocket on alias-server that pushes updates to Cashtab (or any subscribing app) when aliases are confirmed. However, even with this, websockets are not 100% reliable so there needs to be some kind of setInterval script that is manually checking.

Since we don't have the websocket on alias-server yet, and we need a setInterval script either way -- best to start with that

I'll add an endpoint to alias-server that checks status by txid.

a txid can either be pending (possibly valid), valid per spec, or invalid (another alias registered in earlier blockheight or same blockheight but alphabetically first txid)

cashtab/src/hooks/useWallet.js
857 ↗(On Diff #41745)

You already have an endpoint to check for alias validity, can't you just use that ? and periodically check 1. the alias is registered and 2. the address is the expected one (so it's not registered by somebody else)

cashtab/src/hooks/useWallet.js
857 ↗(On Diff #41745)

Yes, it's not necessary. However seems like the kind of thing other app devs could use + relatively simple to implement in the server. It makes Cashtab's implementation check only one step.

cashtab/src/hooks/useWallet.js
857 ↗(On Diff #41745)

hm actually nm it's a dumb idea, because we don't want alias-server to keep track of txids that tried to register but failed. so, the app dev doesn't really get any special info from querying a txid from alias-server.

adding failed txid statuses to alias-server would be interesting but imo overkill and many potentially bad second order effects.

You already have an endpoint to check for alias validity, can't you just use that ? and periodically check 1. the alias is registered and 2. the address is the expected one (so it's not registered by somebody else)

yup -- this is the best way

Alias.js:

  • Retained the existing check for alias confirmation upon startup and expanded it to include use of pendingAliasCheck which compares the pending aliases list to the latest registered alias list from API
  • Added periodic 30 second calls to pendingAliasCheck to refresh pending list

aliasUtils.js

  • Added filterRegisteredAliases helper function which filters pending aliases that exists in a registered alias array

useWallet.js

  • Removed the BlockConnected websocket logic
  • There is now updatePendingAlias for singular pendingAlias updates at the point of registration broadcast, and updatePendingAliasArray for full replacement of the pending list as part of the periodic pending checks
Fabien requested changes to this revision.Aug 10 2023, 07:38

This doesn't work. If a pending alias was registered by another user it will remain pending forever.

cashtab/src/components/Alias/Alias.js
178 ↗(On Diff #41762)

Make it a constant and you can remove the comment

178 ↗(On Diff #41762)

That will create a new periodic task everytime the useEffect hook is called ?

This revision now requires changes to proceed.Aug 10 2023, 07:38
emack marked 2 inline comments as done.

If a pending alias was registered by another user it will remain pending forever.

Updated the pending alias validation in Alias.js' pendingAliasCheck function so that it simply calls the /alias endpoint to validate the registration status of each pending alias before setting the validated pending list to the UI dropdown and localForage.

Once registered, a pending alias will no longer be displayed in the pending dropdown list regardless of whether it was confirmed for the current user or someone else.

This made filterRegisteredAliases redundant so removed it and its corresponding unit tests.

cashtab/src/components/Alias/Alias.js
178 ↗(On Diff #41762)

Added to aliasSettings

178 ↗(On Diff #41762)

Good point, made two key changes regarding this.

  1. Updated this block to set the interval id returned from the setInterval call into localForage , so that the next time useEffect() runs, it checks this interval id in localForage and skips the interval creation block if the interval ID already exists, so there will only ever be one interval task running at any time.
  1. The change in 1. created an issue whereby the interval Id will still exist when reopening the browser thus not starting the interval task, hence the loadCashtabSettings function in useWallet will reset it upon the initial loading of the app.

Internal state variables were not used to track this intervalId as this ID must not be reset upon reloading/navigating back to the Alias page.

I thought about moving the whole interval block into useWallet which runs upon app startup, but useWallet does not have access to Alias.js' functions (pendingAliasCheck) and state variables to update the pending dropdown list.

Once registered, a pending alias will no longer be displayed in the pending dropdown list regardless of whether it was confirmed for the current user or someone else.

What do you think of having a tristate dropdown:

  • yellow: pending or unable to retrieve the status
  • green: registered successfully => add it to contact list automatically
  • red: registered by someone else

Then put a delete button (X) next to the red and green ones so the user can remove them from the list/forage manually so we're sure he knows about the status.

Need some more thought on how this lifecycle is managed and what needs to be in local storage vs state, see comments

cashtab/src/components/Alias/Alias.js
109

I guess this could work but I don't think anywhere else in cashtab do we initialize a state variable as undefined

initialize it is as false and then, instead of setting it to true, every time you trigger it, set it to !pendingAliasRefreshTrigger

This way we can be confident it is changing every time and the useEffect loop will be entered

187

function names should be active, the function is doing something. pendingAliasCheck is a kind of helpful noun but it is still a noun. Function should be named after what it does.

I need to read the function to figure out what pendingAliasCheck is

Please add some comments to explain the function. It's pretty confusing since a dev could reasonable expect we are checking the server for pending aliases, vs local forage.

324

we're calling this twice now -- it's also being called by setPendingAliasRefreshTrigger below, bumping to the use effect, which calls it

Is this intentional?

cashtab/src/hooks/useWallet.js
1077

is this a cashtab setting? if this is just shimmed in here to conveniently do something "on app startup" -- should just do it in the useEffect block below

if it needs to be in this function for some reason, should be commented

1422

This is confusing since one could reasonably expect you would get this info from the server

getPendingAliasesFromLocalForage is a mouthful but imo its important to avoid this ambiguity

1437
1462
1471

I don't see clearInterval called anywhere in this diff -- why are we saving this intervalId? We're adding a whole new key to localforage and it seems we could just avoid it.

We want Cashtab to know if there are pending aliases or not if the user closes the app and then comes back to it. We don't need to know the specific intervalId, it's not like that will have been running in the background if the user closes the app and comes back.

So, we should be adding pendingAliases to cashtabCache. Then -- on app startup -- if there are pending aliases, create an intervalId to watch them. In most cases, the app will startup, realize these have confirmed, and clear them.

We should also be calling clearInterval on the intervalId after the lifecycle is complete.

The interval id should be stored in state in Alias.js, not in local storage.

This revision now requires changes to proceed.Aug 10 2023, 18:13
cashtab/src/components/Alias/Alias.js
109

Update: the right way to do this is to add pendingAliases to cashtabCache, which is also available as a state variable from wallet context.

This way there is no need for this manual flag to "change something" to get into the useEffect. We want to get into useEffect when the pendingAliases array changes -- so, make that one of the dependencies (i.e. cashtabCache.pendingAliases).

cashtabCache is already setup to be available in both localForage and walletContext for this reason.

  • red: registered by someone else

I don't think we should dedicate a portion of the UI to an edge case, particularly given the limited real estate and amount of scrolling the user will need to do for 3 dropdowns. It'd be even worse on a mobile device. 2 dropdowns is probably the limit to a semi-clean mobile UI imo.

I'm leaning towards the notification approach where upon the periodic pending alias check, if the pending alias was registered but the address does not match the wallet, then do a notification indicating this alias was unsuccessful. If the user had cashtab closed at the time of this unsuccessful registration, this notification will still pop up the next time they open cashtab because of the same periodic pending checks. For UX consistency, this notification should also be run for successful alias registrations too.

I don't think we should dedicate a portion of the UI to an edge case, particularly given the limited real estate and amount of scrolling the user will need to do for 3 dropdowns. It'd be even worse on a mobile device. 2 dropdowns is probably the limit to a semi-clean mobile UI imo.

I was thinking having a single dropdown with all the aliases, not 1 per state.

Anyway if you feel like this is not the best UX then go with notifications. We can always adjust the UI later if needed depending on the feedback of the users.

Fabien requested changes to this revision.Aug 11 2023, 06:36
Fabien added inline comments.
cashtab/src/components/Alias/Alias.js
109

Is that true ? Like if I have 10 pending aliases that get validated during the same refresh cycle, I don't want to trigger a state change 10 times.

152

Can you explain why this is no longer needed ? What did it do ?

199

This confused me: the name is still validatedPendingList but the content is actually the opposite of what the name implies

cashtab/src/hooks/useWallet.js
1471

if there are pending aliases, create an intervalId to watch them

Or keep it simple and just do it unconditionally. If there is no pending alias it will return immediately so it's harmless and it makes the logic much simpler as you don't have to clear/recreate the interval.

emack marked 12 inline comments as done.

Responding to feedback

cashtab/src/components/Alias/Alias.js
109

initialize it is as false and then, instead of setting it to true, every time you trigger it, set it to !pendingAliasRefreshTrigger

Updated

109

don't want to trigger a state change 10 times

That was the reason I used a dedicated refresh trigger to avoid unintended state refreshes.

152

this was duplicated as there was already a passLoadingStatus(false); at the bottom of this function. This call removes the grey layer that locks the screen whilst the logic is executed. Since there's a pending alias check below it makes sense for it to be lifted once the pending check is completed.

187

Added comments above function

199

the validated part refers to the fact the pending alias has been validated as still pending. I've updated the comment above the array declaration to be clearer.

324

No this is unintentional, have removed this redundant call.

cashtab/src/hooks/useWallet.js
1077

yea this is shimmed in here to trigger on app startup. I originally had it in useWallet's useEffect block but at that point in the lifecycle localForage has not been initialized yet hence won't be able to reset this interval id.

1422

Updated function name and references in useWallet.js and Alias.js

1437

Updated function name and references in useWallet.js and Alias.js

1462

Updated function name and references in useWallet.js and Alias.js

1471

don't see clearInterval called anywhere

clearInterval isn't called because the interval task is meant to be permanently running every 30 seconds whilst cashtab is open. Clearing it stops the periodic checks.

why are we saving this intervalId?

Yea the ID itself is not relevant but I needed a way to check if there is already an interval running to avoid creating duplicated intervals each time you navigate away and back to the Alias.js page

on app startup -- if there are pending aliases, create an intervalId to watch them

on app startup, even if there are no pending aliases we still want to create the interval so it can keep polling for changes to the pending aliases every 30 seconds.

should also be calling clearInterval on the intervalId after the lifecycle is complete

As above, when the component lifecycle is complete we still want the interval to keep ticking along every 30 seconds

The interval id should be stored in state in Alias.js, not in local storage.

The issue with storing it in state in Alias.js is that it gets reset every time you re-render the Alias page.

1471

Or keep it simple and just do it unconditionally.

yea that's the current approach. On app startup it clears the intervalid so that it will create it unconditionally, unless the pending list is empty in which case it returns immediately. Otherwise it ticks along in the background.

cashtab/src/hooks/useWallet.js
1471

Not sure we're on the same page: by unconditionally I mean unconditionally, i.e. even if there is no pending alias you always run the periodic task. The consequence is just that this task will do nothing every 30s, which is not a problem.

cashtab/src/hooks/useWallet.js
1471

Not sure we're on the same page: by unconditionally I mean unconditionally, i.e. even if there is no pending alias you always run the periodic task. The consequence is just that this task will do nothing every 30s, which is not a problem.

Sorry I got confused, it is starting the interval unconditionally, unless an interval already exists, which is when the user navigates away and back to the Alias page. This is regardless of whether there are pending aliases.

image.png (255×720 px, 45 KB)

Note: the logic to trigger notification on successful and unsuccessful registrations will be done in a separate diff to help with ease of reviewing this diff.

I think this diff needs to be rebuilt with a simpler approach.

  1. If we are looking to check something every 30s no matter what, we do not need to create and save interval ids. We only need to create and save interval id if we need to use it in the future, for example to clear an interval. Otherwise, we can just always run this interval every 30s on app startup, similar to how we check the utxo set when the websocket is down or update the XEC price.
  1. Caching pending aliases in the available cashtabCache wallet context / local storage object is the easiest way to keep persistent state. This context object is available to the Alias.js screen. We don't need to use a useEffect loop at all to check on pending aliases -- if we have useWallet updating the pendingAlias array every 30s, and this is passed to Alias.js as context -- then Alias.js can just render this context variable. It will update as it is updated in the background, there is no need for a manual trigger.
cashtab/src/hooks/useWallet.js
1076–1079 ↗(On Diff #41774)

still not a good reason to do this in the loadCashtabSettings function ... sounds like it happens to work here (but not other places) and we don't know exactly why, which we would also like to avoid.

You also still have this interval at its own key, there should not be any issue with calling this at any point on app startup. What behavior are you observing if you call it on, say, line 1416?

1410–1416 ↗(On Diff #41774)

If we want something to happen on app startup, it should be happening here

This revision now requires changes to proceed.Aug 11 2023, 20:01

I think this diff needs to be rebuilt with a simpler approach.