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

Research Area: Introduce Nonce with each Consensus Influencing Event #330

Open
tynes opened this issue Aug 13, 2024 · 4 comments
Open

Research Area: Introduce Nonce with each Consensus Influencing Event #330

tynes opened this issue Aug 13, 2024 · 4 comments

Comments

@tynes
Copy link
Contributor

tynes commented Aug 13, 2024

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 the OptimismPortal
  • ConfigUpdated from the SystemConfig

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 #122 and a generic form of this

@tynes
Copy link
Contributor Author

tynes commented Aug 24, 2024

TransactionDeposited V2

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 #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.

@puma314
Copy link

puma314 commented Aug 25, 2024

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.

@tynes
Copy link
Contributor Author

tynes commented Sep 16, 2024

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: #330 (comment)

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.

@puma314
Copy link

puma314 commented Sep 17, 2024

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 noncees 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.

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

No branches or pull requests

2 participants