Open tynes opened 3 months ago
The purpose of the V2 TransactionDeposited
event is to add a nonce such that the fault proof program no longer needs to iterate over all possible logs to know that it has correctly included all of the deposits. The fault proof program can be given proofs to each subsequent log and ensure that the nonces increment by one each time.
The definition of a TransactionDeposited
event is as follows:
event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData);
The version
field can be used to determine the encoding of the opaqueData
. The v0 definition of opaqueData
is:
bytes memory opaqueData = abi.encodePacked(_mint, _value, _gasLimit, _isCreation, _data);
For v1, we can add a nonce to opaqueData
like so:
bytes memory opaqueData = abi.encodePacked(nonce, _mint, _value, _gasLimit, _isCreation, _data);
The nonce
would be incremented after each deposit transaction and held in storage in the OptimismPortal
.
Lets say that there were 3 deposits in block 10 with the nonces 5, 6 and 7. To guarantee that we observed all of the deposits in block 10, we would need proofs for deposits with nonces 4, 5, 6, 7 and 8. We would observe that 5-7 are in block 10 and 4 and 8 are not in block 10. If we used a more complicated data structure on chain, it would be possible to relax needing to observe the boundaries. We would need to be careful with edge cases around 0. Note that this can be added to the system without any need for the fault proof programs to instantly upgrade to and adopt, they can adopt it at any time.
In the derivation pipeline, we would need to update the parsing of the data here but we could ignore the nonce. It is only useful in the fault proof program, to guarantee that we have observed all deposits.
A nice property to observe would be to have a nonce in the L1Block
contract that accumulates the number of deposits. This would essentially give the ability to look at the nonce on L1 in the OptimismPortal
and know how many deposits are waiting to be pulled into L2. This can be useful for applications that depend on censorship resistance or to learn the size of the deposit queue, as specified in https://github.com/ethereum-optimism/specs/pull/82. The only way to do this in a backwards compatible way would be to introduce the logic for parsing v1 opaqueData
in the op-node
before upgrading the OptimismPortal
on L1. The op-node
would need to include a "number of deposits" parameter in the L1 attributes transaction. This parameter would only increment for v1 deposits, so until the OptimismPortal
is updated to use TransactionDeposited
v1, the "number of deposits" parameter would always be 0.
This would be a great change that would make derivation much cheaper (much smaller witness data from not having to pull in all L1 receipts for every L1 block & also not requiring iteration through all of these receipts).
I believe that having the nonce in the L1Block
contract is required for sound derivation, because otherwise there is an attack vector where during derivation once can pull in 0 DepositTransactions even if there are many outstanding.
Updated approach, much more simple. The idea is to add a nonce to the events in a backwards compatible way. Rather than trying to reduce the number of consensus influencing events, we continue to have 2 contracts that both have their own consensus influencing event. We create a unified way of encoding the nonce into the event.
event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data);
event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData);
These are the definitions of the consensus influencing events. Both of them have a uint256
version. We can tightly pack this in a backwards compatible way to include the nonce. The nonce could be a uint128
or a uint64
in the more significant bits of the version
.
This would mean that both the OptimismPortal
and the SystemConfig
both have a nonce that is incremented each time that a consensus influencing event is emitted. This approach skips the idea of keeping a counter for number of deposits processed in L2 that is mentioned here: https://github.com/ethereum-optimism/specs/issues/330#issuecomment-2308569699
This would require some small modifications to op-node
, which would involve the assertions on the version to only take a subset of the value rather than the full value. This could be done in a soft fork without breaking anything.
The version
field is indexed in both events, meaning that it could be easy to query for all logs between two nonces by computing the version client side for the first event that you care about and the last event that you care about, then look at the blocknumbers on those events, then send another logs query for getting all logs within that blocknumber range for the event itself.
Edited: This seems super clean and would love to assist with implementing this. Seems like a very small diff that would be a very small change in op-node
(and op-program
+ kona
to only parse the lower bits of the version
field) & also adding these nonce
es to the smart contracts.
One thing to note is that when implementing this in a fault proof program, in order to not iterate through all L1 receipts, we have to make sure that at each block boundary, we get the state of the OptimismPortal
and SystemConfig
contract and check that the final processed TransactionDeposited
nonce and SystemConfig
nonce match those nonces in the state. Otherwise, an attack vector exists where we just claim that there are no such events.
I think that @ajsutton mentioned that we have to also store a nonce
state variable in both the OptimismPortal
and SystemConfig
to make sure that when we process an L1 block that we are indeed including all of the ConfigUpdate
and TransactionDeposited
events.
Otherwise there's an attack vector where if we don't iterate through all of the L1 logs, we just ignore all of those events & progress the chain without them. If we include the nonce
state variable, then we can just witness only relevant L1 logs & then at the end check that the last processed nonce matches the nonce in the contract.
Thinking more, I think anyways we require having a nonce
state variable to keep it incremented with each event.
We should consider introducing a nonce with each log that influences consensus. This would allow for an optimization within the proof programs where they no longer need to observe and filter all logs to guarantee that they have the full set of logs. A witness can be used to populate the logs and the program can check that the nonces all line up to guarantee that the complete set of logs are present within the program.
There are currently 2 logs that impact consensus:
TransactionDeposited
from theOptimismPortal
ConfigUpdated
from theSystemConfig
TransactionDeposited
We could have a nonce on L1 and on L2 and keep them up to date, this could be generally useful to know how many deposits have yet to be processed by offchain software. If we want to hold the invariant that $$L1nonce <= L2nonce$$ then we would need to add the number of deposits inbound in the L1 attributes transaction, since there would be a race condition with upgrading the contracts for existing chains if we simply incremented on L2. This would require a new deposit version and the accumulator would in the L1 attributes transaction would only count deposits that are of the new version
ConfigUpdated
I think that these events can be removed completely from consensus given we follow the pattern designed in https://github.com/ethereum-optimism/specs/issues/122 and a generic form of this