From 0bfc81f2fc44f6969c3d0e85bf98592c15587dfd Mon Sep 17 00:00:00 2001 From: Samuel Laferriere Date: Mon, 9 Dec 2024 11:38:59 -0500 Subject: [PATCH] feat: separate simple client into its own module (#208) * feat: separate simple client into its own module This will make it easier to import it into other projects without importing all of the other dependencies of our root go.mod. We will first merge this PR with a replace directive in the main go.mod, then release v0.1.0 of the new client module, and then make a new PR to use that version in the main go.mod file. * chore: SimpleClient -> Standard Client (#210) --------- Co-authored-by: Ethen --- README.md | 4 ++-- client/{simple.go => client.go} | 40 ++++++++++++++++----------------- client/go.mod | 10 +++++++++ commitments/mode.go | 12 +++++----- e2e/main_test.go | 4 ++-- e2e/safety_checks_test.go | 2 +- e2e/server_test.go | 20 ++++++++--------- go.mod | 4 ++++ server/handlers.go | 12 +++++----- server/handlers_test.go | 12 +++++----- server/routing.go | 18 +++++++-------- store/manager.go | 6 ++--- 12 files changed, 79 insertions(+), 65 deletions(-) rename client/{simple.go => client.go} (76%) create mode 100644 client/go.mod diff --git a/README.md b/README.md index 907e616c..ac039a7c 100644 --- a/README.md +++ b/README.md @@ -192,8 +192,8 @@ Both `keccak256` (i.e, S3 storage using hash of pre-image for commitment value) 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: +### Standard Commitment Mode +For standard clients (i.e, `client/client.go`) communicating with proxy (e.g, arbitrum nitro), the following commitment schema is supported: ``` 0 1 N diff --git a/client/simple.go b/client/client.go similarity index 76% rename from client/simple.go rename to client/client.go index 18360def..b7f31649 100644 --- a/client/simple.go +++ b/client/client.go @@ -21,28 +21,28 @@ type HTTPClient interface { Do(req *http.Request) (*http.Response, error) } -// SimpleCommitmentClient implements a simple client for the eigenda-proxy -// that can put/get simple commitment data and query the health endpoint. +type ClientOption func(c *Client) + +// WithHTTPClient ... Embeds custom http client type +func WithHTTPClient(client HTTPClient) ClientOption { + return func(c *Client) { + c.httpClient = client + } +} + +// Client implements a standard client for the eigenda-proxy +// that can put/get standard commitment data and query the health endpoint. // Currently it is meant to be used by Arbitrum nitro integrations but can be extended to others in the future. // Optimism has its own client: https://github.com/ethereum-optimism/optimism/blob/develop/op-alt-da/daclient.go // so clients wanting to send op commitment mode data should use that client. -type SimpleCommitmentClient struct { +type Client struct { cfg *Config httpClient HTTPClient } -type SimpleClientOption func(scc *SimpleCommitmentClient) - -// WithHTTPClient ... Embeds custom http client type -func WithHTTPClient(client HTTPClient) SimpleClientOption { - return func(scc *SimpleCommitmentClient) { - scc.httpClient = client - } -} - -// New ... Constructor -func New(cfg *Config, opts ...SimpleClientOption) *SimpleCommitmentClient { - scc := &SimpleCommitmentClient{ +// New ... constructor +func New(cfg *Config, opts ...ClientOption) *Client { + scc := &Client{ cfg, http.DefaultClient, } @@ -56,7 +56,7 @@ func New(cfg *Config, opts ...SimpleClientOption) *SimpleCommitmentClient { // Health indicates if the server is operational; useful for event based awaits // when integration testing -func (c *SimpleCommitmentClient) Health() error { +func (c *Client) Health() error { url := c.cfg.URL + "/health" req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil) if err != nil { @@ -77,8 +77,8 @@ func (c *SimpleCommitmentClient) Health() error { } // GetData fetches blob data associated with a DA certificate -func (c *SimpleCommitmentClient) GetData(ctx context.Context, comm []byte) ([]byte, error) { - url := fmt.Sprintf("%s/get/0x%x?commitment_mode=simple", c.cfg.URL, comm) +func (c *Client) GetData(ctx context.Context, comm []byte) ([]byte, error) { + url := fmt.Sprintf("%s/get/0x%x?commitment_mode=standard", c.cfg.URL, comm) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { @@ -107,8 +107,8 @@ func (c *SimpleCommitmentClient) GetData(ctx context.Context, comm []byte) ([]by // SetData writes raw byte data to DA and returns the associated certificate // which should be verified within the proxy -func (c *SimpleCommitmentClient) SetData(ctx context.Context, b []byte) ([]byte, error) { - url := fmt.Sprintf("%s/put?commitment_mode=simple", c.cfg.URL) +func (c *Client) SetData(ctx context.Context, b []byte) ([]byte, error) { + url := fmt.Sprintf("%s/put?commitment_mode=standard", c.cfg.URL) req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(b)) if err != nil { return nil, fmt.Errorf("failed to create HTTP request: %w", err) diff --git a/client/go.mod b/client/go.mod new file mode 100644 index 00000000..dce7dcfd --- /dev/null +++ b/client/go.mod @@ -0,0 +1,10 @@ +// We use a separate module for the client to allow dependencies to import it without importing all of proxy's main module's dependencies. +// This follows the recommendation in: https://go.dev/wiki/Modules#should-i-have-multiple-modules-in-a-single-repository +// +// Two example scenarios where it can make sense to have more than one go.mod in a repository: +// 1. [omitted] +// 2. if you have a repository with a complex set of dependencies, but you have a client API with a smaller set of dependencies. +// In some cases, it might make sense to have an api or clientapi or similar directory with its own go.mod, or to separate out that clientapi into its own repository. +module github.com/Layr-Labs/eigenda-proxy/client + +go 1.21.0 diff --git a/commitments/mode.go b/commitments/mode.go index 4931bb9c..7eaee4a9 100644 --- a/commitments/mode.go +++ b/commitments/mode.go @@ -13,9 +13,9 @@ type CommitmentMeta struct { type CommitmentMode string const ( - OptimismKeccak CommitmentMode = "optimism_keccak256" - OptimismGeneric CommitmentMode = "optimism_generic" - SimpleCommitmentMode CommitmentMode = "simple" + OptimismKeccak CommitmentMode = "optimism_keccak256" + OptimismGeneric CommitmentMode = "optimism_generic" + Standard CommitmentMode = "standard" ) func StringToCommitmentMode(s string) (CommitmentMode, error) { @@ -24,8 +24,8 @@ func StringToCommitmentMode(s string) (CommitmentMode, error) { return OptimismKeccak, nil case string(OptimismGeneric): return OptimismGeneric, nil - case string(SimpleCommitmentMode): - return SimpleCommitmentMode, nil + case string(Standard): + return Standard, nil default: return "", fmt.Errorf("unknown commitment mode: %s", s) } @@ -42,7 +42,7 @@ func EncodeCommitment(b []byte, c CommitmentMode) ([]byte, error) { altDACommit := NewGenericCommitment(svcCommit).Encode() return altDACommit, nil - case SimpleCommitmentMode: + case Standard: return NewV0CertCommitment(b).Encode(), nil } diff --git a/e2e/main_test.go b/e2e/main_test.go index f8a60cb3..0af174fb 100644 --- a/e2e/main_test.go +++ b/e2e/main_test.go @@ -65,8 +65,8 @@ func requireWriteReadSecondary(t *testing.T, cm *metrics.CountMap, bt common.Bac require.True(t, readCount > 0) } -// requireSimpleClientSetGet ... ensures that simple proxy client can disperse and read a blob -func requireSimpleClientSetGet(t *testing.T, ts e2e.TestSuite, blob []byte) { +// requireStandardClientSetGet ... ensures that std proxy client can disperse and read a blob +func requireStandardClientSetGet(t *testing.T, ts e2e.TestSuite, blob []byte) { cfg := &client.Config{ URL: ts.Address(), } diff --git a/e2e/safety_checks_test.go b/e2e/safety_checks_test.go index 9f248278..571bf02b 100644 --- a/e2e/safety_checks_test.go +++ b/e2e/safety_checks_test.go @@ -121,7 +121,7 @@ func TestProxyClientMalformedInputCases(t *testing.T) { t.Run("get data edge cases - huge cert", func(t *testing.T) { // TODO: we need to add the 0 version byte at the beginning. - // should this not be done automatically by the simple_commitment client? + // should this not be done automatically by the std_commitment client? testCert := append([]byte{0}, e2e.RandBytes(10000)...) _, err := daClient.GetData(ts.Ctx, testCert) require.Error(t, err) diff --git a/e2e/server_test.go b/e2e/server_test.go index 19accff1..380da2bf 100644 --- a/e2e/server_test.go +++ b/e2e/server_test.go @@ -63,8 +63,8 @@ func TestProxyClientWriteRead(t *testing.T) { ts, kill := e2e.CreateTestSuite(tsConfig) defer kill() - requireSimpleClientSetGet(t, ts, e2e.RandBytes(100)) - requireDispersalRetrievalEigenDA(t, ts.Metrics.HTTPServerRequestsTotal, commitments.SimpleCommitmentMode) + requireStandardClientSetGet(t, ts, e2e.RandBytes(100)) + requireDispersalRetrievalEigenDA(t, ts.Metrics.HTTPServerRequestsTotal, commitments.Standard) } func TestProxyWithMaximumSizedBlob(t *testing.T) { @@ -78,8 +78,8 @@ func TestProxyWithMaximumSizedBlob(t *testing.T) { ts, kill := e2e.CreateTestSuite(tsConfig) defer kill() - requireSimpleClientSetGet(t, ts, e2e.RandBytes(16_000_000)) - requireDispersalRetrievalEigenDA(t, ts.Metrics.HTTPServerRequestsTotal, commitments.SimpleCommitmentMode) + requireStandardClientSetGet(t, ts, e2e.RandBytes(16_000_000)) + requireDispersalRetrievalEigenDA(t, ts.Metrics.HTTPServerRequestsTotal, commitments.Standard) } /* @@ -99,9 +99,9 @@ func TestProxyCaching(t *testing.T) { ts, kill := e2e.CreateTestSuite(tsConfig) defer kill() - requireSimpleClientSetGet(t, ts, e2e.RandBytes(1_000_000)) + requireStandardClientSetGet(t, ts, e2e.RandBytes(1_000_000)) requireWriteReadSecondary(t, ts.Metrics.SecondaryRequestsTotal, common.S3BackendType) - requireDispersalRetrievalEigenDA(t, ts.Metrics.HTTPServerRequestsTotal, commitments.SimpleCommitmentMode) + requireDispersalRetrievalEigenDA(t, ts.Metrics.HTTPServerRequestsTotal, commitments.Standard) } func TestProxyCachingWithRedis(t *testing.T) { @@ -118,9 +118,9 @@ func TestProxyCachingWithRedis(t *testing.T) { ts, kill := e2e.CreateTestSuite(tsConfig) defer kill() - requireSimpleClientSetGet(t, ts, e2e.RandBytes(1_000_000)) + requireStandardClientSetGet(t, ts, e2e.RandBytes(1_000_000)) requireWriteReadSecondary(t, ts.Metrics.SecondaryRequestsTotal, common.RedisBackendType) - requireDispersalRetrievalEigenDA(t, ts.Metrics.HTTPServerRequestsTotal, commitments.SimpleCommitmentMode) + requireDispersalRetrievalEigenDA(t, ts.Metrics.HTTPServerRequestsTotal, commitments.Standard) } /* @@ -162,7 +162,7 @@ func TestProxyReadFallback(t *testing.T) { require.NoError(t, err) require.Equal(t, expectedBlob, actualBlob) - requireSimpleClientSetGet(t, ts, e2e.RandBytes(1_000_000)) + requireStandardClientSetGet(t, ts, e2e.RandBytes(1_000_000)) requireWriteReadSecondary(t, ts.Metrics.SecondaryRequestsTotal, common.S3BackendType) - requireDispersalRetrievalEigenDA(t, ts.Metrics.HTTPServerRequestsTotal, commitments.SimpleCommitmentMode) + requireDispersalRetrievalEigenDA(t, ts.Metrics.HTTPServerRequestsTotal, commitments.Standard) } diff --git a/go.mod b/go.mod index 2e17db67..c77ad910 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ toolchain go1.22.7 require ( github.com/Layr-Labs/eigenda v0.8.5-rc.0.0.20241101212705-fa8776ae648c + github.com/Layr-Labs/eigenda-proxy/client v0.0.0-00010101000000-000000000000 github.com/avast/retry-go/v4 v4.6.0 github.com/consensys/gnark-crypto v0.12.1 github.com/ethereum-optimism/optimism v1.9.5 @@ -24,6 +25,9 @@ require ( google.golang.org/grpc v1.64.1 ) +// TODO: Remove this after we have published v0.1.0 of the new client module. +replace github.com/Layr-Labs/eigenda-proxy/client => ./client/ + require ( dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect diff --git a/server/handlers.go b/server/handlers.go index 8cb4e27c..d1acb29c 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -21,14 +21,14 @@ func (svr *Server) handleHealth(w http.ResponseWriter, _ *http.Request) error { // GET ROUTES // ================================================================================================= -// handleGetSimpleCommitment handles the GET request for simple commitments. -func (svr *Server) handleGetSimpleCommitment(w http.ResponseWriter, r *http.Request) error { +// handleGetStdCommitment handles the GET request for std commitments. +func (svr *Server) handleGetStdCommitment(w http.ResponseWriter, r *http.Request) error { versionByte, err := parseVersionByte(w, r) if err != nil { return fmt.Errorf("error parsing version byte: %w", err) } commitmentMeta := commitments.CommitmentMeta{ - Mode: commitments.SimpleCommitmentMode, + Mode: commitments.Standard, CertVersion: versionByte, } @@ -118,10 +118,10 @@ func (svr *Server) handleGetShared(ctx context.Context, w http.ResponseWriter, c // POST ROUTES // ================================================================================================= -// handlePostSimpleCommitment handles the POST request for simple commitments. -func (svr *Server) handlePostSimpleCommitment(w http.ResponseWriter, r *http.Request) error { +// handlePostStdCommitment handles the POST request for std commitments. +func (svr *Server) handlePostStdCommitment(w http.ResponseWriter, r *http.Request) error { commitmentMeta := commitments.CommitmentMeta{ - Mode: commitments.SimpleCommitmentMode, + Mode: commitments.Standard, CertVersion: byte(commitments.CertV0), // TODO: hardcoded for now } return svr.handlePostShared(w, r, nil, commitmentMeta) diff --git a/server/handlers_test.go b/server/handlers_test.go index 97958826..4385da28 100644 --- a/server/handlers_test.go +++ b/server/handlers_test.go @@ -25,7 +25,7 @@ import ( ) const ( - simpleCommitmentPrefix = "\x00" + stdCommitmentPrefix = "\x00" // [alt-da, da layer, cert version] opGenericPrefixStr = "\x01\x00\x00" @@ -145,14 +145,14 @@ func TestHandlerPutSuccess(t *testing.T) { expectedBody: "", }, { - name: "Success Simple Commitment Mode", - url: "/put?commitment_mode=simple", + name: "Success Standard Commitment Mode", + url: "/put?commitment_mode=standard", body: []byte("some data that will successfully be written to EigenDA"), mockBehavior: func() { mockStorageMgr.EXPECT().Put(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte(testCommitStr), nil) }, expectedCode: http.StatusOK, - expectedBody: simpleCommitmentPrefix + testCommitStr, + expectedBody: stdCommitmentPrefix + testCommitStr, }, } @@ -198,8 +198,8 @@ func TestHandlerPutErrors(t *testing.T) { url: fmt.Sprintf("/put/0x00%s", testCommitStr), }, { - name: "Simple Commitment Mode", - url: "/put?commitment_mode=simple", + name: "Standard Commitment Mode", + url: "/put?commitment_mode=standard", }, } diff --git a/server/routing.go b/server/routing.go index ca235935..fe4fd458 100644 --- a/server/routing.go +++ b/server/routing.go @@ -16,13 +16,13 @@ const ( func (svr *Server) registerRoutes(r *mux.Router) { subrouterGET := r.Methods("GET").PathPrefix("/get").Subrouter() - // simple commitments (for nitro) + // std commitments (for nitro) subrouterGET.HandleFunc("/"+ "{optional_prefix:(?:0x)?}"+ // commitments can be prefixed with 0x "{"+routingVarNameVersionByteHex+":[0-9a-fA-F]{2}}"+ // should always be 0x00 for now but we let others through to return a 404 "{"+routingVarNameRawCommitmentHex+":[0-9a-fA-F]*}", - withLogging(withMetrics(svr.handleGetSimpleCommitment, svr.m, commitments.SimpleCommitmentMode), svr.log), - ).Queries("commitment_mode", "simple") + withLogging(withMetrics(svr.handleGetStdCommitment, svr.m, commitments.Standard), svr.log), + ).Queries("commitment_mode", "standard") // op keccak256 commitments (write to S3) subrouterGET.HandleFunc("/"+ "{optional_prefix:(?:0x)?}"+ // commitments can be prefixed with 0x @@ -51,13 +51,13 @@ func (svr *Server) registerRoutes(r *mux.Router) { commitType := mux.Vars(r)[routingVarNameCommitTypeByteHex] http.Error(w, fmt.Sprintf("unsupported commitment type %s", commitType), http.StatusBadRequest) }, - ).MatcherFunc(notCommitmentModeSimple) + ).MatcherFunc(notCommitmentModeStandard) subrouterPOST := r.Methods("POST").PathPrefix("/put").Subrouter() - // simple commitments (for nitro) + // std commitments (for nitro) subrouterPOST.HandleFunc("", // commitment is calculated by the server using the body data - withLogging(withMetrics(svr.handlePostSimpleCommitment, svr.m, commitments.SimpleCommitmentMode), svr.log), - ).Queries("commitment_mode", "simple") + withLogging(withMetrics(svr.handlePostStdCommitment, svr.m, commitments.Standard), svr.log), + ).Queries("commitment_mode", "standard") // op keccak256 commitments (write to S3) subrouterPOST.HandleFunc("/"+ "{optional_prefix:(?:0x)?}"+ // commitments can be prefixed with 0x @@ -80,7 +80,7 @@ func (svr *Server) registerRoutes(r *mux.Router) { r.HandleFunc("/health", withLogging(svr.handleHealth, svr.log)).Methods("GET") } -func notCommitmentModeSimple(r *http.Request, _ *mux.RouteMatch) bool { +func notCommitmentModeStandard(r *http.Request, _ *mux.RouteMatch) bool { commitmentMode := r.URL.Query().Get("commitment_mode") - return commitmentMode == "" || commitmentMode != "simple" + return commitmentMode == "" || commitmentMode != "standard" } diff --git a/store/manager.go b/store/manager.go index d6bd4b3b..93721d89 100644 --- a/store/manager.go +++ b/store/manager.go @@ -22,7 +22,7 @@ type IManager interface { type Manager struct { log log.Logger // primary storage backends - eigenda common.GeneratedKeyStore // ALT DA commitment type for OP mode && simple commitment mode for standard /client + eigenda common.GeneratedKeyStore // ALT DA commitment type for OP mode && std commitment mode for standard /client s3 common.PrecomputedKeyStore // OP commitment mode && keccak256 commitment type // secondary storage backends (caching and fallbacks) @@ -63,7 +63,7 @@ func (m *Manager) Get(ctx context.Context, key []byte, cm commitments.Commitment } return value, nil - case commitments.SimpleCommitmentMode, commitments.OptimismGeneric: + case commitments.Standard, commitments.OptimismGeneric: if m.eigenda == nil { return nil, errors.New("expected EigenDA backend for DA commitment type, but none configured") } @@ -117,7 +117,7 @@ func (m *Manager) Put(ctx context.Context, cm commitments.CommitmentMode, key, v switch cm { case commitments.OptimismKeccak: // caching and fallbacks are unsupported for this commitment mode return m.putKeccak256Mode(ctx, key, value) - case commitments.OptimismGeneric, commitments.SimpleCommitmentMode: + case commitments.OptimismGeneric, commitments.Standard: commit, err = m.putEigenDAMode(ctx, value) default: return nil, fmt.Errorf("unknown commitment mode")