-
Notifications
You must be signed in to change notification settings - Fork 790
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
VM: Generate Access Lists in runTx() #1170
Conversation
Codecov Report
Flags with carried forward coverage won't be shown. Click here to find out more. |
7547637
to
aaecc9b
Compare
@@ -75,6 +75,14 @@ tape('Address', (t) => { | |||
st.end() | |||
}) | |||
|
|||
t.test('should provide correct precompile check', (st) => { | |||
const precompile = Address.fromString('0x0000000000000000000000000000000000000009') | |||
st.true(precompile.isPrecompileOrSystemAddress(), 'should detect precompile address') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm this EIP is actually a little bit confusing. It does specify which address range could be assumed to be system precompiles. However, for access lists, we are only allowed to push precompiles which actually have code to the access lists. There was a bug in Besu where they also included BLS addresses to the access lists, which does not match the EIP. What is weird is that EIP2930 in combination with EIP1352 is not very consistent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I'm just using this to remove precompile addresses, so there should be no problem? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"which actually have code to the access lists"
What do you mean by that actually? 😀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It means that once we setup the initial access list, we add the caller and the target to the access list, but we also add every precompile which has attached code to it. So, Besu made a mistake and also added the BLS precompile addresses. However, in Berlin, we do not have BLS, so there is no precompiled code at these BLS addresses and they should thus not be added to the access lists.
packages/vm/lib/runTx.ts
Outdated
@@ -123,6 +142,10 @@ export default async function runTx(this: VM, opts: RunTxOpts): Promise<RunTxRes | |||
const result = await _runTx.bind(this)(opts) | |||
await state.commit() | |||
debug(`tx checkpoint committed`) | |||
if (this._common.isActivatedEIP(2929) && opts.reportAccessList) { | |||
result.accessList = state.clearWarmedAccounts(true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is WIP, but this currently only supports the accessed items for the root transaction right? It will not report access lists if you *CALL*
into another address.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm it might actually report it, but it will not report if we REVERT
one of these inner calls, since that removes the accessed items from warmed storage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, does this happen very often in practice and do you think it's an important miss?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I can add extra logic for it though)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, internal reverts sometimes happen, and if you don't put these storage slots on the access lists then you are going to get charged to touch a cold account multiple times. So if we want to generate "correct" access lists (we want to be friendly and provide the complete list of the slots and accounts we are going to add in the access list), then we should also include the reverted slots.
This is actually a bit mind-boggling... There is an edge case here. What if an internal call reverts, but if we add it to the access list then it does not revert? This might also change the behavior of the call; we might now touch other slots. I think we could try to rebuild the access lists based upon whatever we just touched, but I think we might get in a flip/flop situation where each time you edit the access list, you get another access list which we already considered before 🤔 (Hope this reasoning makes sense)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will ask on R&D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checkout geth's implementation; ethereum/go-ethereum#22550
They cap the count of attempts to generate an access list at 10.
… own method generateAccessList (not part of interface yet)
…eration logic, unified folding logic, add note on AL generation edge case
Hi @jochem-brouwer, thanks for digging in so deeply here, really cool! 😄 I won't do this edge case implementation here since I simply don't have the development resources for this given all our other tasks atm, instead I've added a note here along the implementation. Since this is not breaking anything if an access list might not be complete on some edge cases (+ also this 10 -run-limit from geth seems also conceptually be rather a practical solution than a 100%-correct implementation) and it is not yet clear how much this whole access list feature will be used, I think this should be acceptable. I have added the additional structure though for Ah, yes, and this is now a separate function, haven't added to the official interface yet though for compatibility reasons. Instead Ok, ready for review. 😄 |
Hi @ryanio, could you have a look at the latest VM API test run, which is failing for the karma browser run? I could just add the |
this._accessedStorage.pop() | ||
const lastItem = this._accessedStorage.pop() | ||
if (lastItem) { | ||
this._accessedStorageReverted.push(lastItem) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will degrade performance, right? If we don't want to get the access lists, then we also should not copy these items since it is unnecessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yes, you are right. With this openly usable generateAccessList()
function we might nevertheless want to leave as-is, but haven't thought about this aspect yes.
if (!address.isPrecompileOrSystemAddress()) { | ||
const storageSlots = Array.from(slots) | ||
.map((s) => `0x${s}`) | ||
.sort() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorting this is fine, but it is not really necessary, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would be a fan of sorting (even if it comes to some performance costs), I think this gives a better overview if one want to manually check the outcome. And I guess in this use case performance is a bit secondary (just an unproven assumption reflecting on how I imagine people using this stuff).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some general remarks, looks great in general, nice tests 👍
…e test must have been removed, as one singular test takes 3 tests)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm!
Question here, do the reported access lists sometimes also report the sender address or the |
@jochem-brouwer ah, good catch, yes, I assume these are still in. Will put the release PR on "blocked" and open a subsequent PR soon. |
@jochem-brouwer ah, actually |
Ah you are right. We only don't want to put |
Ok, here is the access list generation PR fixing #1152. This needs significantly more tests, but this should generally do what is expected. I just tried to generate the following super-simple access list with this:
Which obviously is not enough yet. 😄 Functionality for more complex AL is there but should be manually tested or optimally by adding additional test cases.
For now I've added the functionality to
StateManger.clearWarmedAccounts()
in a non-breaking way. On a second thought along pushing I rather think it might be better and more flexible to just add a distinct method for that which can be called separately (which then still can be called somewhat along the clearing-call in our context).I also added a new helper function
Address.isPrecompileOrSystemAddress()
to Util which checks an address to be a precompile or system address following EIP-1352.