diff --git a/cmd/server/entrypoint.go b/cmd/server/entrypoint.go index ec45143d..b0f2f537 100644 --- a/cmd/server/entrypoint.go +++ b/cmd/server/entrypoint.go @@ -5,9 +5,9 @@ import ( "fmt" "github.com/Layr-Labs/eigenda-proxy/metrics" + "github.com/Layr-Labs/eigenda-proxy/server" "github.com/urfave/cli/v2" - "github.com/Layr-Labs/eigenda-proxy/server" oplog "github.com/ethereum-optimism/optimism/op-service/log" "github.com/ethereum-optimism/optimism/op-service/opio" ) diff --git a/commitments/da_service_op_commitment.go b/commitments/da_service_op_commitment.go new file mode 100644 index 00000000..ada7b4c1 --- /dev/null +++ b/commitments/da_service_op_commitment.go @@ -0,0 +1,67 @@ +package commitments + +import ( + "fmt" + "log" +) + +type DAServiceOPCommitmentType byte + +const ( + EigenDAByte DAServiceOPCommitmentType = 0 +) + +// DAServiceOPCommitment represents a value of one of two possible types (Keccak256Commitment or DAServiceCommitment). +type DAServiceOPCommitment struct { + eigendaCommitment *EigenDACommitment +} + +var _ Commitment = (*DAServiceOPCommitment)(nil) + +func EigenDAWrapperCommitment(value EigenDACommitment) DAServiceOPCommitment { + return DAServiceOPCommitment{eigendaCommitment: &value} +} + +func (e DAServiceOPCommitment) IsEigenDA() bool { + return e.eigendaCommitment != nil +} + +func (e DAServiceOPCommitment) MustEigenDAValue() EigenDACommitment { + if e.eigendaCommitment != nil { + return *e.eigendaCommitment + } + log.Panic("CommitmentEither does not contain a Keccak256Commitment value") + return EigenDACommitment{} // This will never be reached, but is required for compilation. +} + +func (e DAServiceOPCommitment) Marshal() ([]byte, error) { + if e.IsEigenDA() { + eigenDABytes, err := e.MustEigenDAValue().Marshal() + if err != nil { + return nil, err + } + return append([]byte{byte(EigenDAByte)}, eigenDABytes...), nil + } else { + return nil, fmt.Errorf("DAServiceOPCommitment is neither a keccak256 commitment or a DA service commitment") + } +} + +func (e *DAServiceOPCommitment) Unmarshal(bz []byte) error { + if len(bz) < 1 { + return fmt.Errorf("OP commitment does not contain generic commitment type prefix byte") + } + head := DAServiceOPCommitmentType(bz[0]) + tail := bz[1:] + switch head { + case EigenDAByte: + eigendaCommitment := EigenDACommitment{} + err := eigendaCommitment.Unmarshal(tail) + if err != nil { + return err + } + e.eigendaCommitment = &eigendaCommitment + default: + return fmt.Errorf("unrecognized generic commitment type byte: %x", bz[0]) + } + return nil +} diff --git a/commitments/eigenda_commitment.go b/commitments/eigenda_commitment.go new file mode 100644 index 00000000..ca0aebfa --- /dev/null +++ b/commitments/eigenda_commitment.go @@ -0,0 +1,64 @@ +package commitments + +import ( + "fmt" + "log" + + "github.com/ethereum/go-ethereum/rlp" +) + +// Define the parent and child types +type EigenDAEncodingCommitmentType byte + +const ( + EigenDAEncoding0Byte EigenDAEncodingCommitmentType = 0 +) + +type EigenDACommitment struct { + certificateEncoding0 []byte +} + +var _ Commitment = (*EigenDACommitment)(nil) + +func EigenDACertV0(value []byte) EigenDACommitment { + return EigenDACommitment{certificateEncoding0: value} +} + +func (e EigenDACommitment) IsCertV0() bool { + return e.certificateEncoding0 != nil +} + +func (e EigenDACommitment) MustCertV0Value() []byte { + if e.certificateEncoding0 != nil { + return e.certificateEncoding0 + } + log.Panic("CommitmentEither does not contain a Keccak256Commitment value") + return nil // This will never be reached, but is required for compilation. +} + +func (e EigenDACommitment) Marshal() ([]byte, error) { + if e.IsCertV0() { + bz, err := rlp.EncodeToBytes(e.certificateEncoding0) + if err != nil { + return nil, err + } + return append([]byte{byte(EigenDAEncoding0Byte)}, bz...), nil + } else { + return nil, fmt.Errorf("EigenDADAServiceOPCommitment is of unknown type") + } +} + +func (e *EigenDACommitment) Unmarshal(bz []byte) error { + if len(bz) < 1 { + return fmt.Errorf("OP commitment does not contain eigenda commitment encoding version prefix byte") + } + head := EigenDAEncodingCommitmentType(bz[0]) + tail := bz[1:] + switch head { + case EigenDAEncoding0Byte: + e.certificateEncoding0 = tail + default: + return fmt.Errorf("unrecognized generic commitment type byte: %x", bz[0]) + } + return nil +} diff --git a/commitments/interface.go b/commitments/interface.go new file mode 100644 index 00000000..da47a3b8 --- /dev/null +++ b/commitments/interface.go @@ -0,0 +1,6 @@ +package commitments + +type Commitment interface { + Marshal() ([]byte, error) + Unmarshal([]byte) error +} diff --git a/commitments/op_commitment.go b/commitments/op_commitment.go new file mode 100644 index 00000000..e3972ef2 --- /dev/null +++ b/commitments/op_commitment.go @@ -0,0 +1,90 @@ +package commitments + +import ( + "fmt" + "log" +) + +type OPCommitmentType byte + +const ( + // Keccak256Byte represents a commitment using Keccak256 hashing. + Keccak256Byte OPCommitmentType = 0 + // DAServiceByte represents a commitment using a DA service. + DAServiceByte OPCommitmentType = 1 +) + +type OPCommitment struct { + keccak256Commitment []byte + daServiceCommitment *DAServiceOPCommitment +} + +var _ Commitment = (*OPCommitment)(nil) + +func Keccak256Commitment(value []byte) OPCommitment { + return OPCommitment{keccak256Commitment: value} +} + +func DAServiceCommitment(value DAServiceOPCommitment) OPCommitment { + return OPCommitment{daServiceCommitment: &value} +} + +func (e OPCommitment) IsKeccak256() bool { + return e.keccak256Commitment != nil +} + +func (e OPCommitment) IsDAService() bool { + return e.daServiceCommitment != nil +} + +func (e OPCommitment) MustKeccak256Value() []byte { + if e.keccak256Commitment != nil { + return e.keccak256Commitment + } + log.Panic("OPCommitment does not contain a Keccak256Commitment value") + return nil // This will never be reached, but is required for compilation. +} + +func (e OPCommitment) MustDAServiceValue() DAServiceOPCommitment { + if e.daServiceCommitment != nil { + return *e.daServiceCommitment + } + log.Panic("OPCommitment does not contain a DAServiceCommitment value") + return DAServiceOPCommitment{} // This will never be reached, but is required for compilation. +} + +func (e OPCommitment) Marshal() ([]byte, error) { + if e.IsDAService() { + bytes, err := e.MustDAServiceValue().Marshal() + if err != nil { + return nil, err + } + return append([]byte{byte(DAServiceByte)}, bytes...), nil + } else if e.IsKeccak256() { + return append([]byte{byte(Keccak256Byte)}, e.MustKeccak256Value()...), nil + } else { + return nil, fmt.Errorf("OPCommitment is neither a Keccak256 commitment nor a DA service commitment") + } +} + +func (e *OPCommitment) Unmarshal(bz []byte) error { + if len(bz) < 1 { + return fmt.Errorf("OPCommitment does not contain a commitment type prefix byte") + } + head := OPCommitmentType(bz[0]) + tail := bz[1:] + switch head { + case Keccak256Byte: + e.keccak256Commitment = tail + case DAServiceByte: + daServiceCommitment := DAServiceOPCommitment{} + err := daServiceCommitment.Unmarshal(tail) + if err != nil { + return err + } + e.daServiceCommitment = &daServiceCommitment + default: + return fmt.Errorf("unrecognized commitment type byte: %x", bz[0]) + } + return nil +} diff --git a/eigenda/commitment.go b/eigenda/commitment.go deleted file mode 100644 index 2775719e..00000000 --- a/eigenda/commitment.go +++ /dev/null @@ -1,73 +0,0 @@ -package eigenda - -import ( - "errors" - - op_plasma "github.com/ethereum-optimism/optimism/op-plasma" - "github.com/ethereum/go-ethereum/common/hexutil" -) - -// ErrCommitmentLength is returned when the commitment length is invalid. -var ErrCommitmentLength = errors.New("invalid commitment length") - -// ErrInvalidCommitment is returned when the commitment cannot be parsed into a known commitment type. -var ErrInvalidCommitment = errors.New("invalid commitment") - -// ErrCommitmentMismatch is returned when the commitment does not match the given input. -var ErrCommitmentMismatch = errors.New("commitment mismatch") - -// ExtDAType is the DA provider type. -type ExtDAType byte - -const ( - EigenDA ExtDAType = 0x00 -) - -// EigenDAVersion is the version being used for EigenDA. -type EigenDAVersion byte - -const ( - EigenV0 EigenDAVersion = 0x00 -) - -type Commitment []byte - -func (c Commitment) Encode() []byte { - return append([]byte{byte(EigenDA), byte(EigenV0)}, c...) -} - -func GenericPrefix(b []byte) []byte { - return append([]byte{byte(op_plasma.GenericCommitmentType)}, b...) -} - -func StringToCommit(key string) (Commitment, error) { - comm, err := hexutil.Decode(key) - if err != nil { - return nil, err - } - return DecodeCommitment(comm) -} - -// DecodeCommitment verifies and decodes an EigenDACommit from raw encoded bytes. -func DecodeCommitment(commitment []byte) (Commitment, error) { - if len(commitment) <= 3 { - return nil, ErrCommitmentLength - } - if commitment[0] != byte(op_plasma.GenericCommitmentType) { - return nil, ErrInvalidCommitment - } - - if commitment[1] != byte(EigenDA) { - return nil, ErrInvalidCommitment - } - - // additional versions will need to be hardcoded here - if commitment[2] != byte(EigenV0) { - return nil, ErrInvalidCommitment - } - - c := commitment[3:] - - // TODO - Add a length check - return c, nil -} diff --git a/operator-setup b/operator-setup index 2a5a91f8..10b5eeac 160000 --- a/operator-setup +++ b/operator-setup @@ -1 +1 @@ -Subproject commit 2a5a91f81f7ff92d28f8febbffbb889d3ca3a855 +Subproject commit 10b5eeac3ec8fd0de16bf59fa82b80812dc0a740 diff --git a/server/commitment.go b/server/commitment.go new file mode 100644 index 00000000..3a1a7b86 --- /dev/null +++ b/server/commitment.go @@ -0,0 +1,97 @@ +package server + +import ( + "encoding/hex" + "fmt" + "net/http" + + "github.com/Layr-Labs/eigenda-proxy/commitments" + "github.com/Layr-Labs/eigenda/api/grpc/disperser" +) + +const ( + OptimismCommitmentType CommitmentType = iota + NakedCommitmentType + UnknownCommitmentType +) + +type Certificate = disperser.BlobInfo + +func StringToCommit(key string, c CommitmentType) ([]byte, error) { + if len(key) == 0 { + return nil, fmt.Errorf("commitment is empty") + } + + if key[:2] != "0x" { + return nil, fmt.Errorf("commitment parameter does not have 0x prefix") + } + + b, err := hex.DecodeString(key[2:]) + if err != nil { + return nil, err + } + + switch c { + case OptimismCommitmentType: + var comm commitments.OPCommitment + err = comm.Unmarshal(b) + if err != nil { + return nil, err + } + if !comm.IsDAService() { + return nil, fmt.Errorf("commitment is not a OP DA service commitment") + } + daComm := comm.MustDAServiceValue() + if !daComm.IsEigenDA() { + return nil, fmt.Errorf("commitment is not an EigenDA OP DA service commitment") + } + eigendaComm := daComm.MustEigenDAValue() + if !eigendaComm.IsCertV0() { + return nil, fmt.Errorf("commitment is not a supported EigenDA cert encoding") + } + return eigendaComm.MustCertV0Value(), nil + case NakedCommitmentType: + return []byte(key), nil + default: + return nil, fmt.Errorf("unknown commitment type") + } +} + +func EncodeCommitment(s []byte, c CommitmentType) ([]byte, error) { + switch c { + case OptimismCommitmentType: + comm := commitments.DAServiceCommitment(commitments.EigenDAWrapperCommitment(commitments.EigenDACertV0(s))) + return comm.Marshal() + case NakedCommitmentType: + return s, nil + case UnknownCommitmentType: + return nil, fmt.Errorf("unknown commitment type") + default: + return nil, fmt.Errorf("unknown commitment type") + } +} + +func StrToCommitmentType(s string) CommitmentType { + switch s { + case "optimism": + return OptimismCommitmentType + case "naked": + return NakedCommitmentType + default: + return UnknownCommitmentType + } +} + +func ReadCommitmentType(r *http.Request) (CommitmentType, error) { + query := r.URL.Query() + key := query.Get(CommitmentTypeKey) + if key == "" { // default + return OptimismCommitmentType, nil + } + dt := StrToCommitmentType(key) + if dt == UnknownCommitmentType { + return UnknownCommitmentType, ErrInvalidDomainType + } + + return dt, nil +} diff --git a/eigenda/config.go b/server/config.go similarity index 84% rename from eigenda/config.go rename to server/config.go index 4a3e8de5..43756fab 100644 --- a/eigenda/config.go +++ b/server/config.go @@ -1,4 +1,4 @@ -package eigenda +package server import ( "fmt" @@ -6,7 +6,7 @@ import ( "runtime" "time" - "github.com/Layr-Labs/eigenda-proxy/common" + "github.com/Layr-Labs/eigenda-proxy/utils" "github.com/Layr-Labs/eigenda/api/clients" "github.com/Layr-Labs/eigenda/api/clients/codecs" "github.com/Layr-Labs/eigenda/encoding/kzg" @@ -25,10 +25,12 @@ const ( PutBlobEncodingVersionFlagName = "eigenda-put-blob-encoding-version" DisablePointVerificationModeFlagName = "eigenda-disable-point-verification-mode" // Kzg flags - G1PathFlagName = "eigenda-g1-path" - G2TauFlagName = "eigenda-g2-tau-path" - CachePathFlagName = "eigenda-cache-path" - MaxBlobLengthFlagName = "eigenda-max-blob-length" + G1PathFlagName = "eigenda-g1-path" + G2TauFlagName = "eigenda-g2-tau-path" + CachePathFlagName = "eigenda-cache-path" + MaxBlobLengthFlagName = "eigenda-max-blob-length" + MemstoreFlagName = "memstore.enabled" + MemstoreExpirationFlagName = "memstore.expiration" ) const BytesPerSymbol = 31 @@ -54,11 +56,15 @@ type Config struct { maxBlobLengthBytes uint64 G2PowerOfTauPath string + + // Memstore Config params + MemstoreEnabled bool + MemstoreBlobExpiration time.Duration } func (c *Config) GetMaxBlobLength() (uint64, error) { if c.maxBlobLengthBytes == 0 { - numBytes, err := common.ParseBytesAmount(c.MaxBlobLength) + numBytes, err := utils.ParseBytesAmount(c.MaxBlobLength) if err != nil { return 0, err } @@ -105,10 +111,12 @@ func ReadConfig(ctx *cli.Context) Config { PutBlobEncodingVersion: codecs.BlobEncodingVersion(ctx.Uint(PutBlobEncodingVersionFlagName)), DisablePointVerificationMode: ctx.Bool(DisablePointVerificationModeFlagName), }, - G1Path: ctx.String(G1PathFlagName), - G2PowerOfTauPath: ctx.String(G2TauFlagName), - CacheDir: ctx.String(CachePathFlagName), - MaxBlobLength: ctx.String(MaxBlobLengthFlagName), + G1Path: ctx.String(G1PathFlagName), + G2PowerOfTauPath: ctx.String(G2TauFlagName), + CacheDir: ctx.String(CachePathFlagName), + MaxBlobLength: ctx.String(MaxBlobLengthFlagName), + MemstoreEnabled: ctx.Bool(MemstoreFlagName), + MemstoreBlobExpiration: ctx.Duration(MemstoreExpirationFlagName), } return cfg } @@ -199,5 +207,16 @@ func CLIFlags(envPrefix string) []cli.Flag { Usage: "Directory path to SRS tables", EnvVars: prefixEnvVars("TARGET_CACHE_PATH"), }, + &cli.BoolFlag{ + Name: MemstoreFlagName, + Usage: "Whether to use mem-store for DA logic.", + EnvVars: []string{"MEMSTORE_ENABLED"}, + }, + &cli.DurationFlag{ + Name: MemstoreExpirationFlagName, + Usage: "Duration that a mem-store blob/commitment pair are allowed to live.", + Value: 25 * time.Minute, + EnvVars: []string{"MEMSTORE_EXPIRATION"}, + }, } } diff --git a/server/domain_type.go b/server/domain_type.go new file mode 100644 index 00000000..f626e3a8 --- /dev/null +++ b/server/domain_type.go @@ -0,0 +1,46 @@ +package server + +import ( + "fmt" + "net/http" +) + +var ( + ErrInvalidDomainType = fmt.Errorf("invalid domain type") +) + +// DomainType is a enumeration type for the different data domains for which a +// blob can exist between +type DomainType uint8 +type CommitmentType uint8 + +const ( + BinaryDomain DomainType = iota + PolyDomain + UnknownDomain +) + +func StrToDomainType(s string) DomainType { + switch s { + case "binary": + return BinaryDomain + case "polynomial": + return PolyDomain + default: + return UnknownDomain + } +} + +func ReadDomainFilter(r *http.Request) (DomainType, error) { + query := r.URL.Query() + key := query.Get(DomainFilterKey) + if key == "" { // default + return BinaryDomain, nil + } + dt := StrToDomainType(key) + if dt == UnknownDomain { + return UnknownDomain, ErrInvalidDomainType + } + + return dt, nil +} diff --git a/store/eigenda.go b/server/eigenda_store.go similarity index 93% rename from store/eigenda.go rename to server/eigenda_store.go index 93a7bc02..d23207b9 100644 --- a/store/eigenda.go +++ b/server/eigenda_store.go @@ -1,10 +1,9 @@ -package store +package server import ( "context" "fmt" - "github.com/Layr-Labs/eigenda-proxy/common" "github.com/Layr-Labs/eigenda-proxy/verify" "github.com/Layr-Labs/eigenda/api/clients" "github.com/ethereum/go-ethereum/rlp" @@ -29,8 +28,8 @@ func NewEigenDAStore(ctx context.Context, client *clients.EigenDAClient, v *veri // Get fetches a blob from DA using certificate fields and verifies blob // against commitment to ensure data is valid and non-tampered. -func (e EigenDAStore) Get(ctx context.Context, key []byte, domain common.DomainType) ([]byte, error) { - var cert common.Certificate +func (e EigenDAStore) Get(ctx context.Context, key []byte, domain DomainType) ([]byte, error) { + var cert Certificate err := rlp.DecodeBytes(key, &cert) if err != nil { return nil, fmt.Errorf("failed to decode DA cert to RLP format: %w", err) @@ -52,9 +51,9 @@ func (e EigenDAStore) Get(ctx context.Context, key []byte, domain common.DomainT } switch domain { - case common.BinaryDomain: + case BinaryDomain: return decodedBlob, nil - case common.PolyDomain: + case PolyDomain: return encodedBlob, nil default: return nil, fmt.Errorf("unexpected domain type: %d", domain) diff --git a/server/flags.go b/server/flags.go index 7d2579a2..9f6547b6 100644 --- a/server/flags.go +++ b/server/flags.go @@ -5,8 +5,6 @@ import ( "github.com/urfave/cli/v2" - "github.com/Layr-Labs/eigenda-proxy/eigenda" - "github.com/Layr-Labs/eigenda-proxy/store" opservice "github.com/ethereum-optimism/optimism/op-service" oplog "github.com/ethereum-optimism/optimism/op-service/log" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" @@ -47,9 +45,8 @@ var optionalFlags = []cli.Flag{} func init() { optionalFlags = append(optionalFlags, oplog.CLIFlags(EnvVarPrefix)...) - optionalFlags = append(optionalFlags, eigenda.CLIFlags(EnvVarPrefix)...) + optionalFlags = append(optionalFlags, CLIFlags(EnvVarPrefix)...) optionalFlags = append(optionalFlags, opmetrics.CLIFlags(EnvVarPrefix)...) - optionalFlags = append(optionalFlags, store.CLIFlags(EnvVarPrefix)...) Flags = append(requiredFlags, optionalFlags...) } @@ -57,16 +54,14 @@ func init() { var Flags []cli.Flag type CLIConfig struct { - MemStoreCfg store.MemStoreConfig - EigenDAConfig eigenda.Config + EigenDAConfig Config MetricsCfg opmetrics.CLIConfig } func ReadCLIConfig(ctx *cli.Context) CLIConfig { return CLIConfig{ - EigenDAConfig: eigenda.ReadConfig(ctx), + EigenDAConfig: ReadConfig(ctx), MetricsCfg: opmetrics.ReadCLIConfig(ctx), - MemStoreCfg: store.ReadConfig(ctx), } } diff --git a/server/load_store.go b/server/load_store.go index 1ac7bc8c..9689bfdd 100644 --- a/server/load_store.go +++ b/server/load_store.go @@ -3,13 +3,12 @@ package server import ( "context" - "github.com/Layr-Labs/eigenda-proxy/store" "github.com/Layr-Labs/eigenda-proxy/verify" "github.com/Layr-Labs/eigenda/api/clients" "github.com/ethereum/go-ethereum/log" ) -func LoadStore(cfg CLIConfig, ctx context.Context, log log.Logger) (store.Store, error) { +func LoadStore(cfg CLIConfig, ctx context.Context, log log.Logger) (Store, error) { log.Info("Using eigenda backend") daCfg := cfg.EigenDAConfig @@ -23,16 +22,16 @@ func LoadStore(cfg CLIConfig, ctx context.Context, log log.Logger) (store.Store, return nil, err } - if cfg.MemStoreCfg.Enabled { + if cfg.EigenDAConfig.MemstoreEnabled { log.Info("Using memstore backend") - return store.NewMemStore(ctx, &cfg.MemStoreCfg, verifier, log, maxBlobLength) + return NewMemStore(ctx, verifier, log, maxBlobLength, cfg.EigenDAConfig.MemstoreBlobExpiration) } client, err := clients.NewEigenDAClient(log, daCfg.ClientConfig) if err != nil { return nil, err } - return store.NewEigenDAStore( + return NewEigenDAStore( ctx, client, verifier, diff --git a/store/memory.go b/server/memory_store.go similarity index 72% rename from store/memory.go rename to server/memory_store.go index ee3c7731..d70806cd 100644 --- a/store/memory.go +++ b/server/memory_store.go @@ -1,4 +1,4 @@ -package store +package server import ( "context" @@ -10,35 +10,23 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" - "github.com/Layr-Labs/eigenda-proxy/common" - eigendacommon "github.com/Layr-Labs/eigenda-proxy/common" "github.com/Layr-Labs/eigenda-proxy/verify" "github.com/Layr-Labs/eigenda/api/clients/codecs" - grpccommon "github.com/Layr-Labs/eigenda/api/grpc/common" + "github.com/Layr-Labs/eigenda/api/grpc/common" "github.com/Layr-Labs/eigenda/api/grpc/disperser" "github.com/ethereum/go-ethereum/crypto" - "github.com/urfave/cli/v2" ) const ( - MemStoreFlagName = "memstore.enabled" - ExpirationFlagName = "memstore.expiration" - DefaultPruneInterval = 500 * time.Millisecond ) -type MemStoreConfig struct { - Enabled bool - BlobExpiration time.Duration -} - // MemStore is a simple in-memory store for blobs which uses an expiration // time to evict blobs to best emulate the ephemeral nature of blobs dispersed to // EigenDA operators. type MemStore struct { sync.RWMutex - cfg *MemStoreConfig l log.Logger keyStarts map[string]time.Time store map[string][]byte @@ -46,24 +34,25 @@ type MemStore struct { codec codecs.BlobCodec maxBlobSizeBytes uint64 + blobExpiration time.Duration } var _ Store = (*MemStore)(nil) // NewMemStore ... constructor -func NewMemStore(ctx context.Context, cfg *MemStoreConfig, verifier *verify.Verifier, l log.Logger, maxBlobSizeBytes uint64) (*MemStore, error) { +func NewMemStore(ctx context.Context, verifier *verify.Verifier, l log.Logger, maxBlobSizeBytes uint64, blobExpiration time.Duration) (*MemStore, error) { store := &MemStore{ - cfg: cfg, l: l, keyStarts: make(map[string]time.Time), store: make(map[string][]byte), verifier: verifier, codec: codecs.NewIFFTCodec(codecs.NewDefaultBlobCodec()), maxBlobSizeBytes: maxBlobSizeBytes, + blobExpiration: blobExpiration, } - if cfg.BlobExpiration != 0 { - l.Info("memstore expiration enabled", "time", cfg.BlobExpiration) + if store.blobExpiration != 0 { + l.Info("memstore expiration enabled", "time", store.blobExpiration) go store.EventLoop(ctx) } @@ -90,7 +79,7 @@ func (e *MemStore) pruneExpired() { defer e.Unlock() for commit, dur := range e.keyStarts { - if time.Since(dur) >= e.cfg.BlobExpiration { + if time.Since(dur) >= e.blobExpiration { delete(e.keyStarts, commit) delete(e.store, commit) @@ -101,11 +90,11 @@ func (e *MemStore) pruneExpired() { } // Get fetches a value from the store. -func (e *MemStore) Get(ctx context.Context, commit []byte, domain eigendacommon.DomainType) ([]byte, error) { +func (e *MemStore) Get(ctx context.Context, commit []byte, domain DomainType) ([]byte, error) { e.RLock() defer e.RUnlock() - var cert common.Certificate + var cert Certificate err := rlp.DecodeBytes(commit, &cert) if err != nil { return nil, fmt.Errorf("failed to decode DA cert to RLP format: %w", err) @@ -124,9 +113,9 @@ func (e *MemStore) Get(ctx context.Context, commit []byte, domain eigendacommon. } switch domain { - case eigendacommon.BinaryDomain: + case BinaryDomain: return e.codec.DecodeBlob(encodedBlob) - case eigendacommon.PolyDomain: + case PolyDomain: return encodedBlob, nil default: return nil, fmt.Errorf("unexpected domain type: %d", domain) @@ -161,9 +150,9 @@ func (e *MemStore) Put(ctx context.Context, value []byte) ([]byte, error) { mockBatchHeaderHash := crypto.Keccak256Hash(entropy) // only filling out commitment fields for now - cert := &common.Certificate{ + cert := &Certificate{ BlobHeader: &disperser.BlobHeader{ - Commitment: &grpccommon.G1Commitment{ + Commitment: &common.G1Commitment{ X: commitment.X.Marshal(), Y: commitment.Y.Marshal(), }, @@ -206,29 +195,3 @@ func (e *MemStore) Put(ctx context.Context, value []byte) ([]byte, error) { return certBytes, nil } - -func ReadConfig(ctx *cli.Context) MemStoreConfig { - cfg := MemStoreConfig{ - /* Required Flags */ - Enabled: ctx.Bool(MemStoreFlagName), - BlobExpiration: ctx.Duration(ExpirationFlagName), - } - return cfg -} - -func CLIFlags(envPrefix string) []cli.Flag { - - return []cli.Flag{ - &cli.BoolFlag{ - Name: MemStoreFlagName, - Usage: "Whether to use mem-store for DA logic.", - EnvVars: []string{"MEMSTORE_ENABLED"}, - }, - &cli.DurationFlag{ - Name: ExpirationFlagName, - Usage: "Duration that a blob/commitment pair are allowed to live.", - Value: 25 * time.Minute, - EnvVars: []string{"MEMSTORE_EXPIRATION"}, - }, - } -} diff --git a/store/memory_test.go b/server/memory_store_test.go similarity index 84% rename from store/memory_test.go rename to server/memory_store_test.go index cbc44de3..0e027fde 100644 --- a/store/memory_test.go +++ b/server/memory_store_test.go @@ -1,4 +1,4 @@ -package store +package server import ( "context" @@ -6,7 +6,6 @@ import ( "testing" "time" - "github.com/Layr-Labs/eigenda-proxy/common" "github.com/Layr-Labs/eigenda-proxy/verify" "github.com/Layr-Labs/eigenda/encoding/kzg" "github.com/ethereum/go-ethereum/log" @@ -34,13 +33,10 @@ func TestGetSet(t *testing.T) { ms, err := NewMemStore( ctx, - &MemStoreConfig{ - Enabled: true, - BlobExpiration: time.Hour * 1000, - }, verifier, log.New(), 1024*1024*2, + time.Hour*1000, ) assert.NoError(t, err) @@ -49,7 +45,7 @@ func TestGetSet(t *testing.T) { key, err := ms.Put(ctx, expected) assert.NoError(t, err) - actual, err := ms.Get(ctx, key, common.BinaryDomain) + actual, err := ms.Get(ctx, key, BinaryDomain) assert.NoError(t, err) assert.Equal(t, actual, expected) } @@ -71,13 +67,10 @@ func TestExpiration(t *testing.T) { ms, err := NewMemStore( ctx, - &MemStoreConfig{ - Enabled: true, - BlobExpiration: time.Millisecond * 10, - }, verifier, log.New(), 1024*1024*2, + time.Millisecond*10, ) assert.NoError(t, err) @@ -89,7 +82,7 @@ func TestExpiration(t *testing.T) { // sleep 1 second and verify that older blob entries are removed time.Sleep(time.Second * 1) - _, err = ms.Get(ctx, key, common.BinaryDomain) + _, err = ms.Get(ctx, key, BinaryDomain) assert.Error(t, err) } diff --git a/server/server.go b/server/server.go index 32b36868..821d9be7 100644 --- a/server/server.go +++ b/server/server.go @@ -11,10 +11,7 @@ import ( "strconv" "time" - "github.com/Layr-Labs/eigenda-proxy/common" - "github.com/Layr-Labs/eigenda-proxy/eigenda" "github.com/Layr-Labs/eigenda-proxy/metrics" - "github.com/Layr-Labs/eigenda-proxy/store" "github.com/ethereum-optimism/optimism/op-service/rpc" "github.com/ethereum/go-ethereum/log" ) @@ -24,27 +21,34 @@ var ( ) const ( - invalidDomain = "invalid domain type" -) + invalidDomain = "invalid domain type" + invalidCommitmentType = "invalid commitment type" -const ( GetRoute = "/get/" PutRoute = "/put/" - DomainFilterKey = "domain" + DomainFilterKey = "domain" + CommitmentTypeKey = "commitment_type" ) +type Store interface { + // Get retrieves the given key if it's present in the key-value data store. + Get(ctx context.Context, key []byte, domain DomainType) ([]byte, error) + // Put inserts the given value into the key-value data store. + Put(ctx context.Context, value []byte) (key []byte, err error) +} + type Server struct { log log.Logger endpoint string - store store.Store + store Store m metrics.Metricer tls *rpc.ServerTLSConfig httpServer *http.Server listener net.Listener } -func NewServer(host string, port int, store store.Store, log log.Logger, m metrics.Metricer) *Server { +func NewServer(host string, port int, store Store, log log.Logger, m metrics.Metricer) *Server { endpoint := net.JoinHostPort(host, strconv.Itoa(port)) return &Server{ m: m, @@ -147,9 +151,14 @@ func (svr *Server) HandleGet(w http.ResponseWriter, r *http.Request) error { svr.WriteBadRequest(w, invalidDomain) return err } + commitmentType, err := ReadCommitmentType(r) + if err != nil { + svr.WriteBadRequest(w, invalidCommitmentType) + return err + } key := path.Base(r.URL.Path) - comm, err := eigenda.StringToCommit(key) + comm, err := StringToCommit(key, commitmentType) if err != nil { svr.log.Info("failed to decode commitment", "err", err, "key", key) w.WriteHeader(http.StatusBadRequest) @@ -172,6 +181,12 @@ func (svr *Server) HandleGet(w http.ResponseWriter, r *http.Request) error { } func (svr *Server) HandlePut(w http.ResponseWriter, r *http.Request) error { + commitmentType, err := ReadCommitmentType(r) + if err != nil { + svr.WriteBadRequest(w, invalidCommitmentType) + return err + } + input, err := io.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) @@ -184,8 +199,14 @@ func (svr *Server) HandlePut(w http.ResponseWriter, r *http.Request) error { return err } + comm, err = EncodeCommitment(comm, commitmentType) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return err + } + // write out encoded commitment - svr.WriteResponse(w, eigenda.Commitment.Encode(comm)) + svr.WriteResponse(w, comm) return nil } @@ -209,17 +230,3 @@ func (svr *Server) WriteBadRequest(w http.ResponseWriter, msg string) { svr.log.Info("bad request", "msg", msg) w.WriteHeader(http.StatusBadRequest) } - -func ReadDomainFilter(r *http.Request) (common.DomainType, error) { - query := r.URL.Query() - key := query.Get(DomainFilterKey) - if key == "" { // default - return common.BinaryDomain, nil - } - dt := common.StrToDomainType(key) - if dt == common.UnknownDomain { - return common.UnknownDomain, common.ErrInvalidDomainType - } - - return dt, nil -} diff --git a/store/types.go b/store/types.go deleted file mode 100644 index 388dea31..00000000 --- a/store/types.go +++ /dev/null @@ -1,14 +0,0 @@ -package store - -import ( - "context" - - "github.com/Layr-Labs/eigenda-proxy/common" -) - -type Store interface { - // Get retrieves the given key if it's present in the key-value data store. - Get(ctx context.Context, key []byte, domain common.DomainType) ([]byte, error) - // Put inserts the given value into the key-value data store. - Put(ctx context.Context, value []byte) (key []byte, err error) -} diff --git a/test/e2e_test.go b/test/e2e_test.go index 4d9bcf23..58a9982d 100644 --- a/test/e2e_test.go +++ b/test/e2e_test.go @@ -9,11 +9,8 @@ import ( "time" "github.com/Layr-Labs/eigenda-proxy/client" - "github.com/Layr-Labs/eigenda-proxy/common" - "github.com/Layr-Labs/eigenda-proxy/eigenda" "github.com/Layr-Labs/eigenda-proxy/metrics" "github.com/Layr-Labs/eigenda-proxy/server" - "github.com/Layr-Labs/eigenda-proxy/store" "github.com/Layr-Labs/eigenda/api/clients" "github.com/Layr-Labs/eigenda/api/clients/codecs" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" @@ -64,7 +61,7 @@ func createTestSuite(t *testing.T, useMemory bool) (TestSuite, func()) { oplog.SetGlobalLogHandler(log.Handler()) - eigendaCfg := eigenda.Config{ + eigendaCfg := server.Config{ ClientConfig: clients.EigenDAClientConfig{ RPC: holeskyDA, StatusQueryTimeout: time.Minute * 45, @@ -78,17 +75,13 @@ func createTestSuite(t *testing.T, useMemory bool) (TestSuite, func()) { MaxBlobLength: "90kib", G2PowerOfTauPath: "../operator-setup/resources/g2_abbr.point.powerOf2", PutBlobEncodingVersion: 0x00, - } - - memstoreCfg := store.MemStoreConfig{ - Enabled: useMemory, - BlobExpiration: 14 * 24 * time.Hour, + MemstoreEnabled: useMemory, + MemstoreBlobExpiration: 14 * 24 * time.Hour, } store, err := server.LoadStore( server.CLIConfig{ EigenDAConfig: eigendaCfg, - MemStoreCfg: memstoreCfg, MetricsCfg: opmetrics.CLIConfig{}, }, ctx, @@ -175,12 +168,12 @@ func TestHoleskyWithProxyClient(t *testing.T) { // 2 - fetch data from EigenDA for generated commitment key t.Log("Getting input data from proxy server...") - preimage, err := daClient.GetData(ts.ctx, blobInfo, common.BinaryDomain) + preimage, err := daClient.GetData(ts.ctx, blobInfo, server.BinaryDomain) require.NoError(t, err) require.Equal(t, testPreimage, preimage) // 3 - fetch iFFT representation of preimage - iFFTPreimage, err := daClient.GetData(ts.ctx, blobInfo, common.PolyDomain) + iFFTPreimage, err := daClient.GetData(ts.ctx, blobInfo, server.PolyDomain) require.NoError(t, err) require.NotEqual(t, preimage, iFFTPreimage) @@ -255,12 +248,12 @@ func TestMemStoreWithProxyClient(t *testing.T) { // 2 - fetch data from EigenDA for generated commitment key t.Log("Getting input data from proxy server...") - preimage, err := daClient.GetData(ts.ctx, blobInfo, common.BinaryDomain) + preimage, err := daClient.GetData(ts.ctx, blobInfo, server.BinaryDomain) require.NoError(t, err) require.Equal(t, testPreimage, preimage) // 3 - fetch iFFT representation of preimage - iFFTPreimage, err := daClient.GetData(ts.ctx, blobInfo, common.PolyDomain) + iFFTPreimage, err := daClient.GetData(ts.ctx, blobInfo, server.PolyDomain) require.NoError(t, err) require.NotEqual(t, preimage, iFFTPreimage) diff --git a/common/common.go b/utils/parse_bytes.go similarity index 56% rename from common/common.go rename to utils/parse_bytes.go index 97a27655..2ecc3fb4 100644 --- a/common/common.go +++ b/utils/parse_bytes.go @@ -1,69 +1,11 @@ -package common +package utils import ( "fmt" "strconv" "strings" - - "github.com/Layr-Labs/eigenda/api/grpc/disperser" -) - -var ( - ErrInvalidDomainType = fmt.Errorf("invalid domain type") -) - -type Certificate = disperser.BlobInfo - -// DomainType is a enumeration type for the different data domains for which a -// blob can exist between -type DomainType uint8 - -const ( - BinaryDomain DomainType = iota - PolyDomain - UnknownDomain ) -func (dt DomainType) String() string { - switch dt { - case BinaryDomain: - return "binary" - - case PolyDomain: - return "polynomial" - - default: - return "unknown" - } -} - -func StrToDomainType(s string) DomainType { - switch s { - case "binary": - return BinaryDomain - case "polynomial": - return PolyDomain - default: - return UnknownDomain - } -} - -// Helper utility functions // - -func EqualSlices[P comparable](s1, s2 []P) bool { - if len(s1) != len(s2) { - return false - } - - for i := 0; i < len(s1); i++ { - if s1[i] != s2[i] { - return false - } - } - - return true -} - func ParseBytesAmount(s string) (uint64, error) { s = strings.TrimSpace(s) diff --git a/common/common_test.go b/utils/parse_bytes_test.go similarity index 92% rename from common/common_test.go rename to utils/parse_bytes_test.go index 498672e0..cf684667 100644 --- a/common/common_test.go +++ b/utils/parse_bytes_test.go @@ -1,10 +1,10 @@ -package common_test +package utils_test import ( "fmt" "testing" - "github.com/Layr-Labs/eigenda-proxy/common" + "github.com/Layr-Labs/eigenda-proxy/utils" ) func TestParseByteAmount(t *testing.T) { @@ -42,7 +42,7 @@ func TestParseByteAmount(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("Input: %s", tc.input), func(t *testing.T) { - got, err := common.ParseBytesAmount(tc.input) + got, err := utils.ParseBytesAmount(tc.input) if (err != nil) != tc.wantErr { t.Errorf("wantErr: %v, got error: %v", tc.wantErr, err) } diff --git a/verify/verifier.go b/verify/verifier.go index c224770c..a6a3c793 100644 --- a/verify/verifier.go +++ b/verify/verifier.go @@ -3,11 +3,11 @@ package verify import ( "fmt" - "github.com/Layr-Labs/eigenda/api/grpc/common" "github.com/Layr-Labs/eigenda/encoding" "github.com/consensys/gnark-crypto/ecc/bn254" "github.com/consensys/gnark-crypto/ecc/bn254/fp" + "github.com/Layr-Labs/eigenda/api/grpc/common" "github.com/Layr-Labs/eigenda/encoding/kzg" "github.com/Layr-Labs/eigenda/encoding/kzg/prover" "github.com/Layr-Labs/eigenda/encoding/rs"