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

feat: blob staleness check #226

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

samlaf
Copy link
Collaborator

@samlaf samlaf commented Jan 8, 2025

We add a check to discard blobs that have been included in the rollup's batcher inbox a long time after it was included in an EigenDA batch by the eigenda disperser.

It is now expected that GET requests to the proxy can include an optional l1_inclusion_block_number query argument, which will cause a blob to be rejected if it was included onchain > RollupBlobInclusionWindow L1 blocks after the blob's batch's RBN.

The RollupBlobInclusionWindow is a flag added to the proxy binary, which is set to 0 by default, which skips the check.

Fixes Issue

Fixes #

Changes proposed

Screenshots (Optional)

Note to reviewers

Commits are:

  • 8eaef49: contains the main feature logic
  • a3b6ca4: test the query param parsing logic
  • 0cf0e78: test the verification logic (also includes refactoring the CertVerifier to an interface to be able to test other verification that doesnt need an eth rpc connection)

We add a check to discard blobs that have been included in the rollup's batcher inbox a long time after it was included in an EigenDA batch by the eigenda disperser.
@samlaf samlaf marked this pull request as draft January 8, 2025 22:32
samlaf added 3 commits January 8, 2025 18:31
Made the Verifier take an interface for the certVerifier instead of the struct, to allow me to test functionality that didn't depend on having a blockchain connection (like the certVerifier does).

This way we can test the blob staleness check by using a noopCertVerifier to skip all the other verification that require an eth rpc.
@samlaf samlaf marked this pull request as ready for review January 9, 2025 02:11
@samlaf samlaf requested review from epociask, litt3 and bxue-l2 January 9, 2025 02:11
// The eigenDA batch header contains a reference block number (RBN) which is used to pin the stake of the eigenda operators at that specific blocks.
// The rollup batch containing the eigenDA cert is only valid if it was included within a certain number of blocks after the RBN.
// validity condition is: RBN < l1_inclusion_block_number < RBN + some_delta
RollupL1InclusionBlockNum int64
Copy link
Collaborator

Choose a reason for hiding this comment

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

shouldn't this be uint64?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm using -1 to mean that this query param was not passed, and hence to skip the test. Could using a pointer to uint64 instead and use nil to mean this? I don't like either tbh... but go is dumb.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Can't you use 0 to indicate "skip the test"?

RollupL1InclusionBlockNum must be strictly greater RBN, and the min value of RBN is 0, therefore RollupL1InclusionBlockNum == 0 isn't sensical, and is free realestate to have special meaning

// to ensure disperser returned fields haven't been tampered with
type CertVerifier struct {
type CertVerification struct {
Copy link
Collaborator

Choose a reason for hiding this comment

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

knit - any reason to make this public if we're binding to an interface?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not really, I can change. Don't think using an interface matters here though, it's more about whether we think people will want to import and use this struct elsewhere.

// If batch.RBN + rollupBlobInclusionWindow < cert.L1InclusionBlock, the batch is considered stale and verification will fail.
// This check is optional and will be skipped when rollupBlobInclusionWindow is set to 0.
// Note: if there are more rollup related properties that we need to check in the future, then maybe create a RollupVerifier struct
rollupBlobInclusionWindow uint32
Copy link
Collaborator

Choose a reason for hiding this comment

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

wouldn't this need to be globally set per the rollup? Otherwise couldn't you have multiple verifiers articulating alternative views due to different values?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes that's a good point. I wrote the code out but now's the time to discuss how we want to handle this feature. cc @bxue-l2 for discussion. I'll at least add a bit WARNING comment to the flag, saying that the feature is experimental and not really meant to be turned on? But then what's our plan for eventually turning it on?

// 1 - verify batch in the cert is confirmed onchain
err := v.cv.verifyBatchConfirmedOnChain(ctx, cert.Proof().GetBatchId(), cert.Proof().GetBatchMetadata())
if err != nil {
return fmt.Errorf("failed to verify batch: %w", err)
}

// 2 - verify merkle inclusion proof
// 2 - verify that the blob cert was submitted to the rollup's batch inbox within the allowed RollupBlobInclusionWindow window.
// This is to prevent timing attacks where a rollup batcher could try to game the fraud proof window by including an old DA blob that is about to expire on the DA layer
Copy link
Collaborator

Choose a reason for hiding this comment

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

how will this verification look when one step proving the certificate? ie, will a challenger need to provide the inbox confirmation tx block #, if so then how do they do so in a way that isn't tamper resistant?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Right precisely the right question to ask. Discussed this at length with Bowen. I'll need to add comments detailing this.

The 2 options for securing this check is:

  1. do it in derivation pipeline
  2. do it in preimage oracle contract (as you mention, this would require a zk proof that the inclusion block # is correct)

We are opting for 1, to do this check in the derivation pipeline. It's a simple check to make in the rust hokulea client, so we'll add it there. Ideally we'd add the same check to the golang derivation pipeline, but the problem is that we haven't had much success lately getting our PRs upstreamed. Also if ever a problem happens and we need to change the logic in the golang binary then we depend on optimism's team to review and merge our PR, which feels less than ideal (this is different from the rust code b/c in rust we have our own Hokulea library which projects using eigenda can import directly, and which we have full control over).

So the idea is that the rust stack will be a requirement to do the fraud proofs. But we still want normal validators running op-node to be able to follow the chain properly, which is why we are adding this check here in the proxy.

cc @bxue-l2 for any more thoughts.

Comment on lines +3 to +7
//
// Generated by this command:
//
// mockgen -package mocks --destination ../mocks/manager.go . IManager
//
Copy link
Collaborator

Choose a reason for hiding this comment

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

There's also make go-gen-mocks

// This is optional, and should be set to -1 to mean to not verify the reference block number distance check.
//
// Used to determine the validity of the eigenDA batch.
// The eigenDA batch header contains a reference block number (RBN) which is used to pin the stake of the eigenda operators at that specific blocks.
Copy link
Collaborator

Choose a reason for hiding this comment

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

"at that specific blocks" -> "at that specific block" ?

if !(blockNumEigenDA < blockNumRollup) {
return fmt.Errorf("eigenda batch reference block number (%d) needs to be < rollup inclusion block number (%d)", blockNumEigenDA, blockNumRollup)
}
if !(blockNumRollup < blockNumEigenDA+int64(v.rollupBlobInclusionWindow)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe you have an off-by-one error here

The comment in the config struct above states that the following constitutes a stale batch:

batch.RBN + RollupBlobInclusionWindow < cert.L1InclusionBlock

but it seems what's actually implemented is this:

batch.RBN + RollupBlobInclusionWindow <= cert.L1InclusionBlock

It might help to have consistent variable names between the two.

blockNumRollup := args.RollupL1InclusionBlockNum
// We need blockNumEigenDA < blockNumRollup < blockNumEigenDA + rollupBlobInclusionWindow
if !(blockNumEigenDA < blockNumRollup) {
return fmt.Errorf("eigenda batch reference block number (%d) needs to be < rollup inclusion block number (%d)", blockNumEigenDA, blockNumRollup)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggest adding a test case which covers this error, since the conditional is non trivial

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.

3 participants