Block-Level Access Lists Breakout #3
Block-Level Access Lists Breakout · 24 Sept 2025 · 47:13
Block-Level Access Lists Breakout #3, held on 24 September 2025. Block-level access lists, defined in EIP-7928, record which accounts and storage slots a block touches, which can help clients process blocks in parallel. Geth was focused on performance bottlenecks in state root updates, and blocks from Besu were rejected by Geth in a local devnet.
Speakers
- Toni Wahrstätter
- Jared Wasinger
- Jason Vranek
- Dragan Rakita
- Karim Taam
- Łukasz Rozmej
- Marius van der Wijden
- Marc Harvey-Hill
Topics
From Block-Level Access Lists Breakout, hosted by Ethereum Foundation.
Transcript
Toni Wahrstätter: Great! I would say we're ready to go. I think we can start this stream.
Akash | ECH: Yep, VLA.
Toni Wahrstätter: Great, thank you. Hello everyone, welcome to today's EAP7928 breakout call. It's the third breakout call today, and we have three things on the agenda. The first thing we want to discuss, which was already kind of a little bit discussed last week, but today we wanted to decide upon it, is are we going for a block-level access list hash in the header, or do we want to switch it out for a block-level access list root, which would then, yeah. Mirror the existing tri structure we already use for receipts and transactions and so on. But of course, at the moment, we use the hash, so the default might be we just stick to that, but very open to discuss that.
The second point is, how do we want to handle transaction self-destruct? There are basically two ways how to handle them. Either we include them and make it explicit that self-destruct in the same transaction is happening, or if you followed the last messages from Jared in the Discord. Basically, he's arguing for just including the address of the self-destructed account, And not including anything else. And, last thing we can discuss today is… an idea that Jared brought up, or Gary brought up, which is including intermediate storage account… including account storage routes into the block level access lists to speed up the hashing. Those are basically the three points, and then we can go into client updates as usual.
Great, let's start with the first topic on the agenda, which is block-level access list hash versus block-level access list. try, or root, and putting everything into a try, and let's see, would be great, Jason, it would be great if you could give us a quick, intro into Why it would matter for pre-coms, and what do you actually expect getting from… from this change, from moving from the hash to a root.
Jason Vranek: Yeah, absolutely. So… I guess in the world of pre-confs, there's different flavors. Like, we start with the basic being inclusion, which are pretty trivial to prove today using the existing tries, but as we move towards execution, there's different ways to do it. But really, an execution preconf would be a guarantee on the post state. And… You can do that technically today using the existing tries, like, and what I mean by that is. What we care about is being able to slash The proposer on-chain? And by proving that they broke their promise. And when we're using the existing tries today for execution, precomps, it's a very disorganized and scattered proof. Like… If your transaction that you're committing to affects many accounts, across these different trees, or tries…
then it's… there's a lot more margin for error. There's kind of data availability issues if… if the proposer isn't committing to every single thing that was touched. And for all of these reasons. Actually, sorry, one other important reason is a lot of the times we care about the… what we really care about is post-transaction state routes, and not, like, post-block, routes. So… I think there's some… information that's lost if we're only trying to prove against the final state after the block executes. So, for all these reasons. Execution precoms are really done through… intent-style programming, and that's, like, a burden on developers, kind of adds friction. And the proposal of using roots, instead of hashes for the BALs is that it would be pretty trivial to prove
From the smart contract. And the way that you'd organized it, Tony, would be… A pretty straightforward commitment to a route, to cover, like, every piece of state that's touched.
Toni Wahrstätter: Awesome, thank you. So, just to summarize, it feels like there are three points. It would… if we move from a block of accesses hash to a try, we would make the life of pre-conf providers easier, because the proof would, would not depend on transactions touching the exact same state in the meantime, but we can get more granular and prove against a certain state at a certain transaction index. Then the proof would become cheaper, because You don't… you wouldn't need to input that much data anymore. And also easier, because it would just be one single proof against one object that we have. So, I totally get… I can totally see those advantages. What would you say against the counter-argument that, the block of access list might be already enough, because
In… in case you want to slash a proposal, you would just… you would just need to input the, the entire block of access list as call data.
Jason Vranek: So, I mean, I definitely see… that possible, because you only have to slash them in the worst case if they did break the commitment. Maybe I'm just not as clear on, like, the size of this, like, guest-wise. Are we… is that reasonable to be able to do?
Toni Wahrstätter: Yeah, I think it would be, yeah, because the Block 5 accesses seems like it's around… 50 kilobytes to 60 kilobytes compressed. I think that's already on the upper end. And, let's say 2X for uncompressed, 100 kilobytes currently would support around 900 kilobytes per block. Compressed, no, call data, so this would be something that would still easily fit into the block.
Jason Vranek: Yeah, then in that case, I mean, as long as it continues to be scalable, like, if the block size increases and the BAL also increases, if it's still able to fit in call data, I think that's definitely a good backup option, but…
Toni Wahrstätter: Yeah, it's not only fitting into call data, we also need to fit in the computations needed to eventually prove it on-chain. Right.
Jason Vranek: I mean, yeah.
Toni Wahrstätter: This would not be that much, we would just hash everything. And compare the hashes, and then prove it against the parent, block hash. But yeah, I'm curious to hear of other clients what you think about this? Another point I heard from Vitalik was Putting it into a try would be a good idea for partial stateless nodes. Because imagine you're a partial stateless node, and you only care about, yeah, a certain segment of the state, for example, specific addresses. and you don't want to download the entire block level access list, then someone could give you Parts of the block line access lists that you care about, together with a proof against the parent hash. So, this might be another use case where, yeah, a try versus a hash might be, better suited.
Yeah, so I'm curious, do client teams have an opinion on it, especially, since it would mean introducing yet another try, which was one of the arguments I've heard.
Jared Wasinger: Yeah, I… I wouldn't say I'm, like, strongly against it, but I… I… I can't say I fully understand the purported benefits, and yeah, it… the added complexity is also… I mean… The… the… the proposal is already, like, done. Turning out to be complex to performantly implement and comprehensively test, so… Yeah, I mean, I want to learn more, but yeah, kind of lukewarm about it right now, personally.
Toni Wahrstätter: Yeah, progress.
Jared Wasinger: Sorry, just… I guess one more thing we could… there's nothing stopping us from updating the BAL format in the future to support this. I mean, it's not easy, but we don't have to… package everything, all the bells and whistles in a single hard fork? No.
Toni Wahrstätter: This is true, yeah. That's right.
Dragan Rakita: wanted to say… wanted to say same thing. Yeah, I think ball should be minimal, and it feels that the introducing tree inside with the same fork, with the ball, seems like additional optimization that we can do later. So I would opt in with some simpler solution right now, and then decide if we need a tree, what kind of tree, maybe it's going to be binary tree, maybe some else tree. It seems, unnecessary to edit in this work.
Toni Wahrstätter: Thank you. I'm also, relaying what Marius is saying in the chat. He's also weakly against it. Yeah, just to summarize, it very much feels like the trade-off here is, complexity versus this additional Use case, or… What it enables, making certain things cheaper and easier. Of course, at the cost of adding protocol complexity. Also, Karim is saying here in the chat that he also prefers To keep it as is. Anyone else, an opinion on that? Okay.
Then, I would say let's keep it for now as it is. I will also reach out to Vitalik to… maybe he can clarify his points further on the testing call, because we have a section on updates on block of access list on the testing call anyway, so it might make sense to get his input, since he's not on the call today. And it would be great, Jason, if you could publish, your post somewhere, so that devs have some Opportunity to get into the topic of pre-coms, and… Yeah, determine how important it would be. Yeah, no worries, Lukasht. The thing we just talked about is the first point of the agenda, the block that access is hash versus the root.
But in this case, if there is nothing else, I would suggest we move on to the second point on the agenda. It's the last opportunity for anyone to… Voice and opinion on the hash versus root topic. Great, then let's move on to a second topic, which is how we want to handle self-destruct. So, as you might have seen in the… in the last few days, I've changed… I've modified the specs a bit, because there were some bugs around edge cases, and one of those edge cases were, self-destruct happening in the same transaction, so not send-all, but a real self-destruct. And… In the test cases, we planned to explicitly handle self-destructs by having a post nonce in the block level access list.
That is set to zero. So usually, you would never see, in the block level access list, a post non-soft zero. But Rahul and me were thinking, okay, it might make sense to signal that a self-destruct happened by having post non-zero. What Charent was today saying in the… in the Discord chat is. that sims self-destructs in the same transaction are basically no ops, and they don't change any state, we should just only include them as if they were a read, so we include the address with no changes in the blocker access list. Of course, we include the balance changes if something was sent through the self-destruct. But we would not include nonce changes or code changes.
And I wanted to double-check what clients think about it, and what would be the best way to handle a self-destruct. Maybe, Jared, you could also give us your rational behind.
Jared Wasinger: Yeah, for sure. just wanted to point out, also, that I don't think there would be a balance change in this case, because… the…
Toni Wahrstätter: Right.
Jared Wasinger: Yeah, it has to be non-existent in order for it to be deleted, created and deleted in the same transaction. Yeah, I think you, summarize my main point well, er, well, also, another thing that I was saying is that I feel like… every other… In every other instance where we… have account fields in the BAL, other… other than for storage reads. It, it's for… A situation where the post state is different than the pre-state. And so… Having this special case, for, for self-destructs. Kind of breaks this… It's sort of just not consistent with the rest of the… of the design for BALs,
Yeah, I'm not sure what direct effects that would have, but it's just kind of another… Just seems kind of… Yeah.
Toni Wahrstätter: Yeah, you're totally right. Yeah, the question, the underlying question, maybe, would be, do we want to differentiate by just looking at the block access list, do we want to be able to differentiate between an account that is, for example. Accessed through balance opcode, or call opcode? Versus an account that is involved in a self-destruct. So, it is deployed in self-destruct. And it feels like there's no… we don't really need it, right? Because what we care about in Block Live Exorcist is really, like, what is the difference between the pre and the post state, or what actually changed, and what is the post state. And since there is no change involved, it might just make sense to treat it just like a read.
Without any special meaning.
Jared Wasinger: Yup. I… I agree.
Toni Wahrstätter: Also, also, jargon to answer the question that you raised in the chat, why do we need special case for self-destruct? It does not remove the storage, the self-destruct that happens in the same transaction does remove the storage, right? It removes the storage that was there after the deployment, that you wrote to it after deployment, but compared to the pre-state, it would be the same. I guess that's what you're up to. So… If you have a self-destruct in the same transaction, then the pre-state and the post state would be the same, because the account is eventfully deleted. And the nonce would be reset to zero, which it was before the transaction. Yeah, it feels like this is, like, the cleanest way to go.
But just to flag it. just reading the chat, what Philippe is saying, I guess the argument is to save space, yeah. Yeah, go ahead.
Jared Wasinger: I guess we should also, include, like, so we should determine when you create a… an account. within the same tran- and destroy it within the same transaction, but it reads storage. That kind of seems like it might be an edge case worth addressing in the specs. So, an account that is deployed, and then it reads from another account, for example, the balance, and then it is… I mean, like, an account… account that is created in the transaction, it… it does something… it either writes or reads its own, storage… and… and then self-destructs, like…
Toni Wahrstätter: Yeah, this is interesting. Yeah, go ahead.
Marius van der Wijden: Those streets and rights would not end up in the tri anywhere, right?
Dragan Rakita: That's a good question. That's the questions I wanted to ask.
Jared Wasinger: They wouldn't… I'm saying, test coverage, it's worth it to have that case be.
Marius van der Wijden: Yeah, definitely, we need to find that. Yep.
Dragan Rakita: The question is, if account is created, and some storages are accessed. Does those stages get included in the bowl?
Marius van der Wijden: Yes. If the account is not self-destructed, then yes. If the account is self-destructed, then the same… Transaction, then no.
Dragan Rakita: So that will be a difference. That would be a big difference, because if you just… but what if you, like… Create account, then access some… just load the storage. You still want to have it in the reads. But if you create an account. check some storage, then self-destruct. You don't want to have that storage. Indeed.
Jared Wasinger: Honestly, I think it's… it probably is such a rare enough occurrence that it… it should just be included in the BAL, and we shouldn't have a special case.
Toni Wahrstätter: Yeah, I tend to agree with that. So there… it feels like there are two cases. We deploy an account that then accesses something from a different account. For example, I deploy a contract that then accesses, I don't know, USDC, and then I self-destruct. Versus, I have a contract that is deployed. It accesses its own storage, or writes to its own storage, And then self-destruct. Right? Because for the first case, if we change some… if we change something in a different account. we still want to include it in the block lab access list, even though The account that is calling is self-destructed. It's more about what do we do about the account that is, in the end, self-destructed.
And there it feels, like what Jared just said, we just include everything that is accessed along the way. in the block lab access list as a read. Okay, great. Currently the specs are off on that. on that front, because we, I just pushed something to the specs, like, yesterday, so you might see those, commits with fixed self-destruct logic or something. I will revert those so that the specs actually match what we… match the ERP again, and I might also clarify that further in the ERP. To make sure that we include all state Including addresses and storage keys that are accessed during, until the self destruct in the block lab access list. Is there any other edge case,
We should, think about when… changing the EAP or the specs that I forgot.
Jared Wasinger: I think you mentioned it earlier, but there was the other… Just the stuff about the precompiles. Calling a pre-compiled address would be included as a read. But, yeah.
Toni Wahrstätter: Yeah, this is another thing that is underspecified in the AP. Yeah, I agree. And it will probably be specified, like, all the precompiled addresses that are accessed. in a transaction must be included as a read. Which would mean including only the account with all MTA changes. The question is, do we really need it? Because, for example, we assume precompiles are warm by default, for example. Do we really need to know, which pre-compiles are accessed? Because it's different for every other part of the state that actually Has something on disk behind it.
Jared Wasinger: to be honest, I'm… I'm not actually sure in Geth if we… if the code path for executing precompiles would result in them being included in the BAL as a read currently. I mean, I could see… The justification for… omitting them. I don't… I personally don't really lean strongly either way, as long as it's… specified.
Dragan Rakita: What about if we transfer some value, it, to the… We compiled.
Toni Wahrstätter: Yeah, this is different. In that case, you must include it. So if you… if you send to the precompile, we always have the balanced diff of that precompile. In the block file access list. It's more like… Should we include the precompile? as a read. For example, if a storage key is red, then we have the storage read change in the account changes object. If an account is read, for example, through the call-up code, or static call, or balance, and so on. Then, we have an account in the block club access list with all empty changes. And what I would think is if we… so we don't have a mapping to a transaction index for REITs, right? This is important to, note here.
So we don't have a mapping from reads in the block of exorcists to the Transaction indices, which would mean for precompiles. we would know, okay, this precompile will be accessed in this block, but we would not be able to map it to a transaction, just like we cannot map any read in the block level access list to transactions. So, I lean towards omitting the precompiles from the reads. Yeah. But I'm happy if it's, like, simpler to just add them, I'm also happy to do that.
Dragan Rakita: Just imagine if we have balance of code that targets pre-compiled. So, in that case, we'll have something different. Where we need to omit pre-compilers to get that case, yeah. For the calls, that's more specific to precompiles, because there is some different path, other than executing the contract. Yeah. To be honest, I would… yeah, I would include it, but I don't have strong stance here.
Toni Wahrstätter: Thank you very much, Lucas.
Łukasz Rozmej: I'm for omitting it, because we don't even track it internally. I know that GEF tracks it internally, but we have a simple if in code, and we can just not track them, which… It's just also an additional optimization. So I would, I would admit that it's worthless information, in my opinion.
Toni Wahrstätter: Yeah, I agree with that, but it feels like it would be an edge case Compared to how we track all other calls. Because suddenly we would be like, okay, now a precompile is called, so we don't care.
Łukasz Rozmej: Okay, but what's the purpose? Just completeness?
Toni Wahrstätter: Yeah, or using existing code. For example, if you put the tracking function on top of the call. Then you don't need any edge cases to handle a call to a pre-compile differently to a call to any other contract. At least in the specs, I think in the specs it's tracked right now, because in the specs, there is basically a tracker that wraps around all the state changes. And it doesn't differentiate between a pre-compiled being called or any other contract. If I remember correctly, I would also need to double-check that.
Dragan Rakita: And if we compare this behavior with access list, we are including only the accounts from… not all accounts from Axis, but only accounts that got, accessed. It's a slightly different version, but… Yeah.
Toni Wahrstätter: Right, yeah.
Dragan Rakita: Yeah, you can argue that you can remove all the accounts that are found inside access list. I don't think that nobody wants that, but just for the argument's sake.
Toni Wahrstätter: Yeah, currently we treat, for example, no matter if a balance opcode happens or a call opcode, we treat it exactly the same. And here, for example, if a transaction calls the balance of a precompile, then we would need to add it, right? No matter if we decide to omit the precompile reads or not, we would need to add it, because we do want to know that this contract is going to be read. But on the other hand, if a precompile is called, Doing some computation. Then we would need to manually omit it. And to me, right now, it feels like it would be cleaner To just keep them. in there, but I can see the argument why this might just be unnecessary data, if this precompile is, like, called.
Yeah, with something like call or static call.
Marc Harvey-Hill: I mean, it seems like it would be… Relatively easier to, if you are tracking it, just have an exception and not log the precompiles than to… Like, for… on our side to change stuff. So that we are tracing these accesses. Yeah, it just seems easier to… the other way around, I guess, and also the data isn't really useful.
Toni Wahrstätter: So, you would also say we should omit the reads. So, every time when a precompile is, Access through call or static call. it should not be included in the popular access list, but if it is accessed through balance, or Xcode size, or something, then we should include it.
Marc Harvey-Hill: Yup, I think so.
Toni Wahrstätter: Okay. Any other opinions on that?
Dragan Rakita: Yeah, well, I'm not sure if this is to type, but yeah. In that case, we'll have, like, the different behaviors, depending on if it's call opcode, or it's balance opcode, or some else, by the opcode.
Toni Wahrstätter: Right. Yeah, I have no insight into client code, but I can definitely tell in the specs and the execution specs, this will make things a little dirtier. I guess there will be a simple way to change it, but this is the first time I really have to… I would really have to differentiate between call and balance, for example.
Marc Harvey-Hill: I guess I don't have a super strong opinion, like, maybe if I could… Kind of look at the code a bit more, and if it turns out to be, like. A lot more com- complex, and… Yeah, I just need to review it, really, to kind of determine.
Toni Wahrstätter: Yeah, sounds good. I would say we, for now, we just keep it as is. Right now, it's kind of underspecified in the EUP, but I will add some proper specifications on that, but right now it would be tracked. So, if it's accessed through a call, it's in the block of access list, and if it's accessed through a balance, opcode, it's also in there. And if… if the precompiler even sees a balance change, it's even in there with a balance change. Map to the transaction index. We can still, re-evaluate that decision in, like, in two weeks. I think it's good that we brought it up, and clients have, Clients know that this is something we should look into and get some clearance on it.
But then I would say, let's just, yeah, keep it as is, and if we decide in two weeks that Yeah, we were wrong, and we should actually do it differently, we can still do it. Right. Yeah, this was, the last, topic we had on the agenda. Last thing would be just client updates. Should we directly start with Jared Gaffigan?
Jared Wasinger: Yeah, sure. I… I think we had one more thing we wanted to talk about, actually. Well, I brought it up in the chat today, so there was this idea in the BAL to… there's this idea to commit in the BAL to the, the post… state, try root for, storage tries, and essentially what that would enable us to do is fully parallelize the account try and storage try, hashing. And I had brought that up as a… because that was a suggestion from Gary on our team, but, Just wanted to say that, It's not clear from the guest side whether… what the benefit of that change would potentially bring.
But the reason I brought it up in the channel was to, to, Just try and put it on people's radars as something to explore. And we will definitely be doing that, yeah.
Toni Wahrstätter: Okay, yeah, this is interesting. Just to summarize, this would mean the block of access list would additionally Contain, for each account, The post-storage route of that account.
Jared Wasinger: So, yeah, so if the account had modified storage, we would include the root. I mean, we could, of course, include the root for every account, but we could also… it could be limited to ones with modified storage.
Toni Wahrstätter: Okay, and it will be the post… State of the post, the state after the block, and not,
Jared Wasinger: Yeah, exactly.
Toni Wahrstätter: Each transaction, right? Okay. Yeah, this is definitely interesting. Curious if other clients… we don't need to decide upon that today, and I think Gary even said he wants to keep that on client-level optimization. But they are curious if other clients… Clients have an opinion on that, or thought about it. I see Karim advocating Even for putting it into the popular access list, I guess?
Karim Taam: is for computation, I think it would be nice to…
Jared Wasinger: I guess I'll just say that my impression right now is that most of our bottleneck with the state root update is in loading the intermediate nodes, so… It's not clear in the end how much this… how much being able to fully parallelize the updates between the account try and the storage tries will bring, but…
Karim Taam: I think in Bezoo, we have this kind of preloading, so we can preload, Intermediate node, and today the main part is doing the… Okay, check computation of intermediates. And mainly also for the account part, because the storage are done in parallel. So I think if we can, at the same time… compute the state root of the tree account. I think it will help for performance, but I'm not completely sure.
Toni Wahrstätter: Yeah, this is definitely an interesting change. I think we should… Proceed with it, you know, and then we first get some more data, how much we would actually get from that. keep the design of blocker access list as it is right now, and just keep it in our… yeah, in our mind that this is, a simple optimization, because Just in terms of additional data footprint, it wouldn't add that much. So it's, it would only be, like, 32 bytes per changed account, so this would not be the problem. But yeah, let's keep it as it is for now, and… In case we ever, need to speed up, the hashing. Then we can still edit.
Jared Wasinger: Yep, I agree.
Toni Wahrstätter: Great, yeah, thanks for reminding me, almost forgot that point. Any other opinion on that? If not, I would just suggest we continue with the client updates.
Jared Wasinger: Yeah, sure, I can give a brief update, there's not too much that has changed from our side to report here. We're still… we're working on improving the performance right now, and… assisting where possible with the DevNet work. So right now, most of the bottleneck for us is in Geth is the… state route update, so that's where… That's where we have and will be focusing on in the immediate future. And, yeah, that's it.
Toni Wahrstätter: Thank you. And what about Millamide?
Marc Harvey-Hill: On my side, just, trying to get our implementation to pass the execution spec tests, just debugging a few differences in the, encoding format. Yeah, that's fun.
Toni Wahrstätter: Thanks, yeah, and just to add on that, also one of the execution spec tests is now wrong, the one affecting self-destruct, so ignore. this one for now, Rahul will fix that, as sooner. As I fix the spec. Just as a notice. Let's continue with Bezo.
Karim Taam: Yeah, so today we implemented the engine API, we are passing the reference test. We are trying to run a local devnet with Git and Bizu. But for the moment, we can see that the blocks created by Bezo are refused by GET, so we are trying to investigate why. But I'm not completely sure, but it seems that the block created by GET are accepted by Viso. So… so the next step is to debug and understand why our block access is not valid.
Toni Wahrstätter: Interesting. So you… this might be something we didn't… Catch, we didn't call with the… With the test yet?
Karim Taam: I think yes, but for the moment, we just saw that before the meeting, so we didn't have time to… to check more. But, I feel that, yeah, maybe we are missing something in the testing.
Toni Wahrstätter: Okay, thank you. Aragon? Is someone from Aragon here? Otherwise, let's continue with ref.
Dragan Rakita: Pushka?
Ishika: Yes, not, much we got to do. Concerning the changes, we were running the new sets of the, tests, and, like, it works pretty well with the consume RLP, but we have some blockers concerning the consumer engine. We are currently looking into the locks and trying to investigate the causes. We'll come to a conclusion soon, I believe. Yeah.
Felipe Selmo: I'd like to… Yep. About the test, too. The latest release, so if it's for Consume Engine. We do have updates there. We were using V4 instead of V5. And so, I'd like to get a new release out, that has all of the updates, and I'm planning on Getting this out by today. So look out for a new release of the tests.
Toni Wahrstätter: Awesome. Thank you very much. Great. Is there anything else we should discuss on this call? Something we should bring up? If not… I would suggest we can end the call at this point. Great. Dan, thank you very much, Erwin. And see you on the next one. Call in 2 weeks.
Marc Harvey-Hill: Right?
Karim Taam: What?
Felipe Selmo: Thanks, bye.
Łukasz Rozmej: Bye.
Transcript adapted from ethereum/pm, CC BY-SA 3.0.