Skip to content

Commit

Permalink
feat: Memconfig client (#301)
Browse files Browse the repository at this point in the history
* feat: Memconfig client

* feat: Memconfig client - demarcate clients into seperate packages && rename module to

* feat: Memconfig client - update Dockerfile to use new clients package instead of /client

* feat: Memconfig client - update docs

* feat: Memconfig client - update Dockerfile

* feat: Memconfig client - update Dockerfile

* feat: Memconfig client - rm client dependency from go.mod

* feat: Memconfig client - update Dockerfile/dockerignore

* feat: Memconfig client - add usability comment
  • Loading branch information
ethenotethan authored Feb 20, 2025
1 parent d7b1642 commit df15cf9
Show file tree
Hide file tree
Showing 17 changed files with 239 additions and 33 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ go.work
/bin
.env

## kzg cache
## kzg caches
resources/SRSTables/
e2e/resources/**

## Idea
.idea
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ WORKDIR /app

# Copy go.mod and go.sum files
COPY go.mod go.sum ./
COPY client/go.mod ./client/
COPY ./clients/go.mod ./clients/

# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed
RUN go mod download
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# Re-include necessary files only
!go.mod
!client/go.mod
!clients/go.mod
!go.sum
!*/**/*.go
!Makefile
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ For `alt-da` Optimism rollups using EigenDA, the following [commitment schemas](
`keccak256` (commitment_type 0x00) uses an S3 storage backend with where a simple keccak commitment of the `blob` is used as the key. For `generic` commitments, we only support `da_layer_byte` 0x00 which represents EigenDA.

#### Standard Commitment Mode
For standard clients (i.e, `client/client.go`) communicating with proxy (e.g, arbitrum nitro), the following commitment schema is supported:
For standard clients (i.e, `clients/standard_client/client.go`) communicating with proxy (e.g, arbitrum nitro), the following commitment schema is supported:

| version_byte | payload |
| ------------ | --------------- |
Expand Down
2 changes: 1 addition & 1 deletion client/go.mod → clients/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
// 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
module github.com/Layr-Labs/eigenda-proxy/clients

go 1.21.0
159 changes: 159 additions & 0 deletions clients/memconfig_client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package memconfig_client

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"time"
)

const (
memConfigEndpoint = "/memstore/config"
)

type Config struct {
URL string // EigenDA proxy REST API URL
}

// MemConfig ... contains properties that are used to configure the MemStore's behavior.
// this is copied directly from /store/generated_key/memstore/memconfig.
// importing the struct isn't possible since it'd create cyclic dependency loop
// with core proxy's go.mod
type MemConfig struct {
MaxBlobSizeBytes uint64
BlobExpiration time.Duration
PutLatency time.Duration
GetLatency time.Duration
PutReturnsFailoverError bool
}

// MarshalJSON implements custom JSON marshaling for Config.
// This is needed because time.Duration is serialized to nanoseconds,
// which is hard to read.
func (c MemConfig) MarshalJSON() ([]byte, error) {
return json.Marshal(intermediaryCfg{
MaxBlobSizeBytes: c.MaxBlobSizeBytes,
BlobExpiration: c.BlobExpiration.String(),
PutLatency: c.PutLatency.String(),
GetLatency: c.GetLatency.String(),
PutReturnsFailoverError: c.PutReturnsFailoverError,
})
}


// intermediaryCfg ... used for decoding into a less rich type before
// translating to a structured MemConfig
type intermediaryCfg struct {
MaxBlobSizeBytes uint64
BlobExpiration string
PutLatency string
GetLatency string
PutReturnsFailoverError bool
}

// IntoMemConfig ... converts an intermediary config into a memconfig
// with structured type definitions
func (cfg *intermediaryCfg) IntoMemConfig() (*MemConfig, error) {
getLatency, err := time.ParseDuration(cfg.GetLatency)
if err != nil {
return nil, fmt.Errorf("failed to parse getLatency: %w", err)
}

putLatency, err := time.ParseDuration(cfg.PutLatency)
if err != nil {
return nil, fmt.Errorf("failed to parse putLatency: %w", err)
}

blobExpiration, err := time.ParseDuration(cfg.BlobExpiration)
if err != nil {
return nil, fmt.Errorf("failed to parse blobExpiration: %w", err)
}

return &MemConfig{
MaxBlobSizeBytes: cfg.MaxBlobSizeBytes,
BlobExpiration: blobExpiration,
PutLatency: putLatency,
GetLatency: getLatency,
PutReturnsFailoverError: cfg.PutReturnsFailoverError,
}, nil
}


// Client implements a standard client for the eigenda-proxy
// that can be used for updating a memstore configuration in real-time
// this is useful for API driven tests in protocol forks that leverage
// custom integrations with EigenDA
type Client struct {
cfg *Config
httpClient *http.Client
}

// New ... memconfig client constructor
func New(cfg *Config) *Client {
cfg.URL = cfg.URL + memConfigEndpoint // initialize once to avoid unnecessary ops when patch/get

scc := &Client{
cfg: cfg,
httpClient: http.DefaultClient,
}

return scc
}

// decodeResponseToMemCfg ... converts http response to structured MemConfig
func decodeResponseToMemCfg(resp *http.Response) (*MemConfig, error) {
var cfg intermediaryCfg
if err := json.NewDecoder(resp.Body).Decode(&cfg); err != nil {
return nil, fmt.Errorf("could not decode response body to intermediary cfg: %w", err)
}
return cfg.IntoMemConfig()
}
// GetConfig retrieves the current configuration.
func (c *Client) GetConfig(ctx context.Context) (*MemConfig, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.cfg.URL, &bytes.Buffer{})
if err != nil {
return nil, err
}

resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to execute request: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to read config. expected status code 200, got: %d", resp.StatusCode)
}

return decodeResponseToMemCfg(resp)
}

// UpdateConfig updates the configuration using the new MemConfig instance
// Despite the API using a PATH method, this function treats the "update" config
// as a POST and modifies every associated field. This could present issues if
// misused in a testing framework which imports it.
func (c *Client) UpdateConfig(ctx context.Context, update *MemConfig) (*MemConfig, error) {
body, err := update.MarshalJSON()
if err != nil {
return nil, fmt.Errorf("failed to marshal config update to json bytes: %w", err)
}

req, err := http.NewRequestWithContext(ctx, http.MethodPatch, c.cfg.URL, bytes.NewBuffer(body))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}

resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to do request: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to update config, status code: %d", resp.StatusCode)
}

return decodeResponseToMemCfg(resp)
}
8 changes: 4 additions & 4 deletions client/client.go → clients/standard_client/client.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package client
package standard_client

import (
"bytes"
Expand Down Expand Up @@ -42,16 +42,16 @@ type Client struct {

// New ... constructor
func New(cfg *Config, opts ...ClientOption) *Client {
scc := &Client{
client := &Client{
cfg,
http.DefaultClient,
}

for _, opt := range opts {
opt(scc)
opt(client)
}

return scc
return client
}

// Health indicates if the server is operational; useful for event based awaits
Expand Down
6 changes: 3 additions & 3 deletions e2e/benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"strconv"
"testing"

"github.com/Layr-Labs/eigenda-proxy/client"
"github.com/Layr-Labs/eigenda-proxy/clients/standard_client"
)

// BenchmarkPutsWithSecondary ... Takes in an async worker count and profiles blob insertions using
Expand All @@ -26,10 +26,10 @@ func BenchmarkPutsWithSecondary(b *testing.B) {
ts, kill := CreateTestSuite(tsConfig)
defer kill()

cfg := &client.Config{
cfg := &standard_client.Config{
URL: ts.Address(),
}
daClient := client.New(cfg)
daClient := standard_client.New(cfg)

for i := 0; i < b.N; i++ {
_, err := daClient.SetData(context.Background(), []byte("I am a blob and I only live for 14 days on EigenDA"))
Expand Down
6 changes: 3 additions & 3 deletions e2e/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"os"
"testing"

"github.com/Layr-Labs/eigenda-proxy/client"
"github.com/Layr-Labs/eigenda-proxy/clients/standard_client"
"github.com/Layr-Labs/eigenda-proxy/commitments"
"github.com/Layr-Labs/eigenda-proxy/common"
"github.com/Layr-Labs/eigenda-proxy/e2e"
Expand Down Expand Up @@ -67,10 +67,10 @@ func requireWriteReadSecondary(t *testing.T, cm *metrics.CountMap, bt common.Bac

// 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{
cfg := &standard_client.Config{
URL: ts.Address(),
}
daClient := client.New(cfg)
daClient := standard_client.New(cfg)

t.Log("Setting input data on proxy server...")
blobInfo, err := daClient.SetData(ts.Ctx, blob)
Expand Down
Binary file removed e2e/resources/kzg/SRSTables/dimE512.coset1
Binary file not shown.
10 changes: 5 additions & 5 deletions e2e/safety_checks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

altda "github.com/ethereum-optimism/optimism/op-alt-da"

"github.com/Layr-Labs/eigenda-proxy/client"
"github.com/Layr-Labs/eigenda-proxy/clients/standard_client"
"github.com/Layr-Labs/eigenda-proxy/e2e"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -79,10 +79,10 @@ func TestProxyClientMalformedInputCases(t *testing.T) {
ts, kill := e2e.CreateTestSuite(tsConfig)
defer kill()

cfg := &client.Config{
cfg := &standard_client.Config{
URL: ts.Address(),
}
daClient := client.New(cfg)
daClient := standard_client.New(cfg)

t.Run("single byte preimage set data case", func(t *testing.T) {
testPreimage := []byte{1} // single byte preimage
Expand Down Expand Up @@ -169,10 +169,10 @@ func TestOversizedBlobRequestErrors(t *testing.T) {
ts, kill := e2e.CreateTestSuite(tsConfig)
defer kill()

cfg := &client.Config{
cfg := &standard_client.Config{
URL: ts.Address(),
}
daClient := client.New(cfg)
daClient := standard_client.New(cfg)
// 17MB blob
testPreimage := e2e.RandBytes(17_000_0000)

Expand Down
6 changes: 3 additions & 3 deletions e2e/server_fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"testing"
"unicode"

"github.com/Layr-Labs/eigenda-proxy/client"
"github.com/Layr-Labs/eigenda-proxy/clients/standard_client"
"github.com/Layr-Labs/eigenda-proxy/e2e"
)

Expand All @@ -26,11 +26,11 @@ func FuzzProxyClientServerIntegration(f *testing.F) {
}
}

cfg := &client.Config{
cfg := &standard_client.Config{
URL: ts.Address(),
}

daClient := client.New(cfg)
daClient := standard_client.New(cfg)

// seed and data are expected. `seed` value is seed: {rune} and data is the one with the random byte(s)
f.Fuzz(func(t *testing.T, data []byte) {
Expand Down
Loading

0 comments on commit df15cf9

Please sign in to comment.