Skip to content

Conversation

@dailinsubjam
Copy link
Collaborator

@dailinsubjam dailinsubjam commented Jul 25, 2025

Closes https://app.asana.com/1/1208976916964769/project/1209393353274209/task/1210651926603246?focus=true

Context: So the only change we made to normal op-node is that “reverted transactions from L1 will be skipped”, and this might lead to a chain divergence if different operators start enabling it at different time, then in such case we might need a timestamp for normal op-node.

This PR:

  • Adds CeloEspressoTimestamp for op-node's migration
  • The pass of Test6 TestE2eDevNetWithoutAuthenticatingBatches can prove the changes work

This PR does not:

Key places to review:

Copy link
Member

@shenkeyao shenkeyao left a comment

Choose a reason for hiding this comment

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

LGTM overall, just one question.

Comment on lines 103 to 110
func isValidBatchTx(tx *types.Transaction, receipt *types.Receipt, l1Signer types.Signer, batchInboxAddr, batcherAddr common.Address, logger log.Logger, celoEspressoTimestamp *uint64) bool {
// If CeloEspresso is activated, return false if the transaction is reverted
if receipt.Status != types.ReceiptStatusSuccessful {
return false
logger.Info("tx in inbox with reverted status", "hash", tx.Hash(), "status", receipt.Status)
if celoEspressoTimestamp != nil && time.Now().Unix() >= int64(*celoEspressoTimestamp) {
logger.Info("tx is dropped since it is reverted")
return false
}
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible that receipt has a failed status, but not because the transaction is reverted? Double-checking this so we won't mark a transaction as valid if it fails due to a different reason.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That's a great point. OP or Celo does not have this check and we added it, so I think wrapping it in the EspressoEnabled flag adds minimal overhead and gives us extra safety. But worth tagging @QuentinI to confirm that this logic only applies to reverted transactions.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it possible that receipt has a failed status, but not because the transaction is reverted?

No, a failed status necessarily means transaction was reverted

Comment on lines 103 to 110
func isValidBatchTx(tx *types.Transaction, receipt *types.Receipt, l1Signer types.Signer, batchInboxAddr, batcherAddr common.Address, logger log.Logger, celoEspressoTimestamp *uint64) bool {
// If CeloEspresso is activated, return false if the transaction is reverted
if receipt.Status != types.ReceiptStatusSuccessful {
return false
logger.Info("tx in inbox with reverted status", "hash", tx.Hash(), "status", receipt.Status)
if celoEspressoTimestamp != nil && time.Now().Unix() >= int64(*celoEspressoTimestamp) {
logger.Info("tx is dropped since it is reverted")
return false
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it possible that receipt has a failed status, but not because the transaction is reverted?

No, a failed status necessarily means transaction was reverted

if receipt.Status != types.ReceiptStatusSuccessful {
return false
logger.Info("tx in inbox with reverted status", "hash", tx.Hash(), "status", receipt.Status)
if celoEspressoTimestamp != nil && time.Now().Unix() >= int64(*celoEspressoTimestamp) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is not deterministic. We need to be comparing to the block timestamp, not current time.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated in 71556a2.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Unfortunately, tx.Time() isn't deterministic either, this is time this particular node has first seen the tx, not a part of consensus. I don't think we can get around pushing thorough the L1 block ref to here and checking against that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated in c0d6688.

@dailinsubjam
Copy link
Collaborator Author

dailinsubjam commented Aug 6, 2025

After discussing with @Sneh1999, we concluded that while using an activation timestamp doesn’t hurt, it might not actually help either.

Here’s the old proposed migration flow:

  1. Stop the old batcher.

  2. Deploy the new contracts and start the Espresso dev node.

  3. Start the new batcher, which should connect to Espresso and begin submitting attestations.

  4. Restart the op-nodes. All op-nodes will switch to the new version at the same timestamp. By this point, the necessary contracts will already be deployed.

The key point is: op-nodes should switch to the new derivation pipeline before the new batcher is integrated. As long as this order is maintained, the check will be triggered once we switch the batcher, and the system remains safe without the activation timestamp.

So, the activation timestamp doesn’t provide additional guarantees—unless the check we add can also filter out any transactions pre-Espresso.

The most likely order is 2 → 4 → 1 → 3, to minimize the gap between stopping the old batcher and starting the new one.

@dailinsubjam
Copy link
Collaborator Author

Pasting @jbearer 's comments and we might use this instead, will also document in the book:

I think the canonical way to handle this type of migration is to have a flag in the chain config like espressoEnabled. This can be set to true via an onchain upgrade action (the chain config is just an L1 smart contract). At any point in the derivation pipeline, we have a well defined L1Origin, so we can get the chain config at that L1 block number (there should already be code for this) and then just gate the revert check with if cfg.espressoEnabled.
I believe it does load the latest chain config every time there is a new L1 origin.
The main advantage of this over the timestamp is that the timestamp approach would be inventing a whole new mechanism to trigger an upgrade, whereas this mechanism (changing some variable in the chain config contract) is already implemented I think.

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.

4 participants