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

tx: add getDataFee to tx cache #1532

Merged
merged 10 commits into from
Oct 27, 2021
Merged

tx: add getDataFee to tx cache #1532

merged 10 commits into from
Oct 27, 2021

Conversation

emersonmacro
Copy link
Contributor

Partially addresses #775 - adds caching to Transaction#getDataFee method

@codecov
Copy link

codecov bot commented Oct 19, 2021

Codecov Report

Merging #1532 (cd0db8f) into master (fcbee4a) will decrease coverage by 0.03%.
The diff coverage is 100.00%.

Impacted file tree graph

Flag Coverage Δ
block 84.81% <ø> (ø)
blockchain 82.53% <ø> (ø)
client 77.13% <ø> (-0.37%) ⬇️
common 94.27% <ø> (+0.18%) ⬆️
devp2p 82.85% <ø> (+0.03%) ⬆️
ethash 90.76% <ø> (ø)
tx 89.04% <100.00%> (+0.38%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

/**
* The amount of gas paid for the data in this tx
*/
getDataFee(): BN {
Copy link
Contributor

Choose a reason for hiding this comment

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

do we need to add this? would it not work falling back to the default baseTransaction method?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's a little weird because the cache is created in baseTransaction, but caching doesn't actually happen in baseTransaction because it doesn't have the option to be frozen. The freeze option only exists on the derived classes. That's my reading of the code but I might be misinterpreting it

Copy link
Contributor

Choose a reason for hiding this comment

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

hm, I think Object.isFrozen(this) would still work in the baseTransaction, but would have to try it out

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah right, just remembered, my first attempt was to implement the caching in baseTransaction, but the issue I ran into was that eip1559Transaction and eip2930Transaction both call super.getDataFee() but then modify the value in their own implementations of getDataFee. But the value of getDataFee was getting calculated and cached at the baseTransaction level, so the incorrect value was being returned in the derived classes.

Copy link
Member

Choose a reason for hiding this comment

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

Ok, guess that makes sense.

Can you structurally align this method with the other two methods? So with this cost constant and generally the same order of calling? That would be easier to read and compare.


if (Object.isFrozen(this) && !this.cache.dataFee) {
this.cache.dataFee = cost
}
Copy link
Member

Choose a reason for hiding this comment

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

On the first if condition you can remove the Object.isFrozen(this) check and on the second the this.cache.dataFee check. Guess this reads a bit better and is less redundant, also for the other tx types.

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 call - updated and it does read better

@jochem-brouwer
Copy link
Member

This invalidates the tx object in case that Common changes (i.e. tx is now in a different hardfork, for instance changing the fee to pay per 0-byte in the calldata or nonzero-byte in the calldata). Doesn't this lead to undesired side-effects?

This can be solved by also caching the current common setting. In case that common has changed (network, hardfork) then the cache is invalid and we should thus re-calculate.

@holgerd77
Copy link
Member

@jochem-brouwer hmm, is this practically relevant, a frozen transaction where the Common changes after instantiation? 🤔

@jochem-brouwer
Copy link
Member

The common object itself does not change, the hardfork which gets reported by the common changes. This might lead to race conditions. For instance, in Istanbul, this EIP the calldata cost can change. So if you would have a tx in mempool which is on Byzantium, and now a block gets mined which is on Istanbul, the Tx will report the Byzantium data fee and not the Istanbul data fee, thus it would report different gas cost at the end of the block, hence causing a bad block.

(Note: this race condition does most likely not happen in practice since the created blocks will re-import the transactions. But it is to illustrate that this could report wrong values of the data fee and thus cause race conditions, especially at fork transitions)

@holgerd77
Copy link
Member

@jochem-brouwer Ok, guess you are right and this might have side effect.

I think we can just listend to the hardforkChanged event here from Common and then invalidate (delete) the data fee cache? 🤔

@emersonmacro
Copy link
Contributor Author

@jochem-brouwer @holgerd77 Thanks for the feedback - added the listener for hardforkChanged and a new test case, and it seems to be working, but I was hoping one of you could confirm that the test is valid.

Also concerned with the performance alert that popped up from the latest change. Did the addition of the event listener cause that much performance degradation?


this.common.on('hardforkChanged', () => {
delete this.cache.dataFee
})
Copy link
Member

Choose a reason for hiding this comment

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

Ah, this should be not only for the legacy tx but for all tx types. So I guess the BaseTransaction constructor is a better place here.

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 tried moving it to the BaseTransaction constructor, but this.common wasn't defined yet so it threw an error. I just added it to the other transaction types - not very DRY but it seems to be working. If there's a way to get it working in BaseTransaction, I can update it

@holgerd77
Copy link
Member

@emersonmacro ah, and I guess you can ignore these performance alerts for now, this doesn't seem to work atm.

@@ -168,6 +171,17 @@ tape('[Transaction]', function (t) {
st.end()
})

t.test('getDataFee() -> should invalidate cached value on hardfork change', function (st) {
const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Byzantium })
const tx = Transaction.fromValuesArray(txFixtures[0].raw.map(toBuffer), {
Copy link
Member

Choose a reason for hiding this comment

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

For completeness, this test should use all Tx types, so this test should also run with EIP1559/EIP2930 transactions (which will probably fail right now as pointed out by @holgerd77 in another comment)

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.

LGTM, thanks! 😄

@holgerd77 holgerd77 merged commit e72d5eb into master Oct 27, 2021
@holgerd77 holgerd77 deleted the tx-getdatafee-cache branch October 27, 2021 19:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants