diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 2c45402..9c1ad4a 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -70,12 +70,11 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.21 + go-version: 1.21 - name: Install project dependencies run: | go mod download - - name: Run holesky tests env: SIGNER_PRIVATE_KEY: ${{ secrets.SIGNER_PRIVATE_KEY }} diff --git a/client/client.go b/client/client.go index d31e273..1a12374 100644 --- a/client/client.go +++ b/client/client.go @@ -7,14 +7,7 @@ import ( "io" "net/http" - "github.com/Layr-Labs/eigenda-proxy/common" - "github.com/Layr-Labs/eigenda-proxy/eigenda" - "github.com/ethereum/go-ethereum/rlp" -) - -const ( - // NOTE: this will need to be updated as plasma's implementation changes - decodingOffset = 3 + "github.com/Layr-Labs/eigenda-proxy/server" ) // TODO: Add support for custom http client option @@ -25,8 +18,8 @@ type Config struct { // ProxyClient is an interface for communicating with the EigenDA proxy server type ProxyClient interface { Health() error - GetData(ctx context.Context, cert *common.Certificate, domain common.DomainType) ([]byte, error) - SetData(ctx context.Context, b []byte) (*common.Certificate, error) + GetData(ctx context.Context, cert []byte, domain server.DomainType) ([]byte, error) + SetData(ctx context.Context, b []byte) ([]byte, error) } // client is the implementation of ProxyClient @@ -35,6 +28,8 @@ type client struct { httpClient *http.Client } +var _ ProxyClient = (*client)(nil) + func New(cfg *Config) ProxyClient { return &client{ cfg, @@ -64,16 +59,8 @@ func (c *client) Health() error { } // GetData fetches blob data associated with a DA certificate -func (c *client) GetData(ctx context.Context, cert *common.Certificate, domain common.DomainType) ([]byte, error) { - b, err := rlp.EncodeToBytes(cert) - if err != nil { - return nil, err - } - - // encode prefix bytes - b = eigenda.Commitment(b).Encode() - - url := fmt.Sprintf("%s/get/0x%x?domain=%s", c.cfg.URL, b, domain.String()) +func (c *client) GetData(ctx context.Context, comm []byte, domain server.DomainType) ([]byte, error) { + url := fmt.Sprintf("%s/get/0x%x?domain=%s&commitment_mode=simple", c.cfg.URL, comm, domain.String()) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { @@ -96,8 +83,8 @@ func (c *client) GetData(ctx context.Context, cert *common.Certificate, domain c } // SetData writes raw byte data to DA and returns the respective certificate -func (c *client) SetData(ctx context.Context, b []byte) (*common.Certificate, error) { - url := fmt.Sprintf("%s/put/", c.cfg.URL) +func (c *client) SetData(ctx context.Context, b []byte) ([]byte, error) { + url := fmt.Sprintf("%s/put/?commitment_mode=simple", 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) @@ -117,14 +104,9 @@ func (c *client) SetData(ctx context.Context, b []byte) (*common.Certificate, er return nil, err } - if len(b) < decodingOffset { - return nil, fmt.Errorf("read certificate is of invalid length: %d", len(b)) - } - - var cert *common.Certificate - if err = rlp.DecodeBytes(b[decodingOffset:], &cert); err != nil { - return nil, err + if len(b) == 0 { + return nil, fmt.Errorf("read certificate is empty") } - return cert, err + return b, err } diff --git a/cmd/server/entrypoint.go b/cmd/server/entrypoint.go index ec45143..b0f2f53 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.go b/commitments/da_service_op.go new file mode 100644 index 0000000..d0b89a9 --- /dev/null +++ b/commitments/da_service_op.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 OptimismEigenDACommitment(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.go b/commitments/eigenda.go new file mode 100644 index 0000000..6d92d65 --- /dev/null +++ b/commitments/eigenda.go @@ -0,0 +1,58 @@ +package commitments + +import ( + "fmt" + "log" +) + +// Define the parent and child types +type CertEncodingVersion byte + +const ( + CertEncodingV0 CertEncodingVersion = 0 +) + +type EigenDACommitment struct { + certV0 []byte +} + +var _ Commitment = (*EigenDACommitment)(nil) + +func EigenDACertV0(value []byte) EigenDACommitment { + return EigenDACommitment{certV0: value} +} + +func (e EigenDACommitment) IsCertV0() bool { + return e.certV0 != nil +} + +func (e EigenDACommitment) MustCertV0Value() []byte { + if e.certV0 != nil { + return e.certV0 + } + 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() { + return append([]byte{byte(CertEncodingV0)}, e.certV0...), 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 := CertEncodingVersion(bz[0]) + tail := bz[1:] + switch head { + case CertEncodingV0: + e.certV0 = tail + default: + return fmt.Errorf("unrecognized EigenDA commitment encoding type byte: %x", bz[0]) + } + return nil +} diff --git a/commitments/interface.go b/commitments/interface.go new file mode 100644 index 0000000..da47a3b --- /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.go b/commitments/op.go new file mode 100644 index 0000000..ef035dd --- /dev/null +++ b/commitments/op.go @@ -0,0 +1,90 @@ +package commitments + +import ( + "fmt" + "log" +) + +type OPCommitmentType byte + +const ( + // Keccak256CommitmentTypeByte represents a commitment using Keccak256 hashing. + Keccak256CommitmentTypeByte OPCommitmentType = 0 + // GenericCommitmentTypeByte represents a commitment using a DA service. + GenericCommitmentTypeByte OPCommitmentType = 1 +) + +type OPCommitment struct { + keccak256Commitment []byte + genericCommitment *DAServiceOPCommitment +} + +var _ Commitment = (*OPCommitment)(nil) + +func Keccak256Commitment(value []byte) OPCommitment { + return OPCommitment{keccak256Commitment: value} +} + +func GenericCommitment(value DAServiceOPCommitment) OPCommitment { + return OPCommitment{genericCommitment: &value} +} + +func (e OPCommitment) IsKeccak256Commitment() bool { + return e.keccak256Commitment != nil +} + +func (e OPCommitment) IsGenericCommitment() bool { + return e.genericCommitment != nil +} + +func (e OPCommitment) MustKeccak256CommitmentValue() []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) MustGenericCommitmentValue() DAServiceOPCommitment { + if e.genericCommitment != nil { + return *e.genericCommitment + } + 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.IsGenericCommitment() { + bytes, err := e.MustGenericCommitmentValue().Marshal() + if err != nil { + return nil, err + } + return append([]byte{byte(GenericCommitmentTypeByte)}, bytes...), nil + } else if e.IsKeccak256Commitment() { + return append([]byte{byte(Keccak256CommitmentTypeByte)}, e.MustKeccak256CommitmentValue()...), 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 Keccak256CommitmentTypeByte: + e.keccak256Commitment = tail + case GenericCommitmentTypeByte: + daServiceCommitment := DAServiceOPCommitment{} + err := daServiceCommitment.Unmarshal(tail) + if err != nil { + return err + } + e.genericCommitment = &daServiceCommitment + default: + return fmt.Errorf("unrecognized commitment type byte: %x", bz[0]) + } + return nil +} diff --git a/common/common.go b/common/common.go deleted file mode 100644 index c1ccdd8..0000000 --- a/common/common.go +++ /dev/null @@ -1,174 +0,0 @@ -package common - -import ( - "fmt" - "math/big" - "strconv" - "strings" - - "github.com/Layr-Labs/eigenda/api/grpc/disperser" -) - -var ( - ErrInvalidDomainType = fmt.Errorf("invalid domain type") -) - -// G1Point struct to represent G1Point in Solidity -type G1Point struct { - X *big.Int - Y *big.Int -} - -// QuorumBlobParam struct to represent QuorumBlobParam in Solidity -type QuorumBlobParam struct { - QuorumNumber uint8 - AdversaryThresholdPercentage uint8 - ConfirmationThresholdPercentage uint8 - ChunkLength uint32 -} - -// BlobHeader struct to represent BlobHeader in Solidity -type BlobHeader struct { - Commitment G1Point - DataLength uint32 - QuorumBlobParams []QuorumBlobParam -} - -type Certificate disperser.BlobInfo - -func (c *Certificate) BlobIndex() uint32 { - return c.BlobVerificationProof.BlobIndex -} - -func (c *Certificate) BatchHeaderRoot() []byte { - return c.BlobVerificationProof.BatchMetadata.BatchHeader.BatchRoot -} - -func (c *Certificate) ReadBlobHeader() BlobHeader { - // parse quorum params - - qps := make([]QuorumBlobParam, len(c.BlobHeader.BlobQuorumParams)) - for i, qp := range c.BlobHeader.BlobQuorumParams { - qps[i] = QuorumBlobParam{ - QuorumNumber: uint8(qp.QuorumNumber), - AdversaryThresholdPercentage: uint8(qp.AdversaryThresholdPercentage), - ConfirmationThresholdPercentage: uint8(qp.ConfirmationThresholdPercentage), - ChunkLength: qp.ChunkLength, - } - } - - return BlobHeader{ - Commitment: G1Point{ - X: new(big.Int).SetBytes(c.BlobHeader.Commitment.X), - Y: new(big.Int).SetBytes(c.BlobHeader.Commitment.Y), - }, - DataLength: c.BlobHeader.DataLength, - QuorumBlobParams: qps, - } -} - -func (c *Certificate) Proof() *disperser.BlobVerificationProof { - return c.BlobVerificationProof -} - -// DomainType is an 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) - - // Extract numeric part and unit - numStr := s - unit := "" - for i, r := range s { - if !('0' <= r && r <= '9' || r == '.') { - numStr = s[:i] - unit = s[i:] - break - } - } - - // Convert numeric part to float64 - num, err := strconv.ParseFloat(numStr, 64) - if err != nil { - return 0, fmt.Errorf("invalid numeric value: %v", err) - } - - unit = strings.ToLower(strings.TrimSpace(unit)) - - // Convert to uint64 based on the unit (case-insensitive) - switch unit { - case "b", "": - return uint64(num), nil - case "kib": - return uint64(num * 1024), nil - case "kb": - return uint64(num * 1000), nil // Decimal kilobyte - case "mib": - return uint64(num * 1024 * 1024), nil - case "mb": - return uint64(num * 1000 * 1000), nil // Decimal megabyte - case "gib": - return uint64(num * 1024 * 1024 * 1024), nil - case "gb": - return uint64(num * 1000 * 1000 * 1000), nil // Decimal gigabyte - case "tib": - return uint64(num * 1024 * 1024 * 1024 * 1024), nil - case "tb": - return uint64(num * 1000 * 1000 * 1000 * 1000), nil // Decimal terabyte - default: - return 0, fmt.Errorf("unsupported unit: %s", unit) - } -} - -type Stats struct { - Entries int - Reads int -} diff --git a/e2e/resources/kzg/SRSTables/dimE512.coset1 b/e2e/resources/kzg/SRSTables/dimE512.coset1 new file mode 100644 index 0000000..3444d72 Binary files /dev/null and b/e2e/resources/kzg/SRSTables/dimE512.coset1 differ diff --git a/e2e/server_test.go b/e2e/server_test.go index 36f9832..359eabf 100644 --- a/e2e/server_test.go +++ b/e2e/server_test.go @@ -5,8 +5,8 @@ import ( "time" "github.com/Layr-Labs/eigenda-proxy/client" + "github.com/Layr-Labs/eigenda-proxy/server" - "github.com/Layr-Labs/eigenda-proxy/common" "github.com/Layr-Labs/eigenda-proxy/e2e" "github.com/Layr-Labs/eigenda/api/clients/codecs" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" @@ -65,7 +65,7 @@ func TestProxyClient(t *testing.T) { daClient := client.New(cfg) t.Log("Waiting for client to establish connection with plasma server...") // wait for server to come online after starting - wait.For(ts.Ctx, time.Second*1, func() (bool, error) { + err := wait.For(ts.Ctx, time.Second*1, func() (bool, error) { err := daClient.Health() if err != nil { return false, nil @@ -73,6 +73,7 @@ func TestProxyClient(t *testing.T) { return true, nil }) + require.NoError(t, err) // 1 - write arbitrary data to EigenDA @@ -84,12 +85,12 @@ func TestProxyClient(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/e2e/setup.go b/e2e/setup.go index 52fd893..8b3b484 100644 --- a/e2e/setup.go +++ b/e2e/setup.go @@ -7,10 +7,8 @@ import ( "testing" "time" - "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/ethereum/go-ethereum/log" @@ -57,7 +55,7 @@ func CreateTestSuite(t *testing.T, useMemory bool) (TestSuite, func()) { Color: true, }).New("role", svcName) - eigendaCfg := eigenda.Config{ + eigendaCfg := server.Config{ ClientConfig: clients.EigenDAClientConfig{ RPC: holeskyDA, StatusQueryTimeout: time.Minute * 45, @@ -69,21 +67,16 @@ func CreateTestSuite(t *testing.T, useMemory bool) (TestSuite, func()) { SvcManagerAddr: "0xD4A7E1Bd8015057293f0D0A557088c286942e84b", // incompatible with non holeskly networks CacheDir: "../resources/SRSTables", G1Path: "../resources/g1.point", - G2Path: "", MaxBlobLength: "90kib", G2PowerOfTauPath: "../resources/g2.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, diff --git a/eigenda/commitment.go b/eigenda/commitment.go deleted file mode 100644 index 211d847..0000000 --- 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(op_plasma.GenericCommitmentType), 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/go.mod b/go.mod index c97a0fe..fcd254c 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/prometheus/client_golang v1.19.1 github.com/stretchr/testify v1.9.0 github.com/urfave/cli/v2 v2.27.1 + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 ) require ( @@ -223,7 +224,6 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.23.0 // indirect - golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/sync v0.7.0 // indirect diff --git a/server/commitment_mode.go b/server/commitment_mode.go new file mode 100644 index 0000000..9edda59 --- /dev/null +++ b/server/commitment_mode.go @@ -0,0 +1,97 @@ +package server + +import ( + "encoding/hex" + "fmt" + "net/http" + + "github.com/Layr-Labs/eigenda-proxy/commitments" +) + +type CommitmentMode string + +const ( + OptimismCommitmentMode CommitmentMode = "optimism" + SimpleCommitmentMode CommitmentMode = "simple" +) + +func StringToCommitmentMode(s string) (CommitmentMode, error) { + switch s { + case string(OptimismCommitmentMode): + return OptimismCommitmentMode, nil + case string(SimpleCommitmentMode): + return SimpleCommitmentMode, nil + default: + return "", fmt.Errorf("unknown commitment mode: %s", s) + } +} + +func StringToCommitment(key string, c CommitmentMode) ([]byte, error) { + if len(key) <= 2 { + 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 OptimismCommitmentMode: + var comm commitments.OPCommitment + err = comm.Unmarshal(b) + if err != nil { + return nil, err + } + if !comm.IsGenericCommitment() { + return nil, fmt.Errorf("commitment is not a OP DA service commitment") + } + daComm := comm.MustGenericCommitmentValue() + 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 SimpleCommitmentMode: + var eigendaComm commitments.EigenDACommitment + err = eigendaComm.Unmarshal(b) + if err != nil { + return nil, err + } + if !eigendaComm.IsCertV0() { + return nil, fmt.Errorf("commitment is not a supported EigenDA cert encoding") + } + return eigendaComm.MustCertV0Value(), nil + default: + return nil, fmt.Errorf("unknown commitment type") + } +} + +func EncodeCommitment(s []byte, c CommitmentMode) ([]byte, error) { + switch c { + case OptimismCommitmentMode: + comm := commitments.GenericCommitment(commitments.OptimismEigenDACommitment(commitments.EigenDACertV0(s))) + return comm.Marshal() + case SimpleCommitmentMode: + comm := commitments.EigenDACertV0(s) + return comm.Marshal() + default: + return nil, fmt.Errorf("unknown commitment type") + } +} + +func ReadCommitmentMode(r *http.Request) (CommitmentMode, error) { + query := r.URL.Query() + key := query.Get(CommitmentModeKey) + if key == "" { // default + return OptimismCommitmentMode, nil + } + return StringToCommitmentMode(key) +} diff --git a/eigenda/config.go b/server/config.go similarity index 85% rename from eigenda/config.go rename to server/config.go index c768a5b..aebfc20 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-proxy/verify" "github.com/Layr-Labs/eigenda/api/clients" "github.com/Layr-Labs/eigenda/api/clients/codecs" @@ -28,10 +28,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 @@ -61,11 +63,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 } @@ -128,12 +134,14 @@ 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), - SvcManagerAddr: ctx.String(SvcManagerAddrFlagName), - EthRPC: ctx.String(EthRPCFlagName), + G1Path: ctx.String(G1PathFlagName), + G2PowerOfTauPath: ctx.String(G2TauFlagName), + CacheDir: ctx.String(CachePathFlagName), + MaxBlobLength: ctx.String(MaxBlobLengthFlagName), + SvcManagerAddr: ctx.String(SvcManagerAddrFlagName), + EthRPC: ctx.String(EthRPCFlagName), + MemstoreEnabled: ctx.Bool(MemstoreFlagName), + MemstoreBlobExpiration: ctx.Duration(MemstoreExpirationFlagName), } return cfg } @@ -237,5 +245,16 @@ func CLIFlags(envPrefix string) []cli.Flag { Usage: "The deployed EigenDA service manager address. The list can be found here: https://github.com/Layr-Labs/eigenlayer-middleware/?tab=readme-ov-file#current-mainnet-deployment", EnvVars: prefixEnvVars("SERVICE_MANAGER_ADDR"), }, + &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 0000000..d161e0c --- /dev/null +++ b/server/domain_type.go @@ -0,0 +1,56 @@ +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 + +const ( + BinaryDomain DomainType = iota + PolyDomain + UnknownDomain +) + +func (d DomainType) String() string { + switch d { + 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 + } +} + +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 92% rename from store/eigenda.go rename to server/eigenda_store.go index b45eed5..6b5066e 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 verify.Certificate err := rlp.DecodeBytes(key, &cert) if err != nil { return nil, fmt.Errorf("failed to decode DA cert to RLP format: %w", err) @@ -57,9 +56,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) @@ -94,6 +93,6 @@ func (e EigenDAStore) Put(ctx context.Context, value []byte) (comm []byte, err e } // Entries are a no-op for EigenDA Store -func (e EigenDAStore) Stats() *common.Stats { +func (e EigenDAStore) Stats() *Stats { return nil } diff --git a/server/flags.go b/server/flags.go index 7d2579a..9f6547b 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 34c0533..41458c2 100644 --- a/server/load_store.go +++ b/server/load_store.go @@ -3,13 +3,13 @@ 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 vCfg := daCfg.VerificationCfg() @@ -29,9 +29,9 @@ 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) } log.Info("Using EigenDA backend") @@ -39,7 +39,7 @@ func LoadStore(cfg CLIConfig, ctx context.Context, log log.Logger) (store.Store, 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 71% rename from store/memory.go rename to server/memory_store.go index 9535c83..77e7d35 100644 --- a/store/memory.go +++ b/server/memory_store.go @@ -1,4 +1,4 @@ -package store +package server import ( "context" @@ -10,61 +10,50 @@ 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 verifier *verify.Verifier codec codecs.BlobCodec - reads int maxBlobSizeBytes uint64 + blobExpiration time.Duration + reads int } 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) } @@ -91,7 +80,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) @@ -102,12 +91,12 @@ 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.reads += 1 - e.Lock() - defer e.Unlock() + e.RLock() + defer e.RUnlock() - var cert common.Certificate + var cert verify.Certificate err := rlp.DecodeBytes(commit, &cert) if err != nil { return nil, fmt.Errorf("failed to decode DA cert to RLP format: %w", err) @@ -126,9 +115,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) @@ -163,9 +152,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 := &verify.Certificate{ BlobHeader: &disperser.BlobHeader{ - Commitment: &grpccommon.G1Commitment{ + Commitment: &common.G1Commitment{ X: commitment.X.Marshal(), Y: commitment.Y.Marshal(), }, @@ -209,37 +198,11 @@ func (e *MemStore) Put(ctx context.Context, value []byte) ([]byte, error) { return certBytes, nil } -func (e *MemStore) Stats() *common.Stats { +func (e *MemStore) Stats() *Stats { e.RLock() defer e.RUnlock() - return &common.Stats{ + return &Stats{ Entries: len(e.store), Reads: e.reads, } } - -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 85% rename from store/memory_test.go rename to server/memory_store_test.go index 202aa2b..0d61848 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" @@ -40,13 +39,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) @@ -55,7 +51,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) } @@ -85,13 +81,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) @@ -103,7 +96,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 d8d6020..1e70d93 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,41 @@ var ( ) const ( - invalidDomain = "invalid domain type" -) + invalidDomain = "invalid domain type" + invalidCommitmentMode = "invalid commitment mode" -const ( GetRoute = "/get/" PutRoute = "/put/" - DomainFilterKey = "domain" + DomainFilterKey = "domain" + CommitmentModeKey = "commitment_mode" ) +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) + // Stats returns the current usage metrics of the key-value data store. + Stats() *Stats +} + +type Stats struct { + Entries int + Reads int +} + 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 +158,14 @@ func (svr *Server) HandleGet(w http.ResponseWriter, r *http.Request) error { svr.WriteBadRequest(w, invalidDomain) return err } + commitmentType, err := ReadCommitmentMode(r) + if err != nil { + svr.WriteBadRequest(w, invalidCommitmentMode) + return err + } key := path.Base(r.URL.Path) - comm, err := eigenda.StringToCommit(key) + comm, err := StringToCommitment(key, commitmentType) if err != nil { svr.log.Info("failed to decode commitment", "err", err, "key", key) w.WriteHeader(http.StatusBadRequest) @@ -172,6 +188,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 := ReadCommitmentMode(r) + if err != nil { + svr.WriteBadRequest(w, invalidCommitmentMode) + return err + } + input, err := io.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) @@ -184,8 +206,15 @@ 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 + } + + fmt.Printf("write cert: %x\n", comm) // write out encoded commitment - svr.WriteResponse(w, eigenda.Commitment.Encode(comm)) + svr.WriteResponse(w, comm) return nil } @@ -210,27 +239,13 @@ func (svr *Server) WriteBadRequest(w http.ResponseWriter, msg string) { 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 -} - -func (svr *Server) Store() store.Store { - return svr.store -} - func (svr *Server) Port() int { // read from listener _, portStr, _ := net.SplitHostPort(svr.listener.Addr().String()) port, _ := strconv.Atoi(portStr) return port } + +func (svr *Server) Store() Store { + return svr.store +} diff --git a/store/types.go b/store/types.go deleted file mode 100644 index 5a283d1..0000000 --- a/store/types.go +++ /dev/null @@ -1,16 +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) - // Stats returns the current usage metrics of the key-value data store. - Stats() *common.Stats -} diff --git a/utils/parse_bytes.go b/utils/parse_bytes.go new file mode 100644 index 0000000..5f9067d --- /dev/null +++ b/utils/parse_bytes.go @@ -0,0 +1,70 @@ +package utils + +import ( + "fmt" + "strconv" + "strings" +) + +// 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) + + // Extract numeric part and unit + numStr := s + unit := "" + for i, r := range s { + if !('0' <= r && r <= '9' || r == '.') { + numStr = s[:i] + unit = s[i:] + break + } + } + + // Convert numeric part to float64 + num, err := strconv.ParseFloat(numStr, 64) + if err != nil { + return 0, fmt.Errorf("invalid numeric value: %v", err) + } + + unit = strings.ToLower(strings.TrimSpace(unit)) + + // Convert to uint64 based on the unit (case-insensitive) + switch unit { + case "b", "": + return uint64(num), nil + case "kib": + return uint64(num * 1024), nil + case "kb": + return uint64(num * 1000), nil // Decimal kilobyte + case "mib": + return uint64(num * 1024 * 1024), nil + case "mb": + return uint64(num * 1000 * 1000), nil // Decimal megabyte + case "gib": + return uint64(num * 1024 * 1024 * 1024), nil + case "gb": + return uint64(num * 1000 * 1000 * 1000), nil // Decimal gigabyte + case "tib": + return uint64(num * 1024 * 1024 * 1024 * 1024), nil + case "tb": + return uint64(num * 1000 * 1000 * 1000 * 1000), nil // Decimal terabyte + default: + return 0, fmt.Errorf("unsupported unit: %s", unit) + } +} 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 11632c8..aab5386 100644 --- a/common/common_test.go +++ b/utils/parse_bytes_test.go @@ -1,15 +1,14 @@ -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) { t.Parallel() - testCases := []struct { input string expected uint64 @@ -44,7 +43,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/cert.go b/verify/cert.go index 35314c3..6f55b43 100644 --- a/verify/cert.go +++ b/verify/cert.go @@ -3,12 +3,12 @@ package verify import ( "fmt" - proxy_common "github.com/Layr-Labs/eigenda-proxy/common" binding "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDAServiceManager" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" + "golang.org/x/exp/slices" ) // CertVerifier verifies the DA certificate against on-chain EigenDA contracts @@ -52,7 +52,7 @@ func (cv *CertVerifier) VerifyBatch(header *binding.IEigenDAServiceManagerBatchH return err } - equal := proxy_common.EqualSlices(expectedHash[:], actualHash[:]) + equal := slices.Equal(expectedHash[:], actualHash[:]) if !equal { return fmt.Errorf("batch hash mismatch, expected: %x, got: %x", expectedHash, actualHash) } @@ -61,7 +61,7 @@ func (cv *CertVerifier) VerifyBatch(header *binding.IEigenDAServiceManagerBatchH } // VerifyMerkleProof -func (cv *CertVerifier) VerifyMerkleProof(inclusionProof []byte, root []byte, blobIndex uint32, blobHeader proxy_common.BlobHeader) error { +func (cv *CertVerifier) VerifyMerkleProof(inclusionProof []byte, root []byte, blobIndex uint32, blobHeader BlobHeader) error { leafHash, err := HashEncodeBlobHeader(blobHeader) if err != nil { return err @@ -72,7 +72,7 @@ func (cv *CertVerifier) VerifyMerkleProof(inclusionProof []byte, root []byte, bl return err } - equal := proxy_common.EqualSlices(root, generatedRoot.Bytes()) + equal := slices.Equal(root, generatedRoot.Bytes()) if !equal { return fmt.Errorf("root hash mismatch, expected: %x, got: %x", root, generatedRoot) } diff --git a/verify/certificate.go b/verify/certificate.go new file mode 100644 index 0000000..54441a0 --- /dev/null +++ b/verify/certificate.go @@ -0,0 +1,70 @@ +package verify + +import ( + "fmt" + "math/big" + + "github.com/Layr-Labs/eigenda/api/grpc/disperser" +) + +var ( + ErrInvalidDomainType = fmt.Errorf("invalid domain type") +) + +// G1Point struct to represent G1Point in Solidity +type G1Point struct { + X *big.Int + Y *big.Int +} + +// QuorumBlobParam struct to represent QuorumBlobParam in Solidity +type QuorumBlobParam struct { + QuorumNumber uint8 + AdversaryThresholdPercentage uint8 + ConfirmationThresholdPercentage uint8 + ChunkLength uint32 +} + +// BlobHeader struct to represent BlobHeader in Solidity +type BlobHeader struct { + Commitment G1Point + DataLength uint32 + QuorumBlobParams []QuorumBlobParam +} + +type Certificate disperser.BlobInfo + +func (c *Certificate) BlobIndex() uint32 { + return c.BlobVerificationProof.BlobIndex +} + +func (c *Certificate) BatchHeaderRoot() []byte { + return c.BlobVerificationProof.BatchMetadata.BatchHeader.BatchRoot +} + +func (c *Certificate) ReadBlobHeader() BlobHeader { + // parse quorum params + + qps := make([]QuorumBlobParam, len(c.BlobHeader.BlobQuorumParams)) + for i, qp := range c.BlobHeader.BlobQuorumParams { + qps[i] = QuorumBlobParam{ + QuorumNumber: uint8(qp.QuorumNumber), + AdversaryThresholdPercentage: uint8(qp.AdversaryThresholdPercentage), + ConfirmationThresholdPercentage: uint8(qp.ConfirmationThresholdPercentage), + ChunkLength: qp.ChunkLength, + } + } + + return BlobHeader{ + Commitment: G1Point{ + X: new(big.Int).SetBytes(c.BlobHeader.Commitment.X), + Y: new(big.Int).SetBytes(c.BlobHeader.Commitment.Y), + }, + DataLength: c.BlobHeader.DataLength, + QuorumBlobParams: qps, + } +} + +func (c *Certificate) Proof() *disperser.BlobVerificationProof { + return c.BlobVerificationProof +} diff --git a/verify/hasher.go b/verify/hasher.go index ca2353c..70a4aae 100644 --- a/verify/hasher.go +++ b/verify/hasher.go @@ -3,7 +3,6 @@ package verify import ( "encoding/binary" - common "github.com/Layr-Labs/eigenda-proxy/common" binding "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDAServiceManager" "github.com/ethereum/go-ethereum/accounts/abi" geth_common "github.com/ethereum/go-ethereum/common" @@ -98,7 +97,7 @@ func HashBatchHashedMetadata(batchHeaderHash [32]byte, signatoryRecordHash [32]b } // HashBlobHeader function to hash BlobHeader -func HashBlobHeader(blobHeader common.BlobHeader) (geth_common.Hash, error) { +func HashBlobHeader(blobHeader BlobHeader) (geth_common.Hash, error) { blobHeaderType, err := abi.NewType("tuple", "", []abi.ArgumentMarshaling{ {Name: "commitment", Type: "tuple", Components: []abi.ArgumentMarshaling{ @@ -133,7 +132,7 @@ func HashBlobHeader(blobHeader common.BlobHeader) (geth_common.Hash, error) { } // Function to hash and encode header -func HashEncodeBlobHeader(header common.BlobHeader) (geth_common.Hash, error) { +func HashEncodeBlobHeader(header BlobHeader) (geth_common.Hash, error) { // Hash the BlobHeader blobHash, err := HashBlobHeader(header) if err != nil { diff --git a/verify/hasher_test.go b/verify/hasher_test.go index db4d09e..3c1c1b5 100644 --- a/verify/hasher_test.go +++ b/verify/hasher_test.go @@ -4,7 +4,6 @@ import ( "math/big" "testing" - "github.com/Layr-Labs/eigenda-proxy/common" eigenda_common "github.com/Layr-Labs/eigenda/api/grpc/common" "github.com/Layr-Labs/eigenda/api/grpc/disperser" binding "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDAServiceManager" @@ -88,7 +87,7 @@ func TestHashBlobHeader(t *testing.T) { }, } - cert := &common.Certificate{ + cert := &Certificate{ BlobHeader: header, } @@ -124,7 +123,7 @@ func TestHashEncodeBlobHeader(t *testing.T) { }, } - cert := &common.Certificate{ + cert := &Certificate{ BlobHeader: header, } diff --git a/verify/verifier.go b/verify/verifier.go index 651d5eb..8b69d0b 100644 --- a/verify/verifier.go +++ b/verify/verifier.go @@ -3,16 +3,14 @@ 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/ethereum/go-ethereum/log" - proxy_common "github.com/Layr-Labs/eigenda-proxy/common" - binding "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDAServiceManager" + "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" @@ -54,7 +52,7 @@ func NewVerifier(cfg *Config, l log.Logger) (*Verifier, error) { }, nil } -func (v *Verifier) VerifyCert(cert *proxy_common.Certificate) error { +func (v *Verifier) VerifyCert(cert *Certificate) error { if !v.verifyCert { return nil } @@ -119,7 +117,6 @@ func (v *Verifier) VerifyCommitment(expectedCommit *common.G1Commitment, blob [] return err } - // convert to field elements expectedX := &fp.Element{} expectedX.Unmarshal(expectedCommit.X) expectedY := &fp.Element{} @@ -136,7 +133,7 @@ func (v *Verifier) VerifyCommitment(expectedCommit *common.G1Commitment, blob [] } // VerifySecurityParams ensures that returned security parameters are valid -func (v *Verifier) VerifySecurityParams(blobHeader proxy_common.BlobHeader, batchHeader binding.IEigenDAServiceManagerBatchHeader) error { +func (v *Verifier) VerifySecurityParams(blobHeader BlobHeader, batchHeader binding.IEigenDAServiceManagerBatchHeader) error { confirmedQuorums := make(map[uint8]bool)