Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add transaction status indicator to SentTransaction #99

Merged
merged 7 commits into from
Apr 16, 2019

Conversation

pkova
Copy link
Contributor

@pkova pkova commented Apr 8, 2019

Fixes #93

@@ -155,6 +155,10 @@ const sendSignedTransaction = (web3, stx) => {
.on('receipt', txn => {
resolve(Just(Ok(txn.transactionHash)))
})
.on('confirmation', (confirmationNum, txn) => {
confirmationCb(txn.transactionHash, confirmationNum + 1)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels weird to have a callback like this inside a promise, but I think this allows us to implement the feature with minimal code changes to sendSignedTransaction.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah web3's PromiEvents have pretty neat affordances.

This callback currently doesn't seem entirely accurate. The display in Bridge rapidly counts up to 25 (approximately one per second, definitely faster than the block time), then stalls there. In the time it takes it to count up to that, Etherscan has only seen 2 confirmations.

Turns out this is a bug that has been fixed in newer versions of web3. We'll have to try upgrading to latest sometime soon. I'll make an issue for this for the record.

@alexmatz alexmatz requested a review from Fang- April 8, 2019 22:12
txnHashCursor={ txnHashCursor } />
txnHashCursor={ txnHashCursor }
setTxnConfirmations={ this.setTxnConfirmations }
txnConfirmations={ txnConfirmations } />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@c-johnson I continue to feel highly uncomfortable with the dumping of state into Bridge.js for the sole purpose of passing it into new/different screens. In a lot of these cases, especially ones like these where only a single, temporary screen needs the data, it seems much more desirable to pass it in via the pushRoute data argument.

How would you feel about standardizing on a pattern for that? Would it be possible to do that in such a way that data is always explicitly passed forward, instead of stored in pseudo-global state? I'm trying to steer towards a slightly more functional style here. @jtobin may also have thoughts.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general I think that's a great idea. I would just try to ensure routeData maintains some sort of sane/predictable structure -- I think using a dynamically-typed blob consisting of whatever the downstream component needs doesn't necessarily scale well, for example.

Copy link
Contributor

@c-johnson c-johnson Apr 9, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple things I can think of;

  • We could bundle the routeData with the actual routeCrumb entry, so it's at least coupled to its view and not a free-floating variable:
    in Bridge.js#pushRoute

    this.setState((state, _) => ({
      routeCrumbs: state.routeCrumbs.push({
        view: symbol,
        data: routeData
      })
    }));
    
  • Adding propType validators on views that take custom route data to shore up the structure somewhat:
    in Bridge.js#pushRoute

    SentTransaction.propTypes = {
      routeData:  PropTypes.shape({
        promptKeyfile: PropTypes.bool
      })
    }
    

I can't think of many other ways to isolate route data from global state, since all pushRoute has access to is updating Bridge.js's state variable. But definitely open for other suggestions if you think of anything.

@@ -85,7 +85,7 @@ class StatelessTransaction extends React.Component {

submit(){
const { props, state } = this
sendSignedTransaction(props.web3.value, state.stx)
sendSignedTransaction(props.web3.value, state.stx, props.setTxnConfirmations)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just noticed that web3 isn't being unwrapped here. I.e., web3 could be Nothing, such that web3.value would be undefined. It should be unwrapped in the standard fashion; something like:

const web3 = props.web3.matchWith({
  Just: w3 => w3.value,
  Nothing: _ => { throw BRIDGE_ERROR.MISSING_WEB3 }
})

sendSignedTransaction(web3, ...)

That folktale lets one just wing it on the value property is pretty lousy, but alas, such is the nature of JS.

(This isn't actually related to your change @pkova -- I just happened to spot it while scrolling through the diff.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it not ok to use .value directly if pre-condition logic (ie whatever you need to do to make submit() accessible) already guarantees/checks for Just? (Of course, figuring that out as the reader isn't always trivial, but "trust the author"? (^: )

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason you want to be pedantic and do it literally everywhere is that it's easy to change the code (perhaps in a refactoring), remove the precondition, but forget to change the yolo .value attempt. It's a recipe for introducing annoying bugs eventually.

Better would probably be to do the unwrapping once, in one place, consistently. This is what I was thinking when I mentioned unwrapping all the necessary props in the constructor, or at least all in one place, and then to use the unwrapped values elsewhere in the component. Maybe something like:

class Foo extends React.Component {
  constructor(props) {
    super(props)

    this.web3 = props.web3.matchWith({
      Nothing: _ => throw { BRIDGE_ERROR.MISSING_WEB3 },
      Just: w3 => w3.value
    })
  }

  render() {
    const web3 = this.web3

    return(
      <div>{ web3 }</div>
    )
  }
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely in favor of unwrapping once in the constructor, yes. I'll put that on my informal refactoring list.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like Maybe. Using it has the upside of 'forcing you to check' for missing values, but the tradeoff is that changes that should be non-breaking become breaking changes. If we have a function

foo :: x -> Maybe y

and we manage to improve it so that we can always return y, eg.

foo :: x -> y

existing callers of our function break. Nullable types a la Kotlin or Typescript seem better. Rich explains this far better than I ever could.

Don't think there's a solution for this in js-land, though!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a Haskeller, so Rich's talk triggers the @#&! out of me. But what I can certainly agree on is that, yes, there's no satisfactory solution for this in JS-land. ;-)

Copy link
Member

@Fang- Fang- left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great! Thank you so much @pkova, really happy to have this! (:

I left some comments. Architecture/style discussion aside, this is almost good to be merged in.

@@ -155,6 +155,10 @@ const sendSignedTransaction = (web3, stx) => {
.on('receipt', txn => {
resolve(Just(Ok(txn.transactionHash)))
})
.on('confirmation', (confirmationNum, txn) => {
confirmationCb(txn.transactionHash, confirmationNum + 1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah web3's PromiEvents have pretty neat affordances.

This callback currently doesn't seem entirely accurate. The display in Bridge rapidly counts up to 25 (approximately one per second, definitely faster than the block time), then stalls there. In the time it takes it to count up to that, Etherscan has only seen 2 confirmations.

Turns out this is a bug that has been fixed in newer versions of web3. We'll have to try upgrading to latest sometime soon. I'll make an issue for this for the record.

src/bridge/views/SentTransaction.js Outdated Show resolved Hide resolved
src/bridge/Bridge.js Show resolved Hide resolved
@Fang- Fang- mentioned this pull request Apr 9, 2019
@pkova
Copy link
Contributor Author

pkova commented Apr 16, 2019

Animations are a go.

Copy link
Member

@Fang- Fang- left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Last little rendering nit! Once that's fixed, I'll merge this, and make a new Bridge release to put it live. (:

src/bridge/views/SentTransaction.js Outdated Show resolved Hide resolved
@Fang-
Copy link
Member

Fang- commented Apr 16, 2019

Looks clean! Thanks a bunch @pkova, this is good!

@Fang- Fang- merged commit 58b8e51 into urbit:master Apr 16, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Show live transaction state after submission
4 participants