-
Notifications
You must be signed in to change notification settings - Fork 238
feat: cert verifier v3 + router #1518
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
Conversation
fix: remove operator state retriever change fix: readd v1 test
9b31539
to
d93e21e
Compare
3ec0ab9
to
4d17389
Compare
contracts/src/periphery/cert/router/EigenDACertVerifierRouter.sol
Outdated
Show resolved
Hide resolved
contracts/src/periphery/cert/router/EigenDACertVerifierRouter.sol
Outdated
Show resolved
Hide resolved
contracts/src/periphery/cert/router/EigenDACertVerifierRouter.sol
Outdated
Show resolved
Hide resolved
contracts/src/periphery/cert/router/EigenDACertVerifierRouter.sol
Outdated
Show resolved
Hide resolved
contracts/src/periphery/cert/router/EigenDACertVerifierRouter.sol
Outdated
Show resolved
Hide resolved
contracts/src/periphery/cert/router/EigenDACertVerifierRouter.sol
Outdated
Show resolved
Hide resolved
contracts/src/periphery/cert/router/EigenDACertVerifierRouter.sol
Outdated
Show resolved
Hide resolved
Co-authored-by: ethenotethan <[email protected]>
contracts/src/periphery/cert/interfaces/IEigenDACertTypeBindings.sol
Outdated
Show resolved
Hide resolved
contracts/src/periphery/cert/interfaces/IEigenDACertVerifierBase.sol
Outdated
Show resolved
Hide resolved
contracts/src/periphery/cert/interfaces/IEigenDACertVerifierRouter.sol
Outdated
Show resolved
Hide resolved
contracts/src/periphery/cert/router/EigenDACertVerifierRouter.sol
Outdated
Show resolved
Hide resolved
contracts/src/periphery/cert/router/EigenDACertVerifierRouter.sol
Outdated
Show resolved
Hide resolved
EigenDATypesV1.NonSignerStakesAndSignature memory nonSignerStakesAndSignature; | ||
nonSignerStakesAndSignature.nonSignerQuorumBitmapIndices = nssas.nonSignerQuorumBitmapIndices; | ||
nonSignerStakesAndSignature.nonSignerPubkeys = nssas.nonSignerPubkeys; | ||
nonSignerStakesAndSignature.quorumApks = nssas.quorumApks; | ||
nonSignerStakesAndSignature.apkG2 = nssas.apkG2; | ||
nonSignerStakesAndSignature.sigma = nssas.sigma; | ||
nonSignerStakesAndSignature.quorumApkIndices = nssas.quorumApkIndices; | ||
nonSignerStakesAndSignature.totalStakeIndices = nssas.totalStakeIndices; | ||
nonSignerStakesAndSignature.nonSignerStakeIndices = nssas.nonSignerStakeIndices; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you look carefully, you will see that none of this is used at all. Only the _nonSignerStakesAndSignature below is. Hence the deletions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
/// @notice Check a DA cert's validity | ||
/// @return status An enum value. Success is always mapped to 1, and other values are errors specific to each CertVerifier. | ||
function checkDACert(bytes calldata certBytes) external view returns (uint8 status); | ||
interface IEigenDACertVerifier is IEigenDACertVerifierBase { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I chatted with @samlaf about this, and thought deeper about the interface structure here. This is how I think it ought to be:

- When you call
IEigenDACertVerifierRouter.getCertVerifierAt
, you get back an address - The address can be cast to an
IVersionedCertVerifier
- You call
IVersionedCertVerifier.getVersion
- Based on the version, you know what specific type of verifier you have (
IEigenDACertVerifierV3
in this case), so you know what to cast the address to, specifically
And this is the current implementation, as I understand it:

I'm not sure what the data flow would look like here. After getting back the address from the router, you'd have to directly cast to the IEigenDACertVerifier
type. But that means we're locked into that specific interface: what if we want to create an implementation of the CertVerifier
which has different types of blob parameters, for example?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// @return The EigenDA signature verifier contract. | ||
function eigenDASignatureVerifier() external view returns (IEigenDASignatureVerifier); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this need to be exposed in the interface? Its just called under the hood when verifying the cert right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree actually. This shouldn't be here. I'll remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please add commit links? Makes the reviewer's job much much easier.
contracts/src/periphery/cert/interfaces/IEigenDACertVerifier.sol
Outdated
Show resolved
Hide resolved
import {IEigenDACertVerifierBase} from "src/periphery/cert/interfaces/IEigenDACertVerifierBase.sol"; | ||
|
||
interface IEigenDACertVerifierRouter is IEigenDACertVerifierBase { | ||
/// @notice Returns the address for a cert verifier at a given reference block number. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// @notice Returns the address for a cert verifier at a given reference block number. | |
/// @notice Returns the address for the active cert verifier at a given reference block number. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prob need to mention the conditions around this. Does every RBN always return an address? Even RBN=0? Why? What about RBN=super-far-block-in-the-future? Or should this only be called with an RBN that is <= current block?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally think that's a bit TMI. If a reader is curious about the edge cases, they can simply read the very short router code and the comments within. But generally, I don't believe general integrators would care about these details since the router handles edge cases already by being initialized with a cert verifier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's completely wrong. This is just basic information that any person calling this function should know. I was wondering myself while reading this. And in fact because of this querying I realized that there was a bug where we were allowing people to call this function with an RBN in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(bug being #1518 (comment))
function getCertVerifierAt(uint32 referenceBlockNumber) public view returns (address) { | ||
return certVerifiers[_findPrecedingRegisteredABN(referenceBlockNumber)]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this not revert if RBN > block.number?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should not introduce this in this PR because the existing cert verifier does not do this check either. And the block number check seems like something that should be happening in the verifier itself, not in the router. Hence, this suggestion IMO is a suggestion of a change in verification logic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I'm following. Where/how does the block number check belong to verifier logic? Its purely for determining which UNIQUE certVerifier NEEDS to attest to the validity of a given cert. Without this check then the validity of a cert can change over time right? First call router with a block BN in the future - cert is valid - then a new cert verifier is activated at the block BN-1
. Then check again at block BN - cert is invalid.
|
||
import {IEigenDACertVerifierBase} from "src/periphery/cert/interfaces/IEigenDACertVerifierBase.sol"; | ||
|
||
interface IEigenDACertVerifierRouter is IEigenDACertVerifierBase { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add comment describing what the purpose of this router is: proxy that holds access to past implementations, and where new implementations can only be added in the future, etc.
contracts/src/periphery/cert/router/EigenDACertVerifierRouter.sol
Outdated
Show resolved
Hide resolved
contracts/src/periphery/cert/router/EigenDACertVerifierRouter.sol
Outdated
Show resolved
Hide resolved
* @return errParams Additional error parameters | ||
* @return confirmedQuorumsBitmap The bitmap of confirmed quorums | ||
*/ | ||
function checkSignaturesAndBuildConfirmedQuorums( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this say signatureS (with an s)? We're only checking a single (aggregate) signature right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is the same nomenclature as the underlying signature verifier though.
function checkBlobQuorumsSubset(bytes memory blobQuorumNumbers, uint256 confirmedQuorumsBitmap) | ||
internal | ||
pure | ||
returns (StatusCode err, bytes memory errParams, uint256 blobQuorumsBitmap) | ||
{ | ||
blobQuorumsBitmap = BitmapUtils.orderedBytesArrayToBitmap(blobQuorumNumbers); | ||
|
||
if (BitmapUtils.isSubsetOf(blobQuorumsBitmap, confirmedQuorumsBitmap)) { | ||
return (StatusCode.SUCCESS, "", blobQuorumsBitmap); | ||
} else { | ||
return (StatusCode.BLOB_QUORUMS_NOT_SUBSET, abi.encode(blobQuorumsBitmap, confirmedQuorumsBitmap), 0); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why don't you compute the blobQuorumsBitmap outside and then just pass both bitmaps to this function? Feels a lot more natural than having this function return a blobQuorumsBitmap, which shouldnt have anything to do with checking blobQuorumsSubset.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is out of scope of this PR since this is the same logic that was in the cert verifier before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You've literally complained about every single thing in our current contracts' implementations though. So why does this one fall outside the purview of the refactor to make this contract more readable/understandable?
My POV is that these PRs are the only chance for reviewers to actually read the code and catch things like this. If we don't do them now these minor improvements never happen and the complexity overflows to our customers who have to read this difficult to understand code. And minor improvements compound (positively!) so I do think we should include small changes that require few lines of code to change (of course I agree things that require 50+ lines of code change should be done in separate PRs).
SUCCESS, // Verification succeeded | ||
INVALID_INCLUSION_PROOF, // Merkle inclusion proof is invalid | ||
SECURITY_ASSUMPTIONS_NOT_MET, // Security assumptions not met | ||
BLOB_QUORUMS_NOT_SUBSET, // Blob quorums not a subset of confirmed quorums |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is "confirmedQuorum" something used outside of this contract? Or is it a term you invented? I think "batchSignedQuorums" might make more sense? Otherwise its unclear what/how they got confirmed, and what confirmed means
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was the nomenclature of the verification logic previously. I did not invent it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Blocking this PR because I'm pretty unhappy with your comments addressal.
Main issue is that I think there's still a bug in the implementation: #1518 (comment)
Bug which you've completely dismissed #1518 (comment)
I might be completely wrong here, but it seems like you just dismissed this without giving it a second's thought.
In order to get this merge and keep the ball moving, I'm happy to merge after the above is fixed (unless I'm completely wrong and the bug isnt actually a bug). But I will add that I expect more of you. You haven't given commit links to addressals, so its very hard to re-review. Plus you've thumbs upped some of my requests without actually addressing them, so I have to go check one by one what is done and what is not... and I generally don't feel confident with this PR because of the bug found above. You also very easily dismiss some of my comments for better code quality like here, despite constantly arguing that our contract code is subpar. The CertVerifierV3 is supposed to be a clean slate, so I don't see why keeping around old shitty code is a valuable strategy when the refactor being asked is very simple and makes for much more readable code.
Dismissing my previous "requested changes" review because I'm going to bed but would like this to get merged today if possible.
We synced offline, and requires changes before merging are:
- adding check in router that RBN cannot be in future
- adding IEigenDACertVerifier interface
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
// We disallow adding cert verifiers at the current block number to avoid a race condition of | ||
// adding a cert verifier at the current block and verifying in the same block | ||
if (activationBlockNumber <= block.number) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is actually a great catch! We want cert verification at a block number to be deterministic. That can only be the case if block numbers are activated as a precursor to a given block's execution, which is exactly what our current design does now.
Otherwise you can have in a single block:
- tx1 verifies against certVerifier N
- certVerifier N+1 gets activated
- tx2 verifies same cert as tx1 but against certVerifier N+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right! I think these checks really need to be specified up front in a spec because it is hard to know whether this is even an invariant I want to protect without that. We should not just add require statements ad hoc because we can miss interactions across different functions.
|
||
import {EigenDATypesV1 as DATypesV1} from "src/core/libraries/v1/EigenDATypesV1.sol"; | ||
|
||
interface IEigenDACertVerifier is IEigenDACertVerifierBase, IVersionedEigenDACertVerifier { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I didn't loop you into this convo but I brought up a concern with sam that there didn't seem to be a good place to put an IEigenDACertVerifierV3 interface. It seemed better to name it IEigenDACertVerifier instead, and as they become deprecated we can add the version number and dump it into legacy. This is because we don't want to go back to the version numbers in the main folder for periphery, and there should only be one canonical interface that outside integrators should need to refer to. Version numbers would be confusing and would bring questions about which versions they should use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My only issue with "legacy" is it sounds like we no longer support it, whereas our clients and proxy need to be backward compatible with ALL versions >= 3 going forward, because we HAVE to assume some customer is running these versions in prod somewhere, and make sure to not break any of them. Something like "LTS" name makes more sense than "legacy" to me. But we can figure out the proper naming and place to put them later.
Why are these changes needed?
Closes DAINT-383.
Checks