Skip to content

Commit

Permalink
refactor: cleaner routing using gorilla mux regexps
Browse files Browse the repository at this point in the history
  • Loading branch information
samlaf committed Oct 14, 2024
1 parent e0af9a4 commit aaad46a
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 80 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ For `alt-da` clients running on Optimism, the following commitment schema is sup

Both `keccak256` (i.e, S3 storage using hash of pre-image for commitment value) and `generic` (i.e, EigenDA) are supported to ensure cross-compatibility with alt-da storage backends if desired by a rollup operator.

OP Stack itself only has a conception of the first byte (`commit type`) and does no semantical interpretation of any subsequent bytes within the encoding. The `da layer type` byte for EigenDA is always `0x0`. However it is currently unused by OP Stack with name space values still being actively [discussed](https://github.com/ethereum-optimism/specs/discussions/135#discussioncomment-9271282).
OP Stack itself only has a conception of the first byte (`commit type`) and does no semantical interpretation of any subsequent bytes within the encoding. The `da layer type` byte for EigenDA is always `0x00`. However it is currently unused by OP Stack with name space values still being actively [discussed](https://github.com/ethereum-optimism/specs/discussions/135#discussioncomment-9271282).

### Simple Commitment Mode
For simple clients communicating with proxy (e.g, arbitrum nitro), the following commitment schema is supported:
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/ethereum/go-ethereum v1.14.11
github.com/go-redis/redis/v8 v8.11.5
github.com/golang/mock v1.2.0
github.com/gorilla/mux v1.8.0
github.com/joho/godotenv v1.5.1
github.com/minio/minio-go/v7 v7.0.77
github.com/prometheus/client_golang v1.20.4
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
Expand Down
179 changes: 146 additions & 33 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package server

import (
"context"
"encoding/hex"
"errors"
"fmt"
"io"
Expand All @@ -17,6 +18,7 @@ import (
"github.com/Layr-Labs/eigenda-proxy/store"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/gorilla/mux"
)

var (
Expand Down Expand Up @@ -95,17 +97,50 @@ func WithLogging(
}

func (svr *Server) Start() error {
mux := http.NewServeMux()

mux.HandleFunc("/get/", WithLogging(WithMetrics(svr.HandleGet, svr.m), svr.log))
r := mux.NewRouter()

// simple commitments (for nitro)
r.HandleFunc("/get/"+
"{optional_prefix:(?:0x)?}"+ // commitments can be prefixed with 0x
"{version_byte_hex:[0-9a-fA-F]{2}}"+ // should always be 0x00 for now but we let others through to return a 404
"{raw_commitment_hex}",
WithLogging(WithMetrics(svr.handleGetSimpleCommitment, svr.m), svr.log),
).Queries("commitment_mode", "simple").Methods("GET")
// op keccak256 commitments (write to S3)
r.HandleFunc("/get/"+
"{optional_prefix:(?:0x)?}"+ // commitments can be prefixed with 0x
"{commit_type_byte_hex:00}"+ // 00 for keccak256 commitments
"{da_layer_byte:[0-9a-fA-F]{2}}"+ // should always be 0x00 for eigenDA but we let others through to return a 404
"{version_byte_hex:[0-9a-fA-F]{2}}"+ // should always be 0x00 for now but we let others through to return a 404
"{raw_commitment_hex}",
WithLogging(WithMetrics(svr.handleGetOPKeccakCommitment, svr.m), svr.log),
).Methods("GET")
// op generic commitments (write to EigenDA)
r.HandleFunc("/get/"+
"{optional_prefix:(?:0x)?}"+ // commitments can be prefixed with 0x
"{commit_type_byte_hex:01}"+ // 01 for generic commitments
"{da_layer_byte:[0-9a-fA-F]{2}}"+ // should always be 0x00 for eigenDA but we let others through to return a 404
"{version_byte_hex:[0-9a-fA-F]{2}}"+ // should always be 0x00 for now but we let others through to return a 404
"{raw_commitment_hex}",
WithLogging(WithMetrics(svr.handleGetOPGenericCommitment, svr.m), svr.log),
).Methods("GET")
// unrecognized op commitment type (not 00 or 01)
r.HandleFunc("/get/"+
"{optional_prefix:(?:0x)?}"+ // commitments can be prefixed with 0x
"{commit_type_byte_hex:[0-9a-fA-F]{2}}",
func(w http.ResponseWriter, r *http.Request) {
commitType := mux.Vars(r)["commit_type_byte_hex"]
svr.WriteNotFound(w, fmt.Errorf("unsupported commitment type %s", commitType))
},
).Methods("GET")
// we need to handle both: see https://github.com/ethereum-optimism/optimism/pull/12081
// /put is for generic commitments, and /put/ for keccak256 commitments
// TODO: we should probably separate their handlers?
mux.HandleFunc("/put", WithLogging(WithMetrics(svr.HandlePut, svr.m), svr.log))
mux.HandleFunc("/put/", WithLogging(WithMetrics(svr.HandlePut, svr.m), svr.log))
mux.HandleFunc("/health", WithLogging(svr.Health, svr.log))
r.HandleFunc("/put", WithLogging(WithMetrics(svr.handlePut, svr.m), svr.log)).Methods("POST")
r.HandleFunc("/put/", WithLogging(WithMetrics(svr.handlePut, svr.m), svr.log)).Methods("POST")
r.HandleFunc("/health", WithLogging(svr.Health, svr.log)).Methods("GET")

svr.httpServer.Handler = mux
svr.httpServer.Handler = r

listener, err := net.Listen("tcp", svr.endpoint)
if err != nil {
Expand Down Expand Up @@ -153,52 +188,104 @@ func (svr *Server) Health(w http.ResponseWriter, _ *http.Request) error {
return nil
}

// HandleGet handles the GET request for commitments.
func (svr *Server) handleGetSimpleCommitment(w http.ResponseWriter, r *http.Request) (commitments.CommitmentMeta, error) {
versionByte, err := parseVersionByte(r)
if err != nil {
return commitments.CommitmentMeta{}, fmt.Errorf("error parsing version byte: %w", err)
}
commitmentMeta := commitments.CommitmentMeta{
Mode: commitments.SimpleCommitmentMode,
CertVersion: versionByte,
}

rawCommitmentHex, ok := mux.Vars(r)["raw_commitment_hex"]
if !ok {
return commitments.CommitmentMeta{}, fmt.Errorf("commitment not found in path: %s", r.URL.Path)
}
commitment, err := hex.DecodeString(rawCommitmentHex)
if err != nil {
return commitments.CommitmentMeta{}, fmt.Errorf("failed to decode commitment %s: %w", rawCommitmentHex, err)
}

svr.log.Info("Processing simple commitment", "commitment", rawCommitmentHex, "commitmentMeta", commitmentMeta)
return commitmentMeta, svr.handleGetShared(r.Context(), w, commitment, commitmentMeta)
}

// handleGetOPKeccakCommitment handles the GET request for optimism keccak commitments.
// Note: even when an error is returned, the commitment meta is still returned,
// because it is needed for metrics (see the WithMetrics middleware).
// TODO: we should change this behavior and instead use a custom error that contains the commitment meta.
func (svr *Server) HandleGet(w http.ResponseWriter, r *http.Request) (commitments.CommitmentMeta, error) {
meta, err := ReadCommitmentMeta(r)
func (svr *Server) handleGetOPKeccakCommitment(w http.ResponseWriter, r *http.Request) (commitments.CommitmentMeta, error) {
versionByte, err := parseVersionByte(r)
if err != nil {
err = fmt.Errorf("invalid commitment mode: %w", err)
svr.WriteBadRequest(w, err)
return commitments.CommitmentMeta{}, err
return commitments.CommitmentMeta{}, fmt.Errorf("error parsing version byte: %w", err)
}
key := path.Base(r.URL.Path)
comm, err := commitments.StringToDecodedCommitment(key, meta.Mode)
commitmentMeta := commitments.CommitmentMeta{
Mode: commitments.OptimismKeccak,
CertVersion: versionByte,
}

rawCommitmentHex, ok := mux.Vars(r)["raw_commitment_hex"]
if !ok {
return commitments.CommitmentMeta{}, fmt.Errorf("commitment not found in path: %s", r.URL.Path)
}
commitment, err := hex.DecodeString(rawCommitmentHex)
if err != nil {
err = fmt.Errorf("failed to decode commitment from key %v (commitment mode %v): %w", key, meta.Mode, err)
svr.WriteBadRequest(w, err)
return commitments.CommitmentMeta{}, MetaError{
Err: err,
Meta: meta,
}
return commitments.CommitmentMeta{}, fmt.Errorf("failed to decode commitment %s: %w", rawCommitmentHex, err)
}

input, err := svr.router.Get(r.Context(), comm, meta.Mode)
svr.log.Info("Processing op keccak commitment", "commitment", rawCommitmentHex, "commitmentMeta", commitmentMeta)
return commitmentMeta, svr.handleGetShared(r.Context(), w, commitment, commitmentMeta)
}

func (svr *Server) handleGetOPGenericCommitment(w http.ResponseWriter, r *http.Request) (commitments.CommitmentMeta, error) {
versionByte, err := parseVersionByte(r)
if err != nil {
return commitments.CommitmentMeta{}, fmt.Errorf("error parsing version byte: %w", err)
}
commitmentMeta := commitments.CommitmentMeta{
Mode: commitments.OptimismGeneric,
CertVersion: versionByte,
}

rawCommitmentHex, ok := mux.Vars(r)["raw_commitment_hex"]
if !ok {
return commitments.CommitmentMeta{}, fmt.Errorf("commitment not found in path: %s", r.URL.Path)
}
commitment, err := hex.DecodeString(rawCommitmentHex)
if err != nil {
return commitments.CommitmentMeta{}, fmt.Errorf("failed to decode commitment %s: %w", rawCommitmentHex, err)
}

svr.log.Info("Processing op keccak commitment", "commitment", rawCommitmentHex, "commitmentMeta", commitmentMeta)
return commitmentMeta, svr.handleGetShared(r.Context(), w, commitment, commitmentMeta)
}

func (svr *Server) handleGetShared(ctx context.Context, w http.ResponseWriter, comm []byte, meta commitments.CommitmentMeta) error {
input, err := svr.router.Get(ctx, comm, meta.Mode)
if err != nil {
err = fmt.Errorf("get request failed with commitment %v (commitment mode %v): %w", comm, meta.Mode, err)
if errors.Is(err, ErrNotFound) {
svr.WriteNotFound(w, err)
} else {
svr.WriteInternalError(w, err)
}
return commitments.CommitmentMeta{}, MetaError{
return MetaError{
Err: err,
Meta: meta,
}
}

svr.WriteResponse(w, input)
return meta, nil
return nil
}

// HandlePut handles the PUT request for commitments.
// handlePut handles the PUT request for commitments.
// Note: even when an error is returned, the commitment meta is still returned,
// because it is needed for metrics (see the WithMetrics middleware).
// TODO: we should change this behavior and instead use a custom error that contains the commitment meta.
func (svr *Server) HandlePut(w http.ResponseWriter, r *http.Request) (commitments.CommitmentMeta, error) {
meta, err := ReadCommitmentMeta(r)
func (svr *Server) handlePut(w http.ResponseWriter, r *http.Request) (commitments.CommitmentMeta, error) {
meta, err := readCommitmentMeta(r)
if err != nil {
err = fmt.Errorf("invalid commitment mode: %w", err)
svr.WriteBadRequest(w, err)
Expand Down Expand Up @@ -299,30 +386,34 @@ func (svr *Server) Port() int {
}

// Read both commitment mode and version
func ReadCommitmentMeta(r *http.Request) (commitments.CommitmentMeta, error) {
func readCommitmentMeta(r *http.Request) (commitments.CommitmentMeta, error) {
// label requests with commitment mode and version
ct, err := ReadCommitmentMode(r)
ct, err := readCommitmentMode(r)
if err != nil {
return commitments.CommitmentMeta{}, err
return commitments.CommitmentMeta{}, fmt.Errorf("failed to read commitment mode: %w", err)
}
if ct == "" {
return commitments.CommitmentMeta{}, fmt.Errorf("commitment mode is empty")
}
cv, err := ReadCommitmentVersion(r, ct)
cv, err := readCommitmentVersion(r, ct)
if err != nil {
// default to version 0
return commitments.CommitmentMeta{Mode: ct, CertVersion: cv}, err
}
return commitments.CommitmentMeta{Mode: ct, CertVersion: cv}, nil
}

func ReadCommitmentMode(r *http.Request) (commitments.CommitmentMode, error) {
func readCommitmentMode(r *http.Request) (commitments.CommitmentMode, error) {
query := r.URL.Query()
// if commitment mode is provided in the query params, use it
// eg. /get/0x123..?commitment_mode=simple
// TODO: should we only allow simple commitment to be set in the query params?
key := query.Get(CommitmentModeKey)
if key != "" {
return commitments.StringToCommitmentMode(key)
}

// else, we need to parse the first byte of the commitment
commit := path.Base(r.URL.Path)
if len(commit) > 0 && commit != Put { // provided commitment in request params (op keccak256)
if !strings.HasPrefix(commit, "0x") {
Expand Down Expand Up @@ -352,7 +443,7 @@ func ReadCommitmentMode(r *http.Request) (commitments.CommitmentMode, error) {
return commitments.OptimismGeneric, nil
}

func ReadCommitmentVersion(r *http.Request, mode commitments.CommitmentMode) (byte, error) {
func readCommitmentVersion(r *http.Request, mode commitments.CommitmentMode) (byte, error) {
commit := path.Base(r.URL.Path)
if len(commit) > 0 && commit != Put { // provided commitment in request params (op keccak256)
if !strings.HasPrefix(commit, "0x") {
Expand Down Expand Up @@ -402,3 +493,25 @@ func (svr *Server) GetStoreStats(bt store.BackendType) (*store.Stats, error) {

return nil, fmt.Errorf("store not found")
}

func parseVersionByte(r *http.Request) (byte, error) {
vars := mux.Vars(r)
// decode version byte
versionByteHex, ok := vars["version_byte_hex"]
if !ok {
return 0, fmt.Errorf("version byte not found in path: %s", r.URL.Path)
}
versionByte, err := hex.DecodeString(versionByteHex)
if err != nil {
return 0, fmt.Errorf("failed to decode version byte %s: %w", versionByteHex, err)
}
if len(versionByte) != 1 {
return 0, fmt.Errorf("version byte is not a single byte: %s", versionByteHex)
}
switch versionByte[0] {
case byte(commitments.CertV0):
return versionByte[0], nil
default:
return 0, fmt.Errorf("unsupported version byte %x", versionByte)
}
}
Loading

0 comments on commit aaad46a

Please sign in to comment.