Skip to content
This repository has been archived by the owner on Apr 6, 2020. It is now read-only.

Update official transaction tests #131

Merged
merged 10 commits into from
Apr 14, 2019
Merged

Conversation

danjm
Copy link
Contributor

@danjm danjm commented Dec 4, 2018

Opening this PR instead of #126 as this branch is in the main repo.

fixes #115

Updates the transactionRunner.js tests as per the changes and instructions described here: ethereum/tests#373

Each test is run for each of the hardforks. If the tx is defined but has an invalid signature after creation, the test passes if the test data for the given hardfork is empty. If the tx is undefined after creation, the test passes if the test data for the given hardfork is empty. If the tx has a sender and hash that matches that in the test data for the given hard fork, the test passes.

This PR also adds the ability to run a single test by passing an exact filename match. For example:
npm run test:node -- -t --file=TransactionWithHighGas | tap-spec

@danjm
Copy link
Contributor Author

danjm commented Dec 4, 2018

@holgerd77 This might be ready to go, depending on whether we want to resolve the following:

This implemenation of transactionRunner.js will flag the transactions this._homestead property as false when running the hardfork tests other than "Homestead". However, there is some behaviour of ethereumjs-tx that should hold true for Homestead and all future hard forks.

For instance, that All transaction signatures whose s-value is greater than secp256k1n/2 are considered invalid has been true since Homestead. In our codebase, this condition only invalidates a tx if this._homestead is true. As such, tests for other hardforks that expect invalidity due to violation of this condition will not currently pass.

To see an example of this, run npm run test:node -- -t --file=TransactionWithHighGas | tap-spec

this._homestead defaults to true. Is it assumed that it will only be set to false if ethereumjs-tx is being used prior to the Homestead hardfork?

If not, we need to explicitly handle these other forks in some way.

@holgerd77
Copy link
Member

Hi Dan, thanks for the update! This whole this._homestead thing is totally legacy and should be replaced. Can you cross-coordinate with this #130 PR currently in progress? I think this would be a perfect match. We should optimally make sure that all hardfork specific logic is supported, or minimally then explicitly limit (using the common library) to that set of hardforks that ARE supported.

Would probably be optimal to have this PR merged and then work here on top of the hardfork and chain integration work.

@danjm danjm changed the title Update official transaction tests Update official transaction tests (Blocked) Jan 4, 2019
@danjm
Copy link
Contributor Author

danjm commented Jan 4, 2019

I put "blocked" in the title as the latest commit makes this PR dependent on #130

Once that is merged, I will rebase here

@danjm danjm force-pushed the update-official-transaction-tests branch from 50dddbd to 857c685 Compare March 1, 2019 07:10
@danjm danjm changed the title Update official transaction tests (Blocked) Update official transaction tests Mar 1, 2019
@danjm
Copy link
Contributor Author

danjm commented Mar 1, 2019

@holgerd77 This is ready for final review and merge. I'll confirm tomorrow, but the latest changes likely make #132 redundant.

@coveralls
Copy link

coveralls commented Mar 1, 2019

Coverage Status

Coverage increased (+1.2%) to 98.936% when pulling 4d75731 on update-official-transaction-tests into 471a13e on master.

@holgerd77
Copy link
Member

Great! 👍

Copy link
Member

@holgerd77 holgerd77 left a comment

Choose a reason for hiding this comment

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

We should have a closer look at this chainId redundancy, one minor formatting suggestions, otherwise looks good! Thanks Dan! 😄

