Skip to content

Commit

Permalink
feat: EigenDAV2 commitment processing and generation (#265)
Browse files Browse the repository at this point in the history
* fix(#251): better error logging for RPC lookup errors against service manager (#254)

* chore: modify verifier to not require eth archive node (#241)

* chore: force verifier's EthConfirmationDepth to be <64

We panic in the flag's action, as well as in the verifier's constructor when this condition is not respected.

This will make sure that an archival node is not required.

* chore: modify verifier to load quorum parameters only once at initialization

This removes the need for running with an eth archive node.

* style: fix minor lint issue

* docs: update README to mention that archival node is no longer needed

* docs: clean-up README archival node requirement explanation

* docs: fix verify/cert.go comment typo

Co-authored-by: Ethen <[email protected]>

* docs: for eg -> e.g.

* style(cert): remove unecessary bound checks from inside loop

* style: create consts package with EthHappyPathFinalizationDepthBlocks = 64

* style: change panic into error return

* docs: change op reference for eth reference

* docs: make flag comment simpler

* Update verify/cert.go

Co-authored-by: EthenNotEthan <[email protected]>

---------

Co-authored-by: Ethen <[email protected]>

* docs: pimp out readme with blob lifecycle diagrams (#233)

* chore: move pull_request_template.md under .github/ dir

* docs: reorder README sections to feel more natural (move flags to bottom)

* docs (wip): add blob lifecycle diagrams to README

* docs: remove Sidecar from README title (proxy is not necessarily a side)

* docs: add blob lifecycle section to README

* docs: add TOC to README

* style(routing): rename raw_commitment -> payload

We changed the name in README so should be consistent in the code

* docs: update README sections to use Payload instead of Blob

Posting Blobs -> Posting Payloads
Retrieving Blobs -> Retrieving Payloads

* docs: remove "obviously" word

* Required quorums glitch (#255)

* Avoid quorum 1 check on range of Holesky blocks

* Improve SVC address check

* Update verify/verifier.go

Co-authored-by: Samuel Laferriere <[email protected]>

* Update verify/verifier.go

Co-authored-by: Samuel Laferriere <[email protected]>

* Avoid unnecessary cast

* Rename constant

* Fix lint

---------

Co-authored-by: Samuel Laferriere <[email protected]>

* docs: update README posting payload image (#256)

* fix: remove last eth_call that required archive node (#259)

Forgot this one in #241

* docs: update SECURITY.md with audit commit + fix small typos (#263)

* docs: update SECURITY.md

* docs: update SECURITY.md with audited release number + release where findings were addressed

* feat: EigenDAV2 commitment processing and generation

* feat: EigenDAV2 commitment processing and generation - add note describing follow up todo

---------

Co-authored-by: Samuel Laferriere <[email protected]>
Co-authored-by: Gaston Ponti <[email protected]>
  • Loading branch information
3 people authored Jan 30, 2025
1 parent 355fc24 commit 33b2cc8
Show file tree
Hide file tree
Showing 20 changed files with 432 additions and 294 deletions.
File renamed without changes.
311 changes: 189 additions & 122 deletions README.md

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ Please see [Releases](https://github.com/Layr-Labs/eigenda-proxy/releases)

## Audit reports

Audit reports are published in the `docs/audits` folder: https://github.com/Layr-Labs/eigenda-proxy/main/docs/audits
Audit reports are published in the [docs/audits](https://github.com/Layr-Labs/eigenda-proxy/tree/main/docs/audits) folder.

| Date | Report Link |
| ------- | ----------- |
| 202501 | [pdf](https://github.com/Layr-Labs/eigenda-proxy/blob/main/docs/audits/Sigma_Prime_EigenDA_Proxy_Security_Assessment_Report.pdf) |
| Date | Release (Commit) Audited | Report Link | Findings Addressed in Release |
| ------ | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
| 202501 | v1.6.1 (9e1b746) | [pdf](https://github.com/Layr-Labs/eigenda-proxy/blob/main/docs/audits/Sigma_Prime_EigenDA_Proxy_Security_Assessment_Report.pdf) | v1.6.2 |

## Reporting a Vulnerability

**Please do not file a public ticket** mentioning the vulnerability.
**Please do not file a public ticket** mentioning a vulnerability.

Please report security vulnerabilities to [email protected] with the all the relavent details included in the email.
Please report security vulnerabilities to [email protected] with all the relavent details included in the email.
38 changes: 19 additions & 19 deletions commitments/eigenda.go
Original file line number Diff line number Diff line change
@@ -1,39 +1,39 @@
package commitments

type CertEncodingCommitment byte
type EigenDACommit byte

const (
CertV0 CertEncodingCommitment = 0
// EigenDA V1
CertV0 EigenDACommit = iota
CertV1
)

// OPCommitment is the binary representation of a commitment.
// CertCommitment is the binary representation of a commitment.
type CertCommitment interface {
CommitmentType() CertEncodingCommitment
CommitmentType() EigenDACommit
Encode() []byte
Verify(input []byte) error
}

type CertCommitmentV0 []byte

// NewV0CertCommitment creates a new commitment from the given input.
func NewV0CertCommitment(input []byte) CertCommitmentV0 {
return CertCommitmentV0(input)
type EigenDACommitment struct {
prefix EigenDACommit
b []byte
}

// DecodeCertCommitment validates and casts the commitment into a Keccak256Commitment.
func DecodeCertCommitment(commitment []byte) (CertCommitmentV0, error) {
if len(commitment) == 0 {
return nil, ErrInvalidCommitment
// NewV0CertCommitment creates a new commitment from the given input.
func NewCertCommitment(input []byte, v EigenDACommit) EigenDACommitment {
return EigenDACommitment{
prefix: v,
b: input,
}
return commitment, nil
}

// CommitmentType returns the commitment type of Keccak256.
func (c CertCommitmentV0) CommitmentType() CertEncodingCommitment {
return CertV0
// CommitmentType returns the commitment type of EigenDACommitment.
func (c EigenDACommitment) CommitmentType() EigenDACommit {
return c.prefix
}

// Encode adds a commitment type prefix self describing the commitment.
func (c CertCommitmentV0) Encode() []byte {
return append([]byte{byte(CertV0)}, c...)
func (c EigenDACommitment) Encode() []byte {
return append([]byte{byte(c.prefix)}, c.b...)
}
14 changes: 7 additions & 7 deletions commitments/mode.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (

type CommitmentMeta struct {
Mode CommitmentMode
// CertVersion is shared for all modes and denotes version of the EigenDA certificate
CertVersion uint8
// version is shared for all modes and denotes version of the EigenDA certificate
Version EigenDACommit
}

type CommitmentMode string
Expand All @@ -31,19 +31,19 @@ func StringToCommitmentMode(s string) (CommitmentMode, error) {
}
}

func EncodeCommitment(b []byte, c CommitmentMode) ([]byte, error) {
switch c {
func EncodeCommitment(b []byte, cm CommitmentMode, daVersion EigenDACommit) ([]byte, error) {
switch cm {
case OptimismKeccak:
return Keccak256Commitment(b).Encode(), nil

case OptimismGeneric:
certCommit := NewV0CertCommitment(b).Encode()
certCommit := NewCertCommitment(b, daVersion).Encode()
svcCommit := EigenDASvcCommitment(certCommit).Encode()
altDACommit := NewGenericCommitment(svcCommit).Encode()
return altDACommit, nil

case Standard:
return NewV0CertCommitment(b).Encode(), nil
case Standard: // (i.e, Arbitrum)
return NewCertCommitment(b, daVersion).Encode(), nil
}

return nil, fmt.Errorf("unknown commitment mode")
Expand Down
8 changes: 8 additions & 0 deletions common/consts/consts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package consts

// EthHappyPathFinalizationDepth is the number of blocks that must be included on top of a block for it to be considered "final",
// under happy-path aka normal network conditions.
//
// See https://www.alchemy.com/overviews/ethereum-commitment-levels for a quick TLDR explanation,
// or https://eth2book.info/capella/part3/transition/epoch/#finalisation for full details.
var EthHappyPathFinalizationDepthBlocks = uint8(64)
11 changes: 8 additions & 3 deletions flags/eigendaflags/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package eigendaflags

import (
"fmt"
"log"
"strconv"
"time"

"github.com/Layr-Labs/eigenda-proxy/common/consts"
"github.com/Layr-Labs/eigenda/api/clients"
"github.com/Layr-Labs/eigenda/api/clients/codecs"
v2_clients "github.com/Layr-Labs/eigenda/api/clients/v2"
Expand Down Expand Up @@ -137,6 +137,7 @@ func CLIFlags(envPrefix, category string) []cli.Flag {
Category: category,
},
&cli.BoolFlag{
// This flag is DEPRECATED. Use ConfirmationDepthFlagName, which accept "finalization" or a number <64.
Name: WaitForFinalizationFlagName,
Usage: "Wait for blob finalization before returning from PutBlob.",
EnvVars: []string{withEnvPrefix(envPrefix, "WAIT_FOR_FINALIZATION")},
Expand Down Expand Up @@ -308,8 +309,12 @@ func validateConfirmationFlag(val string) error {
return fmt.Errorf("confirmation-depth must be either 'finalized' or a number, got: %s", val)
}

if depth >= 64 {
log.Printf("Warning: confirmation depth set to %d, which is > 2 epochs (64). Consider using 'finalized' instead.\n", depth)
if depth >= uint64(consts.EthHappyPathFinalizationDepthBlocks) {
// We keep this low (<128) to avoid requiring an archive node (see how this is used in CertVerifier).
// Note: assuming here that no sane person would ever need to set this to a number >64.
// But perhaps someone testing crazy reorg scenarios where finalization takes >2 epochs might want to set this to a higher number.
// Do keep in mind if you ever change this that it might affect a LOT of validators on your rollup who would now need an archival node.
return fmt.Errorf("confirmation depth set to %d, which is > 2 epochs (64). Use 'finalized' instead", depth)
}

return nil
Expand Down
8 changes: 4 additions & 4 deletions mocks/manager.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added resources/payload-blob-poly-lifecycle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/sequence-diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion server/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func (me MetaError) Error() string {
return fmt.Sprintf("Error: %s (Mode: %s, CertVersion: %b)",
me.Err.Error(),
me.Meta.Mode,
me.Meta.CertVersion)
me.Meta.Version)
}

func (me MetaError) Unwrap() error {
Expand Down
38 changes: 19 additions & 19 deletions server/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ func (svr *Server) handleGetStdCommitment(w http.ResponseWriter, r *http.Request
return fmt.Errorf("error parsing version byte: %w", err)
}
commitmentMeta := commitments.CommitmentMeta{
Mode: commitments.Standard,
CertVersion: versionByte,
Mode: commitments.Standard,
Version: commitments.EigenDACommit(versionByte),
}

rawCommitmentHex, ok := mux.Vars(r)[routingVarNameRawCommitmentHex]
rawCommitmentHex, ok := mux.Vars(r)[routingVarNamePayloadHex]
if !ok {
return fmt.Errorf("commitment not found in path: %s", r.URL.Path)
}
Expand All @@ -59,11 +59,11 @@ func (svr *Server) handleGetOPKeccakCommitment(w http.ResponseWriter, r *http.Re
// return err
// }
commitmentMeta := commitments.CommitmentMeta{
Mode: commitments.OptimismKeccak,
CertVersion: byte(commitments.CertV0),
Mode: commitments.OptimismKeccak,
Version: commitments.CertV0,
}

rawCommitmentHex, ok := mux.Vars(r)[routingVarNameRawCommitmentHex]
rawCommitmentHex, ok := mux.Vars(r)[routingVarNamePayloadHex]
if !ok {
return fmt.Errorf("commitment not found in path: %s", r.URL.Path)
}
Expand All @@ -82,11 +82,11 @@ func (svr *Server) handleGetOPGenericCommitment(w http.ResponseWriter, r *http.R
return fmt.Errorf("error parsing version byte: %w", err)
}
commitmentMeta := commitments.CommitmentMeta{
Mode: commitments.OptimismGeneric,
CertVersion: versionByte,
Mode: commitments.OptimismGeneric,
Version: commitments.EigenDACommit(versionByte),
}

rawCommitmentHex, ok := mux.Vars(r)[routingVarNameRawCommitmentHex]
rawCommitmentHex, ok := mux.Vars(r)[routingVarNamePayloadHex]
if !ok {
return fmt.Errorf("commitment not found in path: %s", r.URL.Path)
}
Expand All @@ -101,7 +101,7 @@ func (svr *Server) handleGetOPGenericCommitment(w http.ResponseWriter, r *http.R
func (svr *Server) handleGetShared(ctx context.Context, w http.ResponseWriter, comm []byte, meta commitments.CommitmentMeta) error {
commitmentHex := hex.EncodeToString(comm)
svr.log.Info("Processing GET request", "commitment", commitmentHex, "commitmentMeta", meta)
input, err := svr.sm.Get(ctx, comm, meta)
input, err := svr.sm.Get(ctx, comm, meta.Mode, meta.Version)
if err != nil {
err = MetaError{
Err: fmt.Errorf("get request failed with commitment %v: %w", commitmentHex, err),
Expand All @@ -126,8 +126,8 @@ func (svr *Server) handleGetShared(ctx context.Context, w http.ResponseWriter, c
// handlePostStdCommitment handles the POST request for std commitments.
func (svr *Server) handlePostStdCommitment(w http.ResponseWriter, r *http.Request) error {
commitmentMeta := commitments.CommitmentMeta{
Mode: commitments.Standard,
CertVersion: byte(commitments.CertV0), // TODO: hardcoded for now
Mode: commitments.Standard,
Version: commitments.CertV0,
}
return svr.handlePostShared(w, r, nil, commitmentMeta)
}
Expand All @@ -142,11 +142,11 @@ func (svr *Server) handlePostOPKeccakCommitment(w http.ResponseWriter, r *http.R
// return err
// }
commitmentMeta := commitments.CommitmentMeta{
Mode: commitments.OptimismKeccak,
CertVersion: byte(commitments.CertV0),
Mode: commitments.OptimismKeccak,
Version: commitments.CertV0,
}

rawCommitmentHex, ok := mux.Vars(r)[routingVarNameRawCommitmentHex]
rawCommitmentHex, ok := mux.Vars(r)[routingVarNamePayloadHex]
if !ok {
return fmt.Errorf("commitment not found in path: %s", r.URL.Path)
}
Expand All @@ -161,14 +161,14 @@ func (svr *Server) handlePostOPKeccakCommitment(w http.ResponseWriter, r *http.R
// handlePostOPGenericCommitment handles the POST request for optimism generic commitments.
func (svr *Server) handlePostOPGenericCommitment(w http.ResponseWriter, r *http.Request) error {
commitmentMeta := commitments.CommitmentMeta{
Mode: commitments.OptimismGeneric,
CertVersion: byte(commitments.CertV0), // TODO: hardcoded for now
Mode: commitments.OptimismGeneric,
Version: commitments.CertV0,
}
return svr.handlePostShared(w, r, nil, commitmentMeta)
}

func (svr *Server) handlePostShared(w http.ResponseWriter, r *http.Request, comm []byte, meta commitments.CommitmentMeta) error {
svr.log.Info("Processing POST request", "commitment", hex.EncodeToString(comm), "commitmentMeta", meta)
svr.log.Info("Processing POST request", "commitment", hex.EncodeToString(comm), "meta", meta)
input, err := io.ReadAll(http.MaxBytesReader(w, r.Body, maxRequestBodySize))
if err != nil {
err = MetaError{
Expand Down Expand Up @@ -199,7 +199,7 @@ func (svr *Server) handlePostShared(w http.ResponseWriter, r *http.Request, comm
return err
}

responseCommit, err := commitments.EncodeCommitment(commitment, meta.Mode)
responseCommit, err := commitments.EncodeCommitment(commitment, meta.Mode, commitments.EigenDACommit(meta.Version))
if err != nil {
err = MetaError{
Err: fmt.Errorf("failed to encode commitment %v (commitment mode %v): %w", commitment, meta.Mode, err),
Expand Down
8 changes: 4 additions & 4 deletions server/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestHandlerGet(t *testing.T) {
name: "Failure - OP Keccak256 Internal Server Error",
url: fmt.Sprintf("/get/0x00%s", testCommitStr),
mockBehavior: func() {
mockStorageMgr.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("internal error"))
mockStorageMgr.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("internal error"))
},
expectedCode: http.StatusInternalServerError,
expectedBody: "",
Expand All @@ -58,7 +58,7 @@ func TestHandlerGet(t *testing.T) {
name: "Success - OP Keccak256",
url: fmt.Sprintf("/get/0x00%s", testCommitStr),
mockBehavior: func() {
mockStorageMgr.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte(testCommitStr), nil)
mockStorageMgr.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte(testCommitStr), nil)
},
expectedCode: http.StatusOK,
expectedBody: testCommitStr,
Expand All @@ -67,7 +67,7 @@ func TestHandlerGet(t *testing.T) {
name: "Failure - OP Alt-DA Internal Server Error",
url: fmt.Sprintf("/get/0x010000%s", testCommitStr),
mockBehavior: func() {
mockStorageMgr.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("internal error"))
mockStorageMgr.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("internal error"))
},
expectedCode: http.StatusInternalServerError,
expectedBody: "",
Expand All @@ -76,7 +76,7 @@ func TestHandlerGet(t *testing.T) {
name: "Success - OP Alt-DA",
url: fmt.Sprintf("/get/0x010000%s", testCommitStr),
mockBehavior: func() {
mockStorageMgr.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte(testCommitStr), nil)
mockStorageMgr.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte(testCommitStr), nil)
},
expectedCode: http.StatusOK,
expectedBody: testCommitStr,
Expand Down
9 changes: 4 additions & 5 deletions server/load_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import (

// TODO - create structured abstraction for dependency injection vs. overloading stateless functions

// populateTargets ... creates a list of storage backends based on the provided target strings
func populateTargets(targets []string, s3 common.PrecomputedKeyStore, redis *redis.Store) []common.PrecomputedKeyStore {
// loadBackends ... creates a list of storage backends based on the user provided target strings
func loadBackends(targets []string, s3 common.PrecomputedKeyStore, redis *redis.Store) []common.PrecomputedKeyStore {
stores := make([]common.PrecomputedKeyStore, len(targets))

for i, f := range targets {
Expand Down Expand Up @@ -135,12 +135,11 @@ func LoadStoreManager(ctx context.Context, cfg CLIConfig, log log.Logger, m metr
}

// create secondary storage router
fallbacks := populateTargets(cfg.EigenDAConfig.StorageConfig.FallbackTargets, s3Store, redisStore)
caches := populateTargets(cfg.EigenDAConfig.StorageConfig.CacheTargets, s3Store, redisStore)
fallbacks := loadBackends(cfg.EigenDAConfig.StorageConfig.FallbackTargets, s3Store, redisStore)
caches := loadBackends(cfg.EigenDAConfig.StorageConfig.CacheTargets, s3Store, redisStore)
secondary := store.NewSecondaryManager(log, m, caches, fallbacks)

if secondary.Enabled() { // only spin-up go routines if secondary storage is enabled
// NOTE: in the future the number of threads could be made configurable via env
log.Debug("Starting secondary write loop(s)", "count", cfg.EigenDAConfig.StorageConfig.AsyncPutWorkers)

for i := 0; i < cfg.EigenDAConfig.StorageConfig.AsyncPutWorkers; i++ {
Expand Down
4 changes: 2 additions & 2 deletions server/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func withMetrics(
var metaErr MetaError
if errors.As(err, &metaErr) {
commitMode = string(metaErr.Meta.Mode)
certVersion = string(metaErr.Meta.CertVersion)
certVersion = string(metaErr.Meta.Version)
}
recordDur(strconv.Itoa(scw.status), commitMode, certVersion)
return err
Expand Down Expand Up @@ -91,7 +91,7 @@ func withLogging(
}
var metaErr MetaError
if errors.As(err, &metaErr) {
args = append(args, "commitment_mode", metaErr.Meta.Mode, "cert_version", metaErr.Meta.CertVersion)
args = append(args, "commitment_mode", metaErr.Meta.Mode, "cert_version", metaErr.Meta.Version)
}
log.Info("request", args...)
}
Expand Down
Loading

0 comments on commit 33b2cc8

Please sign in to comment.