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

Avoid unnecessary 100 gas cost in CALL & add missing write-event for CODEHASH #508

Merged
merged 4 commits into from
Oct 15, 2024

Conversation

jsign
Copy link
Collaborator

@jsign jsign commented Oct 14, 2024

No description provided.

@@ -814,7 +814,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
}
}

if interpreter.evm.chainRules.IsEIP4762 && !chargeCallVariantEIP4762(interpreter.evm, scope) {
if interpreter.evm.chainRules.IsEIP4762 && !transfersValue && !chargeCallVariantEIP4762(interpreter.evm, scope) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is something that surfaced when I was helping Luis with some work on Besu. I've chatted about this with some people, but re-explaining here for completeness.

Imagine we have a value-bearing CALL (it can be for an existing address, so nothing weird). In this case, we'll execute L811 first which is adding the "value transfer" part to the witness -- this includes doing a write-event on the target BASIC_DATA.

Then in L817 we were (i.e: before this PR) doing the usuasl *CALL touch on BASIC_DATA for the target. In this case, we'd be charging 100 of gas... since the "value transfer" touching we did before already added BASIC_DATA to the witness -- so a "warm charge" is done.

This looks wrong? This extra 100 of gas doesn't make much sense, in the sense that could be easily removed (as I do in this PR).

Copy link

@lu-pinto lu-pinto Oct 14, 2024

Choose a reason for hiding this comment

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

Yes, that's because we consider a write event to be a read event as well, so it should be reasonable to assume that the client has the read value in the runtime. If we already read then we don't need to do it again.
If I'm not mistaken you could have swapped this if check with the transfer one too, being that you add the warm charges before anything else and then check if you need to write.

Copy link
Owner

Choose a reason for hiding this comment

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

hmmm yeah we should at least merge the two checks for eip4762 into a single if, and have an else to call chargeCallVariantEIP4762.

And adding a comment would be nice, so that we remember it in 2 weeks: just explain why we don't charge the first 100 in this case.

Copy link
Owner

Choose a reason for hiding this comment

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

BTW, I think there is a bug here:what if we call the same address with values multiple times? the 2nd time, TouchAndChargeValueTransfer should charge 100 gas. I think the best way to do this would be to change TouchAndChargeValueTransfer to return 100 if nothing gets added to the witness.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Agree! Apparently this is the only place we use TouchAndChargeValueTransfer so looks safe to solve it that way. I'll make the change.

core/vm/evm.go Outdated
Comment on lines 202 to 205
if !evm.StateDB.Exist(addr) {
if !isPrecompile && evm.chainRules.IsEIP4762 {
// add proof of absence to witness
if !isPrecompile && evm.chainRules.IsEIP4762 && value.Sign() != 0 {
gc := gasConsumer{availableGas: gas}
ok := evm.Accesses.TouchFullAccount(addr.Bytes(), false, gc.consumeGas)
ok := evm.Accesses.TouchCodeHash(addr.Bytes(), true, gc.consumeGas)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In a CALL targeting an account that doesn't exist, we were doing this read-event on TouchFullAccount. This looks like it's missing charging the write we'll do on the created account CODE_HASH=emptyCodeHash.

But my code change is apparently a bit more weird, so let me explain.

There're two ways in which we can create an account via a call:

  1. We're processing a value-bearing tx targeting an account that doesn't exist. In this case, we already added the target ("for free") in the witness. So this line will be a "noop" reg adding things to the witness and not chargin any extra gas.
  2. We're coming from a CALL instruction. In this case, we have a value-bearing CALL which means the gasCall already write-touched the BASIC_DATA. So, at this point the only missing thing seems to be write-touching the CODE_HASH, thus why I removed the FullAccount to CodeHash in the touching.

Note also I added value.Sign() != 0 in the if since we only want to do this write-event on value-bearing contexts. If this "call" isn't value bearing, I don't see why we ned to do any new touching if !Exist(addr) since the gasCall already added everything needed in the witness to check for account existance check in stateless clients (if we agree stateless client doesn't need checking for codeHash for that).

This is also why the deleted line L204 I believe was incorrect -- nothing in this place of hte code is about adding "proof of absence" .. this is indirectly done in the gasCall.

gballet
gballet previously approved these changes Oct 14, 2024
Copy link
Owner

@gballet gballet left a comment

Choose a reason for hiding this comment

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

Looks correct. I suggested a comment so that we don't keep asking ourselves the same questions over and over again.

core/vm/evm.go Show resolved Hide resolved
@gballet gballet dismissed their stale review October 14, 2024 15:11

didn't see the 2nd part of the PR

Copy link
Owner

@gballet gballet left a comment

Choose a reason for hiding this comment

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

I think I found a bug, let me know what you think.

@@ -814,7 +814,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
}
}

if interpreter.evm.chainRules.IsEIP4762 && !chargeCallVariantEIP4762(interpreter.evm, scope) {
if interpreter.evm.chainRules.IsEIP4762 && !transfersValue && !chargeCallVariantEIP4762(interpreter.evm, scope) {
Copy link
Owner

Choose a reason for hiding this comment

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

hmmm yeah we should at least merge the two checks for eip4762 into a single if, and have an else to call chargeCallVariantEIP4762.

And adding a comment would be nice, so that we remember it in 2 weeks: just explain why we don't charge the first 100 in this case.

@@ -814,7 +814,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
}
}

if interpreter.evm.chainRules.IsEIP4762 && !chargeCallVariantEIP4762(interpreter.evm, scope) {
if interpreter.evm.chainRules.IsEIP4762 && !transfersValue && !chargeCallVariantEIP4762(interpreter.evm, scope) {
Copy link
Owner

Choose a reason for hiding this comment

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

BTW, I think there is a bug here:what if we call the same address with values multiple times? the 2nd time, TouchAndChargeValueTransfer should charge 100 gas. I think the best way to do this would be to change TouchAndChargeValueTransfer to return 100 if nothing gets added to the witness.

Signed-off-by: Ignacio Hagopian <[email protected]>
@jsign jsign requested a review from gballet October 14, 2024 15:28
Co-authored-by: Guillaume Ballet <[email protected]>
@jsign jsign marked this pull request as ready for review October 15, 2024 14:49
Copy link
Owner

@gballet gballet left a comment

Choose a reason for hiding this comment

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

LGTM

@gballet gballet merged commit 4b91d84 into jsign-witness-fix Oct 15, 2024
3 of 7 checks passed
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.

3 participants