index.js Outdated
@@ -151,7 +151,7 @@ class Transaction {
if (chainId < 0) chainId = 0

// set chainId
this._chainId = chainId || data.chainId || 0
this._chainId = opts.chainId || chainId || data.chainId || 0
Copy link
Member

Choose a reason for hiding this comment

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

The Common object is set with a specific chain and can be used to access the chain ID, can we use this instead of adding yet another option here?

I would also tend to remove the date.chainId parameter and also set this from Common after instantiation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good suggestions, thanks. I also overlooked how the opts.chain param was added with #130.

So we might also be able to get rid of the code on lines 150, 151 and 152. No need to calculate the chainId from the signature. Either an opts.chain is passed or we assume it is mainnet. Then Common can do the rest.

index.js Outdated
@@ -179,7 +179,8 @@ class Transaction {
if (includeSignature) {
items = this.raw
} else {
if (this._chainId > 0) {
const v = ethUtil.bufferToInt(this.v)
if ((v === this._chainId * 2 + 35 || v === this._chainId * 2 + 36) && this._common.gteHardfork('spuriousDragon')) {
Copy link
Member

Choose a reason for hiding this comment

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

I would prefer to have this separated into two if clauses so that this get's more readable. So first the hardfork check on spuriousDragon to indicate the HF dependent logic and then a separate v === this._chainId * 2 + 35 || v === this._chainId * 2 + 36 check. For the second check a short comment what's happening would also be helpful for readability.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changes made in 812c99d

I didn't quite use two if statements, probably because of stylistic preference. It should be much clearer. I'm fine to go with two if statements explicitly if that is your preference :)

this._senderPubKey = ethUtil.ecrecover(msgHash, v, this.r, this.s)
const v = ethUtil.bufferToInt(this.v)
const useChainIdWhileRecoveringPubKey = v >= this._chainId * 2 + 35 && this._common.gteHardfork('spuriousDragon')
this._senderPubKey = ethUtil.ecrecover(msgHash, v, this.r, this.s, useChainIdWhileRecoveringPubKey && this._chainId)
Copy link
Member

Choose a reason for hiding this comment

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

Ok.

.then(() => {
t.end()
})
})
Copy link
Member

Choose a reason for hiding this comment

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

Looks good, clean and nice and easy to read test setup! 👍

@holgerd77
Copy link
Member

Also: can you still confirm regarding #132?

@danjm
Copy link
Contributor Author

danjm commented Mar 6, 2019

@holgerd77 I've closed #132, this PR covers the important pieces of it

@danjm
Copy link
Contributor Author

danjm commented Mar 6, 2019

In addition to the above changes, I am going to give myself one more task before I merge:

  • QA to verify that this does not break anything on ropsten or rinkeby

@danjm
Copy link
Contributor Author

danjm commented Mar 7, 2019

Oops, my latest update broke the build. I will fix it.

@holgerd77
Copy link
Member

Sounds good, let me know once this is ready for re-review!

@holgerd77
Copy link
Member

What's the status of this?

@danjm danjm force-pushed the update-official-transaction-tests branch from c011704 to 8ea361a Compare March 22, 2019 03:46
@danjm
Copy link
Contributor Author

danjm commented Mar 22, 2019

@holgerd77 This is now complete, but there is a caveat that may mean another change is necessary: this PR have introduces three breaking changes with regards to how this._chaindId is set.

Previously chainId was calculated this way:

    // calculate chainId from signature
    let sigV = ethUtil.bufferToInt(this.v)
    let chainId = Math.floor((sigV - 35) / 2)
    if (chainId < 0) chainId = 0

    // set chainId
    this._chainId = chainId || data.chainId || 0

After this PR it is just: this._chainId = this._common.chainId() (where this._common.chainId() depends on passed opts.chain params)

So, the caller is explicitly required to set the opts.chainproperty if they want the chainId to be something other than the default associated with mainnet (i.e. 1).

This means two breaking changes:
(1) this._chainId will no longer be calculated from the v value included in the data param passed to the constructor.
(2) a specific chain id cannot be included in the data param passed to the constructor
(3) The chainId will now default to 1 instead of 0 if no other info is specified

This is in line with this comment #131 (comment), but goes a step further to include the removal of calculation of chainId from v and the defaulting to 0 (which seems to be more aligned with the specification https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md#specification)

@danjm danjm force-pushed the update-official-transaction-tests branch 2 times, most recently from 8773c7b to d7c98ea Compare March 31, 2019 00:14
@danjm danjm force-pushed the update-official-transaction-tests branch from d7c98ea to 4d75731 Compare March 31, 2019 01:30
@danjm
Copy link
Contributor Author

danjm commented Mar 31, 2019

@holgerd77 I have revised this PR so that my previous comment is no longer true: there are no longer breaking changes.

Instead, this PR updates the codebase to support the new way of setting chainId and the legacy approach:

    if (opts.chain || opts.common) {
      this._chainId = this._common.chainId()
    } else {
      this._chainId = chainId || data.chainId || 0
    }

Developers that us opts.chain or opts.common will be able to explicitly set chainId to a value of their choice. Developers that do not set it will have it set by default according to the existing rules:

    // calculate chainId from signature
    let sigV = ethUtil.bufferToInt(this.v)
    let chainId = Math.floor((sigV - 35) / 2)
    if (chainId < 0) chainId = 0

I've decided to leave support for the use of data.chainId (despite your comment) as I think it would be better to give notice of the deprecation before breaking the functionality. Of course, if you think it is okay given this will be a major version upgrade, then it is easy to remove this entirely.

Meanwhile, this PR includes an update to run all transaction tests on mainnet, which seems to be an assumption baked into those tests.

Also not that #143 is based off this PR. Among other things, it addresses an outstanding issue with this PR.

I believe this PR is now complete and ready to merge, notwithstanding the a possible decision on the removal of data.chainId from the constructor, as described above.

@holgerd77
Copy link
Member

Trusting your choices here, will try to do a short-term final review. I am thinking a bit if you guys want to completely take over responsibility for the continued work and directly do cross-reviews with e.g. @chikeichan and eventually new contributors from Nomic, I think you are understanding this library at least as good as me anyhow. And I would rather watch from the sideline and try to make sure it fits into overall strategy, modernization efforts and technical guidelines.

So Jacky e.g., if you want to do a review on this and then merge, feel free (but generally: please take reviews seriously, you can very well take 1-2 hours for this and also put this in the timesheet, this is as important as the work itself).

Best
Holger

@holgerd77
Copy link
Member

Or @s1na could eventually also do a review on this

@alcuadrado
Copy link
Member

alcuadrado commented Apr 3, 2019

I reviewed this PR today, and it looks good. Note that this is my first review here, so I may be missing something. I didn't make any comment on style either, as I'm still getting used to the ethereumjs's standards.

The only thing that raised my attention is that after this PR the function hash doesn't include the _chainId info when being used to sign new transactions. I don't know if this is intentional, as there's #143, but I haven't seen it yet. I belive this may be a bug, as sign is encoding the v like EIP155 dictates anyway.

I created this test which can be added to test/api.js as an example of the issue raised in the previous paragraph. It is failing in this branch and passing in master.

  t.test('EIP155 hashing when singing', function (st) {
    txFixtures.slice(0, 3).forEach(function (txData) {
      const tx = new Transaction(txData.raw.slice(0, 6), {chain: 1})

      var privKey = new Buffer(txData.privateKey, 'hex')
      tx.sign(privKey)

      st.equal(
        tx.getSenderAddress().toString("hex"),
        txData.sendersAddress,
        'computed sender address should equal the fixture\'s one'
      )
    });

    st.end()
  })


const onEIP155BlockOrLater = this._common.gteHardfork('spuriousDragon')
const v = ethUtil.bufferToInt(this.v)
const vAndChainIdMeetEIP155Conditions = v === this._chainId * 2 + 35 || v === this._chainId * 2 + 36
Copy link
Contributor

Choose a reason for hiding this comment

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

nice

@chikeichan
Copy link
Contributor

reviewed coding style and structure - LGTM! I also agree with the decision to not introduce breaking changes without a waring period.

And thanks for the detailed review @alcuadrado - @danjm can you please take a look at the hash function tests @alcuadrado added?

if (opts.chain || opts.common) {
this._chainId = this._common.chainId()
} else {
this._chainId = chainId || data.chainId || 0
Copy link
Contributor

Choose a reason for hiding this comment

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

The discussion on here is pretty useful - can we add an inline comment to summarize (or paste the link to comment)? This would help future devs to gather context quickly.

@danjm
Copy link
Contributor Author

danjm commented Apr 4, 2019

Great catch @alcuadrado. That bug has been addressed on #143

I just pushed the test you wrote to that branch. It passed for me locally.

Copy link
Member

@holgerd77 holgerd77 left a comment

Choose a reason for hiding this comment

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

Seems we are ready, thanks Dan, feel free to merge! 😀

@alcuadrado alcuadrado mentioned this pull request Apr 13, 2019
@holgerd77
Copy link
Member

Ok, will now merge here myself so that we can move on on top of the work here.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Update official Transaction tests
5 participants